.
7. 解决赋值的问题
对于由于默认赋值操作符不合适而导致的问题,解决办法是提供赋值操作符(进行深度复制)定义。
其实现与复制构造函数相似,但也有一些差别。
● 由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]来释放这些数据。
函数应当避免将对象赋给自身;否则,给对象重新赋值之前,释放内存操作可能删除对象的内容。
● 函数返回一个指向调用对象的引用。
通过返回一个对象,函数可以像常规赋值操作那样,连续进行赋值,即如果S0、S1和S2都是StringBad
对象,则可以编写这样的代码:
S0 = S1 = S2:
使用函数表示法时,上述代码为:
S0.operator= (S1.operator= (s2) ) :
因此,S1.operator=(S2)的返回值是函数SO.operator=()的参数。
因为返回值是一个指向StringBad对象的引用,因此参数类型是正确的。
下面的代码说明了如何为StringBad类编写赋值操作符:
StringBad & StringBad :: operator= (const StringBad & st)
if (this == &st)
return *this:
delete [] str:
len = st.len:
str = new char [len + 1]:
// object assigned to itself
// all done
// free old string
// get space for new string
std :: strcpy (str. st.str);
return *this:
第12章 类和动态内存分配
// copy the string
// return reference to invoking object
代码首先检查自我复制,这是通过查看赋值操作符右边的地址(&s)是否与接收对象(this)的地址
相同来完成的。如果相同,程序将返回*this,然后结束。第10章介绍过,赋值操作符是只能由类成员函数
重载的操作符之一。
如果地址不同,函数将释放str指向的内存,这是因为稍后将把一个新字符串的地址赋给str。如果不
首先使用delete操作符,则上述字符串将保留在内存中。由于程序中不再包含指向该字符串的指针,因此
这些内存被浪费掉。
接卜来的操作与复制构造函数相似,即为新字符串分配足够的内存空间,然后将赋值操作符右边的对
象中的字符串复制到新的内存单元中。
上述操作完成后,程序返回*this并结束。
赋值操作并不创建新的对象,因此不需要调整静态数据成员 num_strings的值。
将前面介绍的复制构造函数和赋值操作符添加到StringBad类中后,所有的问题都解决了。例如,下
面是在完成上述修改后,程序输出的最后几行:
End of main()
"Celery Stalks at Midnight" object deleted, 4 left
"Spinach Leaves Bowl for Dollars" object deleted, 3 left
"Spinach Leaves Bowl for Dollars" object deleted, 2 left
"Lettuce Prey" object deleted. 1 left
"Celery Stalks at Midnight" object deleted, 0 left
现在,对象计数是正确的,字符串也没有被损坏。
12.1.3 改进后的新String类
有了更丰富的知识后,可以对StringBad类进行修订,将它重命名为String。首先,添加前面介绍过的
复制构造函数和赋值操作符,使类能够正确管理类对象使用的内存。其次,由于我们已经知道对象何时被
创建和释放,因此可以让类构造函数和析构函数保持沉默,不再在每次被调用时都显示消息。另外,也不
用再监视构造函数的工作情况,因此可以简化默认构造函数,使之创建一个空字符串,而不是“C++”。
接下来,可以在类中添加一些新功能。String类应该包含标准字符串函数库cstring的所有功能,才会
比较有用,但这里只添加足以说明其工作原理的功能(注意,String类只是一个用作说明的范例,而C++
标准string类的内容丰富得多)。具体地说,将添加以下方法:
int length()const { return len: }
friend bool operator< (const String &st, const String &st2);
friend bool operator> (const String astl, const String &st2);
friend bool operator == (const String &st, const String &st2);
friend operator>> (istream & is, String & st):
char & operator(] (int i):
const char & operator[] (int i) const:
static int HowMany ():
第…个新方法返回被存储的字符串的长度。接下来的3个友元函数能够对字符串进行比较。Operator>>()
函数提供了简单的输入功能:两个operator[](函数提供了以数组表示法访问字符串中各个字符的功能。静
态类方法 Howmany()将补充静态类数据成员num_string。下面来看一看具体情况。
1. 修订后的默认构造函数
需要注意的是新的默认构造函数,它与下面类似:
String: :String()
len = 0:
str = new char[1]:
str[0] = '\0':
-
// default string
390
C++Primer Plus(第五版)中文版
读者可能会问,为什么代码为:
str = new char[1]:
而不是:
str = new char:
上面两种方式分配的内存量相同,区别在于前者与类析构函数兼容,而后者不兼容。析构函数中包含
如下代码:
delete [] str:
delete[]与使用 new[]初始化的指针和空指针都兼容。因此对于下述代码:
str = new char[1]:
str{0] = '\0';
可修改为:
str - 0: // sets str to the null pointer
对于以其他方式初始化的指针,使用delete[]时,结果将是不确定的:
char words [15] = "bad idea";
char * pl= words:
char * p2 = new char;
char * p3:
delete [] pl: // undefined. so don't do it
delete [] p2: // undefined, so don't do it
delete [] p3: // undefined, so don't do it
// default string
2. 比较成员函数
在String类中,执行比较操作的方法有3个。如果按字母顺序(更准确地说,按照机器排序序列),第
一个字符串在第二个字符串之前,则Operator<()函数返回true。要实现字符串比较函数,最简单的方法是
使用标准的trcmp()函数,如果依照字母顺序,第一个参数位于第二个参数之前,则该函数返回一个负值;
如果两个字符串相同,则返回0;如果第一个参数位于第二个参数之后,则返回一个正值。因此,可以这
样使用 strcmp():
bool operator< (const String sst1, const String sst2)
if (std :: strcmp (stl.str, st2.str) > 0)
return true:
else
return false:
因为内置的>操作符返回的是一个布尔值,所以可以将代码进一步简化为:
bool operator< {const String sstl, const String sst2)
return (std :: stremp (stl.str. st2.str) < 0);
同样,可以按照下面的方式来编写另外两个比较函数:
bool operator> (const String &st1, const String &st2)
return st2.str < stl.str:
bool operatorm= (const String &stl, const String &st2)
return (std :: stremp (stl.str. st2.str) == 0);
第一一个定义利用了<操作符来表示>操作符,对于内联函数,这是一种很好的选择。
将比较函数作为友元,有助于将String对象与常规的C字符串进行比较。例如,假设answer 是String
对象,则下面的代码:
if ("love" -- answer)
将被转换为:
-
第12章 类和动态内存分配
391
if loperator == ("love". answer) )
然后,编译器将使用某个构造函数将代码转换为:
if (operator == (String ("love") , answer) )
这与原型是相匹配的。