程序清单 12.2 strgbad.cpp
// strngbad.cpp -" StringBad class methods
// string.h for some
#include <cstring>
#include "strngbad.h"
using std :: cout;
// initializing static class member
int StringBad :: num_strings = 0;
// class methods
// construct StringBad from C string
StringBad :: StringBad(const char * s)
len = std: :strlen(s);
Str m new char[len + 1];
std :: strcpy(str, s) ;
num_strings++;
cout << num_strings << ": \"" << str
<< "\" object created\n";
// set size
// allot storage
// initialize pointer
// set object count
// For Your Information
-
StringBad: :StringBad ()
len = 4;
str = new char[4];
std :: strepy(str, "C++");
num_strings++;
cout << num_strings << ": \"" << str
<< "\" default object created\n"; // FYI
// default constructor
// default string
1
StringBad ::~ StringBad()
// necessary destructor
380
// FYI
// required
// required
C++Primer Plus(第五版)中文版
cout << "\"" << str << "\" object deleted, ";
-- num_strings;
cout << num_strings << " left\n"; // FYI
delete [| str;
std :: ostream & operator << (std :: ostream & os, const StringBad & st)
os << st.str;
return os;
len = std :: strlen(s);
str = new char[len + 1];
std :: strcpy(str, s);
num_strings++;
首先,请注意程序清单12.2中的下面一条语句:
int StringBad :: num_strings = 0:
这条语句将静态成员 num_strings的值初始化为0。请注意,不能在类声明中初始化静态成员变量,这
是因为声明描述了如何分配内存,但并不分配内存。您可以使用这种格式来创建对象,从而分配和初始化
内存。对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存
储的,而不是对象的组成部分。请注意,初始化语句指出了类型,并使用了作用域操作符。
初始化是在方法文件中,而不是在类声明文件中进行的,这是因为类声明位于头文件中,程序可能将
头文件包括在其他几个文件中。如果在头文件中进行初始化,将出现多个初始化语句副本,从而引发错误。
对于不能在类声明中初始化静态数据成员的一种例外情况(见第10章)是,静态数据成员为整型或枚
举型 const:
记住:静态数据成员在类声明中声明,在包含类方法的文件中初始化。初始化时使用作用域操作符来
指出静态成员所属的类,但如果静态成员是整型或枚举型const,则可以在类声明中初始化。
每个构造函数都包含表达式 num_strings++,这确保程序每创建一个新对象,共享变量num_strings的
值都将增加1,从而记录String对象的总数。另外,析构函数包含表达式 -- num_strings,因此String类也将
跟踪对象被删除的情况,从而使num_string成员的值是最新的。
现在来看程序清单12.2中的第一个构造函数,它使用一个常规C字符串来初始化String对象:
StringBad: :StringBad(const char * s)
// set size
// allot storage
// initialize pointer
// set object count
cout << num_strings << ": \"" << str
<< "\" object created\n"; // For Your Information
类成员str是·个指针,因此构造函数必须提供内存来存储字符串。初始化对象时,可以给构造函数传
递一个字符串指针:
String boston ("Boston") :
构造函数必须分配足够的内存来存储字符串,然后将字符串复制到内存中。下面介绍其中的每一个
步骤。
首先,使用strlen()函数计算字符串的长度,并对len成员进行初始化。接着,使用new分配足够的空
间来保存字符串,然后将新内存的地址赋给 str 成员(strlen()返回字符串长度,但不包括末尾的空字符,
因此构造函数将len加1,使分配的内存能够存储包含空字符的字符串)。
接着,构造函数使用 strcpy()将传递的字符串复制到新的内存中,并更新对象计数。最后,构造函数
显示当前的对象数目和当前对象中存储的字符串,以助于掌握程序运行情况。稍后当我们故意使String类
出错时,该特性将派上用场。
要理解这种方法,必须知道字符串并不保存在对象中。字符串单独保存在堆内存中,对象仅保存了指
出到哪里去查找字符串的信息。
-
不能这样做:
str = s: // not the way to go
这里只保存了地址,而没有创建字符串副本。
默认构造函数与此相似,不过它提供了一个默认字符串:“C++”。
析构函数中包含了范例中对处理类来说最重要的东西:
StringBad ::~ StringBad ()
cout << "\"" << str << "\" object deleted, ";
-- num strings:
cout << num strings << " left\n";
delete [] str:
析构函数首先指出自己何时被调用。这部分包含了丰富的信息,但并不是必不可少的。不过,delete
语句却是至关重要的。str成员指向new 分配的内存。当StringBad对象过期时,str指针也将过期。但 str
指向的内存仍被分配,除非使用delete将其释放。删除对象可以释放对象本身占用的内存,但并不能自动
释放属于对象成员的指针指向的内存。因此,必须使用析构函数。在析构函数中使用delete语句可确保对
象过期时,由构造函数使用new分配的内存被释放。
记住:在构造函数中使用new来分配内存时,必须在相应析构函数中使用delete来释放内存。如果使
用new[](包括中括号)来分配内存,则应使用delete[](包括中括号)来释放内存。