本文主要包括构造函数、复制构造函数、赋值操作重载和析构函数。做一个Primer阅读笔记。
构造函数(没有定义,系统合成)
构造函数可以重载,只要创建一个对象,构造函数自动被调用。new一个对象的时候,先分配空间,再调用构造函数(STL中将整个过程分开)。构造函数分为初始化阶段,普通的计算阶段。不管成员是否在构造函数初始化列表中显示初始化,类类型的数据成员总是在初始化阶段初始化,然后才进行计算阶段。
如果没有在初始化列表中对成员进行初始化,编译器会隐式的使用成员类型的默认构造函数。如果类的成员没有默认构造函数,则初始化失败。有些成员必须在初始化列表中进行初始化:没有默认构造函数的类类型成员、const或引用类型成员。
class ConstRef{
public:
ConstRef(int ii)
private :
int i;
const int& ci;
int &ri;
};
ConstRef::ConstRef(int ii)
{
i =ii;
ci= ii;//错,放在初始化列表中
ri= I;//错,放在初始化列表中
}
类中内置和复合类型的成员,只对定义在全局作用域中的对象才初始化对象。如果类中包含内置或复合类型的成员,则类不应该依赖于合成的默认构造函数,它应该定义自己的构造函数来初始化这些成员。
类通常应该定义一个默认构造函数,如果没有定义,则会出现下列错误:(1)定义类的对象时必须初始化;(2)不能用作动态分配数组的元素类型;(3)静态数组必须为每个元素提供一个显示的初始化值;(4)如果有一个保存该类的容器,就不能使用接受容器大小而没有同时提供一个元素初始化的构造函数。
用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。通过加explicit来禁止这种隐式转换(可以通过显示调用构造函数来提供显示转换)。
注:如果类中的函数在类外定义,由于返回类型在类的作用域外,如果用到类中的类型需要作用域符号。
例如:inline screen::index screen::get_cursor()const
复制构造函数(没有定义,系统合成)
特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示的调用复制构造函数。当该类型的对象传递给函数或从函数返回给类型的对象时,将隐式使用复制构造函数。
以下几种情况将显示调用构造函数:根据另一个同类型的对象显示或隐式初始化一个对象;复制一个对象,将它作为函数实参;函数返回对象;初始化容器中的元素;根据元素初始化列表初始化数组元素。
对象的初始化分为:直接初始化和复制初始化。复制初始化使用“=”,而直接初始化将初始化式放在原括号中。
两种初始化方式略微有区别:直接初始化直接调用与实参匹配的构造函数(不是复制构造函数)。复制初始化一定是调用复制构造函数(要调用这个构造函数,必须保证另一方是同类型的对象,如果不是先调用构造函数转换为临时对象,再调用复制构造函数)。
范例1:
string null_book = “add”;//首先调用构造函数string(char*)将“add”转换为string对象,即生成临时对象,再调用复制构造函数。
//上面这种方式要求string(char*)没有声明为explicit,如果已被声明为explicit。
string null_book = string(“add”);//显示调用构造函数生成临时对象,再调用复制构造函数
string dots(10,’.’);//调用构造函数
string empty_copy = string();//先调用string()创建临时对象,再调用复制构造函数
string empty_str;//直接调用默认构造函数
范例2:
//编译器首先调用string默认构造函数创建一个临时对象
//然后再调用复制构造函数来初始化svec的每个元素
vector<string> svec(5);
范例3:
//如果没有为类类型数组提供元素初始化方式,将用默认构造函数初始化每个元素
//如果像下面提供了显示初始化,则先创建临时对象,再调用复制构造函数
string str[2]={string(“lsj”),”is”}
如何禁止复制:复制构造函数设为私有的,则不允许用户复制该类对象,但是类中的成员和友元函数还是能复制对象,为了完全禁止,可以声明一个private复制构造函数但不对其定义。
有一个问题需要思索:为什么复制构造函数需要const而且还是引用?为什么const答案参见沉思录(第四章),为什么是引用,如果不是引用将导致无穷递归复制,直至内存被耗光。
复制操作符(没有定义,系统合成)
复制操作符的形式:classType& operator=(const classType&);
复制操作符重载的时候注意四点:(1)检查是不是自身赋值;(2)释放原来占用的空间;(3)分配新的空间,并复制新的值。(当然两个占用的空间大小一样直接覆盖掉原来的值,不用释放)(4)返回自身的引用。
有一个问题需要思索:为什么赋值操作符需要const引用,如果不这样有什么影响?答案参见沉思录(第四章)
析构函数(没有定义,系统合成)
new出来的对象,只有显示的delete才会去运行析构函数,如果不显示delete会造成内存泄露,切记!---lsj
容器中的元素总是按逆序撤销。
与复制构造函数或赋值操作符不同,编译器总是会为我们合成一个析构函数。合成析构函数按照对象创建时的逆序撤销每个非static成员。因此,它按照成员在类中声明次序的逆序撤销成员。即使我们自己编写了析构函数,合成析构函数仍然运行。
三法则:如果类需要析构函数,则它也需要赋值操作符和复制构造函数。
有一个问题需要思索:什么时候需要析构函数,什么时候需要虚析构函数,为什么需要虚析构函数?答案参见沉思录(第四章)