记录下目前理解到的复制控制,析构函数。还有好多不理解的。。。。
一、复制构造函数(拷贝构造函数):是种特殊的构造函数,形参是对该类类型的引用,用const修饰。
1.1三种情况下使用复制构造函数。定义一个新对象并用一个同类型的对象对其进行初始化式,显示使用复制构造函数;该类型对象作为函数参数时,隐式调用;作为函数返回值时,隐式调用。
1.2如果我们自己没有显示定义,编译器会为我们生成一个,该函数将对成员变量进行拷贝---浅拷贝,将现有对象的每个非static成员,依次复制到正在创建的对象。对于内置类型成员直接复制,对于类类型使用该类的复制构造函数进行赋值。数组成员复制是个例外,对于数组成员,复制数组中的每一个元素。
1.3由于复制构造函数是一种特殊的构造函数,因此其执行过程也和构造函数一样,可以使用初始化式进行成员变量的复制(在这里调用的是复制构造函数而不是构造函数),即便自己定义了构造函数,如果没有定义复制构造函数,编译器还是为我们生成一个。
测试例子
class B
{
public:
B(int b){cout<<"B constructor1"<<endl;this->_b=b;};
B(){cout<<"B constructor"<<endl;};
B & operator=(const B &b){if(this==&b){cout<<"自我赋值"<<endl;return *this;}this->_b=b._b;cout<<"复制赋值运算符B"<<endl;return *this;}
B(const B &b){cout<<"hello"<<endl;this->_b=b._b;cout<<"拷贝构造函数B"<<endl;}
void print(){cout<<this->_b<<endl;}
private:
int _b;
};
B foo(B b)
{
b.print();
return b;
}
class C
{
public:
B b;
C(int c);
C(double c){cout<<"C constructor2"<<endl;}
C(){cout<<"C constructor"<<endl;};
C(const C &c):b(c.b){cout<<"拷贝构造函数C"<<endl;}
//C(const C &c){this->b=c.b;cout<<"拷贝构造函数C"<<endl;}
};
C::C(int c):b(c){cout<<"C constructor1"<<endl;}
int main()
{
B b1(1);//B的int参数构造函数
B b2=b1;//B的拷贝构造函数
b2.print();
foo(b2);//函数参数是B类型,返回值是B类型,因此调用两次B的拷贝构造函数
C c1(0);//C的int参数构造函数
cout<<"point"<<endl;
C c2=c1;//调用C的拷贝构造函数,利用初始化式调用B的拷贝构造函数
return 0;
}
运行结果
如果对于C的自定义的拷贝构造函数采用注释掉的代码,未显示的对b进行复制初始化,因此执行过程变为:利用B的构造函数先初始化其成员变量b,然后在C拷贝构造函数对已经初始化的b进行赋值,因此调用的就是赋值运算符。运行结果如下所示。(这种不应该是合理的形式........)
1.4使用复制构造函数一些其他情况
(1) 初始化顺序容器中的元素:当使用表示容量的单个形参来初始化容器的时候,使用了默认构造函数和复制构造函数。
eg:vector<string> svec(5);具体过程是,使用string的默认构造函数创建一个临时值来初始化svec,然后使用复制构造函数将临时值复制到svec的每个元素。
(2)如果显示的为数组元素提供初始化式,则使用复制构造函数进行复制。
总结
复制构造函数在使用一个类对象去初始化另一个同类型类对象时调用;复制构造函数是一种特殊的构造函数,参数是该类类型的引用,没有返回值。
当类类型中指针,或者有成员需要在初始化时需要分配其他资源或者有额外特定工作要做,应该显示定义自己的复制构造函数,应该使用构造函数的初始化列表初始化新创建的对象成员(调用的是该成员的复制构造函数),如果没有这样做,而是在函数体内部进行赋值,则其过程是首先调用该成员对象的默认构造函数,然后在函数体内使用赋值运算符(下一节进行说明)。
不定义复制构造函数和默认构造函数会严重限制类的使用。一般来说,如果定义了复制构造函数,那么赋值操作符和析构函数也应该自己定义。
二、赋值操作符
如果没有定义赋值操作符,则编译器会合成一个。赋值操作符可以重载,通过右操作数的参数不同。现在只研究右操作数是该类类型引用的形式。使用一个对象为已经初始化的同类型的对象进行赋值。
eg: B& operator=(const B &){...}
编译器生成的赋值操作符的操作是,执行逐个成员的赋值:右操作数对象的每个成员赋值给左操作数对象的对应成员-----浅拷贝,对于类对象,调用其类型的赋值操作符。对于数组元素则采用为每个数组元素进行赋值。
注意在编写自己的赋值操作符操作的时候,要注意自我赋值的时候。加上判断if(this==&类对象)
三、析构函数
如果一个类类型变量在超过作用域时候会调用析构函数。一个动态分配的对象在指向该对象的指针被删除时调用析构函数。撤销一个容器的时候,也对进行容器中类类型元素的析构函数,并且是按逆序撤销的。
无论我们自己是否定义一个析构函数,编译器总是为我们生成一个析构函数。这个生成的析构函数按照非静态成员变量在类中的声明的逆序来撤销对象。
自己定义的析构函数不能重载,只能有一个,无参数,无返回值。形式如:~B();B为类名。