C++第十二章_关于复制构造函数(解释了StringBad sailor = sports;会出现的问题以及解决方法)__复习new和delete以及学习静态类成员变量__关于赋值运算符(重构)__进一步重载赋值运算符(解析了name=temp两个对象的具体执行步骤)__比较重载运算符(使用友元函数重载)__静态成员函数__无缺陷的String类方法总结__在构造函数中使用new时应该注意的问题(
目录
- 复习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是为了给空字符'\0'留出位置,new返回的是一个地址,所以str也是一个指针 15 std::strcpy(str, s); //将指针字符串s复制给指针字符串str 16 //str=s; 这样做是不可以的,因为这样做只是复制了地址,而没有创建字符串副本 17 num_strings++; //统计新建的对象的个数 18 std::cout << num_strings << ": \"" << str << "\"object created\n";//显式多少个对象已创建 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\n";//显式多少个对象已创建 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\n"; 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 = '\0'; //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;\n";
cout<<num_string<<" was left\n";
}
//在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\n"将会出现乱码。(因为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\n";
}
//此时再执行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 = '\0'; 31 num_strings++; //对象数目加1 32 } 33 34 //复制构造函数定义 例如String name = sports; 35 //sports为一个String对象,在这个过程中会创建一个临时对象,复制构造函数就负责将该临时对象复制给name 36 String::String(const String & st) 37 { 38 len = st.len; 39 str = new char[len + 1]; 40 std::strcpy(str, st.str); 41 num_strings++; //对象数目加1 42 } 43 44 //析构函数定义 45 String::~String() 46 { 47 num_strings--; 48 delete[] str; //释放内存 49 } 50 51 //赋值运算符重构函数定义 52 //调用方法为:String name = sports; (name和sports都是String类对象) 53 //实际调用方法为:name.operator=(sports); 54 String & String::operator=(const String & st) //刚刚类解析运算符的位置放错了,放在最先前边导致出错 55 { 56 if (this == &st) //首先判断一下name对象和sports是不是同一个对象,如果是,那么该方法结束 57 return *this; 58 delete[] str; //要首先删除name.str原来指向的内存,防止内存浪费 59 len = st.len; 60 str = new char[len + 1]; 61 std::strcpy(str, st.str); 62 num_strings++; //对象数目加1 63 return *this; //返回调用该方法的指针 64 } 65 66 //赋值运算符重构函数定义 67 //调用方法为String name = "Hello world!" 68 String & String::operator=(const char* pt) 69 { 70 delete[] str; //释放name.str原来就有的内存 71 len = std::strlen(pt); 72 str = new char[len + 1]; 73 std::strcpy(str, pt); 74 num_strings++; //对象数目加1 75 return *this; //返回调用该方法的指针 76 } 77 78 //对[]的重构函数定义 79 //调用方法为: 假如有String name("Hello"); 80 //那么可以使用 name[1],name[1]就等价于字符串中的第二个元素 81 char & String::operator[](int i) 82 { 83 return str[i]; //直接返回字符串指针中的第i+1个元素就好了 84 } 85 86 //用法和上边的是一样的,只不过该方法不允许修改值 87 //比如修改name.str中第二个元素: name[1]='M'; 在此方法下是不合法的 88 const char & String::operator[](int i) const 89 { 90 return str[i]; //直接返回字符串指针中的第i+1个元素就好了 91 } 92 93 /* 以下为友元函数定义 */ 94 //strcmp(a,b); 如果a参数位于第二个参数b之前,则返回一个负值 95 //如果第一个参数位于第二个参数之后,则返回一个正值 96 //如果两个参数相等,则返回0 97 98 //对小于号的重载 99 bool operator<(const String & st1, const String & st2) 100 { 101 return (std::strcmp(st1.str, st2.str) < 0); 102 //如果std::strcmp(st1.str, st2.str) < 0 这个表达式成立则返回ture,否则返回false 103 } 104 105 //对大于号的重载 106 bool operator>(const String & st1, const String & st2) 107 { 108 return st1 < st2; //调用上面的operator<()函数 109 } 110 111 //对恒等于号的重载 112 bool operator==(const String & st1, const String & st2) 113 { 114 return (std::strcmp(st1.str, st2.str) == 0); 115 } 116 117 //对输出运算符的重载函数的定义 118 ostream & operator<<(ostream & os, const String & st) 119 { 120 os << st.str; 121 return os; 122 } 123 124 //对输入运算符的重载函数的定义 125 //调用方法为:String name; cin>>name; 126 istream & operator>>(istream & is, String & st) 127 { 128 char temp[String::CINLIM]; 129 is.get(temp, String::CINLIM); 130 if (is) //判断上一句是否输入成功 131 st = temp; 132 while (is && is.get() != '\n') //过滤掉输入流中的换行符 133 continue; 134 return is; 135 }
1 //usret_main.cpp 2 #include <iostream> 3 #include "string1.h" 4 5 const int Arsize = 10; 6 const int MaxLen = 81; 7 8 int main() 9 { 10 using std::cout; 11 using std::cin; 12 using std::endl; 13 14 String name; //使用默认构造函数创建一个对象name 15 cout << "Hi,what's your name?" << endl; 16 cin >> name; //使用对>>的重构函数输入到对象name中的str中去 17 cout << name << ", please enter up to " << Arsize << " short sayings <empty line to quit>" << endl; 18 String sayings[Arsize]; //创建一个数组,该数组内包含了Arsize个String类对象 19 char temp[MaxLen]; //新建一个字符串数组,用来存储从键盘输入的字符串 20 int i; 21 for (i = 0; i < Arsize; i++) 22 { 23 cout << i + 1 << " :"; 24 cin.get(temp, MaxLen); //输入字符串到字符串数组temp中去,最多可输入MaxLen个字符 25 while (cin && cin.get() != '\n') //过滤掉最后输入的换行符 26 continue; 27 if (!cin || temp[0] == '\0') //结束最外层while循环的条件,输入为空,则temp的第一个字符为空字符,且cin输入失败,返回值为0 28 break; 29 else 30 sayings[i] = temp; //调用String & String::operator=(const char* pt)函数,调用方法为sayings[i].operator=(temp); 31 } 32 int total = i; //保存输入的字符串的总个数 33 if (total > 0) 34 { 35 cout << "Here are your sayings:" << endl; 36 for (i = 0; i < total; i++) 37 cout << sayings[i][0] << ": " << sayings[i] << endl; 38 //sayings[i][0]表示调用char & String::operator[](int i)函数,调用方法为sayings[i].operator[](0),取出对象sayings[i].str中的第一个字符 39 //cout<<sayings[i][0]首先调用对<<的重载函数,返回一个cout,之后再调用对[]的重载函数, 40 //cout<<sayings[i]则直接调用对<<的重载函数了 41 42 /* 接下来找到字符串最短的和字符串首字母拍在最前的字符串 */ 43 int shortest = 0; 44 int first = 0; 45 for (i = 0; i < total; i++) 46 { 47 if (sayings[i].length() < sayings[shortest].length()) //仅仅是比较对象中的字符串长度 48 shortest = i; 49 if (sayings[i] < sayings[first]) //调用对<的重载函数 50 first = i; 51 } 52 cout << "Shortest saying:\n" << sayings[shortest] << endl; 53 cout << "First alphabetically:\n" << sayings[first] << endl; 54 cout << "The program used " << String::HowMany() << " String objects. Bye.\n"; 55 } 56 else 57 cout << "No input! bye.\n"; 58 59 system("pause"); 60 return 0; 61 }
执行结果:
在上面对输入运算符(>>)重载的函数中operator>>(istream & is, String st)使用了这个语句:st = temp; 其中st是一个String对象,temp是一个char型数组,所以该句会调用对等号的重载函数operator=(const char* pt)
而temp是一个char型数组名,本身就是一个地址,所以直接赋值给char型指针是可以的,如下进行了验证:
在构造函数中使用new时应该注意的问题(什么时候该编写复制构造函数和赋值重构函数)
01)如果在构造函数中使用new来初始化指针成员,则应该在析构函数中使用delete;
02)new和delete应该互相兼容: new对应delete,new[]对应delete[];
03)可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空(0或C++11中的nullptr)
因为delete(无论是delete还是delete[])都可以用于空指针;
04)如果在构造函数中使用new来初始化指针成员,则应该定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象
,具体的说,赋值构造函数一个分配足够的空间来存储复制的数据,并复制数据,而不是仅仅复制数据的地址,
复制构造函数的结构与上述的复制构造函数结构类似;
05)如果在构造函数中使用new来初始化指针成员,则应该定义一个赋值运算符重构函数,通过深度复制将一个对象复制给另一个对象
具体的说,该方法完成以下操作:检查是否进行了自我赋值,释放成员指针以前指向的内存,复试数据而不是仅仅复制数据的地址
返回一个指向调用对象的引用(提供this指针来完成),
赋值运算符重构函数与上述的赋值运算符重构函数结构类似;
以下列出了另个不正确的例子(构造函数)
1 String::String() 2 { 3 str = "default string"; //错误,没有为str分配存储空间 4 len = std::strlen(str); 5 } 6 String::String(const char* s) 7 { 8 len = std::strlen(s); 9 str = new char; //错误,没有使用[],分配的空间是不确定的 10 std::strcpy(str, s); 11 }
对于第一个错误的实例,可以使用以下任意一种方法
1 String::String() 2 { 3 len = 0; 4 str = new char[1]; 5 str = '\0'; 6 } 7 String::String() 8 { 9 len = 0; 10 str = 0; //直接给str赋值为空指针 11 } 12 String::String() 13 { 14 static const char* s = "C++"; //静态变量,只会执行一次 15 len = std::strlen(s); 16 str = new char[len + 1]; 17 std::strcpy(str, s); 18 }
包含类成员的类的逐成员复制
1 class Magazine 2 { 3 private: 4 String title; //使用自己定义的String类去定义对象 5 string publisher; //使用标准string类去定义对象 6 };
01)String类和string类都是要动态内存分配,这是否意味着也需要给Magazine类去编写复制构造函数和赋值运算符重构函数呢
02)答案是不需要。
03)如果您将一个Magazine对象复制或赋值给另一个Magazine对象,在复制成员title时,将使用String类中的复制构造函数
接下来将title赋值给另一个Magazine对象时,将使用String类的赋值重构函数;同理复制或赋值publisher将使用string类
中的复制构造函数和赋值重构函数
返回对象还是指向对象的引用?
case1:返回类型为指向const对象的引用
1 Vector Max(const Vector v1, const Vector v2) //返回类型为对象 2 { 3 if (v1.magval() > v2.magval()) 4 return v1; 5 else 6 return v2; 7 } 8 const Vector & Max(const Vector v1, const Vector v2) //返回类型为指向对象的引用 9 { 10 if (v1.magval() > v2.magval()) 11 return v1; 12 else 13 return v2; 14 }
说明:
01) 返回类型为对象则将调用复制构造函数,而返回指向对象的引用则不会;
02) 引用指向的对象应该在调用执行函数时存在,即该类方法执行完毕后。返回的对象还存在;
03) 由于v1和v2都是const常量,那么返回的类型也必须是const常量。
case2:返回类型为指向非const对象的引用
01)在等号运算符重构函数的声明中使用了指向非const对象的引用:String & operator=(const String & st)
对于如下代码,解释其原因:
String s1("Good Stuff");
String s2,s3;
s3 = s2 = s1;
此时返回类型为String或者是String &均可,但是为了提高效率,使用String &,而返回类型不是const,是因为
方法s2=s1中,具体的调用方法为s2.operator=(s1),operator=()返回一个指向s2的引用,可以对其进行修改
02)在对<<运算符的重构函数声明中:friend ostream & operator<<(ostream & os, const String & st);
使用了指向ostream对象的引用,是因为ostream类中不存在复制构造函数,所以必须使用引用。
case3:返回类型为对象
如果返回的对象时被调用函数中的一个局部变量,则不应该按引用方式返回它
如果返回的对象时一个局部变量,那么返回类型只能是对象,如下代码:
Vector force1(50,60);
Vector force2(40,70);
Vector net;
net = force1+force2; //将会调用复制构造函数来创建临时对象来表示返回值,后该临时对象被复制给net,这是无法避免的
那么在编写对+运算符的重载函数时,返回值的类型只能是对象:
Vector Vector::operator+(const Vector &b) const //最后一个const表示不可修改调用该方法的对象中的数据
{
return Vector(x+b.x,y+b.y); //返回值为局部变量,所以返回值类型不能为引用
}
case4:返回类型为const对象
将对+运算符的重载函数的返回值声明为const常量的好处为(const Vector operator+(const Vector &b) const):
01) net = force1+force2; //合法
02) force1+force2 = net; //非法
指向对象的指针
01)假如Class_name是类名,变量value的类型为Type_name,则指向对象的指针一般形式为:
Class_name* pclass = new Class_name(value);
将调用如下的构造函数:
Class_name(Type_name);
02)下面的初始化方式将调用默认构造函数:
Class_name* ptr = new Class_name;
03)指针和对象的小结:
A 使用常规表示方法来声明指向对象的指针:
String* glamour; //声明指向String类的指针
B 可以将指针初始化为指向已有的对象:
Stirng* first = &sayings[0]; //注:sayings[]是一个对象数组
C 对类使用new将调用相应的构造函数来初始化新创建的对象:
String* gleep = new String; //使用默认构造函数创建对象,并让指针gleep指向该对象
String* glop = new String("my my my"); //使用String(const char*)构造函数来创建对象
String* favorite new String(sayings[choice]); //使用String(const String &)构造函数来创建对象
D 可以使用->运算符通过指针来访问类方法:
String* ptr = &sayings[0]; //其中sayings[]是一个对象数组,ptr指向数组内的第一个对象
ptr->length(); //使用ptr访问类方法length()
E 可以对对象使用接触引用运算符(*)来获得对象:
String* first = &sayings[0]; //first同样指向对象数组sayings[]内的第一个元素
if (sayings[i] < *first) //通过*来获取first指向的对象,并调用对小于号的重载函数operator<()
firts = &sanyings[i];
在对象的基础上再谈new和delete
01)假如有如下cpp文件:
class Act { ... };
...
Act nice; //在函数外定义的为静态变量,在整个程序执行期间都存在,对象nice即为静态对象
//定义静态变量的方法:(1)在函数外定义 (2)在变量定义时使用关键字static
...
int main()
{
Act* pt = new Act; //创建指向Act的指针,并未pt分配内存,pt即为动态变量对象
{
Act up; //up对象时自动变量,在该程序块执行完后消失
...
} //该程序块执行完后,将调用up对应的析构函数
delete pt; //对指针pt应用delete时,将调用*pt对应的析构函数
...
} //整个程序结束时,将调用静态对象nice对应的析构函数
02)如果对象时由new创建的,则仅当显式使用delete删除对象时,其析构函数才会被调用
2019-05-06 09:14 周一
使用new定位运算符为指针对象分配内存空间,但是此版本有问题 m14
第九章介绍了有关new定位运算符的相关知识
问题一:
JustTesting *pc1, *pc2; //创建两个指向JustTesting对象的指针
pc1 = new (buffer) JustTesting; //使用定位new运算符返回buffer的(JustTesting对象的)地址给pc1,并使用默认构造函数的默认参数创建JustTesting对象
pc2 = new JustTesting("Heap1", 20); //使用常规new运算符返回一个可以存储JustTesting对象中数据的首地址,并使用新的数据创建对象
JustTesting *pc3, *pc4; //创建两个指向JustTesting对象的指针
pc3 = new (buffer) JustTesting("Bad Idea",6); //再次使用new定位运算符,但是此时新对象中的数据会覆盖掉pc1指向的对象中的数据
//解决方法:pc3 = new (buffer+sizeof(JustTesting)) JustTesting("Bad Idea",6);
pc4 = new JustTesting("Heap2", 10); //再次使用new常规运算符为新的对象中的数据分配内存,此时不会覆盖掉任何数据
问题二:
//如果程序员使用定位new运算符为对象分配内存,那么一定要确保其析构函数被调用
//释放内存,由于delete只能释放由常规new运算符创建的内存,由定位new运算符是不能直接使用delete释放内存的
//即 delete pc1;和delete pc3;是会报错的,
//解决方法是使用指向对象的指针显示的调用析构函数:pc3->~JustTesting(); pc1->~JustTesting();
1 //使用new定位运算符为指针对象分配内存空间,但是此版本有问题 2 //第九章介绍了有关new定位运算符的相关知识 3 #include <iostream> 4 #include <string> 5 #include <new> //在#include <iostream>中就已经包含了new头文件,这里不加该句也可以 6 7 using namespace std; 8 9 const int BUF = 512; 10 11 class JustTesting 12 { 13 private: 14 string words; 15 int number; 16 public: 17 /* 带默认参数构造函数的定义 */ 18 JustTesting(const string & s = "Just Testing", int n = 0) 19 { 20 words = s; 21 number = n; 22 cout << words << " was constructed\n"; 23 } 24 /* 析构函数定义 */ 25 ~JustTesting() 26 { 27 cout << words << " was destroyed\n"; 28 } 29 /* 普通类方法定义 */ 30 void Show() const //最后的const表明调用该类方法的对象中的数据不可更改 31 { 32 cout << words << ", " << number << endl; 33 } 34 }; 35 36 int main() 37 { 38 char* buffer = new char[BUF]; //创建一个512字节的缓冲区,由于是用new创建的,所以是动态存储,该存储区位于堆中 39 //一个char型变量占用一个字节 40 41 JustTesting *pc1, *pc2; //创建两个指向JustTesting对象的指针 42 43 pc1 = new (buffer) JustTesting; //使用定位new运算符返回buffer的(JustTesting对象的)地址给pc1,并使用默认构造函数的默认参数创建JustTesting对象 44 pc2 = new JustTesting("Heap1", 20); //使用常规new运算符返回一个可以存储JustTesting对象中数据的首地址,并使用新的数据创建对象 45 46 cout << "Memory bloak addresses:\n" << "buffer: " << (void *)buffer << " heap: " << pc2 << endl; 47 cout << "Memory contents:\n"; 48 cout << pc1 << ": "; //此处打印(pc1的)地址 49 pc1->Show(); 50 cout << pc2 << ": "; //此处打印(pc2的)地址 51 pc2->Show(); 52 53 JustTesting *pc3, *pc4; //创建两个指向JustTesting对象的指针 54 pc3 = new (buffer) JustTesting("Bad Idea",6); //再次使用new定位运算符,但是此时新对象中的数据会覆盖掉pc1指向的对象中的数据 55 //解决方法:pc3 = new (buffer+sizeof(JustTesting)) JustTesting("Bad Idea",6); 56 pc4 = new JustTesting("Heap2", 10); //再次使用new常规运算符为新的对象中的数据分配内存,此时不会覆盖掉任何数据 57 cout << "Memory contents:\n"; 58 cout << pc3 << ": "; //此处打印(pc3的)地址 59 pc3->Show(); 60 cout << pc4 << ": "; //此处打印(pc2的)地址 61 pc4->Show(); 62 63 //释放内存,由于delete只能释放由常规new运算符创建的内存,由定位new运算符是不能直接使用delete释放内存的 64 //且 delete pc1;和delete pc3;是会报错的, 65 //解决方法是显示的调用析构函数:pc3->~JustTesting(); pc1->~JustTesting(); 66 delete pc2; //先删除后创建的指向对象的指针 67 delete pc4; 68 delete[] buffer; 69 70 cout << "Done\n"; 71 72 system("pause"); 73 return 0; 74 75 }
执行结果:这样执行的确是不会报错,但是该程序是有问题的,即没有释放(删除)pc3和pc1指向的对象
1 //使用new定位运算符为指针对象分配内存空间,正确的版本 2 //第九章介绍了有关new定位运算符的相关知识 3 #include <iostream> 4 #include <string> 5 #include <new> //在#include <iostream>中就已经包含了new头文件,这里不加该句也可以 6 7 using namespace std; 8 9 const int BUF = 512; 10 11 class JustTesting 12 { 13 private: 14 string words; 15 int number; 16 public: 17 /* 带默认参数构造函数的定义 */ 18 JustTesting(const string & s = "Just Testing", int n = 0) 19 { 20 words = s; 21 number = n; 22 cout << words << " was constructed\n"; 23 } 24 /* 析构函数定义 */ 25 ~JustTesting() 26 { 27 cout << words << " was destroyed\n"; 28 } 29 /* 普通类方法定义 */ 30 void Show() const //最后的const表明调用该类方法的对象中的数据不可更改 31 { 32 cout << words << ", " << number << endl; 33 } 34 }; 35 36 int main() 37 { 38 char* buffer = new char[BUF]; //创建一个512字节的缓冲区,由于是用new创建的,所以是动态存储,该存储区位于堆中 39 //一个char型变量占用一个字节 40 41 JustTesting *pc1, *pc2; //创建两个指向JustTesting对象的指针 42 43 pc1 = new (buffer) JustTesting; //使用定位new运算符返回buffer的(JustTesting对象的)地址给pc1,并使用默认构造函数的默认参数创建JustTesting对象 44 pc2 = new JustTesting("Heap1", 20); //使用常规new运算符返回一个可以存储JustTesting对象中数据的首地址,并使用新的数据创建对象 45 46 cout << "Memory bloak addresses:\n" << "buffer: " << (void *)buffer << " heap: " << pc2 << endl; 47 cout << "Memory contents:\n"; 48 cout << pc1 << ": "; //此处打印(pc1的)地址 49 pc1->Show(); 50 cout << pc2 << ": "; //此处打印(pc2的)地址 51 pc2->Show(); 52 53 JustTesting *pc3, *pc4; //创建两个指向JustTesting对象的指针 54 pc3 = new (buffer + sizeof(JustTesting)) JustTesting("Bad Idea", 6); //再次使用new定位运算符这样就不会覆盖掉pc1指向的内存中的数据 55 pc4 = new JustTesting("Heap2", 10); //再次使用new常规运算符为新的对象中的数据分配内存,此时不会覆盖掉任何数据 56 cout << "Memory contents:\n"; 57 cout << pc3 << ": "; //此处打印(pc3的)地址 58 pc3->Show(); 59 cout << pc4 << ": "; //此处打印(pc2的)地址 60 pc4->Show(); 61 62 delete pc2; //先删除后创建的指向对象的指针,释放在堆中创建的内存 63 delete pc4; 64 pc3->~JustTesting(); //显示的调用析构函数,以删除pc3指向的对象 65 pc1->~JustTesting(); //显示的调用析构函数 66 delete[] buffer; 67 68 cout << "Done\n"; 69 70 system("pause"); 71 return 0; 72 73 }
执行结果: