C++基础05-类构造函数与析构函数
總結:
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; }#endif05深拷貝與淺拷貝應用場景
防止內存泄漏(開辟的空間沒有釋放):導致內存爆掉 程序崩潰
防止釋放同一塊內存
當類的數據成員中有指針時,拷貝構造函數必須顯式說明(為指針所存內存開辟空間)
同時析構函數 手動釋放空間
# 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?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的C++基础05-类构造函数与析构函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++基础17-纯虚函数和抽象类
- 下一篇: Python与C++ 局部变量/全部变量