1.动态内存和类
在类中使用动态分配内存,要深入怎么书写类成员以及,new和delete的工作原理。
Class Stringbad
{
Private:
Char *str;
Int len;
Static int num_strings;
Public:
Stringbad(const char *s);
Stringbad();
~stringbad();
Friend std::ostream & operator<<(std::ostream &os,const stringbad &st);
};
注意以上示例中,首先它使用char指针(而不是char数组)来表示姓名。这意味着类声明没有为字符串本身分配存储空间,而是在构造函数中使用new来为字符串分配空间。这避免了在类声明中预先定义字符串的长度。其次将num_strings声明为静态存储类,静态类成员有一个特点,无论创建了多少对象,都只创建一个静态类变量副本,类的所有对象共享一个同一个静态成员。
在cpp实现类方法的文件中,要在内部作用域(类声明之外)直接初始化静态类成员变量,如
Int Stringbad::num_strings=0;//注意不用再次使用static关键字
不能在类声明中初始化静态静态成员变量。因为类声明仅仅描述如何分配内存,但并不分配内存。且静态类成员是单独存储的,而不是对象的组成部分。
有一种例外:静态数据成员是const整数类型或者枚举类型,则可以在类声明中初始化。
构造函数实现:
Stringbad::stringbad(const char *s)
{
Len=std::strlen(s);
Str=new char [len+1];
Std::strcpy(str,s);
Num_strings++;
}
初始化对象时,可以给构造函数传递一个字符串指针:
String boston(“boston”);
Strlen返回字符串长度,但不包括末尾的空字符,申请内存时应该+1。并且不能使用str=s;这只保存了地址而没有创建字符串副本。在析构函数中需要delete []str;因为对象过期时,str指针仍被分配,删除对象可以释放对象本身所占的内存,但并不能自动释放属于对象成员的指针指向的内存。
关于析构函数何时被调用:
对象生命周期结束被销毁时
Delete指向对象的指针时,或delete指向对象的基类类型指针时,而其基类虚构函数是虚函数时。
对象i是对象o的成员,o的析构函数被调用时,i的析构函数也被调用。
对于在全局作用域中定义的对象,它们的构造函数是在文件中所有其他函数(包括main)开始执行之前被调用的(但无法保证不同文件的全局对象构造函数的执行顺序)。对应的析构函数是在终止main之后调用的。
自动局部变量的构造函数是在程序的执行到达定义这个对象的位置时调用的,而对应的析构函数是在程序离开这个对象的作用域时调用的(即定义这个对象的代码完成了执行)。每次执行进入和离开自动对象的作用域时,都会调用它的构造函数和析构函数。如果程序调用了exit或abort函数而终止,则不会调用自动对象的析构函数。
静态局部对象的析构函数只调用一次,即执行首次到达定义这个对象的位置时。对应的析构函数是在main终止或程序调用exit函数时调用的。
全局对象和静态对象是以创建它们时相反的顺序销毁的。如果程序由于调用了exit函数而终止,则不会调用静态对象的析构函数。最后创建的最先消失。
当类对象传值传递给某函数后,那么有可能在该函数结束时,会执行一次析构函数。且关于类对象的初始化在使用动态内存的时候非常讲究:
Stringbad sailor=sports;相当于
Stringbad sailor=stringbad(sports);
即当使用一个对象来初始化另一个对象时,编译器将自动生成上述构造函数(复制构造函数,因为它创建一个对象的副本)。自动生成的构造函数不知道需要更新静态变量num_string,因此计数方案会乱。
2.特殊成员函数
C++自动提供了以下这些默认构造函数,默认析构函数,复制构造函数,赋值运算符,地址运算符(在这些函数都没有定义的情况下)
默认构造函数:如果未提供任何构造函数,则c++将创建默认构造函数,如Klunt::Klunt(){},编译器将提供一个不接受任何参数,也不执行任何操作的构造函数。这就允许值在初始化时是未知的。若定义了构造函数,那c++将不会定义默认构造函数,同时又希望在