总结:
1、类对象的作用域为两个{}之间。在遇到}后开始执行析构函数
2、当没有任何显式的构造函数(无参,有参,拷贝构造)时,默认构造函数才会发挥作用
一旦提供显式的构造函数,默认构造函数不复存在,默认构造函数都会被覆盖掉。若想调用,则显示提供默认构造函数
3、析构函数不能重载,没有参数,没有返回值。显示提供析构,默认析构就会被覆盖掉
4、当调用默认构造函数时,类对象的数据成员值是在栈上随机分配的随机值
5、当没有显式的拷贝构造函数时,默认拷贝构造才会出现
6、每个类对象都会有默认的拷贝构造函数 但只是单纯的将一个类对象的数据成员变量赋值给本身
//构造函数是对象初始化的时候调用
Test t3 = t1; //初始化t3时调用t3构造函数 依然是调用t3的拷贝构造函数
t3.printT();Test t4; //已经调用默认无参构造函数 初始化
t4 = t1; //调用的不是t4拷贝构造函数,而是t4的赋值操作符函数
7、需要有额外空间释放时,调用析构函数。否则无需显式写出。
8、析构函数的作用,并不是删除对象,而在对象销毁前完成的一些清理工作。对象的释放是由操作系统控制(存放在栈)
9、当没有显式的析构函数时,默认析构函数被调用
10、析构函数的调用顺序 和构造相反:谁先构造,谁后析构 (栈结构)
11、当类的数据成员中有指针时,拷贝构造函数必须显式说明(为指针所存内存开辟空间)同时析构函数 手动释放空间
12、拷贝构造函数的应用场景:
<1>Test t2 = t1; //⽤对象t1 初始化 对象 t2
<2>Test t2(t1); //⽤对象t1 初始化 对象 t2
<3>void func(Test p) //会执⾏ p = t1 的操作,p会调⽤copy构造函数进⾏初始化
<4>函数的返回值是⼀个元素 (复杂类型的), 返回的是⼀个新的匿名对象(所以会调⽤匿名对象类的copy构造函数
注意:
有关 匿名对象的去和留
如果⽤匿名对象 初始化 另外⼀个同类型的对象, 匿名对象 转成有名对象
如果⽤匿名对象 赋值给 另外⼀个同类型的对象, 匿名对象 被析构
13、系统提供默认的拷贝构造器,一经定义不再提供。但系统提供的默认拷贝 构造器是 等位拷贝,也就是通常意义上的浅拷贝。如果类中包含的数据元素全部在栈上,浅拷贝 也可以满足需求的。但如果堆上的数据,则会发生多次析构行 为。
14、如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成 员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,
15、构造函数的初始化列表 初始化对象时需要用到
构造对象成员的顺序跟初始化列表的顺序无关
而是跟成员对象的定义顺序有关
16、在构造函数执行时,先执行初始化列表,实现变量的初始化,然后再执行函数内部的语句
17、
《C++ Primer》中提到在以下三种情况下需要使用初始化成员列表:
情况一、需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化);
情况二、需要初始化const修饰的类成员或初始化引用成员数据;
情况三、子类初始化父类的私有成员;
情况一的说明:数据成员是对象,并且这个对象只有含参数的构造函数,没有无参数的构造函数;
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。
情况二的说明:对象引用或者cosnt修饰的数据成员
情况二:当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
情况三的说明:子类初始化父类的私有成员,需要在(并且也只能在)参数初始化列表中显示调用父类的构造函数,因为只有初始化列表可以构造父类的private成员(通过显示调用父类的构造函数)
18、构造函数就是用来初始化自己数据成员的。所以先有构造函数的调用,再有数据成员的初始化(若有继承关系,先调用父类构造函数,在调用子类构造函数,再初始化数据成员,再执行构造函数中的语句。)
class Test{
public:Test(){};Test (int x){ int_x = x;};void show(){cout<< int_x << endl;}
private:int int_x;
};
class Mytest:public Test{
public:Mytest() :Test(110){//Test(110); // 构造函数只能在初始化列表中被显示调用,不能在构造函数内部被显示调用};
};
int _tmain(int argc, _TCHAR* argv[])
{Test *p = new Mytest();p->show();return 0;
}
结果:如果在构造函数内部被显示调用输出结果是:-842150451(原因是这里调用了无参构造函数);
如果在初始化列表中被显示调用输出结果是:110
参考自https://blog.csdn.net/sinat_20265495/article/details/53670644
17、使用构造函数初始化列表的好处:
类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对成员变量的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障。
注意:构造函数需要初始化的数据成员,不论是否显示的出现在构造函数的成员初始化列表中,都会在该处完成初始化,并且初始化的顺序和其在类中声明时的顺序是一致的,与列表的先后顺序无关,所以要特别注意,保证两者顺序一致才能真正保证其效率和准确性。
01构造和析构函数
# if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Test {
public://test类的构造函数//在对象被创建的时候,用来初始化对象的函数Test()//无参数的构造函数{m_x = 0;m_y = 0;}Test(int x){m_x = x;m_y = 0;}Test(int x, int y) {m_x = x;m_y = y;name = (char *)malloc(100);strcpy(name, "zhangsan");}void printT(){cout << "x = " << m_x << " y = " << m_y << endl;}//析构函数和构造函数都没有返回值,//析构函数没有形参~Test() {cout << "~Test 执行" << endl;if (name != NULL){free(name);name = NULL;cout << "free succ!" << endl;}}
private:int m_x;int m_y;char *name;
};void test01() {Test t1(10, 20); //类对象的作用域为两个{}之间。在遇到}后开始执行析构函数t1.printT();Test t2(100);t2.printT();Test t3;//就是调用类的无参数构造函数t3.printT();cout << "操作完毕" << endl;
}int main() {test01();return 0;
}
#endif
运行结果:
02构造函数分类
# if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Test {
public://test类的构造函数//在对象被创建的时候,用来初始化对象的函数Test(int x, int y) {m_x = x;m_y = y;name = (char *)malloc(100);strcpy(name, "zhangsan");}void printT(){cout << "x = " << m_x << " y = " << m_y << endl;}//析构函数和构造函数都没有返回值,//析构函数没有形参~Test() {cout << "~Test 执行" << endl;if (name != NULL){free(name);name = NULL;cout << "free succ!" << endl;}}
private:int m_x;int m_y;char *name;
};
void test01() {//Test t1; //报错 “Test” : 没有合适的默认构造函数可用Test t1(1, 2); //ok
}
int main() {test01();return 0;
}#endif
运行结果:
03拷贝构造函数
# if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Test {
public://test类的构造函数//在对象被创建的时候,用来初始化对象的函数Test(){m_x = 0;m_y = 0;}Test(int x, int y) {m_x = x;m_y = y;}void printT(){cout << "x = " << m_x << " y = " << m_y << endl;}
#if 0//显示的拷贝构造函数Test(const Test &another) { //加const表示只读。要去初始化另一个值。自己本身不能被修改m_x = another.m_x;m_y = another.m_y;cout << "调用Test(const Test &another)拷贝 " << endl;}
#endif
#if 0//会有默认的拷贝构造函数 单纯的将一个类对象的数据成员变量赋值给本身Test(const Test &another) { m_x = another.m_x;m_y = another.m_y;}
#endif //=赋值操作符void operator=(const Test& another) {m_x = another.m_x;m_y = another.m_y;}
private:int m_x;int m_y;
};
void test01() {Test t1(100,200);Test t2(t1); //提供默认拷贝构造函数t2.printT();//构造函数是对象初始化的时候调用Test t3 = t1; //初始化t3时调用t3构造函数 依然是调用t3的拷贝构造函数t3.printT();Test t4; //已经调用默认无参构造函数 初始化t4 = t1; //调用的不是t4拷贝构造函数,而是t4的赋值操作符函数
}
int main() {test01();return 0;
}#endif
运行结果:
04拷贝构造函数的应用场景
# if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Test {
public://test类的构造函数//在对象被创建的时候,用来初始化对象的函数Test() {cout << "Test()" << endl;m_x = 0;m_y = 0;}Test(int x, int y) {cout << "Test(int x, int y)" << endl;m_x = x;m_y = y;}void printT(){cout << "x = " << m_x << " y = " << m_y << endl;}//显示的拷贝构造函数Test(const Test &another) { //加const表示只读。要去初始化另一个值。自己本身不能被修改m_x = another.m_x;m_y = another.m_y;cout << "调用Test(const Test &another)拷贝 " << endl;}void operator=(const Test& another) {cout << "调用operator=(const Test& another) " << endl;m_x = another.m_x;m_y = another.m_y;}~Test() {cout << "~Test()...be done" << endl;}
private:int m_x;int m_y;
};
//析构函数的调用顺序 和构造相反:谁先构造,谁后析构 (栈结构)//场景1
void test01() {Test t1(10, 20);Test t2(t1); //调用拷贝构造函数 等价于Test t2 = t1;
}
/*
运行结果:
Test(int x, int y)
调用Test(const Test &another)拷贝
~Test()...be done
~Test()...be done
*/
//场景2
void test02() {Test t1(10, 20);Test t2;t2 = t1;
}
/*
运行结果:
Test(int x, int y)
Test()
调用operator=(const Test& another)
~Test()...be done
~Test()...be done
*/
void func(Test t) { //调用func时 Test t=t1; Test t的拷贝构造函数cout << "Func begin.." << endl;t.printT();cout << "Func end..." << endl;
} //析构t
//场景3
void test03() {cout << "test03 begin.." << endl;Test t1(10, 20);func(t1);cout << "test03 end..." << endl;
} //析构t1
/*
运行结果:
test03 begin..
Test(int x, int y)
调用Test(const Test &another)拷贝
Func begin..
x = 10 y = 20
Func end...
~Test()...be done
test03 end...
~Test()...be done
*/
Test func2() {cout << "func2 begin..." << endl;Test temp(10, 20); temp.printT();cout << "func2 end..." << endl;return temp; //会有 Test的匿名对象 匿名对象(temp) temp去初始化匿名构造 匿名对象的拷贝构造
} //析构temp
//场景4
void test04() {cout << "test04 begin..." << endl;func2(); //返回一个匿名对象 当一个函数返回匿名对象时,//如果函数外部没有任何变量去接收它,这个匿名对象将不会再被使用//编译器会直接将这个匿名对象回收掉,而不是等待整个函数执行完毕后再回收//匿名对象被回收cout << "test04 end..." << endl;
}
/*
运行结果:
test04 begin...
func2 begin...
Test(int x, int y)
x = 10 y = 20
func2 end...
调用Test(const Test &another)拷贝
~Test()...be done
~Test()...be done
test04 end...
*///场景5
void test05() {cout << "test05 begin..." << endl;Test t1=func2(); //会不会触发t1的拷贝构造函数 t1.拷贝(匿名) //不会触发 将匿名对象转正为t1//给匿名对象 起了个名字就叫t1cout << "test05 end..." << endl;
} //析构t1
/*
运行结果:
test05 begin...
func2 begin...
Test(int x, int y)
x = 10 y = 20
func2 end...
调用Test(const Test &another)拷贝
~Test()...be done
test05 end...
~Test()...be done
*///场景6
void test06() {cout << "test06 begin..." << endl;Test t1; //t1已经被初始化t1 = func2(); //匿名对象赋值给t1,匿名对象并没有转正,所以即刻析构cout << "test06 end..." << endl;
}
/*
运行结果:
test06 begin...
Test()
func2 begin...
Test(int x, int y)
x = 10 y = 20
func2 end...
调用Test(const Test &another)拷贝
~Test()...be done
调用operator=(const Test& another)
~Test()...be done
test06 end...
~Test()...be done
*/
int main() {//test01();//test02();//test03();//test04();//test05();test06();return 0;
}#endif
05深拷贝与浅拷贝应用场景
防止内存泄漏(开辟的空间没有释放):导致内存爆掉 程序崩溃
防止释放同一块内存
当类的数据成员中有指针时,拷贝构造函数必须显式说明(为指针所存内存开辟空间)
同时析构函数 手动释放空间
# if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Teacher {
public:Teacher(int id, char *name) {cout << "Teacher(int id, char *name)" << endl;m_id = id;int len = strlen(name);m_name = (char*)malloc(len + 1); //strlen长度计算不包括'\0' 所以此处加一strcpy(m_name, name);}void printT() {cout << "m_id:"<< m_id << " m_name : " << m_name << endl;}//默认拷贝构造函数Teacher(const Teacher &another) {m_id = another.m_id;m_name = another.m_name; //指向同一块内存空间}~Teacher(){cout << "~Teacher()...." << endl;if (m_name != NULL) {free(m_name);}}
private:int m_id;//char m_name[64]; 没有深拷贝 浅拷贝分解 因为此时m_name在栈上分配空间char *m_name;
};void test01() {Teacher t1(1, "zhangsan");t1.printT();Teacher t2(t1); //调用t2的默认拷贝构造 程序崩溃t2.printT();
} //t2析构 清空zhangsan所在空间 //t1析构时 也要清空zhangsan所在空间 然而已经清空 所以程序崩溃
int main()
{test01();return 0;
}
#endif
上述程序崩溃原因如下:
上述程序崩溃的主要原因就是默认拷贝构造函数的浅拷贝。所以使拷贝构造函数为深拷贝即可解决问题
程序如下:
# if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Teacher {
public:Teacher(int id, char *name) {cout << "Teacher(int id, char *name)" << endl;m_id = id;int len = strlen(name);m_name = (char*)malloc(len + 1); //strlen长度计算不包括'\0' 所以此处加一strcpy(m_name, name);}void printT() {cout << "m_id:"<< m_id << " m_name : " << m_name << endl;}
#if 0//默认拷贝构造函数Teacher(const Teacher &another) {m_id = another.m_id;m_name = another.m_name;}
#endif//显式提供拷贝构造函数 完成深拷贝动作Teacher(const Teacher &another) {m_id = another.m_id;int len = strlen(another.m_name);m_name = (char *)malloc(len + 1);strcpy(m_name, another.m_name);}~Teacher(){cout << "~Teacher()...." << endl;if (m_name != NULL) {free(m_name);}}
private:int m_id;//char m_name[64]; 没有深拷贝 浅拷贝分解 因为此时m_name在栈上分配空间char *m_name;
};void test01() {Teacher t1(1, "zhangsan");t1.printT();Teacher t2(t1); //调用t2的默认拷贝构造 程序崩溃t2.printT();
} //t2析构 清空zhangsan所在空间 //t1析构时 也要清空zhangsan所在空间 然而已经清空 所以程序崩溃
int main()
{test01();return 0;
}
#endif
运行结果:
不出错原因:
06构造函数的初始化列表
#include<iostream>
using namespace std;
class A
{
public:A(int a){cout << "A()..." << a << endl;m_a = a;}~A() {cout << "~A()" << endl;}void printA() {cout << "a = " << m_a << endl;}private:int m_a;
};
class B {
public:
#if 0B(int b, A &a1, A &a2) {//m_a1 = a1; //赋值编译错误 //m_a2 = a2; //m_a2(a2); 拷贝构造 编译错误 m_b = b;}
#endif/*构造函数的初始化列表 初始化对象时需要用到构造对象成员的顺序跟初始化列表的顺序无关而是跟成员对象的定义顺序有关*/B(A &a1, A &a2, int b) :m_a1(a1), m_a2(a2) { //调用拷贝构造cout << "B(A&, A&, int)..." << endl;m_b = b;}B(int a1, int a2, int b) : m_a2(a2) ,m_a1(a1) //调用有参构造{cout << "B(int, int, int)..." << endl;m_b = b;}void printB() {cout << "b = " << m_b << endl;m_a1.printA();m_a2.printA();}~B(){cout << "~B().." << endl;}
private:A m_a1;A m_a2;int m_b;
};void test01() {A a1(10),a2(100);B b(a1, a2, 1000);b.printB(); //b析构 b中A A析构 test01中a1 a2释放
}
/*
A()...10
A()...100
B(A&, A&, int)...
b = 1000
a = 10
a = 100
~B()..
~A()
~A()
~A()
~A()
*/
void test02() {B b(10, 20, 1000);b.printB();//b析构 b中A A析构 test01中a1 a2释放
}
/*
A()...10
A()...20
B(int, int, int)...
b = 1000
a = 10
a = 20
~B()..
~A()
~A()
*/
int main() {//test01();test02();return 0;
}
练习1(构造函数和析构函数的执行时间)
#if 0
#include<iostream>
using namespace std;
class ABCD
{
public:ABCD(int a, int b, int c){_a = a;_b = b;_c = c;printf("ABCD() construct, a:%d,b:%d,c:%d \n", _a, _b, _c);}~ABCD(){printf("~ABCD() construct,a:%d,b:%d,c:%d \n", _a, _b, _c);}int getA(){return _a;}
private:int _a;int _b;int _c;
};
class MyE
{
public:MyE() :abcd1(1, 2, 3), abcd2(4, 5, 6), m(100){cout << "MyD()" << endl;}~MyE(){cout << "~MyD()" << endl;}MyE(const MyE & obj) :abcd1(7, 8, 9), abcd2(10, 11, 12), m(100){printf("MyD(const MyD & obj)\n");}
public:ABCD abcd1; //c++编译器不知道如何构造abc1ABCD abcd2;const int m;
};
int doThing(MyE mye1)
{printf("doThing() mye1.abc1.a:%d \n", mye1.abcd1.getA());return 0;
}
int run()
{MyE myE;doThing(myE);return 0;
}
/*
ABCD() construct, a:1,b:2,c:3
ABCD() construct, a:4,b:5,c:6
MyD()
ABCD() construct, a:7,b:8,c:9
ABCD() construct, a:10,b:11,c:12
MyD(const MyD & obj)
doThing() mye1.abc1.a:7
~MyD()
~ABCD() construct,a:10,b:11,c:12
~ABCD() construct,a:7,b:8,c:9
~MyD()
~ABCD() construct,a:4,b:5,c:6
~ABCD() construct,a:1,b:2,c:3
*/
int run2()
{printf("run2 start..\n");ABCD(400, 500, 600); //临时对象的⽣命周期printf("run2 end\n");return 0;
}
/*
run2 start..
ABCD() construct, a:400,b:500,c:600
~ABCD() construct,a:400,b:500,c:600
run2 end
*/
int run3()
{printf("run2 start..\n");ABCD abcd=ABCD(100, 200, 300);printf("run2 end\n");return 0;
}
/*
run2 start..
ABCD() construct, a:100,b:200,c:300
run2 end
~ABCD() construct,a:100,b:200,c:300
*/
int main(void)
{//run();//run2();run3();return 0;
}
#endif
练习2:
#include<iostream>
using namespace std;
//构造中调⽤构造是危险的⾏为
class MyTest
{
public:MyTest(int a, int b, int c){_a = a;_b = b;_c = c;}MyTest(int a, int b){_a = a;_b = b;MyTest(a, b, 100); //产⽣新的匿名对象}~MyTest(){printf("MyTest~:%d, %d, %d\n", _a, _b, _c);}int getC(){return _c;}void setC(int val){_c = val;}
private:int _a;int _b;int _c;
};
int main()
{MyTest t1(1, 2);printf("c:%d\n", t1.getC()); //请问c的值是?return 0;
}
/*
MyTest~:1, 2, 100
c:-858993460
MyTest~:1, 2, -858993460
*/
父类子类构造函数调用顺序
#include<iostream>
#include<string>
using namespace std;
class A
{
public:A() {std::cout << "cons A" << std::endl;}A(int i) {m_i = i;cout << "cons A with" << i << endl;}~A() {std::cout << "des A with" << m_i<< endl;}int m_i = 0;
};
# if 0
class B :public A {
public:B(){m_i = 3;cout << "cons B" << endl;}~B() {cout << "des B with" << m_i << endl;m_i = 4;}
private:A a1;A a2;
};
int main() {B b;return 0;
}
/*
cons A
cons A
cons A
cons B
des B with3
des A with0
des A with0
des A with4构造函数就是初始化数据成员的。所以现有构造函数的调用,再有数据成员的初始化
*/#endif# if 0
class B :public A {
public:B():a1(1),a2(2){m_i = 3;cout << "cons B" << endl;}~B() {cout << "des B with" << m_i << endl;m_i = 4;}
private:A a1;A a2;
};
int main() {B b;return 0;
}
/*
cons A
cons A with1
cons A with2
cons B
des B with3
des A with2
des A with1
des A with4
构造函数就是初始化数据成员的。所以现有构造函数的调用,再有数据成员的初始化
*/#endif# if 1
class B :public A {
public:B(){m_i = 3;cout << "cons B" << endl;}~B() {cout << "des B with" << m_i << endl;m_i = 4;}
};
int main() {B b;return 0;
}
/*
cons A
cons B
des B with3
des A with4
因为m_i 本身就是B从A继承而来,所以B中对m_i做更改,A也能感知到
*/#endif