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相對delete;new[]相對delete[]- 因?yàn)橹挥幸粋€(gè)析構(gòu)函數(shù),所有的構(gòu)造函數(shù)都必須與它兼容
注意的是:delete或者delete[]都可以對空指針操作.
NULl和0和nullptr:空指針可以用0或者NULL來表示,C++11使用一個(gè)特殊的關(guān)鍵詞:nullptr來表示空指針.
應(yīng)該定義一個(gè)復(fù)制構(gòu)造函數(shù),通過深度復(fù)制將一個(gè)對象初始化成另一個(gè)對象.
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)用對象的引用.
一個(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對象復(fù)制或者賦值給另一個(gè)Magazine對象,逐成員復(fù)制將使用成員類型定義的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符.也就是說復(fù)制title時(shí),將調(diào)用String的復(fù)制構(gòu)造函數(shù),而將title賦值給另一個(gè)Magazine對象時(shí),也會(huì)使用String的賦值運(yùn)算符.
1.2 有關(guān)返回對象的說明
返回指向const對象的引用
返回對象會(huì)調(diào)用復(fù)制構(gòu)造函數(shù)生成臨時(shí)對象,而返回const對象的引用不會(huì).
引用指向的對象不能是局部變量. 總之,返回指向const對象的引用,就是按值傳遞的升級版,但是它不能返回局部變量.
返回指向非const對象的引用
例如我們重載<<時(shí),
ostream& operator<<(ostream & os,class_name object); 返回指向非const對象的引用,主要是我們希望對函數(shù)返回對象進(jìn)行修改.
返回對象
就是按值傳遞.
如果我們返回的對象是局部變量,那么我們不能使用引用來返回了,只能采用返回對象.
返回const對象
不太常用.防止用戶對臨時(shí)對象進(jìn)行賦值操作,而編譯器不會(huì)對這種操作報(bào)錯(cuò).
總之,如果要返回局部對象就必須返回對象;如果,那必須返回對象的引用;如果返回對象也行,返回指向?qū)ο蟮囊靡残?那優(yōu)先使用引用版本,因?yàn)樾矢?
1.3 使用new創(chuàng)建對象
String * glop=new String("my my my");
這句話會(huì)使用構(gòu)造函數(shù)String(const char *);
glop->類成員
可以使用這種方式調(diào)用對象成員,學(xué)過C語言的應(yīng)該明白。
對于動(dòng)態(tài)分配的對象,它的析構(gòu)函數(shù)當(dāng)且僅當(dāng)使用delete刪除對象時(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)建對象,如何確保其析構(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ù)。對于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)分配的空間被釋放。
偽私有方法(搶占式定義)
既然我們在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)方法了,那么對象就無法調(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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
cmake跨平臺(tái)構(gòu)建工具的學(xué)習(xí)筆記
CMake是一個(gè)跨平臺(tái)的安裝/編譯工具,通過CMake我們可以通過簡單的語句來描述所有平臺(tái)的安裝/編譯過程,下面這篇文章主要給大家介紹了關(guān)于cmake跨平臺(tái)構(gòu)建工具的相關(guān)資料,需要的朋友可以參考下2023-02-02
OpenCV實(shí)現(xiàn)特征檢測和特征匹配方法匯總
一幅圖像中總存在著其獨(dú)特的像素點(diǎn),這些點(diǎn)我們可以認(rèn)為就是這幅圖像的特征,成為特征點(diǎn),本文主要介紹了OpenCV實(shí)現(xiàn)特征檢測和特征匹配方法,感興趣的可以了解一下2021-08-08
Qt實(shí)現(xiàn)進(jìn)程界面之間的鼠標(biāo)焦點(diǎn)切換
這篇文章主要為大家詳細(xì)介紹了Qt實(shí)現(xiàn)進(jìn)程界面之間的鼠標(biāo)焦點(diǎn)切換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09

