一、概念
堆內存對象需要手動使用delete銷毀,如果沒有使用delete銷毀就會造成內存泄漏。
所以C++在ISO98標準中引入了智能指針的概念,并在ISO11中趨于完善。
使用智能指針可以讓堆內存對象具有棧內存對象的特點,原理是給需要手動回收的內內存對象套上一個棧內存的模板類對象的即可。
使用智能指針需要引入頭文件#include<memory>
C++有四種智能指針
●?auto_ptr(自動指針)(C++ISO98 ,已廢棄)
●?unique_ptr(唯一指針)(C++ISO11)
●?shared_ptr(共享指針)(C++ISO11)
●?weak_ptr(虛指針)(C++ISO11)
二、auto_ptr?
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:string s;
public:Test(string s):s(s){cout<<s<<"構造函數"<<endl;}~Test(){cout<<s<<"析構函數"<<endl;}void show(){cout<<s<<"對象執行"<<endl;}
};
int main()
{{//創建一個Test對象,t1指針指向對象Test *t1=new Test("A");//將對象t1交給ap1管理auto_ptr<Test>ap1(t1);//訪問對象的成員函數ap1.get()->show(); //ap1.get() <=等價=> t1//釋放智能指針ap1對對象t1的管理權,但不銷毀對象
// ap1.release();//釋放智能指針ap1對對象t1的管理權,且銷毀對象
// ap1.reset();//創建新的對象t2,銷毀原t1對象,并將ap1管理的對象換為t2Test *t2=new Test("B");ap1.reset(t2);// //以上兩句代碼可簡寫為
// ap1.reset(new Test("B"));ap1.get()->show();cout<<"局部代碼塊執行結束"<<endl;}cout<<"程序運行結束"<<endl;return 0;
}/*
A構造函數
A對象執行
B構造函數
A析構函數
B對象執行
局部代碼塊執行結束
B析構函數
程序運行結束
*/
由于成員變量存在指針類型,其拷貝構造函數和賦值運算符重載函數的使用會出現問題,與淺拷貝不同,auto_ptr的復制語義(拷貝構造函數、賦值運算符重載函數)會導致智能指針對對象控制權的轉移(非程序員本意),這也是他被廢棄的原因
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:string s;
public:Test(string s):s(s){cout<<s<<"構造函數"<<endl;}~Test(){cout<<s<<"析構函數"<<endl;}void show(){cout<<s<<"對象執行"<<endl;}
};
int main()
{{//創建一個Test對象,t1指針指向對象Test *t1=new Test("A");//將對象t1交給ap1管理auto_ptr<Test>ap1(t1);cout<<ap1.get()<<endl;//顯示調用拷貝構造函數auto_ptr<Test>ap2(ap1);//對t1的管理權從ap1到了ap2cout<<ap1.get()<<" "<<ap2.get()<<endl;//隱式調用拷貝構造函數auto_ptr<Test>ap3=ap2;//對t1的管理權從ap2到了ap3cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<endl;//使用賦值運算符auto_ptr<Test>ap4;ap4=ap3; //對t1的管理權從ap3到了ap4cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<" "<<ap4.get()<<endl;cout<<"局部代碼塊執行結束"<<endl;}cout<<"程序運行結束"<<endl;return 0;
}/*
A構造函數
0x1162670
0 0x1162670
0 0 0x1162670
0 0 0 0x1162670
局部代碼塊執行結束
A析構函數
程序運行結束
*/
三、unique_ptr?
作為auto_ptr的改進,unique_ptr對其他持有的資源對象具有唯一控制權,即不可以通過常規的復制語法轉移或者拷貝資源對象的控制權。
?
?
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:string s;
public:Test(string s):s(s){cout<<s<<"構造函數"<<endl;}~Test(){cout<<s<<"析構函數"<<endl;}void show(){cout<<s<<"對象執行"<<endl;}
};
int main()
{{//創建一個Test對象,t1指針指向對象Test *t1=new Test("A");//將對象t1交給up1管理unique_ptr<Test>up1(t1);cout<<up1.get()<<endl;//顯示調用拷貝構造函數unique_ptr<Test>up2(move(up1));cout<<up1.get()<<" "<<up2.get()<<endl;//隱式調用拷貝構造函數unique_ptr<Test>up3=move(up2);cout<<up1.get()<<" "<<up2.get()<<" "<<up3.get()<<endl;//使用賦值運算符unique_ptr<Test>up4;up4=move(up3);cout<<up1.get()<<" "<<up2.get()<<" "<<up3.get()<<" "<<up4.get()<<endl;cout<<"局部代碼塊執行結束"<<endl;}cout<<"程序運行結束"<<endl;return 0;
}/*
A構造函數
0xed2670
0 0xed2670
0 0 0xed2670
0 0 0 0xed2670
局部代碼塊執行結束
A析構函數
程序運行結束
*/
?四、shared_ptr
?
每次多一個shared_ptr對資源進行管理,引用計數將+1,每個指向該對象的shared_ptr對象銷毀時,引用計數-1,最后一個shared_ptr對象銷毀時,計數清零,資源對象銷毀。(即對于所有管理某對象A的 shared_ptr,這些 shared_ptr共享的資源有:引用計數、對象資源)?
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:string s;
public:Test(string s):s(s){cout<<s<<"構造函數"<<endl;}~Test(){cout<<s<<"析構函數"<<endl;}void show(){cout<<s<<"對象執行"<<endl;}
};
int main()
{shared_ptr<Test>sp3;{shared_ptr<Test>sp1=make_shared<Test>("A");cout<<"引用計數:"<<sp1.use_count()<<endl;cout<<"-------------"<<endl;shared_ptr<Test>sp2(sp1);cout<<"引用計數:"<<sp2.use_count()<<endl;cout<<"引用計數:"<<sp1.use_count()<<endl;cout<<"-------------"<<endl;sp3=sp2;cout<<"引用計數:"<<sp3.use_count()<<endl;cout<<"局部代碼塊執行結束"<<endl;}//此時sp1、sp2都已銷毀,所以引用計數減2cout<<"引用計數:"<<sp3.use_count()<<endl;cout<<"程序運行結束"<<endl;return 0;
}/*
A構造函數
引用計數:1
-------------
引用計數:2
引用計數:2
-------------
引用計數:3
局部代碼塊執行結束
引用計數:1
程序運行結束
A析構函數*/
?五、weak_ptr
weak_ptr是一個不控制資源對象的智能指針,不會影響資源的引用計數,其主要的目的是協助shared_ptr工作。通過weak_ptr的構造函數,參數傳入一個持有資源對象的shared_ptr或者weak_ptr對象即可創建。weak_ptr與資源對象呈弱相關性,因此不支持get等函數直接操作資源對象.
建議weak_ptr調用lock函數之前,先檢測引用計數是否大于0,或使用expired()函數檢測是否可轉換為shared_ptr.
- 因為不會增加引用計數,所以作為觀察者,觀察對象是否被釋放
- 若shared_ptr失效,weak_ptr可以通過lock函數“轉正”
- shared_ptr在特殊場景下也會造成內存泄漏(循環引用導致智能指針內存泄漏的問題),可以使用weak_ptr來解決
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:string s;
public:Test(string s):s(s){cout<<s<<"構造函數"<<endl;}~Test(){cout<<s<<"析構函數"<<endl;}void show(){cout<<s<<"對象執行"<<endl;}
};
int main()
{weak_ptr<Test>wp3;{shared_ptr<Test>sp1=make_shared<Test>("A");weak_ptr<Test>wp1=sp1;weak_ptr<Test>wp2(wp1);cout<<sp1.use_count()<<endl;cout<<wp1.use_count()<<endl;cout<<wp2.use_count()<<endl;cout<<"-----------"<<endl;//weak_ptr可以通過lock函數“轉正”shared_ptr<Test>sp2=wp1.lock();wp3=wp2;cout<<wp3.use_count()<<endl;cout<<"局部代碼塊執行結束"<<endl;}//判斷能否轉正cout<<wp3.use_count()<<endl;if(wp3.expired()){cout<<"weak_ptr無法轉正"<<endl;}cout<<"程序運行結束"<<endl;return 0;
}/*
A構造函數
1
1
1
-----------
2
局部代碼塊執行結束
A析構函數
0
weak_ptr無法轉正
程序運行結束*/
?六、其他
1.nullptr
在C++11中使用nullptr代替NULL,作為空指針的表示方式,在C++中,可以用作空指針常量,表示指針不指向任何有效的內存地址。
?2.類型推導
2.1? auto
使用auto關鍵字可以推導類型,C++11引入的。
#include <iostream>
#include <memory>
using namespace std;int main()
{auto i1=10;//typeof(i1)-->整型auto i2=1.5;//typeof(i2)-->浮點型auto i3=new auto(10); //typeof(i3)-->int *//int* i3=new int(10);cout<<*i3<<" "<<i3<<endl;auto i4='a';//typeof(i4)-->charreturn 0;
}
2.2?decltypt
decltypt可以推導表達式的類型,需要注意,decltypt只會分析表達式的類型,不會具體計算表達式的值。
3.初始化列表
#include <iostream>
#include <array>
#include <vector>using namespace std;class Student
{
private:string name;int age;
public:Student(string name,int age):name(name),age(age){}void show(){cout << name << " " << age << endl;}
};int main()
{Student s = {"張勝男",18}; // 列表初始化s.show();array<int,5>arr1 = {1,3,4,5,6};vector<int> vec1 ={1,2,3};int arr2[] = {2,3,45,6};int a{};cout << a << endl; // 0return 0;
}
4.面試題
【面試題】:C++11學過那些特性?
- auto(類型推導)
- 智能指針(unique_ptr、?shared_ptr、weak_ptr)
- 初始化列表
- for-each
- 類型轉換函數
- 繼承構造
- array
- override
?5.? 進制輸出
#include <iostream>using namespace std;int main()
{// 為了區分不同進制,可以增加進制顯式的功能,此功能設定持久cout << showbase;cout << dec << 1234 << endl; // 1234cout << oct << 1234 << endl; // 02322// 輸出進制是持久的cout << 9 << endl; // 011cout << hex << 256 << endl; // 0x100// 取消進制顯式的功能cout << noshowbase;cout << 16 << endl; // 10return 0;
}
6.??設定輸出域寬度
可以使用setw()來制定一個整數或者一個字符串輸出占用的與寬度。●?當設定域寬度小于數據本身時,仍然會顯示為數據本身的寬度。
●?當設定域寬度大于數據本身時,會顯式未設定的寬度。
?
#include <iostream>
#include <iomanip>using namespace std;int main()
{// 仍然會按照實際的寬度輸出cout << setw(5) << 123456 << setw(5) << 123456 << endl;cout << setw(10) << 123456 << setw(10) << 123456 << endl;// 域寬度 只能作用于下一行cout << setw(10);cout << 123456 << endl;cout << 123456 << endl;return 0;
}
拓展學習:
C++如何實現多線程、多進程。(案例:線程池)
文件處理。(案例:日志系統)
客戶端、服務器、高并發??
Lambda表達式