本章内容包括:对类成员使用动态内存分配;隐式和显式复制构造函数;隐式和显式重载赋值运算符;在构造函数中使用new所必须完成的工作;使用静态类成员;将定位new运算符用于对象;使用指向对象的指针;实现队列抽象数据类型(ADT)。
12.1 动态内存和类
C++在分配内存时采取的策略是让程序在运行时决定内存分配,而不是在编译时再做决定。C++使用new和delete运算符来动态控制内存。遗憾的是,在类中使用这些运算符就会出现许多新的编程问题。在这种情况下,析构函数将是必不可少的。
- 让程序在运行时决定内存分配,而不是在编译时决定。
- C++使用new和delete运算符来动态控制内存,在类中使用这些运算符,析构函数将是必不可少的。
- 类对象定义的字符串并不保存在对象中。字符串单独保存在堆内存中,对象仅保存了指出到哪里去查找字符串的信息。
- 在构造函数中使用new来分配内存时,必须在相应的析构函数中使用delete来释放内存。如果使用new[](包括中括号)来分配内存,则应使用delete[](包括中括号)来释放内存。
12.1.1 复习示例和静态类成员
程序清单12.1,12.2,12.3;一个不完整的类,类方法实现,演示了StringBad类的构造函数和析构函数何时运行以及如何运行的; · d1e76f9 · Kite/C和C++ - Gitee.com
上述代码需要注意的有两点,私有成员中使用了char指针,而不是char数组来表示姓名。这意味着类声明没有为字符串本身分配存储空间,而是在构造函数中使用new来为字符串分配空间。这避免了在类声明中预先定义字符串的长度。
其次,将num_strings成员声明为静态存储类。静态存储类有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本。
下面解释一下程序清单12.2,
int StringBad::num_strings = 0;// 初始化静态变量,用于记录创建的类数量
不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。
对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。
注意,初始化部分指出了类型,并使用了作用域运算符,但是没有使用关键字static。
静态数据成员在类声明中声明,在包含类方法的文件中初始化。初始化时使用作用域运算符来指出静态成员所属的类。但如果静态成员是const整数类型或枚举型,则可以在类声明中初始化。
说明一下为什么不要在类声明中初始化,这是因为类声明位于头文件中,这个头文件可能在多个文件中引用,那么就会出现多个初始化语句副本,也就是重复了,就会引发错误。
StringBad::StringBad(const char * s)
{
// str = s; // 这只保存了地址,而没有创建字符串副本。
len = std::strlen(s); // 不包括末尾的空字符
str = new char[len+1]; // 使用new分配足够的空间来保存字符串,然后将新内存的地址赋给str成员。
std::strcpy(str, s);
num_strings++;
cout << num_strings << " : \"" << str << "\" object created.\n";
}
String boston(“Boston”);//可以这么用,字符串就是地址
看一下这个非默认构造函数,首先strlen()计算字符串的长度,但不包括末尾的空字符,所以需要将len+1。
strcpy()函数是将后者复制到前者。
这种方法,字符串并不保存在对象中,字符串单独保存在堆内存中。
最后析构函数,当对象过期时,str指针也将过期。但str指向的内存仍被分配,除非使用delete释放。删除对象可以释放对象本身占用的内存,但并不能自动释放属于对象成员的指针指向的内存。因此,必须要使用析构函数。在析构函数中使用delete语句可确保对象过期时,由构造函数使用new分配的内存被释放。
由于缺陷比较多,所以可能运行输出结果是不确定的。输出中出现的各种非标准字符随系统而异。
程序执行到这里应该还是没有问题的(也许只是看起来);
callme1(headline1);
当执行到这里就会出现明显的错误了,callme2()按值(而不是按引用)传递headline2。
callme2(headline2); // 复制构造函数被用来初始化 callme2()的形参