c++建構函式總結
阿新 • • 發佈:2019-01-02
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;
};