// pass by value-程序清单 12.3 vegnews.cpp
// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include <iostream>
using std :: cout:
finclude "strngbad.h"void callmel (StringBad 6): // pass by reference
void callme2 (StringBad);int main()using std :: endl;
StringBad headlinel ("Celery Stalks at Midnight");
StringBad headline2 ("Lettuce Prey");
StringBad sports ("Spinach leaves Bowl for Dollars"):
cout << "headlinel: " << headlinel << endl;
cout << "headline2: " << headline2 << endl:
cout << "sports: " << sports << endl;
callmel (headlinel):
cout << "headlinel: " << headlinel << endl:
callme2 (headline2):
cout << "headline2: " << headline2 << endl:
cout << "Initialize one object to another: \n",
StringBad sailor - sports:
cout << "sailor: " << sailor << endl:
cout << "Assign one object to another: \n";
StringBad knot:
knot = headlinel:
cout << "knot: " << knot << endl:
cout << "End of mainlb\n"rreturn 0:382C++Primer Plus(第五版)中文版void callmel (StringBad & rsb)cout << "String passed by referencer \n":
cout << " \*" << rsb << "\"\n":-void callme2 (StringBad sb)cout << "String passed by value: \n";
cout << " \"" << sb << "\"\n";-
注意:StringBad的第一个版本有许多故意留下的缺陷,这些缺陷使得输出是不确定的。例如,有些编
译器无法编译它。虽然输出的具体内容有所差别,但基本问题和解决方法(精后将介绍)是相同的。
下面是使用Borland C++5.5命令行编译器进行编译时,该程序的输出:
1: "Celery Stalks at Midnight" object created
2: "Lettuce Prey" object created
3: "Spinach Leaves Bowl for Dollars" object created
headlinel: Celery Stalks at Midnight
headline2: Lettuce Prey
sports: Spinach Leaves Bowl for Dollars
String passed by reference:
"Celery Stalks at Midnight"
headlinel: Celery Stalks at Midnight
String passed by value:
"Lettuce Prey"
"Lettuce Prey" object deleted, 2 left
headline2: D o
Initialize one object to another:
sailor: Spinach Leaves Bowl for Dollars
Assign one objeet to another;
3: "C++" default object created
knot: Celery Stalks at Midnight
End of main()
"Celery Stalks at Midnight" object deleted. 2 left
"Spinach Leaves Bowl for Dollars" object deleted, 1 left
"Spinach Leaves Bowl for Doll8" object deleted, 0 left
"@g" object deleted, -1 left
"=|" object deleted, -2 left
输出中出现的各种非标准字符随系统而异,这些字符表明,StringBad类名副其实(是一个糟糕的类)。
另一种迹象是对象计数为负。在使用较新的编译器和操作系统的机器上运行时,该程序通常会在显示有关
还有-1个对象的信息之前中断,而有些这样的机器将报告通用保护错误(GPF)。GPF表明程序试图访问禁
止它访问的内存单元,这是另一种糟糕的信号。
程序说明
程序清单12.3中的程序开始时还是正常的,但逐渐变得异常,最终导致了灾难性结果。首先来看正常
的部分。构造函数指出自己创建了3个StringBad对象,并为这些对象进行了编号,然后程序使用重载操
作符>>列出了这些对象:
l: "Celery Stalks at Midnight" object created
2: "Lettuce Prey" object created
3: "Spinach Leaves Bowl for Dollars" object created
headlinel: Celery Stalks at Midnight
headline2: Lettuce Prey
sports: Spinach Leaves Bowl for Dollars
第12章 类和动态内存分配
然后,程序将headline1传递给cllme1()函数,并在调用后重新显示headlinel。代码如下:
callmel (headlinel) :
cout << "headlinel: " << headlinel << endl;
下面是运行结果:
String passed by reference:
"Celery Stalks at Midnight"
headlinel: Celery Stalks at Midnight
这部分代码看起来也是正常的。
但是随后程序执行了如下代码:
callme2 (headline2) :
cout << "headline2: " << headline2 << endl:
这里,callme2()按值(而不是按引用)传递headline2,结果表明这是一个严重的问题!
String passed by value:
"Lettuce Prey"
"Lettuce Prey" object deleted, 2 left
headline2: D ọ
首先,将headline2作为函数参数来传递从而导致析构函数被调用。其次,虽然按值传递可以防止原始
参数被修改,但实际上函数已使原始字符串无法识别,导致显示一些非标准字符(显示的具体内存取决于
内存中包含的内容)。
请看输出结果,在为每一个创建的对象自动调用析构函数时,情况更糟糕:
End of main ()
"Celery Stalks at Midnight" object deleted, 2 left
"Spinach Leaves Bowl for Dollars" object deleted, 1 left
"Spinach Leaves Bowl for Doll8" object deleted, 0 left
"@g" object deleted, -1 left
"-|" object deleted, -2 left
因为自动存储对象被删除的顺序与创建顺序相反,所以最先删除的3个对象是knots、sailor和sport。
删除knots和 sailor时是正常的,但在删除 sport时,Dollars变成了Doll8。对于sport,程序只使用它来初
始化sailor,但这种操作修改了sport。最后被删除的两个对象(headline2和headline1)已经无法识别。这
些字符串在被删除之前,有些操作将它们搞乱了。另外,计数也很奇怪,如何会余下-2个对象呢?
实际上,计数异常是一条线索。因为每个对象被构造和析构一次,因此调用构造函数的次数应当与析
构函数的调用次数相同。对象计数(num_strings)递减的次数比递增次数多2,这表明使用了不将 num_string
递增的构造函数创建了两个对象。类定义声明并定义了两个构造函数(这两个构造函数都使num_string递
增),但结果表明程序使用了3个构造函数。例如,请看下面的代码:
StringBad sailor = sports:
这使用的是哪个构造函数呢?不是默认构造函数,也不是参数为const char*的构造函数。记住,这种
形式的初始化等效于下面的语句:
StringBad sailor = StringBad (sports) : //constructor using sports
因为 sports的类型为StringBad,因此相应的构造函数原型应该如下:
StringBad (const StringBad &) :
当您使用…个对象来初始化另一个对象时,编译器将自动生成上述构造函数(称为复制构造函数,
因为它创建对象的一个副本)。自动生成的构造函数不知道需要更新静态变量num_string,因此会将计数
方案搞乱。实际上,这个例子说明的所有问题都是由编译器自动生成的成员函数引起的,下面介绍这一
主题。