第37課 智慧指標分析(指標特徵操作符( -> 、 *)過載)
1.永恆的話題:記憶體洩漏
(1)動態申請堆空間,用完後不歸還
(2)C++語言中沒有垃圾回收的機制
(3)指標無法控制所指堆空間的生命週期------------指標是變數,可以指向記憶體堆空間,但是無法控制所指堆空間的生命週期
說明記憶體洩漏:
1 #include<iostream> 2 #include<string> 3 4 using namespace std; 5 6 class Test 7 { 8 int i; 9 10 public: 11 12 Test(int i) 13 { 14 this->i = i; 15 } 16 17 int value() 18 { 19 return i; 20 } 21 22 ~Test() 23 { 24 } 25 }; 26 27 int main() 28 { 29 for (int i = 0; i < 5; i++) 30 { 31 Test* p = new Test(i); //P指向堆空間的物件 32 //指標是區域性變數for()迴圈之後消失,但是指標開闢的空間沒有進行釋放 記憶體洩漏33 //長時間執行會出現Bug 34 35 cout << p->value() << endl; 36 37 } 38 39 return 0; 40 }
2.深度的思考:我們需要什麼?
(1)需要一個特殊的指標,指標生命期結束時主動釋放堆空間
(2)一片堆空間最多只能由一個指標來標識------------避免記憶體多次釋放
(3)杜絕指標運算和指標比較-----------(因為只有一個該類物件的指標能指向堆空間,避免指標越界,野指標)
3.解決方案
定義一個物件,讓物件取模擬真正意思上的指標行為
(1)過載指標特徵操作符(->和*)
(2)只能通過類的成員函式過載
(3)過載函式不能使用引數
(4)只能定義一個過載函式
(5)注意:智慧指標只能用來指向堆空間中的物件或變數(不能指向棧)
實驗:(1)實現特殊的指標,指標生命期結束時主動釋放堆空間
1 #include<iostream> 2 #include<string> 3 4 using namespace std; 5 6 class Test 7 { 8 int i; 9 public: 10 Test(int i) 11 { 12 cout <<"Test (int i)"<< endl; 13 14 this->i = i; 15 } 16 int value() 17 { 18 return i; 19 } 20 ~Test() 21 { 22 cout << "~Test ()" << endl; 23 } 24 25 }; 26 27 28 //智慧指標 本質是物件--------首先建立智慧指標類 29 class Pointer 30 { 31 Test* mp; //指標mp指向物件Test,用於儲存要被管理物件的指標 32 33 public: 34 35 //建構函式被堆空間上記憶體地址初始化 36 37 Pointer(Test* p = NULL) //指標被堆空間上的地址初始化---引數Test*指標 38 { 39 mp = p; 40 } 41 42 //過載->操作符 43 44 Test* operator -> () //不能使用引數,所以也就只能定義一個過載函式 45 { 46 return mp; //返回成員指標 47 } 48 49 //過載*操作符 50 51 Test& operator * () //不能使用引數,所以也就只能定義一個過載函式 52 { 53 return *mp; //*作用指標意義:返回當前指標所指變數或物件 54 } 55 56 ~Pointer() 57 { 58 delete mp; //智慧指標被析構時,同時刪除其所管理的Test類的物件,釋放成員指標指向的堆空間 59 } 60 }; 61 62 int main() 63 { 64 for (int i = 0; i < 5; i++) 65 { 66 // Test* p = new Test(i); //那麼就可以用類物件名代替指標 67 68 Pointer p = new Test(i); //可以動態的將申請的空間釋放 69 70 cout << p->value() << endl; //01234 71 72 } 73 return 0; 74 }
實現:一片堆空間最多只能由一個指標來標識------------避免記憶體多次釋放
不進行指標運算和比較
方法:過載賦值操作符和拷貝構造
1 #include<iostream> 2 #include<string> 3 4 //智慧指標 本質是物件 5 using namespace std; 6 7 class Test 8 { 9 int i; 10 public: 11 Test(int i) 12 { 13 cout <<"Test (int i)"<< endl; 14 this->i = i; 15 } 16 int value() 17 { 18 return i; 19 } 20 ~Test() 21 { 22 cout << "~Test (int i)" << endl; 23 } 24 25 }; 26 27 class Pointer 28 { 29 Test* mp; //資料成員為指標 30 31 public: 32 33 Pointer(Test* p = NULL) //建構函式被堆空間上記憶體地址初始化 34 { 35 mp = p; 36 } 37 38 39 // 實現一片堆空間最多隻能由一個指標標識 杜絕多次釋放 40 41 //拷貝建構函式 42 Pointer(const Pointer& obj) //自定義深拷貝建構函式 43 { 44 // delete mp; //拷貝構造階段,mP野指標不能刪除---記憶體錯誤 45 46 mp = obj.mp; //當前物件的成員指標mp指向初始化物件的成員指標所對應的堆空間---兩個指標指向一片記憶體空間 47 //初始化物件把他指向的堆空間完全交給當前物件-----------即保證只有一個智慧指標指向同一片記憶體 48 49 const_cast<Pointer&>(obj).mp = NULL; //所有權的傳遞,自己置空 const_cast去除只讀屬性 50 } 51 52 53 //賦值操作符過載函式 54 Pointer& operator=(const Pointer& obj) // 1,返回值型別一定是引用Test&為了連續賦值 2,引數是const引用型別 55 { 56 //進行賦值操作 57 if (this != &obj) //3,賦值操作符不是自賦值a=a,要避免賦值,通過This判斷,this指向當前物件的地址和引數地址不同才進行賦值操作 58 { 59 delete mp; 60 61 mp = obj.mp; //保證賦值操作時,只能由一個智慧指標指向同一堆空間 62 63 const_cast<Pointer&>(obj).mp = NULL; 64 } 65 return *this; //4,返回當前物件地址 66 } 67 68 //過載指標特徵操作符 69 Test* operator -> () 70 { 71 return mp; //返回成員指標 72 } 73 Test& operator * () 74 { 75 return *mp; //返回當前指標所指變數或物件 76 } 77 78 bool isNull() //判斷當前智慧指標是否為空 79 { 80 return(mp == NULL); 81 } 82 83 ~Pointer() 84 { 85 delete mp; 86 } 87 }; 88 89 int main() 90 { 91 Pointer p1 = new Test(0); //定義智慧指標P1 92 93 cout << p1->value() << endl; //0 94 95 Pointer p2 = p1; //定義智慧指標P2,用p1進行初始化,p1管理的堆空間轉給P2 96 97 //不能指標比較的指標運算 98 // p2++; 99 // if(p1==p2) 100 101 cout << p1.isNull() << endl; //1空 p1指向NULL,不再指向原來的堆空間 102 103 cout << p2->value() << endl; //0 p2接管P1指向的堆空間------管理92行堆物件 104
//p2++;不過載++,因為每次只能一個智慧指標指向堆空間,這種++操作沒意義
//p2智慧指標生命期結束,會自動釋放接管的堆空間
105 return 0; 106 }
//輸出結果:
//Test(int i)
//5
//1
//5
//~Test()
存在問題: 智慧指標對應的類只能指向Test固定的類型別,不能指向其他型別,能不能指向各類他能管理的類型別------以後會學到模板實現
4.小結
掌握智慧指標的建立
智慧指標在自己生命週期結束,自動釋放所指的記憶體空間
要求c++避免使用原生指標,那麼以後就可以使用智慧指標
(1)指標特徵操作符( -> 和 * )可以被過載
(2)過載指標特徵符能夠使用物件代替指標
(3)智慧指標只能用於指向堆空間中的記憶體
(4)智慧指標的意義在於最大程度的避免記憶體問題