C語(yǔ)言探索之旅 | 第二部分第八課:動(dòng)態(tài)分配

上一課是 [C語(yǔ)言探索之旅 | 第二部分第七課:文件讀寫(xiě)(http://www.reibang.com/p/277660752195)
曙旭。

經(jīng)歷了第二部分的一些難點(diǎn)課程擦秽,我們終于來(lái)到了這一課芳室,一個(gè)聽(tīng)起來(lái)有點(diǎn)酷酷的名字: 動(dòng)態(tài)分配 虹钮。

“萬(wàn)水千山總是情坝冕,分配也由系統(tǒng)定”梨树。

到目前為止,我們創(chuàng)建的變量都是系統(tǒng)的編譯器為我們自動(dòng)構(gòu)建的,這是簡(jiǎn)單的方式纽帖。

其實(shí)還有一種更偏手動(dòng)的創(chuàng)建變量的方式宠漩,我們稱為“動(dòng)態(tài)分配”(Dynamic Allocation)。dynamic 表示“動(dòng)態(tài)的”懊直,allocation 表示“分配”扒吁。

動(dòng)態(tài)分配的一個(gè)主要好處就是可以在內(nèi)存中“預(yù)置”一定空間大小,在編譯時(shí)還不知道到底會(huì)用多少室囊。

使用這個(gè)技術(shù)雕崩,我們可以創(chuàng)建大小可變的數(shù)組。到目前為止我們所創(chuàng)建的數(shù)組都是大小固定不可變的波俄。而學(xué)完這一課后我們就會(huì)創(chuàng)建所謂“動(dòng)態(tài)數(shù)組”了晨逝。

學(xué)習(xí)這一章需要對(duì)指針有一定了解,如果指針的概念你還沒(méi)掌握好懦铺,可以回去復(fù)習(xí) [C語(yǔ)言探索之旅 | 第二部分第二課:進(jìn)擊的指針捉貌,C語(yǔ)言的王牌!(http://www.reibang.com/p/02321ae2df1a) 那一課冬念。

我們知道當(dāng)我們創(chuàng)建一個(gè)變量時(shí)趁窃,在內(nèi)存中要為其分配一定大小的空間。例如:

int number = 2;
復(fù)制代碼

當(dāng)程序運(yùn)行到這一行代碼時(shí)急前,會(huì)發(fā)生幾件事情:

  1. 應(yīng)用程序詢問(wèn) 操作系統(tǒng) (Operating System醒陆,簡(jiǎn)稱 OS。例如Windows裆针,Linux刨摩,macOS,Android世吨,iOS澡刹,等)是否可以使用一小塊內(nèi)存空間。

  2. 操作系統(tǒng)回復(fù)我們的程序耘婚,告訴它可以將這個(gè)變量存儲(chǔ)在內(nèi)存中哪個(gè)地方(給出分配的內(nèi)存地址)罢浇。

  3. 當(dāng)函數(shù)結(jié)束后,你的變量會(huì)自動(dòng)從內(nèi)存中被刪除沐祷。你的程序?qū)Σ僮飨到y(tǒng)說(shuō):“我已經(jīng)不需要內(nèi)存中的這塊地址了嚷闭,謝謝!” (當(dāng)然赖临,實(shí)際上你的程序不可能對(duì)操作系統(tǒng)說(shuō)一聲“謝謝”胞锰,但是確實(shí)是操作系統(tǒng)在掌管一切,包括內(nèi)存兢榨,所以對(duì)它還是客氣一點(diǎn)比較好...)胜蛉。

可以看到挠进,以上的過(guò)程都是自動(dòng)的。當(dāng)我們創(chuàng)建一個(gè)變量誊册,操作系統(tǒng)就會(huì)自動(dòng)被程序這樣調(diào)用。

那么什么是手動(dòng)的方式呢暖璧?說(shuō)實(shí)在的案怯,沒(méi)人喜歡把事情復(fù)雜化,如果自動(dòng)方式可行澎办,何必要大費(fèi)周章來(lái)使用什么手動(dòng)方式呢嘲碱?但是要知道,很多時(shí)候我們是不得不使用手動(dòng)方式局蚀。

這一課中麦锯,我們將會(huì):

  1. 探究?jī)?nèi)存的機(jī)制(是的,雖然以前的課研究過(guò)琅绅,但是還是要繼續(xù)深入)扶欣,了解不同變量類(lèi)型所占用的內(nèi)存大小。

  2. 接著千扶,探究這一課的主題料祠,來(lái)學(xué)習(xí)如何向操作系統(tǒng)動(dòng)態(tài)請(qǐng)求內(nèi)存。也就是所謂的“動(dòng)態(tài)內(nèi)存分配”澎羞。

  3. 最后髓绽,通過(guò)學(xué)習(xí)如何創(chuàng)建一個(gè)在編譯時(shí)還不知道其大小(只有在程序運(yùn)行時(shí)才知道)的數(shù)組來(lái)了解動(dòng)態(tài)內(nèi)存分配的好處妆绞。

準(zhǔn)備好了嗎顺呕?Let's Go !

2. 變量的大小

根據(jù)我們所要?jiǎng)?chuàng)建的變量的類(lèi)型(char,int括饶,double株茶,等等),其所占的內(nèi)存空間大小是不一樣的巷帝。

事實(shí)上忌卤,為了存儲(chǔ)一個(gè)大小在 -128 至 127 之間的數(shù)(char 類(lèi)型),只需要占用一個(gè)字節(jié)(8 個(gè)二進(jìn)制位)的內(nèi)存空間楞泼,是很小的驰徊。

然而,一個(gè) int 類(lèi)型的變量就要占據(jù) 4 個(gè)字節(jié)了堕阔;一個(gè) double 類(lèi)型要占據(jù) 8 個(gè)字節(jié)棍厂。

問(wèn)題是:并不總是這樣。

什么意思呢超陆?

因?yàn)轭?lèi)型所占內(nèi)存的大小還與操作系統(tǒng)有關(guān)系牺弹。不同的操作系統(tǒng)可能就不一樣浦马,32 位和 64 位的操作系統(tǒng)的類(lèi)型大小一般會(huì)有區(qū)別。

這一節(jié)中我們的目的是學(xué)習(xí)如何獲知變量所占用的內(nèi)存大小张漂。

有一個(gè)很簡(jiǎn)單的方法:使用 sizeof() 晶默。

雖然看著有點(diǎn)像函數(shù),但其實(shí) sizeof 不是一個(gè)函數(shù)航攒,而是一個(gè) C語(yǔ)言的關(guān)鍵字磺陡,也算是一個(gè)運(yùn)算符吧。

我們只需要在 sizeof 的括號(hào)里填入想要檢測(cè)的變量類(lèi)型漠畜,sizeof 就會(huì)返回所占用的字節(jié)數(shù)了币他。

例如,我們要檢測(cè) int 類(lèi)型的大小憔狞,就可以這樣寫(xiě):

sizeof(int)
復(fù)制代碼

在編譯時(shí)蝴悉, sizeof(int) 就會(huì)被替換為 int 類(lèi)型所占用的字節(jié)數(shù)了。

在我的電腦上瘾敢, sizeof(int) 是 4拍冠,也就是說(shuō) int 類(lèi)型在我的電腦的內(nèi)存中占據(jù) 4 個(gè)字節(jié)。在你的電腦上廉丽,也許是 4倦微,但也可能是其他的值。

我們用一個(gè)例子來(lái)測(cè)試一下吧:

// octet 是英語(yǔ)“字節(jié)”的意思正压,和 byte 類(lèi)似
printf("char : %d octets\n", sizeof(char));
printf("int : %d octets\n", sizeof(int));
printf("long : %d octets\n", sizeof(long));
printf("double : %d octets\n", sizeof(double));
復(fù)制代碼

在我的電腦(64 位)運(yùn)行欣福,輸出:

char : 1 octets
int : 4 octets
long : 8 octets
double : 8 octets
復(fù)制代碼

我們并沒(méi)有測(cè)試所有已知的變量類(lèi)型,你也可以課后自己去測(cè)試一下其他的類(lèi)型焦履,例如:short拓劝,float。

曾幾何時(shí)嘉裤,當(dāng)電腦的內(nèi)存很小的年代郑临,有這么多不同大小的變量類(lèi)型可供選擇是一件很好的事,因?yàn)槲覀兛梢赃x“夠用的最小的”那種變量類(lèi)型屑宠,以節(jié)約內(nèi)存厢洞。

現(xiàn)在,電腦的內(nèi)存一般都很大典奉,“有錢(qián)任性”么躺翻。所以我們?cè)诰幊虝r(shí)也沒(méi)必要太“拘謹(jǐn)”。不過(guò)在嵌入式領(lǐng)域卫玖,內(nèi)存大小一般是有限的公你,我們就得斟酌著使用變量類(lèi)型了。

既然 sizeof 這么好用假瞬,我們可不可以用它來(lái)顯示我們自定義的變量類(lèi)型的大小呢陕靠?例如 struct迂尝,enum,union剪芥。

是可以的垄开。寫(xiě)一個(gè)程序測(cè)試一下:

#include <stdio.h>

typedef struct Coordinate
{
    int x;
    int y;
} Coordinate;

int main(int argc, char *argv[]) {
    printf("Coordinate 結(jié)構(gòu)體的大小是 : %d 個(gè)字節(jié)\n", sizeof(Coordinate));

    return 0;
}
復(fù)制代碼

運(yùn)行輸出:

Coordinate 結(jié)構(gòu)體的大小是 : 8 個(gè)字節(jié)
復(fù)制代碼

對(duì)于內(nèi)存的全新視角

之前,我們?cè)诶L制內(nèi)存圖示時(shí)粗俱,還是比較不精準(zhǔn)的∷涤埽現(xiàn)在,我們知道了每個(gè)變量所占用的大小寸认,我們的內(nèi)存圖示就可以變得更加精準(zhǔn)了。

假如我定義一個(gè) int 類(lèi)型的變量:

int age = 17;
復(fù)制代碼

我們用 sizeof 測(cè)試后得知 int 的大小為 4串慰。假設(shè)我們的變量 age 被分配到的內(nèi)存地址起始是 1700偏塞,那么我們的內(nèi)存圖示就如下所示:

image

我們看到,我們的 int 型變量 age 在內(nèi)存中占用 4 個(gè)字節(jié)邦鲫,起始地址是 1700(它的內(nèi)存地址)灸叼,一直到 1703。

如果我們對(duì)一個(gè) char 型變量(大小是一個(gè)字節(jié))同樣賦值:

char number = 17;
復(fù)制代碼

那么庆捺,其內(nèi)存圖示是這樣的:

image

假如是一個(gè) int 型的數(shù)組:

int age[100];
復(fù)制代碼

用 sizeof() 測(cè)試一下古今,就可以知道在內(nèi)存中 age 數(shù)組占用 400 個(gè)字節(jié)。4 * 100 = 400滔以。

即使這個(gè)數(shù)組沒(méi)有賦初值捉腥,但是在內(nèi)存中仍然占據(jù) 400 個(gè)字節(jié)的空間。變量一聲明你画,在內(nèi)存中就為它分配一定大小的內(nèi)存了抵碟。

那么,如果我們創(chuàng)建一個(gè)類(lèi)型是 Coordinate 的數(shù)組呢坏匪?

Coordinate coordinate[100];
復(fù)制代碼

其大小就是 8 * 100 = 800 個(gè)字節(jié)了拟逮。

3. 內(nèi)存的動(dòng)態(tài)分配

好了,現(xiàn)在我們就進(jìn)入這一課的關(guān)鍵部分了适滓,重提一次這一課的目的:學(xué)會(huì)如何手動(dòng)申請(qǐng)內(nèi)存空間敦迄。

我們需要引入 stdlib.h 這個(gè)標(biāo)準(zhǔn)庫(kù)頭文件,因?yàn)榻酉聛?lái)要使用的函數(shù)是定義在這個(gè)庫(kù)里面凭迹。

這兩個(gè)函數(shù)是什么呢罚屋?就是:

  • malloc:是 Memory Allocation 的縮寫(xiě),表示“內(nèi)存分配”蕊苗。詢問(wèn)操作系統(tǒng)能否預(yù)支一塊內(nèi)存空間來(lái)使用沿后。

  • free:表示“解放,釋放朽砰,自由的”尖滚。意味著“釋放那塊內(nèi)存空間”喉刘。告訴操作系統(tǒng)我們不再需要這塊已經(jīng)分配的空間了,這塊內(nèi)存空間會(huì)被釋放漆弄,另一個(gè)程序就可以使用這塊空間了睦裳。

當(dāng)我們手動(dòng)分配內(nèi)存時(shí),須要按照以下三步順序來(lái):

  1. 調(diào)用 malloc 函數(shù)來(lái)申請(qǐng)內(nèi)存空間撼唾。

  2. 檢測(cè) malloc 函數(shù)的返回值廉邑,以得知操作系統(tǒng)是否成功為我們的程序分配了這塊內(nèi)存空間。

  3. 一旦使用完這塊內(nèi)存倒谷,不再需要時(shí)蛛蒙,必須用 free 函數(shù)來(lái)釋放占用的內(nèi)存,不然可能會(huì)造成內(nèi)存泄漏渤愁。

以上三個(gè)步驟是不是讓我們回憶起關(guān)于上一課“文件讀寫(xiě)”的內(nèi)容了牵祟?

這三個(gè)步驟和文件指針的操作有點(diǎn)類(lèi)似,也是先申請(qǐng)內(nèi)存抖格,檢測(cè)是否成功诺苹,用完釋放。

malloc 函數(shù):申請(qǐng)內(nèi)存

malloc 分配的內(nèi)存是在堆上雹拄,一般的局部變量(自動(dòng)分配的)大多是在棧上收奔。

關(guān)于堆和棧的區(qū)別,還有內(nèi)存的其他區(qū)域滓玖,如靜態(tài)區(qū)等坪哄,大家可以自己延伸閱讀。

之前“字符串”那一課里已經(jīng)給出過(guò)一張圖表了呢撞。再來(lái)回顧一下吧:

名稱 內(nèi)容
代碼段 可執(zhí)行代碼损姜、字符串常量
數(shù)據(jù)段 已初始化全局變量、已初始化全局靜態(tài)變量殊霞、局部靜態(tài)變量摧阅、常量數(shù)據(jù)
BSS段 未初始化全局變量,未初始化全局靜態(tài)變量
局部變量绷蹲、函數(shù)參數(shù)
動(dòng)態(tài)內(nèi)存分配

給出 malloc 函數(shù)的原型棒卷,你會(huì)發(fā)現(xiàn)有點(diǎn)滑稽:

void* malloc(size_t numOctetsToAllocate);
復(fù)制代碼

可以看到,malloc 函數(shù)有一個(gè)參數(shù) numOctetsToAllocate祝钢,就是需要申請(qǐng)的內(nèi)存空間大斜裙妗(用字節(jié)數(shù)表示),這里的 size_t(之前的課程有提到過(guò))其實(shí)和 int 是類(lèi)似的拦英,就是一個(gè) define 宏定義蜒什,實(shí)際上很多時(shí)候就是 int。

對(duì)于我們目前的演示程序疤估,可以將 sizeof(int) 置于 malloc 的括號(hào)中灾常,表示要申請(qǐng) int 類(lèi)型的大小的空間霎冯。

真正引起我們興趣的是 malloc 函數(shù)的返回值:

void*
復(fù)制代碼

如果你還記得我們?cè)诤瘮?shù)那章所說(shuō)的,void 表示“空”钞瀑,我們用 void 來(lái)表示函數(shù)沒(méi)有返回值沈撞。

所以說(shuō),這里我們的函數(shù) malloc 會(huì)返回一個(gè)指向 void 的指針雕什,一個(gè)指向“空”(void 表示“虛無(wú)缠俺,空”)的指針,有什么意義呢贷岸?malloc 函數(shù)的作者不會(huì)搞錯(cuò)了吧壹士?

不要擔(dān)心,這么做肯定是有理由的偿警。

難道有人敢質(zhì)疑老爺子 Dennis Ritchie(C語(yǔ)言的作者)的智商墓卦? 來(lái)人吶,拖出去... 罰寫(xiě) 100 個(gè) C語(yǔ)言小游戲户敬。

事實(shí)上,這個(gè)函數(shù)返回一個(gè)指針睁本,指向操作系統(tǒng)分配的內(nèi)存的首地址尿庐。

如果操作系統(tǒng)在 1700 這個(gè)地址為你開(kāi)辟了一塊內(nèi)存的話,那么函數(shù)就會(huì)返回一個(gè)包含 1700 這個(gè)值的指針呢堰。

但是抄瑟,問(wèn)題是:malloc 函數(shù)并不知道你要?jiǎng)?chuàng)建的變量是什么類(lèi)型的。

實(shí)際上枉疼,你只給它傳遞了一個(gè)參數(shù): 在內(nèi)存中你需要申請(qǐng)的字節(jié)數(shù)皮假。

如果你申請(qǐng) 4 個(gè)字節(jié),那么有可能是 int 類(lèi)型骂维,也有可能是 long 類(lèi)型惹资。

正因?yàn)?malloc 不知道自己應(yīng)該返回什么變量類(lèi)型(它也無(wú)所謂,只要分配了一塊內(nèi)存就可以了)航闺,所以它會(huì)返回 void* 這個(gè)類(lèi)型褪测。這是一個(gè)可以表示任意指針類(lèi)型的指針。

void* 與其他類(lèi)型的指針之間可以通過(guò)強(qiáng)制轉(zhuǎn)換來(lái)相互轉(zhuǎn)換潦刃。例如:

int *i = (int *)p;  // p 是一個(gè) void* 類(lèi)型的指針

void *v = (void *)c;  // c 是一個(gè) char* 類(lèi)型的指針
復(fù)制代碼

實(shí)踐

如果我實(shí)際來(lái)用 malloc 函數(shù)分配一個(gè) int 型指針:

int *memoryAllocated = NULL;  // 創(chuàng)建一個(gè) int 型指針

memoryAllocated = malloc(sizeof(int));  // malloc 函數(shù)將分配的地址賦值給我們的指針 memoryAllocated
復(fù)制代碼

經(jīng)過(guò)上面的兩行代碼侮措,我們的 int 型指針 memoryAllocated 就包含了操作系統(tǒng)分配的那塊內(nèi)存地址的首地址值。

假如我們用之前我們的圖示來(lái)舉例乖杠,這個(gè)值就是 1700分扎。

檢測(cè)指針

既然上面我們用兩行代碼使得 memoryAllocated 這個(gè)指針包含了分配到的地址的首地址值,那么我們就可以通過(guò)檢測(cè) memoryAllocated 的值來(lái)判斷申請(qǐng)內(nèi)存是否成功了:

  1. 如果為 NULL胧洒,則說(shuō)明 malloc 調(diào)用沒(méi)有成功畏吓。

  2. 否則墨状,就說(shuō)明成功了。

一般來(lái)說(shuō)內(nèi)存分配不會(huì)失敗庵佣,但是也有極端情況:

  1. 你的內(nèi)存(堆內(nèi)存)已經(jīng)不夠了歉胶。

  2. 你申請(qǐng)的內(nèi)存值大得離譜(比如你申請(qǐng) 64 GB 的內(nèi)存空間,那我想大多數(shù)電腦都是不可能分配成功的)巴粪。

希望大家每次用 malloc 函數(shù)時(shí)都要做指針的檢測(cè)通今,萬(wàn)一真的出現(xiàn)返回值為 NULL 的情況,那我們需要立即停止程序肛根,因?yàn)闆](méi)有足夠的內(nèi)存辫塌,也不可能進(jìn)行下面的操作了。

為了中斷程序的運(yùn)行派哲,我們來(lái)使用一個(gè)新的函數(shù):

exit()
復(fù)制代碼

exit 函數(shù)定義在 stdlib.h 中臼氨,調(diào)用此函數(shù)會(huì)使程序立即停止。

這個(gè)函數(shù)也只有一個(gè)參數(shù)芭届,就是返回值储矩,這和 return 函數(shù)的參數(shù)是一樣原理的。實(shí)例:

int main(int argc, char *argv[]) {
    int *memoryAllocated = NULL;

    memoryAllocated = malloc(sizeof(int));

    if (memoryAllocated == NULL)  // 如果分配內(nèi)存失敗
    {
        exit(0);  // 立即停止程序
    }

    // 如果指針不為 NULL褂乍,那么可以繼續(xù)進(jìn)行接下來(lái)的操作

    return 0;
}
復(fù)制代碼

另外一個(gè)問(wèn)題:用 malloc 函數(shù)申請(qǐng) 0 字節(jié)內(nèi)存會(huì)返回 NULL 指針嗎持隧?

可以測(cè)試一下,也可以去查找關(guān)于 malloc 函數(shù)的說(shuō)明文檔逃片。

申請(qǐng) 0 字節(jié)內(nèi)存屡拨,函數(shù)并不返回 NULL,而是返回一個(gè)正常的內(nèi)存地址褥实。 但是你卻無(wú)法使用這塊大小為 0 的內(nèi)存呀狼!

這就好比尺子上的某個(gè)刻度,刻度本身并沒(méi)有長(zhǎng)度损离,只有某兩個(gè)刻度一起才能量出長(zhǎng)度哥艇。

對(duì)于這一點(diǎn)一定要小心,因?yàn)檫@時(shí)候 if(NULL != p) 語(yǔ)句校驗(yàn)將不起作用草冈。

free函數(shù):釋放內(nèi)存

記得上一課我們使用 fclose 函數(shù)來(lái)關(guān)閉一個(gè)文件指針她奥,也就是釋放占用的內(nèi)存。

free 函數(shù)的原理和 fclose 是類(lèi)似的怎棱,我們用它來(lái)釋放一塊我們不再需要的內(nèi)存哩俭。原型:

void free(void* pointer);
復(fù)制代碼

free 函數(shù)只有一個(gè)目的:釋放 pointer 指針?biāo)赶虻哪菈K內(nèi)存。

實(shí)例程序:

int main(int argc, char *argv[]) {
    int* memoryAllocated = NULL;

    memoryAllocated = malloc(sizeof(int));

    if (memoryAllocated == NULL)  // 如果分配內(nèi)存失敗
    {
        exit(0);  // 立即停止程序
    }

    // 此處添加使用這塊內(nèi)存的代碼

    free(memoryAllocated);  // 我們不再需要這塊內(nèi)存了拳恋,釋放之

    return 0;
}
復(fù)制代碼

綜合上面的三個(gè)步驟凡资,我們來(lái)寫(xiě)一個(gè)完整的例子:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int* memoryAllocated = NULL;

    memoryAllocated = malloc(sizeof(int));  // 分配內(nèi)存

    if (memoryAllocated == NULL)  // 檢測(cè)是否分配成功
    {
        exit(0);  // 不成功,結(jié)束程序
    }

    // 使用這塊內(nèi)存
    printf("您幾歲了 ? ");

    scanf("%d", memoryAllocated);

    printf("您已經(jīng) %d 歲了\n", *memoryAllocated);

    free(memoryAllocated);  // 釋放這塊內(nèi)存

    return 0;
}
復(fù)制代碼

運(yùn)行輸出:

您幾歲了 ? 32
您已經(jīng) 32 歲了
復(fù)制代碼

以上就是我們用動(dòng)態(tài)分配的方式來(lái)創(chuàng)建了一個(gè) int 型變量,使用它隙赁,釋放它所占用的內(nèi)存垦藏。

但是,我們也完全可以用以前的方式來(lái)實(shí)現(xiàn)伞访,如下:

int main(int argc, char *argv[]) {
    int myAge = 0; // 分配內(nèi)存 (自動(dòng))

    // 使用這塊內(nèi)存
    printf("您幾歲了 ? ");

    scanf("%d", &myAge);

    printf("你已經(jīng) %d 歲了\n", myAge);

    return 0;
}  // 釋放內(nèi)存 (在函數(shù)結(jié)束后自動(dòng)釋放)
復(fù)制代碼

在這個(gè)簡(jiǎn)單使用場(chǎng)景下掂骏,兩種方式(手動(dòng)和自動(dòng))都是能完成任務(wù)的。

總結(jié)說(shuō)來(lái)厚掷,創(chuàng)建一個(gè)變量(說(shuō)到底也就是分配一塊內(nèi)存空間)有兩種方式:自動(dòng)和手動(dòng)弟灼。

  • 自動(dòng):我們熟知并且一直使用到現(xiàn)在的方式。

  • 手動(dòng)(動(dòng)態(tài)):這一課我們學(xué)習(xí)的內(nèi)容冒黑。

你可能會(huì)說(shuō):“我發(fā)現(xiàn)動(dòng)態(tài)分配內(nèi)存的方式既復(fù)雜又沒(méi)什么用嘛田绑!”

復(fù)雜么?還行吧抡爹,確實(shí)相對(duì)自動(dòng)的方式要考慮比較多的因素掩驱。

沒(méi)有用么?絕不冬竟!

因?yàn)楹芏鄷r(shí)候我們不得不使用手動(dòng)的方式來(lái)分配內(nèi)存欧穴。

接下來(lái)我們就來(lái)看一下手動(dòng)方式的必要性。

4. 動(dòng)態(tài)分配一個(gè)數(shù)組

暫時(shí)我們只是用手動(dòng)方式來(lái)創(chuàng)建了一個(gè)簡(jiǎn)單的變量泵殴。

然而苔可,一般說(shuō)來(lái),我們的動(dòng)態(tài)分配可不是這樣“大材小用”的袋狞。

如果只是創(chuàng)建一個(gè)簡(jiǎn)單的變量,我們用自動(dòng)的方式就夠了映屋。

那你會(huì)問(wèn):“啥時(shí)候須要用動(dòng)態(tài)分配肮堆臁?”

問(wèn)得好棚点。動(dòng)態(tài)分配最常被用來(lái)創(chuàng)建在運(yùn)行時(shí)才知道大小的變量早处,例如動(dòng)態(tài)數(shù)組。

假設(shè)我們要存儲(chǔ)一個(gè)用戶的朋友的年齡列表瘫析,按照我們以前的方式(自動(dòng)方式)砌梆,我們可以創(chuàng)建一個(gè) int 型的數(shù)組:

int ageFriends[18];
復(fù)制代碼

很簡(jiǎn)單對(duì)嗎?那問(wèn)題不就解決了贬循?

但是以上方式有兩個(gè)缺陷:

  1. 你怎么知道這個(gè)用戶只有 18 個(gè)朋友呢咸包?可能他有更多朋友呢。

  2. 你說(shuō):“那好杖虾,我就創(chuàng)建一個(gè)數(shù)組:

int ageFriends[10000];
復(fù)制代碼

足夠儲(chǔ)存 1 萬(wàn)個(gè)朋友的年齡烂瘫。”

但是問(wèn)題是:可能我們使用到的只是這個(gè)大數(shù)組的很小一部分奇适,豈不是浪費(fèi)內(nèi)存嘛坟比。

最恰當(dāng)?shù)姆绞绞窃儐?wèn)用戶他有多少朋友芦鳍,然后創(chuàng)建對(duì)應(yīng)大小的數(shù)組。

而這樣葛账,我們的數(shù)組大小就只有在運(yùn)行時(shí)才能知道了柠衅。

Voila,這就是動(dòng)態(tài)分配的優(yōu)勢(shì)了:

  1. 可以在運(yùn)行時(shí)才確定申請(qǐng)的內(nèi)存空間大小籍琳。

  2. 不多不少剛剛好菲宴,要多少就申請(qǐng)多少,不怕不夠或過(guò)多巩割。

所以借著動(dòng)態(tài)分配裙顽,我們就可以在運(yùn)行時(shí)詢問(wèn)用戶他到底有多少朋友。

如果他說(shuō)有 20 個(gè)宣谈,那我們就申請(qǐng) 20 個(gè) int 型的空間愈犹;如果他說(shuō)有 50 個(gè),那就申請(qǐng) 50 個(gè)闻丑。經(jīng)濟(jì)又環(huán)保漩怎。

我們之前說(shuō)過(guò),C語(yǔ)言中禁止用變量名來(lái)作為數(shù)組大小嗦嗡,例如不能這樣:

int ageFriends[numFriends];  // numFriends 是一個(gè)變量
復(fù)制代碼

盡管有的 C編譯器可能允許這樣的聲明勋锤,但是我們不推薦。

我們來(lái)看看用動(dòng)態(tài)分配的方式如何實(shí)現(xiàn)這個(gè)程序:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int numFriends = 0, i = 0;

    int *ageFriends= NULL;  // 這個(gè)指針用來(lái)指示朋友年齡的數(shù)組

    // 詢問(wèn)用戶有多少個(gè)朋友
    printf("請(qǐng)問(wèn)您有多少朋友 ? ");

    scanf("%d", &numFriends);

    if (numFriends > 0)  // 至少得有一個(gè)朋友吧侥祭,不然也太慘了 :P
    {
        ageFriends = malloc(numFriends * sizeof(int));  // 為數(shù)組分配內(nèi)存
        if (ageFriends== NULL)  // 檢測(cè)分配是否成功
        {
            exit(0); // 分配不成功叁执,退出程序
        }

        // 逐個(gè)詢問(wèn)朋友年齡
        for (i = 0 ; i < numFriends; i++)  {
            printf("第%d位朋友的年齡是 ? ", i + 1);
            scanf("%d", &ageFriends[i]);
        }

        // 逐個(gè)輸出朋友的年齡
        printf("\n\n您的朋友的年齡如下 :\n");
        for (i = 0 ; i < numFriends; i++) {
            printf("%d 歲\n", ageFriends[i]);
        }

        // 釋放 malloc 分配的內(nèi)存空間,因?yàn)槲覀儾辉傩枰?        free(ageFriends);
    }

    return 0;
}
復(fù)制代碼

運(yùn)行輸出:

請(qǐng)問(wèn)您有多少朋友 ? 7
第1位朋友的年齡是 ? 25
第2位朋友的年齡是 ? 21
第3位朋友的年齡是 ? 27
第4位朋友的年齡是 ? 18
第5位朋友的年齡是 ? 14
第6位朋友的年齡是 ? 32
第7位朋友的年齡是 ? 30

您的朋友的年齡如下 :
25歲
21歲
27歲
18歲
14歲
32歲
30歲
復(fù)制代碼

當(dāng)然了矮冬,這個(gè)程序比較簡(jiǎn)單谈宛,但我向你保證以后的課程會(huì)使用動(dòng)態(tài)分配來(lái)做更有趣的事。

其實(shí)做為一個(gè)學(xué)習(xí)者胎署,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要這里我推薦一個(gè)C/C++基礎(chǔ)交流583650410吆录,不管你是小白還是轉(zhuǎn)行人士歡迎入駐,大家一起交流成長(zhǎng)琼牧。



?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恢筝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子巨坊,更是在濱河造成了極大的恐慌撬槽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趾撵,死亡現(xiàn)場(chǎng)離奇詭異恢氯,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)勋拟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)勋磕,“玉大人,你說(shuō)我怎么就攤上這事敢靡」易遥” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵啸胧,是天一觀的道長(zhǎng)赶站。 經(jīng)常有香客問(wèn)我,道長(zhǎng)纺念,這世上最難降的妖魔是什么贝椿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮陷谱,結(jié)果婚禮上烙博,老公的妹妹穿的比我還像新娘。我一直安慰自己烟逊,他們只是感情好渣窜,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著宪躯,像睡著了一般乔宿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上访雪,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天详瑞,我揣著相機(jī)與錄音,去河邊找鬼臣缀。 笑死蛤虐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肝陪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吃环,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼炬搭!你這毒婦竟也來(lái)了扑馁?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狼讨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后柒竞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體政供,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了布隔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片离陶。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖衅檀,靈堂內(nèi)的尸體忽然破棺而出招刨,到底是詐尸還是另有隱情,我是刑警寧澤哀军,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布沉眶,位于F島的核電站,受9級(jí)特大地震影響杉适,放射性物質(zhì)發(fā)生泄漏谎倔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一猿推、第九天 我趴在偏房一處隱蔽的房頂上張望片习。 院中可真熱鬧,春花似錦彤守、人聲如沸毯侦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)侈离。三九已至,卻和暖如春筝蚕,著一層夾襖步出監(jiān)牢的瞬間卦碾,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工起宽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洲胖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓坯沪,卻偏偏與公主長(zhǎng)得像绿映,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腐晾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345