c++ primer plus第十二章-动态内存和类,复制构造函数
1)类声明没有为字符串本身分配存储空间,而是在构造函数中使用new来为字符串分配空间。这避免了在类声明中预先定义字符串的长度。
2)静态成员有一个特点,无论创建多少个对象,程序都是只创建一个静态变量副本。也就是说,类的所有对象共享一个静态成员,就像家中的电话可供全部人使用一样。
而且不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。对于静态成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。而且要注意:初始化语句指出了类型,并使用了作用域运算符,但没有使用关键字static。
3)注意:静态数据成员在类声明中声明,在包含类方法的文件中初始化。初始化时使用作用域运算符来指出静态成员所属的类。但如果静态成员是const整数类型或枚举类型,则可以在类声明中初始化。
4)默认构造函数:编译器将提供一个不接受任何参数,也不执行任何操作的构造函数(默认的默认构造函数),这是因为创建对象时总会调用构造函数。
Klunk::Klunk(){ }
带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值。
Klunk(int n=0 ){ klunk_ct = n; }
但是只能有一个默认构造函数。
5)复制构造函数:用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。类的复制构造函数的原型如下:
Class_name( const Class_name &);
它接受一个指向类对象的常量引用作为参数。
StringBad(const StringBad &);
6)对于复制构造函数,需要知道两点:何时调用和有何作用。
7)新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。最常见的情况是将新对象显式地初始化为现有的对象。
StringBad motto; //motto为StringBad对象
StringBad ditto(motto); //新建对象,初始化为motto
StringBad metto = motto; //新建对象,初始化为motto
StringBad also = StringBad(motto); //新建对象,初始化为motto
StringBad *pStringBad = new StringBad(motto); //新建指向对象的指针,指向motto内存的地址
上面4种声明都将调用复制构造函数StringBad(const StringBad &);
8)每当程序生成了对象副本时,编译器都将使用复制构造函数。具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。按值传递意味着创建原始变量的一个副本。编译器生成临时对象时,也将使用复制构造函数。
由于按值传递对象将调用复制构造函数,因此应该按引用传递对象,这样可以节省调用构造函数的时间以及存储新对象的空间。
9)默认的复制构造函数的功能:逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。如果成员本身就是类对象,则将使用这个类的复制构造函数来复制成员对象。静态函数不受影响,因为它是属于整个类,而不是各个对象。
10)解决类中这种尝试两次释放内存的问题的方法是进行深度复制。也就是说,复制构造函数应当复制字符串并将副本的地址赋给新new内存成员,而不仅仅是复制字符串地址。这样每个对象都有自己的字符串,而不是引用另一个对象的字符串。调用析构函数时都将释放不同的字符串,而不会试图去释放已经被释放的字符串。
11)警告:如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这被称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制只是浅浅地复制指针信息,而不会深入挖掘以复制指针引用的结构。
12)赋值运算符:类对象赋值,通过自动为类重载赋值运算符来实现的。
Class_name & Class_name :: operator=(const Class_name &);
它接受并返回一个指向类对象的引用。
将已有的对象赋给另一个对象时,将使用重载的赋值运算符。
StringBad headline1("Celery Stalks at Midnight");
StringBad knot;
knot = headline1;
13)解决赋值的问题:
由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]来释放这些数据。
函数应当避免将对象赋给自身;否则,给对象重新赋值前,释放内存操作可能删除对象的内容。
函数返回一个指向对象的引用。
StringBad & StringBad::operator=(const StringBad & st)
{
if(this==&st) //避免对象赋给自身
return *this;
delete [] str; //释放旧的内存数据
len = st.len; //
str= new char [len+1]; //获得新的内存字符串
std::strcpy(str,st.str); //复制字符串
return *this;
}
14)空指针:字面值0有两个含义:可以表示数字值零,也可以表示空指针。有些程序员使用(void *)0来标识空指针(空指针本身的内部表示可能不是零),也有人使用NULL,这是一个表示空指针的C语言宏。C++11引入了新关键字nullptr,用于表示空指针。
str=nullptr;
15)静态成员函数:可以将成员函数声明为静态的(函数声明必须包含关键字static,但如果函数定义是独立的,则其中不能包含关键字static)。
首先,不能通过对象调用静态成员函数,实际上,静态成员函数甚至不能使用this指针。如果静态成员函数是在公有部分声明的,则可以使用类名和作用域解析运算符来调用它。
其次,由于静态成员函数不与特定的对象相关联,因此只能使用静态数据成员。也可以使用静态成员函数设置类级标记,以控制某些类接口的行为。
16)