1. 程式人生 > 其它 >儲存類別和記憶體管理

儲存類別和記憶體管理

導讀:關於儲存類別的知識點

一個變數的儲存類別由三個方面構成:1.作用域 2.連結 3.儲存期,這三方面相互依存,不能分離。

1)作用域

作用域是程式訪問變數的區域,一個變數的作用域是什麼,程式就在什麼區域去訪問這個變數。

作用域分為四大類:

1.塊作用域

塊作用域指的是函式體塊或者語句塊,在這些塊中宣告的變數具有塊作用域。

具有塊作用域的變數,程式只能在相應的塊中去訪問,離開這個塊,程式就不能去訪問這個變數。

2.函式原型作用域

在函式原型中的形參變數具有函式原型作用域。(還應記住,函式原型中的形參所使用的變數名稱可以和定義函式內容時所使用的變數名稱不相同)

3.檔案作用域

首先理解一下什麼是翻譯單元。

對於一個原始碼檔案,需要引用一個或者多個頭檔案,引用的標頭檔案也是一個獨立的檔案,這些標頭檔案中的內容在編譯過後會覆蓋#include這條指令,從而原始碼和標頭檔案中的內容就在一個檔案中了,這個檔案我們成為翻譯單元。

作用域是這個翻譯單元的變數具有檔案作用域。

如果一個變數具有檔案作用域,那麼在這個翻譯單元中的任何位置,都可以去訪問這個變數。

4.全域性作用域

在編寫程式的時候,可能會有多個原始碼檔案同時進行編譯,這時就會形成多個翻譯單元,作用域為這些翻譯單元的變數具有全域性作用域。

如果一個變數具有全域性作用域,那麼在這些翻譯單元中的任何位置(在整個程式中),都可以去訪問這個變數。

2)連結

(可以把這個連結想象成平常見到的超連結,通過連結可以去訪問變數)

1.外部連結

外部連結對應的是具有全域性作用域的變數,通過這個外部連結,其他的翻譯單元也能夠去訪問這個變數。

2.內部連結

內部連結對應的是具有檔案作用域的變數,在一個翻譯單元中,可以通過這個內部連結去訪問這個變數。

內部連結的宣告需要新增static關鍵字。

3.無連結

無連結對應的是具有塊作用域的變數,在一個塊內,無論變數是否使用static關鍵字進行宣告,都是無連結的變數,因為程式進入這個塊內才會訪問這個變數,離開這個塊就不會訪問這個變數。

3)儲存期

儲存期指的就是一個變數的記憶體存在的時間。

1.自動儲存期

自動儲存期一般對應具有塊作用域的變數,程式進入這些塊內時,會為變數分配記憶體,離開這些塊時,會將這些記憶體釋放,如果使用static關鍵字宣告的具有塊作用域的變數,就不具有自動儲存期,具有的是靜態儲存期。

2.靜態儲存期

靜態儲存期對應的是具有檔案作用域的變數和具有全域性作用域的變數,當程式編譯完後,編譯器就會為這些變數分配記憶體,當程式執行完成後,這些變數的記憶體才會釋放掉(期間這些變數的記憶體一直存在)。

4)宣告

宣告包括兩種型別:

1.定義式宣告:對於變數來說,初次定義這個變數就是定義式宣告。對於函式來講,定義函式內容就是定義式宣告。

2.引用式宣告:對於變數來說,使用extern關鍵字在某處引用已經定義過的變數。對於函式來講,宣告函式模型就是引用式宣告。

一、儲存類別

1)自動儲存(自動變數)

自動變數:具有塊作用域、無連結、自動儲存期的變數。當程式訪問這些變數所在的塊時,會為這些變數分配記憶體,並且訪問他們,當程式離開相應的塊時,會釋放掉變數的記憶體,並且變數不可訪問。

可以使用關鍵字auto來定義宣告一個變數是自動變數,但是通常,我們預設不加儲存類別關鍵字的變數就是自動變數。

2)暫存器儲存(暫存器變數)

暫存器變數:在自動變數之前使用關鍵字:register。

double、long、longlong等佔據記憶體較大的變數不能夠定義宣告為暫存器變數,因為暫存器可能沒有空間去儲存這些變數。

這個關鍵字並不是萬能的,只有當暫存器中擁有能夠儲存這個變數的空間時,才會將這個自動變數儲存在暫存器中(不儲存在記憶體中)

由於不儲存在記憶體中,因此無法檢視這個變數的地址。如果使用register關鍵字定義宣告失敗,那麼這個變數就是自動變數,但是即使定義宣告失敗,也不能檢視這個變數的地址,因此這個關鍵字可以用來隱藏變數的地址。

暫存器變數具有塊作用域、無連結、自動儲存期,當程式訪問塊時,將變數儲存在暫存器中,並且訪問這個變數,當程式離開這個塊時,變數從暫存器中釋放,並且無法訪問。

3)靜態外部連結儲存(外部變數)

外部變數:具有全域性作用域、外部連結、靜態儲存期。

外部變數定義宣告在所有函式的上面(一般來講,我們把主函式放在其他函式的上面,因此外部變數宣告在主函式的上面),不加任何儲存型別關鍵字,這樣的變數就是外部變數。雖然外部變數具有全域性作用域,但是外部變數也是定義宣告在某一個翻譯單元中的,其他翻譯單元要是想訪問或使用這個變數,必須要用extern在所要引用的位置進行引用式宣告。

外部變數可以不人為進行初始化,這樣的話編譯器會自動將其初始化為0,定義在外面的陣列同樣適用。

//外部變數的自動初始化
#include	<stdio.h>
const int names;

int	main(void)
{
	printf("print the names in main() :%d\n",names);
	
	return	0;
} 

列印結果如下:

print the names in main() :0

4)靜態內部連結儲存(內部變數)

內部變數:具有檔案作用域、內部連結、靜態儲存期。

內部變數同樣定義宣告在所有函式之上,只不過需要使用static關鍵字進行宣告。在同一個翻譯單元中,訪問或使用內部變數不需要進行引用式宣告,可以直接訪問或者使用。(內部變數最好直接使用,不要用extern再次宣告一遍,原因是內部變數如果使用const+static關鍵字,再使用extern關鍵字會出現關鍵字種類過多這種錯誤,程式不能夠編譯成功,因此內部變數最好不要進行引用式宣告。)

5)補充

一般來講,我們定義外部變數或者內部變數,都希望這個變數是個不可更改的變數,但是對於外部變數來講,程式中的所有函式都能夠更改他的值;對於內部變數來講,同一個翻譯單元中的所有函式都能夠更改他的值,因此在定義宣告一個外部變數或者內部變數的時候加上關鍵字const。

下面通過程式來了解extern關鍵字、內外部變數的定義宣告和引用宣告。

//檔案1
//使用extern關鍵字來引用宣告一個外部變數或者內部變數
#include	<stdio.h>
#include	"head_1.h" 
const int names=5;//我們說過,定義宣告外部變數或者內部變數的時候最好加上const關鍵字。 
const double dates=2.6;
const static int times=30;//使用static關鍵字來宣告這是一個內部變數。 

int	main(void)
{
	printf("print the names in the main():%d\n",names);//雖然names是一個外部變數,但是定義宣告在這個翻譯單元中,因此使用的時候不需要引用宣告。
	
	extern const double dates;//這是對這個外部變數進行引用宣告,前面說過,這個引用宣告可以去掉。
	printf("print the dates in the main() :%d\n",dates);
	
	printf("print the times in the main() :%d\n",times);//內部變數最好不要在宣告一遍 
	
	read_();//建立一個read_函式,這個函式定義宣告在另一個原始碼檔案中。 
	
	return	0;	
} 
//檔案2
//定義read_函式
#include	<stdio.h>

void read_(void)
{
	extern const int names;
	extern const double dates;//如果外部變數沒有定義在這個翻譯單元中,若要訪問這個外部變數,必須使用extern進行引用宣告。
	
	printf("print dates in names times\n");
	for (int i=0;i<names;i++)
	{
		printf("Count %d  %d\n",i+1,dates);
	}
	
	return; 
}
//檔案3
void read_(void);//最好是用一個頭檔案來儲存函式原型的宣告 

6)靜態無連結儲存(靜態無連結變數)

重點:如果想在被調函式中實現計數器,那麼被調函式中的這個變數就要被定義宣告為靜態無連結變數。

靜態無連結變數:具有塊作用域、無連結、靜態儲存期。在自動變數之前加上static關鍵字即可。

對於靜態無連結變數,程式在編譯時就為這個變數分配了記憶體,當程式進入所在的塊時,可以訪問這個變數,當程式離開這個塊時,不能夠訪問這個變數,但是記憶體只有在程式結束之後才會被釋放掉。

通過一個程式來了解在被調函式中實現計數器:

//在被調函式中實現計數器設定
#include	<stdio.h>

void loop_(void);

int	main(void)
{
	printf("Then we will creat a loop:\n");
	int loop_times=5;
	
	for (int i=0;i<loop_times;i++)
	{
		loop_();	
	} 
	
	return	0;	
}
void loop_(void)
{
	int i=1;
	static int n=5;
	printf("print the value of i :%d\n",i);
    i++;
	n++;
	printf("print the value of n:%d\n",n);
	putchar('\n'); 
	
	return;
}

列印結果如下:

Then we will creat a loop:
print the value of i :1
print the value of n:6

print the value of i :1
print the value of n:7

print the value of i :1
print the value of n:8

print the value of i :1
print the value of n:9

print the value of i :1
print the value of n:10

我們結合列印結果分下一下:在被調函式中,變數i是自動變數,而變數n是靜態無連結變數,因此編譯器在編譯的時候就對這個變數分配了記憶體,直到程式結束才會釋放這個記憶體,所以當每次程式進入被調函式中,相當於忽略掉這條定義宣告。

第一次進入被調函式,分配給變數i記憶體,並執行初始化,跳過static int n=5;這條語句,接著往下執行,i++,此時i=2,n++,此時n=6,離開被調函式,釋放掉變數i的記憶體,因此i中的資料消失。

第二次進入被調函式,分配記憶體給變數i,並執行初始化,此時n=6,i=1,接著執行,i++,此時i=2,n++,此時n=7,離開被調函式,釋放掉變數i的記憶體......

7)同名變數*

深入理解同名變數在不同的塊中的執行規則。

如果在等級低的塊中出現和等級高的塊(或者是整個翻譯單元又或者是多個翻譯單元)中名字相同的變數,那麼在低等級的塊中,會自動將高等級中的那個同名變數隱藏。

//多層巢狀塊中的同名變數
#include	<stdio.h>
const int names=5;//定義一個外部變數 
const static int dates=8;//定義一個內部變數 
int	main(void)
{
	int x=5;//1
	printf("print the names: %d\n",names);//2
	printf("print the dates :%d\n",dates);//3
	printf("print the x :%d\n",x);//4
	{//塊可以自己隨意設定,不論在哪。 
		int x=8;//5
		int names=10;//6
		printf("print the names :%d\n",names);//7
		printf("print the x :%d\n",x);//8
	}
	printf("print the names: %d\n",names);//9
	printf("print the x :%d\n",x);//10
	int i=0;//11
	while (i<x)//12
	{
		int x=0;//13
		x++;//14
		i++;//15
		printf("x=%d i=%d\n",x,i);//16
	}
	printf("print x and i :%d,%d\n",x,i);//`17
	for (int i=1;i<x;i++)//18
	{
		int x=2;//19
		printf("print the x :%d\n",x);//20
	}
	printf("print the x and i:%d\n",x,i);//21
	do
	{
		int dates=3;//22
		i++;//23
		printf("print the dates :%d\n",dates);//24
	} while (i<dates);//25
	printf("print the dates :%d\n",dates);//26
	printf("print the i :%d\n",i);//27
	
	return	0;
} 

列印結果如下:

print the names: 5
print the dates :8
print the x :5
print the names :10
print the x :8
print the names: 5
print the x :5
x=1 i=1
x=1 i=2
x=1 i=3
x=1 i=4
x=1 i=5
print x and i :5,5

print the x :2
print the x :2
print the x :2
print the x :2
print the x and i:5 5
print the dates :3
print the dates :3
print the dates :3
print the dates :8
print the i :8

第2、3、4行在列印的時候就是正常的列印names和dates和x的值:5 8 5

第5、6行我定義聲明瞭同名變數,由於重新定義變數的塊等級低,因此會隱藏原來定義的變數,這兩個變數相當於是重新定義的,地址和原來不同,此時在這個塊中進行變數的列印,打印出的結果就是新定義的值。

第9、10行有從新列印names和x,由於程式離開了原來的塊,所以在上一個塊中定義的names和x記憶體被釋放,並且解除隱藏。列印值是正常的。

第11行定義了一個自動變數i=0;

第12行是while迴圈,我們發現,這個迴圈迴圈了五次,因此可以判斷出,while迴圈的迴圈頭不屬於while語句塊中的部分,而是屬於main()這個函式塊中的部分。

第13、14、15、16行中,在while語句的塊內(低階塊)重新定義宣告x,因此,在這個塊中有關x的語句全部都使用這個新定義x(因為外層塊中的x被隱藏)而i還是外層的i,由於每次進入這個迴圈,x都會從新定義宣告,並進行一次++運算,所以迴圈五次,x都是1。

第17行,在main()中列印x和i,i經過迴圈後變為5,而離開迴圈塊後,原來隱藏的x取消隱藏,回到5。

第18行,進入for迴圈,可以發現這個迴圈迴圈了4次,並且最後打印出來的x和i都是進如迴圈前的值,所以可以發現,for迴圈的迴圈頭是比主函式低一級的塊,而for迴圈的語句是比迴圈頭更低一級的塊,在內部定義的x=2,並不影響外部的x=5(每次檢驗迴圈頭的時候x=5,進入迴圈的時候x=2)退出迴圈後打印出來的值又是進入迴圈之前的,並沒受影響(如果不在迴圈內定義變數,那麼會影響到main()塊中的變數)。

第22行進入do迴圈,並且迴圈了三次,因此可以發現,do while迴圈的迴圈尾並不屬於迴圈塊中,他和main()塊是一個等級,i從5到8正好迴圈三次(5-6,6-7,7-8),而dates在迴圈內部列印每次都是3。在外部的時候每次都是8。

結束!

二、函式的儲存類別

函式同變數一樣,也有儲存類別。

1)外部函式

在定義函式的時候,什麼關鍵字都不加,這個函式就預設是外部函式。

有了這個概念,我們在翻譯單元較少的情況下可以不用將函式原型宣告在標頭檔案中,其他翻譯單元使用的時候該函式的時候,只需要像引用式宣告變數一樣,使用extern進行引用即可。

2)內部函式

在定義函式的時候,加上static關鍵字,這個函式就是內部函數了,內部函式只能在本翻譯單元中使用。

3)通過例項來理解一下內部函式和外部函式

//內部函式和外部函式
#include	<stdio.h>

void read_(void);
static void listen_(void);//宣告函式原型的時候要和定義一樣,加上static關鍵字 
//假設引用外部函式
//extern void write_(void);//這樣就完成了引用式宣告。
int	main(void)
{
	int x=4;
	for (int i=0;i<x;i++)
	{
		read_();
		listen_();
	}
	return 0;
} 
void read_(void)//定義的時候,什麼關鍵字都沒有,因此這是一個外部函式,其他翻譯單元使用需要進行引用式宣告 
{
	int n=9;
	n++;
	printf("print the value of the n :%d\n",n);
	
	return;
}
static void listen_(void)//我想把listen_()函式設定成內部函式,因此在定義最開始使用static關鍵字 
{
	char names='A';
	names+=6;
	putchar(names);
	putchar('\n');
	
	return;
}

列印結果如下:

print the value of the n :10
G
print the value of the n :10
G
print the value of the n :10
G
print the value of the n :10
G

三、函式形參中使用關鍵字

函式形參中可以使用的關鍵字:const、register、restrict、

不能夠使用的關鍵字:static

如果要傳遞的實際引數(變數)在定義宣告的時候使用了關鍵字,那麼形參中也一定要使用關鍵字。

如果要傳遞的實際引數(變數)在定義宣告的時候沒有使用某些關鍵字,但是形參中有關鍵字,那麼傳遞進來的這個引數將按照這個關鍵字進行執行。

這些關鍵字中:const可以用在變數也可用在指標身上,register只能用在變數身上,restrict只能用在指標身上。

四、time()函式、rand()函式、srand()函式

1)time()函式

time()函式原型宣告在time.h這個標頭檔案中。

在c語言中,time()函式的函式原型如下:

time_t time(time_t *second);

time_t是一種新的資料型別,這個資料型別表示的是秒數,轉換說明是%ld(原因是秒數很大,是可以理解為long型)其實就是整數型的一種推廣,這個函式接受一個time_t型別變數的地址,(儲存秒的變數的地址),並且這個函式的返回值也是time_t型別的。

這個函式的作用是計算從1970年1月1日0時起,到執行程式截止,一共經過的秒數。

第一種用法,使用引數,不使用返回值。

//time()函式的使用1
#include	<stdio.h>
#include	<time.h>

int	main(void)
{
	time_t seconds;//設定一個time_t型別的變數。
	time_t *ptime=&seconds;//由於time()函式接受的是time_t型別的指標,因此設定一個指標。 
	time(ptime);
	printf("print the second from 1970-01-01-00:00 to now:%ld\n",seconds);
	
	return 0;	
} 

第一次列印結果:

print the second from 1970-01-01-00:00 to now:1640056840

第二次列印結果:

print the second from 1970-01-01-00:00 to now:1640056864

第二種用法,不使用引數,使用返回值。

這種情況下,需要將time()的引數使用成空指標。

//time()函式的使用2
#include	<stdio.h>
#include	<time.h>

int	main(void)
{
	time_t seconds;
	seconds=time(NULL);
	printf("print the seconds from 1970-01-01-00:00 to now :%ld\n",seconds);
	
	return 0;	
} 

列印結果如下:

print the seconds from 1970-01-01-00:00 to now :1640057094

2)rand()函式

偽隨機數:偽隨機數指的是所生成的數字並不是隨機的。

我們可以想象一個很大的陣列,裡面的數字各不相等,rand()函式會按順序取出這個陣列中的數字,由於這個陣列很大,因此rand()函式好似能夠生成隨機數,但是當我有兩個這樣的陣列,並且生成次數能夠到達第二個陣列中時,就會生成和原來一模一樣的數字。

rand()函式生成數字的範圍是(想象出來的陣列的範圍):0~INT_MAX(int 所能表示的最大值)

我們先來分析一下rand的原始碼:

//rand()函式的原始碼
static unsigned long next=1;//我們把next叫做種子。

unsigned rand(void)
{
	next=next*1103515245+12345;
	return (unsigned)(next/65536)%32768;//強制轉換成unsigned型別 
}

rand()函式通過返回值來返回這個隨機數,內部變數next=1,返回值是一個固定的值:16838,此時的next儲存著另外的值,如果不結束程式,再次使用rand()函式,返回值是一個固定的:5758,此時next儲存著的又是別的值......

當結束程式再次進去的時候,next=1,生成的數字還和原來一樣,因為next的值是一樣的。

通過一個程式驗證:

//通過一個程式驗證rand()函式的偽隨機 檔案一
#include	<stdio.h>
extern unsigned rand(void);//引用宣告其他翻譯單元中的函式,這裡有一點需要注意,為什麼不引用宣告next,原因是next宣告定義在和 
int	main(void)//rand()一個翻譯單元中,訪問rand()的時候就會進入到那個翻譯單元,因此很自然的就會在那個翻譯單元中讀取next,所以不用引用。 
{
	int times=3;
	for (int i=0;i<times;i++)
	{
		peinrf("print the gauss number :%u\n",rand());//返回值是unsigned型別因此轉換說明是%u 
	}
	return 0;
} 
//檔案二
//rand()函式的原始碼
static unsigned long next=1;

unsigned rand(void)
{
	next=next*1103515245+12345;
	return (unsigned)(next/65536)%32768;//強制轉換成unsigned型別 
}

第一次列印結果:

print the gauss number :16838
print the gauss number :5758
print the gauss number :10113

在前三次中,看似生成的是隨機數,當我再次執行時:

print the gauss number :16838
print the gauss number :5758
print the gauss number :10113

生成的還是這些,順序都不會發生改變,原因就是種子都是從1按照某個固定的順序進行改變的。

3)srand()函式

由於rand()函式中的種子不是隨機的,所以我如果把rand()中的種子設定成隨機的,那麼rand()生成的就是確確實實的隨機數。

srand()函式的原始碼:

//srand()函式的原始碼
void srand(unsigned seed)
{
	next=seed;	
} 

srand()函式的原始碼非常簡單,srand()函式接受一個seed,並且把seed的值賦值給next,所以如果seed是一個隨機的數字,那麼就能夠使next隨機(種子隨機)。因此我們想到了time()函式。

由於rand()函式和srand()函式在stdlib.h標頭檔案中,time()在time.h中,所以引用這兩個標頭檔案即可(這個seed我們用time()函式的返回值代替,值得注意的是,這個返回值我們需要強制轉換成unsigned型別)

//使用rand()srand()time()生成隨機數
#include	<stdio.h>
#include	<stdlib.h>
#include	<time.h>

int	main(void) 
{	
	srand((unsigned)time(NULL));//這一步我們叫做使用srand()函式設定隨機種子 如果一次要生成多個隨機數,srand()函式需要放置在迴圈外部。(意味著我在一次程式中我只需要設定一次隨機種子)程式在讀取time()函式的時候進行時間的生成,由於放在一個迴圈中,編譯時間不足一秒,因此會是的time()函式的返回值一樣,所以要一次性獲得多個隨機數的時候,將srand()函式放在迴圈之外。
	int times;
	puts("Enter the amount of the guess number you want:");
	scanf("%d",&times);
	
	for (int i=0;i<times;i++)
	{ 
		printf("To create a guess number :%ld\n",rand());//這一步我們叫做使用rand()函式建立隨機數 
	}
	
	return 0;
} 

第一次輸入5的列印結果:

Enter the amount of the guess number you want:
5
To create a guess number :25580
To create a guess number :12645
To create a guess number :4220
To create a guess number :4705
To create a guess number :3618

第二次輸入5的列印結果:

To create a guess number :25704
To create a guess number :27868
To create a guess number :27699
To create a guess number :1607
To create a guess number :2356

現在終於達到了我們所要的隨機數。

4)根據隨機數程式設計一個擲骰子程式

這個程式能夠生成骰子的面數以及骰子的個數

//設定一個程式來擲骰子。 
#include	<stdio.h>
#include	<stdlib.h>
#include	<time.h>

int input_sides(void);
int input_dice(void);

int	main(void)
{
	int dice_sides;
	int number_dice;
	int sum_dice_number;
	int ren_val=1; 
	int loop_=0;
	while (ren_val == 1)
	{ 	
		puts("Enter the sides of the dice you want:");
		dice_sides=input_sides();
		puts("Enter the amounts of the dice you want to :");
		number_dice=input_dice();
	
		srand((unsigned)time(NULL));//設定隨機種子 ,注意的點就是強制轉換成unsigned型別 //讓編譯器讀取srand()函式的時間岔開就好。 
		for (int i=0;i<number_dice;i++)
		{
			int agent=(rand()%dice_sides)+1;//編譯器每次讀到rand()函式都會生成一個新的隨機數,並且這個agent是塊作用域變數,每次進入都會重新賦值。 
			printf("The %d dice point is :%d\n",i+1,agent); 
			sum_dice_number+=agent; 
		}
		printf("The sum of the points you roll is :%d that in %d dice(s) which has %d sides.\n"
				,sum_dice_number,number_dice,dice_sides);
		puts("Input q to quit the game and input a number to play again!");
		ren_val=scanf("%d",&loop_);
		getchar();//由於輸入其他的,程式將停止,所以說只需要吃掉和換行符即可 
	}
	return	0;	
}
int input_sides(void)
{
	int num;
	int ren_val;
	while ((ren_val=scanf("%d",&num)) != 1 || num<=1)
	{
		if (ren_val != 1)
		{
			puts("Please enter a number like 2!");
			while (getchar() != '\n')
			{
				continue;
			}
		}
		else
		{
			puts("Please enter a number that bigger than 1!");
			getchar();//吃掉換行符	
		}	
	}
	return num;	
}
int input_dice(void)
{
	int num;
	int ren_val;
	while ((ren_val=scanf("%d",&num)) != 1 || num<=0)
	{
		if (ren_val != 1)
		{
			puts("You need to enter a number like 2!");
			while (getchar() != '\n')
			{
				continue;
			}
		}
		else
		{
			puts("Must have 1 dice at least!");
			getchar();
		}
	}
	return num;
} 

第一次輸入4,4:

Enter the sides of the dice you want:
4
Enter the amounts of the dice you want to :
4
The 1 time point is :1
The 2 time point is :1
The 3 time point is :3
The 4 time point is :1
The sum of the points you roll is :6 that in 4 dice(s) which has 4 sides.
Input q to quit the game and input a number to play again!
q

第二次輸入4,4:

Enter the sides of the dice you want:
4
Enter the amounts of the dice you want to :
4
The 1 time point is :3
The 2 time point is :3
The 3 time point is :1
The 4 time point is :1
The sum of the points you roll is :8 that in 4 dice(s) which has 4 sides.
Input q to quit the game and input a number to play again!
q

可以發現,程式是隨機的。

五、記憶體分配函式

1)malloc()函式、free()函式&&動態陣列

1.malloc()函式可以人為的在記憶體中建立一塊牛記憶體,叫做記憶體塊,在這個記憶體塊中,還能夠設定一個個的小單元,記憶體塊的大小以及小單元的大小都是人為設定的。

2.malloc()函式的返回值返回的就是這個記憶體塊首位元組的地址(返回一個指標)。(我們之前所說的陣列的首元素的地址,其實就是他相應記憶體塊的首位元組的地址)

3.使用malloc()函式相當於建立了一塊動態記憶體,這塊動態記憶體只有在程式結束的時候才會被釋放,我們一般的做法是使用free()函式,在使用完成malloc()函式分配的記憶體後就將這塊記憶體釋放掉。

4.free()函式的引數是使用malloc()函式分配的記憶體的首地址。

5.動態陣列,用malloc建立的記憶體塊能夠以小單元的形式存在,類似於陣列,在設定記憶體塊大小的時候可以使用變數,因此叫做動態陣列。由於必須要有一個指標去指向這個動態陣列(沒有的話極易使陣列流失在記憶體中,也就是這個陣列存在,但是我找不到他,叫做記憶體洩漏),因此這個指標就相當於我們所說的陣列名。

6.注意,這兩個函式都是stdlib.h標頭檔案中的函式。

malloc()函式的使用方法:

//malloc()函式的使用
#include	<stdio.h>
#include	<stdlib.h>
int	main(void)
{
	int *ptint;
	ptint=(int *)malloc(30*sizeof(int));//1
	printf("print the adress of the ptint :%p\n",ptint);//2
	printf("print the adress od the ptint+1 :%p\n",ptint+1);//3
	free(ptint);//4
	
	return	0;	
} 

列印結果如下:

print the adress of the ptint :0000000000C91420
print the adress od the ptint+1 :0000000000C91424

第一行中:malloc()的引數寫法很規範,指的是30個int記憶體,也就是這個記憶體塊中能夠儲存30個int型別的值,返回一個指向首位元組的指標,預設狀態下,malloc()建立的記憶體塊,一個小單元是一個char記憶體,通過下面程式來理解:

//深入剖析malloc()函式
#include	<stdio.h>
#include	<stdlib.h>

int	main(void)
{
	void *point;
	point=malloc(30*sizeof(int));
	printf("print the adress of point :%p\n",point);
	printf("print the adress of point+1 :%p\n",point+1);
	
	return	0;
}

列印結果:

print the adress of point :00000000009E1420
print the adress of point+1 :00000000009E1421

在這裡,設定一個通用指標,void *point; 這個指標將自動匹配資料型別。

可以發現,對指標+1表示的是動態陣列中下一個小單元的首位元組地址(下一個小單元地止),地址由20變成21,也就是加了一個位元組,因此可以發現,一個小單元就是一個char的記憶體。

接著分析,

在第一行中,malloc前面使用了(int* ),這相當於將這個將返回的地址強制轉換成int型別(將返回的指標強制轉換成int型別)

第二行中:列印首單元地址是:20

第三行中:列印第二個小單元的地址是:24,相差4位元組,正好是int記憶體,這也證實了強制轉換成int型別指標的作用。

第四行:養成良好習慣,使用完動態記憶體及時釋放。

2)restrict關鍵字

restrict關鍵字只能使用在指標身上,並且當這個指標是某一塊記憶體唯一的訪問方式的時候,restrict才對其生效(才能夠進行優化)否則是無效的。

注意:restrict關鍵字夾在*和指標名稱之間,如果指向動態陣列的指標,一般我們習慣加restrict關鍵字,養成好習慣。

在下面的程式中,對於我們的動態陣列,int * restrict ptint_2=(int* )malloc(30*sizeof(int));,這裡面的ptint_2就是malloc()函式分配的記憶體塊的唯一的訪問方式,對ptint_2加restrict關鍵字進行宣告,會對接下來ptint_2訪問這塊記憶體中的資料或者修改記憶體中的資料做出優化。

而names這個陣列這塊記憶體,除了names能夠訪問,ptint_1這個指標也能夠訪問,因此這塊記憶體存在著兩個或者兩個以上的訪問方式,所以能夠訪問這塊記憶體的指標都不能使用restrict關鍵字進行優化,儘管宣告的過程中使用了restrict關鍵字,但是對其沒有作用。

//restrict()關鍵字
#include	<stdio.h>
#include	<stdlib.h>

int	main(void)

{
	int names[10]={1,2,3,4,5,6,7,8,9,0};
	int * restrict ptint_1=names;
	for (int i=0;i<30;i++)
	{
		*names+=3;
		*names+=6;
	}
	printf("print the value of the changed first number of the array:%d\n",*names);
	
	int * restrict ptint_2=(int* )malloc(30*sizeof(int));
	*ptint_2=1;
	for (int i=0;i<30;i++)
	{
		*ptint_2+=3;
		*ptint_2+=6;
	}
	printf("print the value of ptint :%d\n",*ptint_2);
	
	return 0;
		
} 

列印結果:

print the value of the changed first number of the array:271
print the value of ptint :271

分析一下如何進行優化的,編譯器看到ptint_2有關鍵字restrict進行宣告,因此會對ptint_2的運算進行優化,把第二個迴圈中,ptint_2+=3;ptint_2+=6;優化成ptint_2+=9;而第一個迴圈中則不會。