构造函数
构造函数的两阶段执行
1、初始化阶段;
所有类型的成员都会在初始化阶段被初始化,即使该成员没有出现在构造函数的初始化列表中。
(1)、初始化列表中显示初始化的成员,按照列表中圆括号内的值初始化。
(2)、初始化列表中没有显式列出的成员,若是类成员,则调用该类型的默认构造函数初始化,若是内置类型或者复合类型,则按照变量初始化的原则,在局部作用域中的不做初始化,全局作用于中的初始化为0。
注意:成员变量的初始化顺序,成员是按他们在类中出现的顺序初始化的.而不是按初始化列表的顺序.
2、普通的计算阶段。
一般是指在构造函数的函数体内对数据成员做赋值工作,在函数体内进行赋值操作之前,数据成员的初始化已经完成。
推荐在构造函数初始化列表中进行初始化
基于一个性能的问题,对于内置类型(int,float,double…),使用初始化列表和在构造函数体内初始化,差别不大。但是存在以下情况,最好使用初始化列表:
1、类存在继承关系
类中的数据成员含有一下内容:
2、没有默认构造函数的类类型成员,因为使用初始化列表可以不必调用默认的构造函数来初始化,而是直接调用拷贝构造函数来初始化。
3、需要初始化的数据成员是对象
4、const数据成员,即常量成员,因为常量只能初始化而不能赋值,所以必须使用初始化列表
5、引用成员,必须在定义的时候初始化,并且不能重新赋值,所以要写在初始化列表里面.
以下代码示例展示为什么推荐在构造函数初始化列表中进行初始化:
#include<iostream>
using namespace std;
class Test1{
public:
Test1() {
cout << "Construct Test1" << endl;
}
Test1(const Test1& t1) {
cout << "Copy Construct for Test1" << endl;
this->a = t1.a;
}
Test1& operator = (const Test1& t1) {
cout << "assignment for Test1" << endl;
this->a = t1.a;
return *this;
}
private:
int a;
};
class Test2 {
public:
Test2(Test1& t1) : test1(t1) {
}
private:
Test1 test1;
};
class Test3 {
public:
Test3(Test1& t1) {
test1 = t1;
}
private:
Test1 test1;
};
int main() {
Test1 t1; // 调用Test1的默认构造函数
cout << "000000" << endl;
Test2 t2(t1); // 根据Test2构造函数的初始化列表,调用Test1的拷贝构造函数
cout << "000000" << endl;
Test2 t22 = t1; // 等价于Test2 t2(t1);
cout << "000000" << endl;
Test3 t3(t1);
// Test3的构造函数没有初始化列表,用默认的构造函数初始化对象test1,这就是所谓的初始化阶段。然后根据Test1的赋值运算符中代码,执行赋值操作,这就是所谓的计算阶段。
cout << "000000" << endl;
Test3 t33 = t1; // 等价于Test3 t3(t1);
return 0;
}
程序输出如下
Construct Test1
000000
Copy Construct for Test1
000000
Copy Construct for Test1
000000
Construct Test1
assignment for Test1
000000
Construct Test1
assignment for Test1
拷贝构造函数
功能:使用一个已经存在的对象来初始化一个新的同一类型的对象
声明:只有一个参数并且参数为该类对象的引用
如果类中没有说明拷贝构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员。
拷贝构造函数调用的几种情况
1、当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。
这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调用拷贝构造函数。
2、当函数的返回值是类对象,函数执行完成返回调用者时使用。
理由也是要建立一个临时对象中,再返回调用者。为什么不直接用要返回的局部对象呢?因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓return 对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果返回的是变量,处理过程类似,只是不调用构造函数。
深拷贝与浅拷贝
在没有提供拷贝构造函数时,系统会提供默认的拷贝函数,系统提供的默认拷贝构造函数是浅拷贝,各个成员进行赋值,但是当成员含义指针时涉及到动态内存的分配,如果涉及浅拷贝就会共享同一块内存,但是当一个对象释放时,另一个对象也会出现无效的状态,有可能出现同一块内存释放两次的情况。因此就要实施深拷贝,将指针指向的对象的值进行拷贝。
赋值操作
系统默认提供的赋值操作也是浅拷贝,各个成员进行赋值。因此也要实施深拷贝
禁止拷贝
将赋值与拷贝构造函数声明为私有,并不实现。
空类默认产生的成员:
class Empty {};
Empty(); // 默认构造函数
Empty( const Empty& ); // 默认拷贝构造函数
~Empty(); // 默认析构函数
Empty& operator=( const Empty& ); // 默认赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
字符串深拷贝实现代码:
#include<iostream>
using namespace std;
class String {
private:
char * _str;
public:
String(char * str) {
_str = AllocCopy(str);
}
~String() {
delete[] _str;
}
String(const String & other) {
_str = AllocCopy(other._str);
}
String & operator = (const String & other) {
if (this == &other) {
return *this;
}
delete[] _str;
_str = AllocCopy(other._str);
return *this;
}
char * AllocCopy(char * str) {
int len = strlen(str) + 1;
char * tmp = new char[len];
memset(tmp, 0, len);
strcpy(tmp, str);
return tmp;
}
void Display() {
cout << _str << endl;
cout << this << endl;
}
};
int main() {
String a("aaaaaaa");
a.Display();
String b(a);
b.Display();
}
代码输出结果如下:
aaaaaaa
0x7ffee3a39ad8
aaaaaaa
0x7ffee3a39ac0
参考:
https://blog.csdn.net/hailong0715/article/details/54018002
https://blog.csdn.net/Alatebloomer/article/details/81299331