1、使用构造函数的目的:为了在定义的时候,马上给变量进行赋值(防止多线程赋值出错)。
2、析构函数 : ~+类名 没有任何的参数,也没有返回值。
3、构造函数的几点总结:
3.1 构造函数名字跟类型是一样的,没有返回值,但可以重载。
3.2 如果显示的写了一个普通构造函数, 会隐藏默认的无惨构造函数,但不会隐藏默认的拷贝构造函数。
3.3 如果显示的写了一个拷贝构造函数 ,会隐藏默认的无参构造函数和默认的拷贝构造函数
3.4 如果显示的写了一个析构函数, 会隐藏默认的析构函数
3.5 不要在构造函数中嵌套构造函数
4、关于拷贝构造函数:
class Test
{
public:
Test(int x, int y)
{
m_x = x;
m_y = y;
}
//拷贝构造函数 ,想通过另一个Test对象 将本对象进行拷贝。
Test(const Test & another)//注意这里不能写成指针 const Test * another 拷贝构造函数的形式是固定的。
{
m_x = another.m_x;
m_y = another.m_y;
}
//等号操作符
void operator = (const Test &t)
{
m_x = t.m_x;
m_y = t.m_y;
}
private:
int m_x;
int m_y;
};
//调用拷贝构造函数的两种方式
Test t2(10, 20);
Test t3(t2); //调用t3的拷贝构造函数
Test t4 = t2; //调用t4的拷贝构造函数
Test t5; //先调用无惨构造。
t5 = t2; //不会调用拷贝构造函数,而是调用=号重载操作符,因为构造函数只有在初始化的时候会调用,赋值是不会调用构造函数的。
5、构造函数执行过程分析
class Test
{
public:
Test(int x, int y)
{
m_x = x;
m_y = y;
cout << "调用了有参数的构造函数" << endl;
}
//无参数的构造函数
Test(){
m_x = 0;
m_y = 0;
cout << "调用了无参数的构造函数" << endl;
}
//拷贝构造函数
Test(const Test & another)
{
m_x = another.m_x;
m_y = another.m_y;
cout << "调用了拷贝构造函数" << endl;
}
//等号操作符
void operator = (const Test &t)
{
cout << "调用了=号操作符" << endl;
m_x = t.m_x;
m_y = t.m_y;
}
void printT()
{
cout << "x : " << m_x << ", y : " << m_y << endl;
}
//提供一个析构函数
~Test()
{
cout << "~Test()析构函数被执行了" << endl;
cout << "(" << m_x << ", " << m_y << ")" << "被析构了" << endl;
}
private:
int m_x;
int m_y;
};
Test func()
{
cout << "func begin..." << endl;
Test temp(10, 20); //调用temp的带参数构造函数
Test temp1(100, 200); //调用temp的带参数构造函数
cout << "func end.." << endl;
return temp; //创建一个临时的匿名对象 = temp ,把temp的数据给到了临时的匿名对象。
//调用这个临时匿名对象的拷贝构造函数, 将temp传进去。
}
void test1()
{
cout << "test1 begin " << endl;
func();
/*
匿名对象在此被析构了, 如果一个临时的匿名对象,没有任何变量去接收它,
编译器认为这个临时匿名对象没有用处。会立刻销毁这个临时的匿名对象。
*/
cout << "test1 end" << endl;
}
/*
调用test1的执行结果:
test1 begin
func begin...
调用了有参数的构造函数
调用了有参数的构造函数
func end..
~Test()析构函数被执行了
(100, 200)被析构了
~Test()析构函数被执行了
(10, 20)被析构了
test1 end
*/
void test2()
{
cout << "test2 begin ..." << endl;
Test t1 = func();
//如果有一个变量去接收这个临时的匿名对象, 编译器认为这个匿名对象有用,就不会立刻给他销毁。
/*
t1 = 匿名的临时对象 为什么不会发生拷贝构造??
此时的t1 去接收这个匿名的临时对象,不是重新创建一个t1,而是给这个匿名对象起个名字就叫t1。
一旦这个匿名对象有了自己的名字,编译器就不会立刻给这个匿名对象销毁了,就当普通局部变量处理了。
可以理解为匿名变量只有灵魂,没有肉体,执行 Test t1 = func() 相当于让 t1 变成他的肉体。
再说说拷贝构造:
Test t2(10,20);
Test t1 = t2;
这种情况是拷贝构造,通过一个对象给另一个对象赋值,t1,t2都是有灵魂有肉体的。
*/
cout << "test2 end..." << endl;
//在此时析构的t1
}
/*
调用test2的执行结果:
test2 begin ...
func begin...
调用了有参数的构造函数
调用了有参数的构造函数
func end..
~Test()析构函数被执行了
(100, 200)被析构了
test2 end...
~Test()析构函数被执行了
(10, 20)被析构了
下面是分析内容:
为什么只被析构了两次??
因为 Test t1 = func(); 从宏观上看只有一个变量t1。
*/
void test3()
{
cout << "test3 begin..." << endl;
Test t1; //调用t1的无参数构造函数
t1 = func();
/*
调用了t1的=号操作符 , t1 = 匿名对象。
此时匿名对象并没有找到一个宿主,因为t1本身就已经存在了,
所以编译器就会立刻销毁他。
*/
cout << "test3 end..." << endl;
}
/*
调用test3的执行结果:
test3 begin...
调用了无参数的构造函数
func begin...
调用了有参数的构造函数
调用了有参数的构造函数
func end..
~Test()析构函数被执行了
(100, 200)被析构了
调用了=号操作符
~Test()析构函数被执行了
(10, 20)被析构了
test3 end...
~Test()析构函数被执行了
(10, 20)被析构了
下面是分析内容:
为什么被析构了三次??
因为从宏观上看有两个变量,一个是t1,一个是匿名的临时变量。
*/
int main(void)
{
// test1();
// test2();
test3();
return 0;
}
6、浅拷贝
class Teacher
{
public:
//有参数的构造函数
Teacher(int id, const char *name)
{
cout << "调用了Teacher 的构造函数" << endl;
//是给id 赋值
m_id = id;
//给姓名赋值
int len = strlen(name);
m_name = (char*)malloc(len + 1);
strcpy(m_name, name);
}
~Teacher() {
//在构造函数中, 已经开辟了内存 所以为了防止泄露
//在析构函数中,在对象销毁之前,把m_name的内存释放掉
if (m_name != NULL) {
free(m_name);
m_name = NULL;
cout << "释放掉了m_name" << endl;
}
}
private:
int m_id;
char *m_name;
};
int main(void)
{
Teacher t1(1, "zhang3");
//如果不提供一个显示的拷贝构造函数, 通过系统自带的默认拷贝构造函数
Teacher t2(t1); //会调用t2的拷贝构造函数,将t1的值拷贝给t2
return 0;
}
/*
执行结果:
调用了Teacher 的构造函数
释放掉了m_name
*** Error in `./a.out': double free or corruption (fasttop): 0x08c79008 ***
Aborted (core dumped)
原因:调用了系统默认的拷贝构造函数,指针只是进行了简单的赋值,两个对象t1,t2
内部的指针成员变量指向的是同一个地址。当t2对象析构后,释放了这块内存。但t1
对象中这个指针值还不是null,所以又进行了一次释放,造成内存泄露。
*/
7、深拷贝
class Teacher
{
public:
//有参数的构造函数
Teacher(int id, const char *name)
{
cout << "调用了Teacher 的构造函数" << endl;
//是给id 赋值
m_id = id;
//给姓名赋值
int len = strlen(name);
m_name = (char*)malloc(len + 1);
strcpy(m_name, name);
}
//显示写一个拷贝构造函数
//通过显示拷贝构造函数提供了深拷贝的动作
Teacher(const Teacher &another)
{
m_id = another.m_id; //给id赋值
int len = strlen(another.m_name);
m_name = (char*)malloc(len + 1);
strcpy(m_name, another.m_name);
}
~Teacher() {
//在构造函数中, 已经开辟了内存 所以为了防止泄露
//在析构函数中,在对象销毁之前,把m_name的内存释放掉
if (m_name != NULL) {
free(m_name);
m_name = NULL;
cout << "释放掉了m_name" << endl;
}
}
private:
int m_id;
char *m_name;
};
int main(void)
{
Teacher t1(1, "zhang3");
Teacher t2(t1);
return 0;
}
/*
执行结果:
调用了Teacher 的构造函数
释放掉了m_name
释放掉了m_name
*/
总结:当类的成员变量有指针时,一定要手动写拷贝构造函数。否则会造成浅拷贝。
8、构造函数的初始化列表
class A
{
public:
A(int a)
{
m_a = a;
cout << "a = " << m_a << "调用了构造函数" << endl;
}
void printA()
{
cout << " a = " << m_a << endl;
}
~A()
{
cout << "a = " << m_a << "被析构了" << endl;
}
private:
int m_a;
};
class B
{
public:
B(int b) :m_a1(10), m_a2(100) //在初始化B的时候通过初始化列表给内部对象a1 和a2 进行了初始化
{
m_b = b;
/*
m_a1(10);//这样子是不行的
m_a2(20);
*/
cout << "b = " << m_b << "调用了构造函数" << endl;
}
B(A &a1,A &a2,int b) :m_a1(a1), m_a2(a2) //调用A的拷贝构造函数,可以在A中手动添加拷贝构造函数进行测试。
{
m_b = b;
cout << "b = " << m_b << "调用了构造函数" << endl;
}
B(int aa1, int aa2, int b) : m_a1(aa1), m_a2(aa2), m_b(b) //通过初始化列表不仅能够初始化成员对象, 还可以初始化成员变量
{
//其中 m_a1(aa1), m_a2(aa2) 会调用A类的有参构造函数
}
~B()
{
cout <<"b = " <<m_b << " 调用了析构函数" << endl;
}
private:
int m_b;
// const int m_m; //常量成员变量不能够赋值,只能通过初始化列表进行初始化
A m_a2;//这样写初始化列表就会先初始化 m_a2 再初始化 m_a1 。跟初始化列表中的顺序无关
A m_a1;
};