1. 程式人生 > >【C++入門筆記】動態記憶體管理

【C++入門筆記】動態記憶體管理

此文針對FishC大佬的《C++快速入門》第三十三講—動態記憶體管理,本文是對其的筆記整理。

到目前為止,所有的示例程式在完成它的任務時所使用的記憶體空間都是固定不變的。

這個固定不變的記憶體空間其實是在編寫程式的時候就可以知道和確定的(一般以變數的形式)。這些程式都不能在程式執行期間動態增加或減少記憶體空間。

前言

在很多時候,需要儲存的資料量到底有多大,事先往往是個未知數,要想處理好這類情況,就需要在C++程式裡使用動態記憶體

動態記憶體支援程式設計師建立和使用種種能夠根據具體需要擴大和縮小的資料結構,它們只受限於計算機的硬體記憶體總量和系統特殊約束。

靜態記憶體

靜態記憶體就是我們此前一直在使用的東西:變數(包括指標變數)、固定長度的陣列、某給定類的物件。我們可以在程式程式碼裡通過它們的名字或者地址來訪問和使用它們。

使用靜態記憶體的最大弊端是:你不得不在編寫程式時為有關變數分配一塊儘可能大的記憶體(以防不能存放資料)。一旦程式開始執行,不管實際情況如何,那個變數都將佔用那麼多的記憶體,沒有任何辦法能夠改變靜態記憶體的大小。

動態記憶體

動態記憶體是由一些沒有名字、只有地址的記憶體塊構成,那些記憶體塊是在程式執行期間動態分配的。

它們來自一個由標準C++庫為你管理的“大池子”(裝B術語稱之為“記憶體池”)

從記憶體池申請一些記憶體需要用new語句,它將根據你提供的資料型別分配一塊大小適當的記憶體。你不必擔心記憶體塊的尺寸問題,編譯器能夠記住每一種資料型別的單位長度並迅速計算出需要分配多少個位元組。

如果有足夠的可用記憶體能滿足你的申請,new語句將返回新分配的地址塊的起始地址。

如果沒有足夠的可用記憶體空間呢???

——那麼new語句將丟擲std::bad_alloc異常!!

主要用完記憶體塊後,應該用delete語句把它還給記憶體池。另外作為一種附加的保險措施,在釋放了記憶體塊之後還應該把之關聯的指標設定為NULL。

圖說程式設計:int *i = new int;

假設 左邊是堆空間,右邊是棧空間。

棧空間聲明瞭一個指標變數i,存放的是一個地址,地址從哪來呢,由new,new出了一個int記憶體塊,並返回它的初始地址也就是4並存放進去。

再來看看delete i;

指標變數i並沒有發生改變,i是一個棧裡宣告的區域性變數,並沒有釋放它,地址還是有的,只是把這個地址的內容釋放掉了。所以為了保險起見,令i = NULL;

 NULL指標

有一個特殊的地址值叫做NULL指標。當把一個指標變數設定為NULL時,它的含義是那個指標將不再指向任何東西:

int *x;
x = NULL;//x這時候啥也不指向

我們無法通過一個被設定為NULL的指標去訪問資料。事實上,試圖對一個NULL指標進行解引用將在執行時被檢測到並導致程式中止執行。

所以在用delete釋放記憶體後,指標會保留一個毫無意義的地址,我們要將指標變數賦值給NULL。

Pay Attention:

  • 靜態記憶體這個術語與C++保留字static沒有任何關係。靜態記憶體意思是指記憶體塊的長度在程式編譯時被設定為一個固定的值,而這個值在程式執行時是無法改變的。
  • new語句返回的記憶體塊很可能充滿“垃圾”資料,所以我們通常先往裡面寫一些東西覆蓋,再訪問它們,或者在類直接寫一個構造器來初始化。
  • 在使用動態記憶體的時候,最重要的原則是每一條new語句都必須由一條與之配對的delete語句,沒有配對的delete語句或者有兩個配對的delete語句都屬於變成漏洞。(尤其前者,將導致記憶體洩露)

為物件分配記憶體

 為物件分配記憶體和為各種個基本資料型別(int,char,float)分配記憶體在做法上完全一樣。

  • 用new想記憶體池申請記憶體
  • 用delete來釋放記憶體

 

#include <iostream>
#include <string>

class Company
{
public:
    Company(std::string theName);//建構函式 
    virtual void printInfo();

protected:
    std::string name;
};

class TechCompany : public Company
{
public:
    TechCompany(std::string theName, std::string product);
    virtual void printInfo();

private:
    std::string product;
};

Company::Company(std::string theName)
{
    name = theName;
}

void Company::printInfo()
{
    std::cout << "這個公司的名字叫:" << name << "。\n";
}

TechCompany::TechCompany(std::string theName, std::string product) : Company(theName)
{
    this->product = product;
}

void TechCompany::printInfo()
{
    std::cout << name << "公司大量生產了 " << product << "這款產品!\n";
}

int main()
{
    Company *company = new Company("APPLE");
    company -> printInfo();

    delete company;
    company = NULL;

    company = new TechCompany("APPLE", "IPHONE");
    company -> printInfo();

    delete company;
    company = NULL;

    return 0;
}

Pay Attention:

  • 搞物件的時候,千萬不要忘記把方法宣告為虛方法,詳情請參見【C++入門筆記】多型的實現原理中關於虛方法的筆記。
  • 在重新使用某個指標之前千萬不要忘記呼叫delete語句,如果不這麼做,那個指標將得到一個新記憶體塊的地址,如果不這樣做,那個指標將得到一個新的記憶體塊的地址,而程式將永遠也無法釋放原先那個記憶體塊,因為它的地址已經被覆蓋掉了。
  • 請記住,delete語句只釋放給定指標變數正指向的記憶體塊,不影響這個指標。在執行delete語句之後,那個記憶體塊被釋放了,但指標變數還依然健在。

參考連結

《C++快速入門--小甲魚》https://www.bilibili.com/video/av7595819/?p=16