C++面向物件基礎--物件的初始化和清理
1.建構函式和解構函式
物件的初始化和清理是兩個非常重要的安全問題
一個物件或者變數沒有初始狀態,對其使用後果是未知
同樣的使用完一個物件或變數,沒有及時清理,也會造成一定的安全問題
C++利用了建構函式和解構函式解決上述問題,這兩個函式將會被編譯器自動呼叫,完成物件初始化和清理工作。
物件的初始化和清理工作是編譯器強制要我們做的事情,因此如果我們不提供構造和析構,編譯器會提供
編譯器提供的建構函式和解構函式是空實現。
建構函式:主要作用在於建立物件時為物件的成員屬性賦值,建構函式由編譯器自動呼叫,無須手動呼叫。
解構函式:主要作用在於物件銷燬前系統自動呼叫,執行一些清理工作。
建構函式語法:類名(){}
1. 建構函式,沒有返回值也不寫void
2. 函式名稱與類名相同
3. 建構函式可以有引數,因此可以發生過載
4. 程式在呼叫物件時候會自動呼叫構造,無須手動呼叫,而且只會呼叫一次
解構函式語法:~類名(){}
1. 解構函式,沒有返回值也不寫void
2. 函式名稱與類名相同,在名稱前加上符號 ~
3. 解構函式不可以有引數,因此不可以發生過載
4. 程式在物件銷燬前會自動呼叫析構,無須手動呼叫,而且只會呼叫一次
示例程式碼:
1 #include<iostream> 2 using namespace std; 3 class Person 4 { 5 public: 6//建構函式 7 Person() 8 { 9 cout << "Person的建構函式呼叫" << endl; 10 } 11 //解構函式 12 ~Person() 13 { 14 cout << "Person的解構函式呼叫" << endl; 15 } 16 17 }; 18 19 void test01() 20 { 21 Person p; 22 } 23 24 int main() 25 { 26 27 test01();28 29 system("pause"); 30 31 return 0; 32 }
執行結果:
2.建構函式的分類及呼叫
兩種分類方式:
按引數分為: 有參構造和無參構造
按型別分為: 普通構造和拷貝構造
三種呼叫方式:
(1)括號法
(2)顯示法
(3)隱式轉換法
1 #include<iostream> 2 using namespace std; 3 //1、建構函式分類 4 // 按照引數分類分為 有參和無參構造 無參又稱為預設建構函式 5 // 按照型別分類分為 普通構造和拷貝構造 6 7 class Person 8 { 9 public: 10 //無參(預設)建構函式 11 Person() 12 { 13 cout << "無參建構函式!" << endl; 14 } 15 //有參建構函式 16 Person(int a) 17 { 18 age = a; 19 cout << "有參建構函式!" << endl; 20 } 21 //拷貝建構函式 22 Person(const Person& p) 23 { 24 age = p.age; 25 cout << "拷貝建構函式!" << endl; 26 } 27 //解構函式 28 ~Person() 29 { 30 cout << "解構函式!" << endl; 31 } 32 public: 33 int age; 34 }; 35 36 //2、建構函式的呼叫 37 //呼叫無參建構函式 38 void test01() 39 { 40 Person p; //呼叫無參建構函式 41 } 42 43 //呼叫有參的建構函式 44 void test02() 45 { 46 47 //2.1 括號法,常用 48 Person p1(10); 49 //注意1:呼叫無參建構函式不能加括號,如果加了編譯器認為這是一個函式宣告 50 //Person p2(); 51 52 //2.2 顯式法 53 Person p2 = Person(10); 54 Person p3 = Person(p2); 55 //Person(10)單獨寫就是匿名物件 當前行結束之後,馬上析構 56 57 //2.3 隱式轉換法 58 Person p4 = 10; // Person p4 = Person(10); 59 Person p5 = p4; // Person p5 = Person(p4); 60 61 //注意2:不能利用 拷貝建構函式 初始化匿名物件 編譯器認為是物件宣告 62 //Person p5(p4); 63 } 64 65 int main() 66 { 67 68 test01(); 69 //test02(); 70 71 system("pause"); 72 73 return 0; 74 }
執行結果:
3.拷貝建構函式呼叫時機
C++中拷貝建構函式呼叫時機通常有三種情況
(1)使用一個已經建立完畢的物件來初始化一個新物件
(2)值傳遞的方式給函式引數傳值
(3)以值方式返回區域性物件
示例程式碼:
1 #include<iostream> 2 using namespace std; 3 class Person 4 { 5 public: 6 Person() 7 { 8 cout << "無參建構函式!" << endl; 9 mAge = 0; 10 } 11 Person(int age) 12 { 13 cout << "有參建構函式!" << endl; 14 mAge = age; 15 } 16 Person(const Person& p) 17 { 18 cout << "拷貝建構函式!" << endl; 19 mAge = p.mAge; 20 } 21 //解構函式在釋放記憶體之前呼叫 22 ~Person() 23 { 24 cout << "解構函式!" << endl; 25 } 26 public: 27 int mAge; 28 }; 29 30 //1. 使用一個已經建立完畢的物件來初始化一個新物件 31 void test01() 32 { 33 34 Person man(100); //p物件已經建立完畢 35 Person newman(man); //呼叫拷貝建構函式 36 Person newman2 = man; //拷貝構造 37 38 //Person newman3; 39 //newman3 = man; //不是呼叫拷貝建構函式,賦值操作 40 } 41 42 //2. 值傳遞的方式給函式引數傳值 43 //相當於Person p1 = p; 44 void doWork(Person p1) {} 45 void test02() 46 { 47 Person p; //無參建構函式 48 doWork(p); 49 } 50 51 //3. 以值方式返回區域性物件 52 Person doWork2() 53 { 54 Person p1; 55 cout << (int*)&p1 << endl; 56 return p1; 57 } 58 59 void test03() 60 { 61 Person p = doWork2(); 62 cout << (int*)&p << endl; 63 } 64 65 66 int main() 67 { 68 69 //test01(); 70 //test02(); 71 test03(); 72 73 system("pause"); 74 75 return 0; 76 }
執行結果:
4.建構函式呼叫規則
預設情況下,c++編譯器至少給一個類新增3個函式
1.預設建構函式(無參,函式體為空)
2.預設解構函式(無參,函式體為空)
3.預設拷貝建構函式,對屬性進行值拷貝
建構函式呼叫規則如下:
如果使用者定義有參建構函式,c++不在提供預設無參構造,但是會提供預設拷貝構造
如果使用者定義拷貝建構函式,c++不會再提供其他建構函式
示例程式碼:
1 #include<iostream> 2 using namespace std; 3 class Person 4 { 5 public: 6 //無參(預設)建構函式 7 Person() 8 { 9 cout << "無參建構函式!" << endl; 10 } 11 //有參建構函式 12 Person(int a) 13 { 14 age = a; 15 cout << "有參建構函式!" << endl; 16 } 17 //拷貝建構函式 18 Person(const Person& p) 19 { 20 age = p.age; 21 cout << "拷貝建構函式!" << endl; 22 } 23 //解構函式 24 ~Person() 25 { 26 cout << "解構函式!" << endl; 27 } 28 public: 29 int age; 30 }; 31 32 void test01() 33 { 34 Person p1(18); 35 //如果不寫拷貝構造,編譯器會自動新增拷貝構造,並且做淺拷貝操作 36 Person p2(p1); 37 38 cout << "p2的年齡為: " << p2.age << endl; 39 } 40 41 void test02() 42 { 43 //如果使用者提供有參構造,編譯器不會提供預設構造,會提供拷貝構造 44 Person p1; //此時如果使用者自己沒有提供預設構造,會出錯 45 Person p2(10); //使用者提供的有參 46 Person p3(p2); //此時如果使用者沒有提供拷貝構造,編譯器會提供 47 48 //如果使用者提供拷貝構造,編譯器不會提供其他建構函式 49 Person p4; //此時如果使用者自己沒有提供預設構造,會出錯 50 Person p5(10); //此時如果使用者自己沒有提供有參,會出錯 51 Person p6(p5); //使用者自己提供拷貝構造 52 } 53 54 int main() 55 { 56 57 test01(); 58 59 system("pause"); 60 61 return 0; 62 }
執行結果:
5.深拷貝與淺拷貝
淺拷貝:簡單的賦值拷貝操作
深拷貝:在堆區重新申請空間,進行拷貝操作
示例程式碼:
1 #include<iostream> 2 using namespace std; 3 class Person 4 { 5 public: 6 //無參(預設)建構函式 7 Person() 8 { 9 cout << "無參建構函式!" << endl; 10 } 11 //有參建構函式 12 Person(int age, int height) 13 { 14 15 cout << "有參建構函式!" << endl; 16 17 m_age = age; 18 m_height = new int(height); 19 20 } 21 //拷貝建構函式 22 Person(const Person& p) 23 { 24 cout << "拷貝建構函式!" << endl; 25 //如果不利用深拷貝在堆區建立新記憶體,會導致淺拷貝帶來的重複釋放堆區問題 26 m_age = p.m_age; 27 m_height = new int(*p.m_height); 28 29 } 30 31 //解構函式 32 ~Person() 33 { 34 cout << "解構函式!" << endl; 35 if (m_height != NULL) 36 { 37 delete m_height; 38 } 39 } 40 public: 41 int m_age; 42 int* m_height; 43 }; 44 45 void test01() 46 { 47 Person p1(18, 180); 48 49 Person p2(p1); 50 51 cout << "p1的年齡: " << p1.m_age << " 身高: " << *p1.m_height << endl; 52 53 cout << "p2的年齡: " << p2.m_age << " 身高: " << *p2.m_height << endl; 54 } 55 56 int main() 57 { 58 59 test01(); 60 61 system("pause"); 62 63 return 0; 64 }
執行結果:
總結:如果屬性有在堆區開闢的,一定要自己提供拷貝建構函式,防止淺拷貝帶來的問題
6.初始化列表
作用:
C++提供了初始化列表語法,用來初始化屬性
語法:建構函式():屬性1(值1),屬性2(值2)... {}
示例程式碼:
1 #include<iostream> 2 using namespace std; 3 class Person 4 { 5 public: 6 7 ////傳統方式初始化 8 //Person(int a, int b, int c) 9 //{ 10 // m_A = a; 11 // m_B = b; 12 // m_C = c; 13 //} 14 15 //初始化列表方式初始化 16 Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {} 17 void PrintPerson() 18 { 19 cout << "mA:" << m_A << endl; 20 cout << "mB:" << m_B << endl; 21 cout << "mC:" << m_C << endl; 22 } 23 private: 24 int m_A; 25 int m_B; 26 int m_C; 27 }; 28 29 int main() 30 { 31 32 Person p(1, 2, 3); 33 p.PrintPerson(); 34 35 36 system("pause"); 37 38 return 0; 39 }
執行結果:
7.類物件作為類成員
C++類中的成員可以是另一個類的物件,我們稱該成員為物件成員
例如:
1 class A {} 2 class B 3 { 4 A a; 5 }
B類中有物件A作為成員,A為物件成員
示例程式碼:
1 #include<iostream> 2 using namespace std; 3 class Phone 4 { 5 public: 6 Phone(string name) 7 { 8 m_PhoneName = name; 9 cout << "Phone構造" << endl; 10 } 11 12 ~Phone() 13 { 14 cout << "Phone析構" << endl; 15 } 16 17 string m_PhoneName; 18 19 }; 20 21 22 class Person 23 { 24 public: 25 26 //初始化列表可以告訴編譯器呼叫哪一個建構函式 27 Person(string name, string pName) :m_Name(name), m_Phone(pName) 28 { 29 cout << "Person構造" << endl; 30 } 31 32 ~Person() 33 { 34 cout << "Person析構" << endl; 35 } 36 37 void playGame() 38 { 39 cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手機! " << endl; 40 } 41 42 string m_Name; 43 Phone m_Phone; 44 45 }; 46 void test01() 47 { 48 //當類中成員是其他類物件時,我們稱該成員為 物件成員 49 //構造的順序是 :先呼叫物件成員的構造,再呼叫本類構造 50 //析構順序與構造相反 51 Person p("張三", "蘋果X"); 52 p.playGame(); 53 54 } 55 56 57 int main() 58 { 59 60 test01(); 61 62 system("pause"); 63 64 return 0; 65 }
執行結果:
8.靜態成員
靜態成員就是在成員變數和成員函式前加上關鍵字static,稱為靜態成員
靜態成員分為靜態成員變數和靜態成員函式
靜態成員變數包括:
(1)所有物件共享同一份資料
(2)在編譯階段分配記憶體
(3)類內宣告,類外初始化
靜態成員函式包括:
(1)所有物件共享同一個函式
(2)靜態成員函式只能訪問靜態成員變數
示例程式碼(靜態成員變數):
1 #include<iostream> 2 using namespace std; 3 class Person 4 { 5 public: 6 7 static int m_A; //靜態成員變數 8 9 //靜態成員變數特點: 10 //1 在編譯階段分配記憶體 11 //2 類內宣告,類外初始化 12 //3 所有物件共享同一份資料 13 14 private: 15 static int m_B; //靜態成員變數也是有訪問許可權的 16 }; 17 int Person::m_A = 10; 18 int Person::m_B = 10; 19 20 void test01() 21 { 22 //靜態成員變數兩種訪問方式 23 24 //1、通過物件 25 Person p1; 26 p1.m_A = 100; 27 cout << "p1.m_A = " << p1.m_A << endl; 28 29 Person p2; 30 p2.m_A = 200; 31 cout << "p1.m_A = " << p1.m_A << endl; //共享同一份資料 32 cout << "p2.m_A = " << p2.m_A << endl; 33 34 //2、通過類名 35 cout << "m_A = " << Person::m_A << endl; 36 37 38 //cout << "m_B = " << Person::m_B << endl; //私有許可權訪問不到 39 } 40 41 int main() 42 { 43 44 test01(); 45 46 system("pause"); 47 48 return 0; 49 }
執行結果:
示例程式碼(靜態成員函式):
1 #include<iostream> 2 using namespace std; 3 class Person 4 { 5 public: 6 7 //靜態成員函式特點: 8 //1 程式共享一個函式 9 //2 靜態成員函式只能訪問靜態成員變數 10 11 static void func() 12 { 13 cout << "func呼叫" << endl; 14 m_A = 100; 15 //m_B = 100; //錯誤,不可以訪問非靜態成員變數 16 } 17 18 static int m_A; //靜態成員變數 19 int m_B; // 20 private: 21 22 //靜態成員函式也是有訪問許可權的 23 static void func2() 24 { 25 cout << "func2呼叫" << endl; 26 } 27 }; 28 int Person::m_A = 10; 29 30 31 void test01() 32 { 33 //靜態成員變數兩種訪問方式 34 35 //1、通過物件 36 Person p1; 37 p1.func(); 38 39 //2、通過類名 40 Person::func(); 41 42 43 //Person::func2(); //私有許可權訪問不到 44 } 45 46 int main() 47 { 48 49 test01(); 50 51 system("pause"); 52 53 return 0; 54 }
執行結果: