目录
- 复习new和delete以及学习静态类成员变量
- 关于赋值运算符(重构)(解释了StringBad sailor = sports;会出现的问题以及解决方法)
- 静态成员函数
- 无缺陷的String类方法总结
- 在构造函数中使用new时应该注意的问题(什么时候该编写复制构造函数和赋值重构函数)
- 包含类成员的类的逐成员复制
- 返回对象还是指向对象的引用?
-
- case4:返回类型为const对象
- 指向对象的指针
- 在对象的基础上再谈new和delete
复习new和delete以及学习静态类成员变量
01)char* str = "Hello world"; //注意str是一个字符串指针哟
int num_strings = strlen(str); //这些是正确的,num_strings=11,strlen()并不计算字符串中的空字符
num_strings = 11
02)对于静态类成员变量:
假如有StringBad类对象s1,s2,s3。则s1,s2,s3都有自己的str和len私有成员变量
但是s1,s2,s3共用静态类成员变量num_strings *****
需要注意的是:
A 静态类成员变量可以在h文件中(类中)声明,也可以在头文件中声明,但是如果多个cpp文件都声明了该h文件,
那么该静态类成员变量就会被初始化好多次。
B 静态类成员变量可以在cpp文件中定义具体的数值
03)子函数的形参如果是一个类对象的话,最好是将该形参设置为引用,一旦设置成一般的类对象,会出现一些问题
比如user_main.cpp中callme2()子函数中的问题
04)StringBad sports("Spinish Leaves Bowl fot Dollars");
StringBad sailor = sports;
//上面的这一句实际等价于StringBad sailor = (StringBad)sports;
//调用的构造函数是StringBad(const StringBad &)
//由于原函数中没有该构造函数,所以编译器会自动创建该构造函数,而编译器自动创建的构造函数是没有将
//num_strings自动加1的,所以就导致了构造函数和析构函数中的num_strings++和num_strings--不一致,导致
//程序运行的结果中,最后析构函数删除的对象的个数出现不对的现象。
1 //stringBad.h 2 //设计一个stringBad类,类似于C++库中的string类 3 #include <iostream> 4 #ifndef STRINGBAD_H_ 5 #define STRINGBAD_H_ 6 7 class StringBad 8 { 9 private: 10 char* str; 11 int len; 12 static int num_strings; //新建一个静态类成员变量 13 public: 14 StringBad(const char* s); //声明构造函数 15 StringBad(); //声明默认构造函数 16 ~StringBad(); //声明析构函数 17 friend std::ostream & operator<<(std::ostream & os, const StringBad & st); //声明友元函数 18 }; 19 20 #endif 21 22 /* 复习new和delete以及学习静态类成员变量 */ 23 /* 24 01)char* str = "Hello world"; 25 int num_strings = strlen(str); //这些是正确的,num_strings=11,strlen()并不计算字符串中的空字符 26 num_strings = 11 27 02)对于静态类成员变量: 28 假如有StringBad类对象s1,s2,s3。则s1,s2,s3都有自己的str和len私有成员变量 29 但是s1,s2,s3共用静态类成员变量num_strings 30 需要注意的是: 31 A 静态类成员变量可以在h文件中(类中)声明,也可以在头文件中声明,但是如果多个cpp文件都声明了该h文件, 32 那么该静态类成员变量就会被初始化好多次。 33 B 静态类成员变量可以在cpp文件中定义具体的数值 34 03)子函数的形参如果是一个类对象的话,最好是将该形参设置为引用,一旦设置成一般的类对象,会出现一些问题 35 比如user_main.cpp中callme2()子函数中的问题 36 04)StringBad sports("Spinish Leaves Bowl fot Dollars"); 37 StringBad sailor = sports; 38 //上面的这一句实际等价于StringBad sailor = (StringBad)sports; 39 //调用的构造函数是StringBad(const StringBad &) 40 //由于原函数中没有该构造函数,所以编译器会自动创建该构造函数,而编译器自动创建的构造函数是没有将 41 //num_strings自动加1的,所以就导致了构造函数和析构函数中的num_strings++和num_strings--不一致,导致 42 //程序运行的结果中,最后析构函数删除的对象的个数出现不对的现象。 43 */
1 //stringbad.cpp 2 #include <cstring> 3 #include "stringbad.h" 4 5 //静态类成员变量的定义,注意要使用类成员限定符StringBad::,但是没有使用关键字static 6 int StringBad::num_strings = 0; 7 8 //定义一个参数的构造函数 9 //主要注意的是,对于StringBad boston("Boston");boston对象中并没有保存字符串"Boston",而是仅仅保存了该字符串的地址信息 10 StringBad::StringBad(const char* s) //创建对象时,可以这样创建StringBad boston("Boston"); 11 { 12 len = std::strlen(s); //这一句很明显是在说strlen()在名称空间std中, 13 //同时也说明了strlen()可以接受一个字符串指针作为参数 14 str = new char[len + 1]; //加1是为了给空字符' '留出位置,new返回的是一个地址,所以str也是一个指针 15 std::strcpy(str, s); //将指针字符串s复制给指针字符串str 16 //str=s; 这样做是不可以的,因为这样做只是复制了地址,而没有创建字符串副本 17 num_strings++; //统计新建的对象的个数 18 std::cout << num_strings << ": "" << str << ""object created ";//显式多少个对象已创建 19 } 20 21 //默认构造函数的定义 22 StringBad::StringBad() 23 { 24 len = 4; 25 str = new char[len + 1]; 26 std::strcpy(str, "C++"); 27 num_strings++;//统计新建的对象的个数 28 std::cout << num_strings << ": "" << str << ""object created ";//显式多少个对象已创建 29 } 30 31 //析构函数的定义 32 //当StrngBad对象过期时,str指针也将过期,但str指向的内存仍然被分配,除非是有delete将其释放 33 //析构函数执行时,先删除最后创建的对象,后删除最先创建的对象 34 StringBad::~StringBad() 35 { 36 std::cout << """ << str << "" object deleted, "; 37 --num_strings; //对象的数据减1 38 std::cout << num_strings << " left "; 39 delete[] str; //释放由new创建的内存 40 } 41 42 //友元函数的定义 43 std::ostream & operator<<(std::ostream & os, const StringBad & st) 44 { 45 os << st.str; 46 return os; 47 }
1 //user_main.cpp 2 #include <iostream> 3 #include "stringbad.h" 4 5 using std::cout; 6 using std::endl; 7 8 void callme1(StringBad &rsb); 9 void callme2(StringBad sb); 10 11 int main() 12 { 13 { 14 cout << "开始创建内部函数快" << endl; 15 StringBad headline1("Celery Stalks at midnight"); 16 StringBad headline2("Lettuce Preey"); 17 StringBad sports("Spinish Leaves Bowl fot Dollars"); 18 cout << "headline1: " << headline1 << endl; 19 cout << "headline2: " << headline1 << endl; 20 cout << "sports: " << sports << endl; 21 22 callme1(headline1); 23 //callme2(headline2); //callme2()的形参为非引用,会出问题 24 /* 25 callme2()会出错误的原因: 26 01)headline2作为参数传递给cellme2(),在callme2()函数执行完毕后,会调用析构函数 27 02)虽然函数按值传递可以防止原始参数被修改,但实际上函数已使原始字符串无法识别,导致显示一些非标准字符 28 */ 29 30 StringBad sailor = sports; 31 cout << "sailor: " << sailor << endl; 32 //上面的这一句实际等价于StringBad sailor = (StringBad)sports; 33 //调用的构造函数是StringBad(const StringBad &) 34 //由于原函数中没有该构造函数,所以编译器会自动创建该构造函数,而编译器自动创建的构造函数是没有将 35 //num_strings自动加1的,所以就导致了构造函数和析构函数中的num_strings++和num_strings--不一致,导致 36 //程序运行的结果中,最后析构函数删除的对象的个数出现不对的现象。 37 } 38 39 system("pause"); 40 return 0; 41 } 42 43 void callme1(StringBad & rsb) 44 { 45 cout << "String passed by reference: " << endl; 46 cout << """ << rsb << """ << endl; 47 } 48 void callme2(StringBad sb) 49 { 50 cout << "String passed by value: " << endl; 51 cout << """ << sb << """ << endl; 52 }
执行结果为:
关于复制构造函数(解释了StringBad sailor = sports;会出现的问题以及解决方法)
//StringBad.h文件
calss StringBad
{
private:
char* str;
int len;
static num_string;
public:
StringBad();//默认构造函数
StringBad(char* s); //构造函数
~StringBad(); //析构函数
};
//StringBad.cpp文件
StringBad::StringBad()
{
str = new char[1]; //与new char; 是等价的,只不过这里要和析构函数中的delete [] str;对应起来
str = ' '; //C++11中添加了nullptr来表示空指针,所以上面两句可以用str=nullptr来代替
len = 0;
}
StringBad::StringBad(char* s)
{
len = std::strlen(s);
str = new char[en+1]; //刚刚自己写成这样了: str = new char(len+1); 导致在析构函数中使用delete的时候不会用
std::strcpy(str,s);
num_string++; //已创建的对象数目加1
}
StringBad::~StringBad();
{
num_string--; //已创建的对象数目减1
delete [] str;
cout<<str<<" was deleted;
";
cout<<num_string<<" was left
";
}
//在main函数中执行的操作
StringBad::num_string=0; //对象数目初始化为0
int main()
{
StringBad sports("Hello world!"); //创建对象sports,并将对象中的数据(str)初始化为Hello world!
StringBad sailor = sports; //这一句将会调用默认的复制构造函数,因为自己没有定义复制构造函数
}
//StringBad sailor = sports;这一句会出现很大的问题
/*
01)由于是调用的默认复制构造函数,在默认的复制构造函数中并没有num_string++; 所以会导致在执行析构函数时候剩余的对象 数目出错
02) StringBad sailor = sports;等价于下面三句(无法通过编译,因为对象无法访问私有数据,这里只是说明一下)
StringBad sailor;
sailor.str = sports.str; //等价的这一句会出现致命的错误,即最后看到的乱码现象
sailor.len = sports.len;
对于sailor.str = sports.str;该句执行的结果是sailor对象中的str指针和sports对象中的str指针,都指向同一块内存,
最后程序执行完毕,在执行析构函数时候,由于析构函数是先删除后创建的对象,也就是先删除sailor对象,同时也释放了sailor对 象中str所指向的内存,且sports对象中的str和sailor对象中的str是指向的同一块内存,则在删除sprots对象时,同时执行 cout<<str<<" was deleted
"将会出现乱码。(因为sports.str指向的内存已经被sailor.str释放)
03)对于上述问题的解决方法:自己编写一个编写复制构造函数
StringBad::StringBad(const & st)
{
/* 解决问题01 */
num_string++;
/* 解决问题02 */
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
cout<<num_string<<": "<<str<<" objects were created
";
}
//此时再执行StringBad sailor = sports;则sailor中的str和sports中的str将不是同一个地址
//释放内存时候,就不会互相干扰
*/
/* 什么时候自己定义复制构造函数 */
//当类成员中有new初始化的、指向数据的指针,此时应该自己去定义复制构造函数,以复制指向的数据,而不是指针,
//这被称为深度复制
关于赋值运算符(重构)
//对于StringBad sailor = sports;的执行过程分两种可能
/*
01)第一种可能是:直接使用复制构造函数,并且将对象sports中的数据复制给对象sailor
02)第二种可能是:首先使用复制构造函数创建临时对象,然后使用赋值运算符(就是等号=)将临时对象赋值给sailor
那么要使程序完美,那么就需要自己定义一个赋值运算符的重构函数
*/
//赋值运算符(即等号)的重构函数的定义
StringBad & StringBad::operator=(StringBad & st)
{
/*首先判断赋值运算符左边的对象地址(this)和右边的对象地址(&st)是不是相同*/
if(this == &st) //this是调用该重构函数的对象的指针,该句就是在判断 a=b 中a和b是不是同一个值
return *this; //如果是,那么程序结束,返回任意一个对象均可(*this或st)
delete [] str; //由于a=b等价于a.operator(b),那么a就是被赋值的对象,所以要首先删除a对象中的成员str原来指向的内存
/* 接下来进行深度复制 */
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
return *this;
}
注意:不要将赋值和初始化混淆了
Star sirius; //创建类对象sirius
Star alpha = sirius; //初始化,调用复制构造函数
Star dogstar;
dogstar = sirius; //赋值,调用赋值构造函数
进一步重载赋值运算符(解析了name=temp两个对象的具体执行步骤)
//对于如下语句:
String name;
char temp[40];
temp = getline(temp,40);
name = temp;
/*
对于最后一句name = temp;执行步骤如下:
01)先使用构造函数StringBad(const char* ps)来创建临时StringBad对象;
02)使用赋值运算符重构函数StringBad & StringBad::operator=(const StringBad & st)将临时对象中的数据复制到name中去;
03)使用析构函数将创建的临时对象删除掉。
*/
//为提高效率,最简单的方法就是直接使用赋值运算符重构函数,使之能够直接使用常规字符串,这样就不用创建和删除临时对象了
方法如下:
StringBad & StringBad::operator=(StringBad & st)
{
delete [] str; //由于a=b等价于a.operator(b),那么a就是被赋值的对象,所以要首先删除a对象中的成员str原来指向的内存
/* 接下来进行深度复制 */
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
return *this;
}
比较重载运算符(使用友元函数重载)
//StringBad.h文件
friend bool operator<(const StringBad & st1, const StringBad & st2)
friend bool operator>(const StringBad & st1, const StringBad & st2)
friend bool operator==(const StringBad & st1, const StringBad & st2)
//StringBad.cpp
/*比较重载运算符(使用友元函数重载)*/
bool StringBad::operator<(const StringBad & st1, const StringBad & st2)
{
if (std::strcmp(st1.str,st2.str)<0)
return true;
else
return false;
}
//strcmp(a,b); 如果a参数位于第二个参数b之前,则返回一个负值
//如果第一个参数位于第二个参数之后,则返回一个正值
//如果两个参数相等,则返回0
//以上函数可以简化为(友元函数定义):
bool operator<(const StringBad & st1, const StringBad & st2)
{
return(std::strcmp(st1.str,st2.str)<0);
}
bool operator>(const StringBad & st1, const StringBad & st2)
{
return st1<st2; //调用上面写的对<重载的友元函数
}
bool operator==(const StringBad & st1, const StringBad & st2)
{
return(std::strcmp(st1.str,st2.str)==0);
}
对[ ]运算符的重载
01)问题的提出:
对于char city[40]="Armsterdan";
那么有city[0]='A',如果city是一个类对象呢?那么就需要对[]进行重载
02)对[ ]的重载实现方法:
char & StringBad::operator[](int i)
{
return str[i]; //由于str是类中的私有数据,是一只存在的,所以该函数的返回类型可以是引用
}
03)调用方法:
StringBad opera("The magic flute");
那么语句cout<<opera[4];就是合法的opera[4]='m'
或者opera[0]='M';也是合法的,将opera中str的第一个字符替换为M
对于opera[4]将被转换为opera.operator[](4)
对于opera[0]='M'将被替换为opera.operator[](0) = 'M';
04)对于const类型的对象是不可以修改的,比如
const StringBad opera("Hello World");
opera[0] = "M"; //不合法,因为对象是const类型的,其值不可修改
05)也可以提供一个仅供const StringBad 对象使用的operator[]()版本:
const char & StringBad[](int i)
{
return str[i];
}
静态成员函数
01)可以将类函数声明为静态的(在声明和定义前加static),需要注意的是:
A 不能通过对象调用静态成员函数,甚至不可以使用this指针;
B 如果静态成员函数是在公有部分中声明的,那么可以使用类作用域解析符来使用(如StringBad::);
C 静态成员函数与对象无关,因此只可以使用静态数据变量,在本例中HowMany()无法使用str和len,
HowMany()只能访问静态变量num_string.
D 如果声明和定义分开的话,那么在声明中要使用static关键字,在定义的时候要把关键字static去掉。
02)声明+定义方法(举例):
static int HowMany() { return num_string; }
03)调用方法(举例):
int count = StringBad::HowMany();
//注意:
StringBad sayings[4]; //表示创建一个数组,数组内的元素为4个StringBad类对象
无缺陷的String类方法总结
本例子涉及到的类方法有:
01)复制重构函数的声明、定义和调用
02)静态变量、静态类方法的声明、定义和调用方法
03)对=号的重构函数
04)对<、>、和==的重构函数
05)对输入(>>)和输出(<<)的重构函数
1 #ifndef STRING1_H_ 2 #define STRING_H_ 3 4 #include <iostream> 5 using std::ostream; //刚刚这里写成cout了,导致下面对<<友元重载出错 6 using std::istream; 7 8 class String 9 { 10 private: 11 char* str; //保存字符串的地址 12 int len; //保存字符串的长度 13 static int num_strings; //保存创建的对象的个数 14 static const int CINLIM = 80; //和对>>的重载有关的一个静态常量 15 public: 16 /* 构造函数和析构函数 */ 17 String(const char* s); //声明构造函数 18 String(); //声明默认构造函数 19 String(const String & st); //声明复制构造函数 20 ~String(); //声明析构函数 21 int length() const { return len; } //声明并定义内联函数,对象因此可以使用私有数据len 22 23 /* 重构函数 */ 24 String & operator=(const String & st); //对等号(赋值运算符的重构) 25 String & operator=(const char* pt); //对等号(赋值运算符的重构) 上下参数不一样 26 char & operator[](int i); //对[]的重构,举例:String str("Hello"); 那么str[1]就等于e,注意此时str是一个对象 27 const char & operator[](int i) const; //上边的那个允许对对象的第二个字符进行修改,即str[1]=E; 但是这个版本不允许,因为使用了const常量关键字 28 //上边最后的那个const表示不会修改调用该方法对象中的数据 29 30 /*友元函数*/ 31 friend bool operator<(const String & st1, const String & st2); //小于号运算符重载+友元函数 32 friend bool operator>(const String & st1, const String & st2); 33 friend bool operator==(const String & st1, const String & st2); 34 friend ostream & operator<<(ostream & os, const String & st); //输出运算符的重载+友元函数 35 friend istream & operator>>(istream & is, String & st); 36 37 /* 静态方法(对象是不能调用的,只能通过类解析运算符(String::)使用) */ 38 static int HowMany(); //声明要加上关键字static,定义时就不用加关键字static了 39 }; 40 41 #endif 42 43 /* 无缺陷的String类方法总结 */ 44 /* 45 本例子涉及到的类方法有: 46 01)复制重构函数的声明、定义和调用 47 02)静态变量、静态类方法的声明、定义和调用方法 48 03)对=号的重构函数 49 04)对<、>、和==的重构函数 50 05)对输入(>>)和输出(<<)的重构函数 51 */
1 //string1.cpp 2 #include <cstring> //for strlen()、strcmp()等 3 #include "string1.h" 4 5 using std::cout; 6 using std::cin; 7 8 //静态变量的定义 9 int String::num_strings = 0; //注意要加类解析运算符 10 //静态函数的定义 11 int String::HowMany() //注意要加类解析运算符 12 { 13 return num_strings; 14 } 15 16 /* 含一个参数的构造函数的定义*/ 17 String::String(const char* s) 18 { 19 len = std::strlen(s); //去掉字符串最后的空字符后,总的字符数 20 str = new char[len + 1]; //len+1是加上最后的空字符 21 std::strcpy(str, s); 22 num_strings++; //对象数目加1 23 } 24 25 //默认构造函数定义 26 String::String() 27 { 28 len = 1; 29 str = new char[1]; 30 str = '