C++動(dòng)態(tài)內(nèi)存分配超詳細(xì)講解
1.在類中使用動(dòng)態(tài)內(nèi)存分配的注意事項(xiàng)
1.1 構(gòu)造函數(shù)中使用new
- 如果在構(gòu)造函數(shù)中使用
new
來初始化指針成員,則應(yīng)在析構(gòu)函數(shù)中使用delete
new
和delete
必須相互兼容,new
相對(duì)delete
;new[]
相對(duì)delete[]
- 因?yàn)橹挥幸粋€(gè)析構(gòu)函數(shù),所有的構(gòu)造函數(shù)都必須與它兼容
注意的是:delete
或者delete[]
都可以對(duì)空指針操作.
NULl
和0
和nullptr
:空指針可以用0
或者NULL
來表示,C++11使用一個(gè)特殊的關(guān)鍵詞:nullptr
來表示空指針.
應(yīng)該定義一個(gè)復(fù)制構(gòu)造函數(shù),通過深度復(fù)制將一個(gè)對(duì)象初始化成另一個(gè)對(duì)象.
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)該定義一個(gè)賦值運(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ù)而不僅僅是地址,返回一個(gè)指向調(diào)用對(duì)象的引用.
一個(gè)典型錯(cuò)誤
String::String() { str="default string"; len=std::strlen(str); }
上面這段代碼定義了默認(rèn)構(gòu)造函數(shù),但是它犯了一個(gè)錯(cuò)誤:無法和析構(gòu)函數(shù)中的delete[]
匹配.
包含類成員的類的逐成員復(fù)制
class Magazine { private: String title; String publisher; }
類成員的類型是String
,這是否意味著要為Magazine
類編寫復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符?不.
如果你將一個(gè)Magazine
對(duì)象復(fù)制或者賦值給另一個(gè)Magazine
對(duì)象,逐成員復(fù)制將使用成員類型定義的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符.也就是說復(fù)制title
時(shí),將調(diào)用String
的復(fù)制構(gòu)造函數(shù),而將title
賦值給另一個(gè)Magazine
對(duì)象時(shí),也會(huì)使用String
的賦值運(yùn)算符.
1.2 有關(guān)返回對(duì)象的說明
返回指向const
對(duì)象的引用
返回對(duì)象會(huì)調(diào)用復(fù)制構(gòu)造函數(shù)生成臨時(shí)對(duì)象,而返回const
對(duì)象的引用不會(huì).
引用指向的對(duì)象不能是局部變量. 總之,返回指向const
對(duì)象的引用,就是按值傳遞的升級(jí)版,但是它不能返回局部變量.
返回指向非const
對(duì)象的引用
例如我們重載<<
時(shí),
ostream& operator<<(ostream & os,class_name object);
返回指向非const
對(duì)象的引用,主要是我們希望對(duì)函數(shù)返回對(duì)象進(jìn)行修改.
返回對(duì)象
就是按值傳遞.
如果我們返回的對(duì)象是局部變量,那么我們不能使用引用來返回了,只能采用返回對(duì)象.
返回const
對(duì)象
不太常用.防止用戶對(duì)臨時(shí)對(duì)象進(jìn)行賦值操作,而編譯器不會(huì)對(duì)這種操作報(bào)錯(cuò).
總之,如果要返回局部對(duì)象就必須返回對(duì)象;如果,那必須返回對(duì)象的引用;如果返回對(duì)象也行,返回指向?qū)ο蟮囊靡残?那優(yōu)先使用引用版本,因?yàn)樾矢?
1.3 使用new創(chuàng)建對(duì)象
String * glop=new String("my my my");
這句話會(huì)使用構(gòu)造函數(shù)String(const char *);
glop->類成員
可以使用這種方式調(diào)用對(duì)象成員,學(xué)過C語言的應(yīng)該明白。
對(duì)于動(dòng)態(tài)分配的對(duì)象,它的析構(gòu)函數(shù)當(dāng)且僅當(dāng)使用delete
刪除對(duì)象時(shí),它的析構(gòu)函數(shù)才會(huì)調(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
的用法,這個(gè)我們之前在內(nèi)存模型中談過。這里需要注意的是,如果使用定位new
創(chuàng)建對(duì)象,如何確保其析構(gòu)函數(shù)被調(diào)用,我們不能使用delete p3;delete p1;
,這是因?yàn)?code>delete和定位new
不匹配,我們必須顯式調(diào)用析構(gòu)函數(shù)p1->~JustTesting();
。
2.隊(duì)列模擬
和棧(Stack)一樣,隊(duì)列(Queue)也是一個(gè)很重要的抽象數(shù)據(jù)結(jié)構(gòu)。這一節(jié)將會(huì)構(gòu)建一個(gè)Queue
類,順便復(fù)習(xí)之前所學(xué)的技術(shù)和學(xué)習(xí)少量新知識(shí)。
我們采用鏈表來實(shí)現(xiàn)隊(duì)列。
2.1 類聲明中的一些思考
typedef std::string Item; class Queue { private: struct Node { Item item; struct Node *next; }; enum{Q_SIZE=10}; Node* front;//隊(duì)首指針 Node* rear;//隊(duì)尾指針 int items;//隊(duì)列中的元素個(gè)數(shù) const int qsize;//隊(duì)列的最大元素個(gè)數(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;//隊(duì)列中元素個(gè)數(shù) bool enqueue(const Item &i);//入隊(duì) bool dequeue(Item & i);//出隊(duì) 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
是隊(duì)列最大元素個(gè)數(shù),它是個(gè)常量數(shù)據(jù)成員
Queue::Queue(int qs) { qsize=qs; front =rear=nullptr; items=0; }
上面這段代碼是錯(cuò)誤的。因?yàn)槌A渴遣辉试S被賦值的。C++提供了一種新的方式來解決這一問題–成員初始化列表。
成員初始化列表語法
它的作用是,在調(diào)用構(gòu)造函數(shù)的時(shí)候,能夠初始化數(shù)據(jù)。對(duì)于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){...}
但是如果你同時(shí)使用類內(nèi)初始化和成員列表語法時(shí),調(diào)用相應(yīng)構(gòu)造函數(shù)時(shí),成員列表語法會(huì)覆蓋類內(nèi)初始化。
Classy::Classy(int n):mem1(n){...}
調(diào)用上面這個(gè)構(gòu)造函數(shù)時(shí),mem1
會(huì)被設(shè)置成n
,而mem2
由于類內(nèi)初始化的原因被設(shè)置成20
.
是否需要顯式析構(gòu)函數(shù)?
Queue
類的構(gòu)造函數(shù)中是不需要使用new
的,因?yàn)闃?gòu)造函數(shù)只是構(gòu)造一個(gè)空隊(duì)列,那這是不是意味著不需要在析構(gòu)函數(shù)中使用delete
?
我們知道,雖然構(gòu)造函數(shù)不需要new
,但是在enqueue
入隊(duì)時(shí),我們需要new
一個(gè)新元素加入隊(duì)列。那么我們必須在析構(gòu)函數(shù)中使用delete
以確保所有動(dòng)態(tài)分配的空間被釋放。
偽私有方法(搶占式定義)
既然我們?cè)?code>Queue類中,使用了動(dòng)態(tài)內(nèi)存分配,那么編譯器提供的默認(rèn)復(fù)制構(gòu)造函數(shù),和默認(rèn)賦值運(yùn)算符是不正確的。我們假設(shè)隊(duì)列是不允許被賦值或者復(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)算符,那么編譯器就不會(huì)提供默認(rèn)方法了,那么對(duì)象就無法調(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;//隊(duì)首指針 Node* rear;//隊(duì)尾指針 int items;//隊(duì)列中的元素個(gè)數(shù) const int qsize;//隊(duì)列的最大元素個(gè)數(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;//隊(duì)列中元素個(gè)數(shù) bool enqueue(const Item &i);//入隊(duì) bool dequeue(Item & i);//出隊(duì) 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)//隊(duì)空 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++動(dòng)態(tài)內(nèi)存分配超詳細(xì)講解的文章就介紹到這了,更多相關(guān)C++動(dòng)態(tài)內(nèi)存分配內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++使用new和delete進(jìn)行動(dòng)態(tài)內(nèi)存分配與數(shù)組封裝
- C++繼承和動(dòng)態(tài)內(nèi)存分配
- C語言編程C++動(dòng)態(tài)內(nèi)存分配示例講解
- 帶你了解C++的動(dòng)態(tài)內(nèi)存分配
- C++ 動(dòng)態(tài)內(nèi)存分配詳解(new/new[]和delete/delete[])
- C++使用動(dòng)態(tài)內(nèi)存分配的原因解說
- c++ 動(dòng)態(tài)內(nèi)存分配相關(guān)總結(jié)
- 關(guān)于C++動(dòng)態(tài)分配內(nèi)存的介紹
- 詳解C++ 動(dòng)態(tài)內(nèi)存分配與命名空間
相關(guān)文章
VS2019配置opencv詳細(xì)圖文教程和測試代碼的實(shí)現(xiàn)
這篇文章主要介紹了VS2019配置opencv詳細(xì)圖文教程和測試代碼的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04C++?OpenCV裁剪圖片時(shí)發(fā)生報(bào)錯(cuò)的解決方式
在圖像處理中,我們經(jīng)常根據(jù)需要截取圖像中某一區(qū)域做處理,下面這篇文章主要給大家介紹了關(guān)于C++?OpenCV裁剪圖片時(shí)發(fā)生報(bào)錯(cuò)的解決方式,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07Ubuntu18.04下QT開發(fā)Android無法連接設(shè)備問題解決實(shí)現(xiàn)
本文主要介紹了Ubuntu18.04下QT開發(fā)Android無法連接設(shè)備問題解決實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06php5系列的apache遠(yuǎn)程執(zhí)行漏洞攻擊腳本
這篇文章主要介紹了php5系列的apache遠(yuǎn)程執(zhí)行漏洞攻擊腳本,需要的朋友可以參考下2014-06-06