C++動態(tài)內(nèi)存分配超詳細(xì)講解
1.在類中使用動態(tài)內(nèi)存分配的注意事項
1.1 構(gòu)造函數(shù)中使用new
- 如果在構(gòu)造函數(shù)中使用
new
來初始化指針成員,則應(yīng)在析構(gòu)函數(shù)中使用delete
new
和delete
必須相互兼容,new
相對delete
;new[]
相對delete[]
- 因?yàn)橹挥幸粋€析構(gòu)函數(shù),所有的構(gòu)造函數(shù)都必須與它兼容
注意的是:delete
或者delete[]
都可以對空指針操作.
NULl
和0
和nullptr
:空指針可以用0
或者NULL
來表示,C++11使用一個特殊的關(guān)鍵詞:nullptr
來表示空指針.
應(yīng)該定義一個復(fù)制構(gòu)造函數(shù),通過深度復(fù)制將一個對象初始化成另一個對象.
String::String(const String &st)//復(fù)制構(gòu)造函數(shù) { len=st.len; str=new char[len+1]; std::strcpy(str,st.str); num_strings++; }
應(yīng)該定義一個賦值運(yùn)算符。
String& String::operator=(const String& st)//賦值運(yùn)算符 { if(this==&st) return *this; delete[] str; len=st.len; str=new char[len+1]; std::strcpy(str,st.str); return *this; }
具體來說,操作是:檢查自我賦值情況,釋放成員指針以前指向的內(nèi)存,復(fù)制數(shù)據(jù)而不僅僅是地址,返回一個指向調(diào)用對象的引用.
一個典型錯誤
String::String() { str="default string"; len=std::strlen(str); }
上面這段代碼定義了默認(rèn)構(gòu)造函數(shù),但是它犯了一個錯誤:無法和析構(gòu)函數(shù)中的delete[]
匹配.
包含類成員的類的逐成員復(fù)制
class Magazine { private: String title; String publisher; }
類成員的類型是String
,這是否意味著要為Magazine
類編寫復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符?不.
如果你將一個Magazine
對象復(fù)制或者賦值給另一個Magazine
對象,逐成員復(fù)制將使用成員類型定義的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符.也就是說復(fù)制title
時,將調(diào)用String
的復(fù)制構(gòu)造函數(shù),而將title
賦值給另一個Magazine
對象時,也會使用String
的賦值運(yùn)算符.
1.2 有關(guān)返回對象的說明
返回指向const
對象的引用
返回對象會調(diào)用復(fù)制構(gòu)造函數(shù)生成臨時對象,而返回const
對象的引用不會.
引用指向的對象不能是局部變量. 總之,返回指向const
對象的引用,就是按值傳遞的升級版,但是它不能返回局部變量.
返回指向非const
對象的引用
例如我們重載<<
時,
ostream& operator<<(ostream & os,class_name object);
返回指向非const
對象的引用,主要是我們希望對函數(shù)返回對象進(jìn)行修改.
返回對象
就是按值傳遞.
如果我們返回的對象是局部變量,那么我們不能使用引用來返回了,只能采用返回對象.
返回const
對象
不太常用.防止用戶對臨時對象進(jìn)行賦值操作,而編譯器不會對這種操作報錯.
總之,如果要返回局部對象就必須返回對象;如果,那必須返回對象的引用;如果返回對象也行,返回指向?qū)ο蟮囊靡残?那優(yōu)先使用引用版本,因?yàn)樾矢?
1.3 使用new創(chuàng)建對象
String * glop=new String("my my my");
這句話會使用構(gòu)造函數(shù)String(const char *);
glop->類成員
可以使用這種方式調(diào)用對象成員,學(xué)過C語言的應(yīng)該明白。
對于動態(tài)分配的對象,它的析構(gòu)函數(shù)當(dāng)且僅當(dāng)使用delete
刪除對象時,它的析構(gòu)函數(shù)才會調(diào)用。
定位new
的用法
#include<iostream> #include<string> #include<new> using std::string; using std::cout; using std::cin; using std::endl; const int BUF=512; class JustTesting { private: string words; int number; public: JustTesting(const string & s="Just Testing",int n=0) :words(s),number(n){cout<<words<<" constructed.\n";} ~JustTesting(){cout<<words<<" destoryed!\n";} void show() const {cout<<words<<", "<<number<<endl;} }; int main() { char * buffer=new char[BUF];//獲得一塊512B內(nèi)存 JustTesting *pc1,*pc2; pc1=new(buffer) JustTesting;//在該塊內(nèi)存中分配空間 pc2=new JustTesting ("Heap1",20); cout<<"Memory block addresses:\n"<<"buffer: "<<(void*)buffer<<" heap: "<<pc2<<endl; cout<<"Memory contents:\n"; cout<<pc1<<": "; pc1->show(); cout<<pc2<<": "; pc2->show(); JustTesting *pc3,*pc4; pc3=new(buffer+sizeof(JustTesting)) JustTesting ("Bad Idea",6); pc4=new JustTesting ("Heap2",10); cout<<"Memory contents:\n"; cout<<pc3<<": "; pc3->show(); cout<<pc4<<": "; pc4->show(); delete pc2; delete pc4; pc3->~JustTesting(); pc1->~JustTesting(); delete [] buffer; cout<<"done !\n"; }
Just Testing constructed.
Heap1 constructed.
Memory block addresses:
buffer: 0xf040a0 heap: 0xf042d0
Memory contents:
0xf040a0: Just Testing, 0
0xf042d0: Heap1, 20
Bad Idea constructed.
Heap2 constructed.
Memory contents:
0xf040c8: Bad Idea, 6
0xf04330: Heap2, 10
Heap1 destoryed!
Heap2 destoryed!
Bad Idea destoryed!
Just Testing destoryed!
done !
上面這段代碼演示了定位new
的用法,這個我們之前在內(nèi)存模型中談過。這里需要注意的是,如果使用定位new
創(chuàng)建對象,如何確保其析構(gòu)函數(shù)被調(diào)用,我們不能使用delete p3;delete p1;
,這是因?yàn)?code>delete和定位new
不匹配,我們必須顯式調(diào)用析構(gòu)函數(shù)p1->~JustTesting();
。
2.隊列模擬
和棧(Stack)一樣,隊列(Queue)也是一個很重要的抽象數(shù)據(jù)結(jié)構(gòu)。這一節(jié)將會構(gòu)建一個Queue
類,順便復(fù)習(xí)之前所學(xué)的技術(shù)和學(xué)習(xí)少量新知識。
我們采用鏈表來實(shí)現(xiàn)隊列。
2.1 類聲明中的一些思考
typedef std::string Item; class Queue { private: struct Node { Item item; struct Node *next; }; enum{Q_SIZE=10}; Node* front;//隊首指針 Node* rear;//隊尾指針 int items;//隊列中的元素個數(shù) const int qsize;//隊列的最大元素個數(shù) //搶占式定義 Queue(const Queue & q):qsize(0){} Queue & operator=(const Queue & q){return *this;} public: Queue(int qs=Q_SIZE); ~Queue(); bool isempty() const;//空 bool isfull() const;//滿 int queuecount() const;//隊列中元素個數(shù) bool enqueue(const Item &i);//入隊 bool dequeue(Item & i);//出隊 void show() const; };
類作用域中的結(jié)構(gòu)體
類似于類作用域中的常量,通過將結(jié)構(gòu)體Node
聲明放在Queue
類的私有部分,就可以在類作用域中使用該結(jié)構(gòu)體。這樣就不用擔(dān)心,Node
聲明和某些全局聲明發(fā)生沖突。此外,類聲明中還能使用Typedef
或者namespace
等聲明,都可以使其作用域變成類中。
利用構(gòu)造函數(shù)初始化const
數(shù)據(jù)成員
在類中qsize
是隊列最大元素個數(shù),它是個常量數(shù)據(jù)成員
Queue::Queue(int qs) { qsize=qs; front =rear=nullptr; items=0; }
上面這段代碼是錯誤的。因?yàn)槌A渴遣辉试S被賦值的。C++提供了一種新的方式來解決這一問題–成員初始化列表。
成員初始化列表語法
它的作用是,在調(diào)用構(gòu)造函數(shù)的時候,能夠初始化數(shù)據(jù)。對于const
類成員,引用數(shù)據(jù)成員,都應(yīng)該使用這種語法。
于是,構(gòu)造函數(shù)可以這樣:
Queue::Queue(int qs):qsize(qs) { front =rear=nullptr; items=0; }
而且這種方法不限于初始化常量,還能初始化非const
變量。則構(gòu)造函數(shù)也可以這樣:
Queue::Queue(int qs):qsize(qs),front(nullptr),rear(nullptr),items(0){}
但是,成員初始化列表語法只能用于構(gòu)造函數(shù)。
類內(nèi)初始化
在C++中,其實(shí)還有一種更直觀的初始化方式,那就是直接在類聲明中進(jìn)行初始化。
class Classy { int mem1=10; const int mem2=20; };
相當(dāng)于在構(gòu)造函數(shù)中使用
Classy::Classy():mem1(10),mem2(20){...}
但是如果你同時使用類內(nèi)初始化和成員列表語法時,調(diào)用相應(yīng)構(gòu)造函數(shù)時,成員列表語法會覆蓋類內(nèi)初始化。
Classy::Classy(int n):mem1(n){...}
調(diào)用上面這個構(gòu)造函數(shù)時,mem1
會被設(shè)置成n
,而mem2
由于類內(nèi)初始化的原因被設(shè)置成20
.
是否需要顯式析構(gòu)函數(shù)?
Queue
類的構(gòu)造函數(shù)中是不需要使用new
的,因?yàn)闃?gòu)造函數(shù)只是構(gòu)造一個空隊列,那這是不是意味著不需要在析構(gòu)函數(shù)中使用delete
?
我們知道,雖然構(gòu)造函數(shù)不需要new
,但是在enqueue
入隊時,我們需要new
一個新元素加入隊列。那么我們必須在析構(gòu)函數(shù)中使用delete
以確保所有動態(tài)分配的空間被釋放。
偽私有方法(搶占式定義)
既然我們在Queue
類中,使用了動態(tài)內(nèi)存分配,那么編譯器提供的默認(rèn)復(fù)制構(gòu)造函數(shù),和默認(rèn)賦值運(yùn)算符是不正確的。我們假設(shè)隊列是不允許被賦值或者復(fù)制的,那么我們可以使用偽私有方法,目的是禁用某些默認(rèn)接口。
class Queue { private: Queue(const Queue & q):qsize(0){} Queue & operator=(const Queue & q){return *this;} }
這樣做的原理是:在私有部分搶先定義了復(fù)制構(gòu)造函數(shù),賦值運(yùn)算符,那么編譯器就不會提供默認(rèn)方法了,那么對象就無法調(diào)用這些方法。
C++提供了另一種禁用方法的方式–使用關(guān)鍵詞delete
class Queue { public: Queue(const Queue & q)=delete; Queue & operator=(const Queue & q)=delete; }
可以直接在公有部分中禁用某種方法。
2.2 代碼實(shí)現(xiàn)
//queue.h #ifndef QUEUE_H_ #define QUEUE_H_ #include<string> typedef std::string Item; class Queue { private: struct Node { Item item; struct Node *next; }; enum{Q_SIZE=10}; Node* front;//隊首指針 Node* rear;//隊尾指針 int items;//隊列中的元素個數(shù) const int qsize;//隊列的最大元素個數(shù) //搶占式定義 Queue(const Queue & q):qsize(0){} Queue & operator=(const Queue & q){return *this;} public: Queue(int qs=Q_SIZE); ~Queue(); bool isempty() const;//空 bool isfull() const;//滿 int queuecount() const;//隊列中元素個數(shù) bool enqueue(const Item &i);//入隊 bool dequeue(Item & i);//出隊 void show() const; }; #endif
//queue.cpp #include"queue.h" #include<iostream> Queue::Queue(int qs):qsize(qs) { front =rear=nullptr; items=0; } Queue::~Queue() { Node * p; while (front!=nullptr) { p=front; front=front->next; delete p; } } bool Queue::isempty() const { return items==0; } bool Queue::isfull() const { return items==qsize; } int Queue::queuecount() const { return items; } bool Queue::enqueue(const Item &i) { if(isfull()) return false; Node *add=new Node; add->item=i; add->next=nullptr; items++; if(front==nullptr)//隊空 front=rear=add; else { rear->next=add; rear=add; } return true; } bool Queue::dequeue(Item & i) { if(isempty()) return false; i=front->item; items--; if(items==0) { delete front; front=rear=nullptr; } else { Node *p=front; front=front->next; delete p; } return true; } void Queue::show() const { using std::cout; using std::endl; cout<<"the items: "<<items<<endl; if(isempty()) cout<<"Empty queue!\n"; else { cout<<"front: "; for(Node*p=front;p!=nullptr;p=p->next) { cout<<p->item; if(p!=rear) cout<<"-> "; } cout<<" :rear\n"; } }
//queuetest.cpp #include"queue.h" #include<iostream> int main() { using std::cin; using std::cout; using std::endl; using std::string; Queue test(8); char choice; cout<<"Enter E to enqueue ,D to dequeue,Q to quit: "; while(cin>>choice) { string temp; switch (choice) { case 'E': cout<<"Enter the string: "; cin>>temp; if(test.enqueue(temp)) test.show(); else cout<<"can't enqueue\n"; break; case 'D': if (test.dequeue(temp)) { cout<<"the item gotten: "<<temp<<endl; test.show(); } else cout<<"can't dequeue\n"; break; case 'Q': goto aa; break; default: break; } cout<<endl; cout<<"Enter E to enqueue ,D to dequeue,Q to quit: "; cin.ignore(); } aa:test.~Queue(); cout<<"Bye!: "; test.show(); }
PS D:\study\c++\path_to_c++> .\queue.exe
Enter E to enqueue ,D to dequeue,Q to quit: E
Enter the string: apple
the items: 1
front: apple :rearEnter E to enqueue ,D to dequeue,Q to quit: E
Enter the string: banana
the items: 2
front: apple-> banana :rearEnter E to enqueue ,D to dequeue,Q to quit: E
Enter the string: candy
the items: 3
front: apple-> banana-> candy :rearEnter E to enqueue ,D to dequeue,Q to quit: E
Enter the string: dizzy
the items: 4
front: apple-> banana-> candy-> dizzy :rearEnter E to enqueue ,D to dequeue,Q to quit: D
the item gotten: apple
the items: 3
front: banana-> candy-> dizzy :rearEnter E to enqueue ,D to dequeue,Q to quit: D
the item gotten: banana
the items: 2
front: candy-> dizzy :rearEnter E to enqueue ,D to dequeue,Q to quit: Q
Bye!: the items: 2
front: :rear
到此這篇關(guān)于C++動態(tài)內(nèi)存分配超詳細(xì)講解的文章就介紹到這了,更多相關(guān)C++動態(tài)內(nèi)存分配內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VS2019配置opencv詳細(xì)圖文教程和測試代碼的實(shí)現(xiàn)
這篇文章主要介紹了VS2019配置opencv詳細(xì)圖文教程和測試代碼的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04Ubuntu18.04下QT開發(fā)Android無法連接設(shè)備問題解決實(shí)現(xiàn)
本文主要介紹了Ubuntu18.04下QT開發(fā)Android無法連接設(shè)備問題解決實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06php5系列的apache遠(yuǎn)程執(zhí)行漏洞攻擊腳本
這篇文章主要介紹了php5系列的apache遠(yuǎn)程執(zhí)行漏洞攻擊腳本,需要的朋友可以參考下2014-06-06