第十章 初窺天機之神用指針

 

10.1指針與指針變量

10.1.1 指針是什麼

指針如何理解?對於現階段來說,爲了便於理解指針,可以把指針稱爲地址

什麼是地址?有這樣一個的例子:如果我們要在一棟樓裏面找到不認識的某一家人,那麼我們需要知道這家人的門牌號,然後通過門牌號找到這戶家人,而我們所說的門牌號就是地址。又或者說我們在發信件時需要寫下接收地址,那麼通過接收地址就會發給需要接受的人。所以地址很重要,而地址就是指針

圖10.1 內存中的數據存儲形式

 

要理解指針,我們需要知道數據具體是如何在內存中存儲和讀取的。如圖10.1爲數據在內存中的存儲形式。假如我們定義三個整型變量:i, j, k,分別賦值爲1, 2, 3。其中,變量i的地址爲1000,值爲1。變量j的地址爲1004,值爲2。變量k的地址爲1008,值爲3。那麼,地址1000就是變量1的“門牌號”,地址1004就是變量j的“門牌號”。變量k也是同樣的。也就是說我們每次找變量i的值的時候我們需要首先知道變量i的地址。由於地址就是指針,所以指針指向地址1000。

另外地址2000表示存放變量i地址的地址。其實,之前的數組名也是地址,也就是說數組名也是可以作爲指針使用的。

變量i的地址1000就是變量i的指針。變量j的地址1004就是變量j的指針。變量k的地址1008就是變量k的指針。此處需要注意的是指針和指針變量不同。指針的操作單位是地址。我們將在下一節介紹指針變量。

 

10.1.2 指針變量是什麼

指針變量是指存放地址的變量,即該變臉裏面存放的是地址。指針變量是一種特殊的變量,它不同於一般的變量,一般變量存放的是數據本身,而指針變量存放的是數據的地址。

在上一節圖10.1中,我們已經說了變量i的地址是1000,這個地址也就是變量i的指針,如果定義一個變量來存放這個1000的地址,那麼這個變量就是指針變量。我們看下面這個小的例子。

【例10.1】指針變量的簡單定義。

解題思路:我們此處先簡單的看一下指針和指針變量是什麼。指針變量的定義,我們將在下一節講解,此處僅起到見微知著的意義。

編寫程序:

#include <stdio.h>
int main()
{
	int a = 1, b = 2;
	int *point1, *point2;
	point1 = &a;
	point2 = &b;
	printf("a = %d, b = %d\n", a, b);
	printf("*point1 = %d, *point2 = %d\n", *point1, *point2);
     printf("&a = %p, &b = %p\n", &a, &b);
	printf("point1 = %p, point2 = %p\n", point1, point2);
	return 0;
}

運行結果:

a = 1, b = 2

*point1 = 1, *point2 = 2

&a = 0012FF44, &b = 0012FF40

point1 = 0012FF44, point2 = 0012FF40

Press any key to continue

 

程序分析:這是一個簡單的程序實例,首先我們在第4行簡單的定義了兩個整型變量a,b。然後在第5行定義了兩個指針變量:point1,point2。就是在普通變量的前面加一個“*”號,這就是我們指針變量的定義。在程序第6、7行就是具體使用了,我們之前說過指針就是變量的地址,那麼如何獲取變量a和b的地址呢?我們在課本的前幾章中有介紹,就是在變量前面加一個取地址符號“&”,就表示對應變量的地址了。我們把變量a,b的地址賦值給指針變量point1,point2後,就表示point1和point2存放變量a,b的地址,也就是說point1和point2指向變量a,b的地址。我們如何輸出指針變量指向地址中存放的變量值呢?程序第9行,我們輸出時只需要在指針變量前加上一個“*”號就可以輸出對應內存地址中存放的數據。程序第10~11行輸出變量對應的地址。從運行結果中也會用了我們的理論,指針變量point1對應變量a的地址,指針變量point2對應變量b的地址。其中,%p表示格式輸出地址。

 

10.1.3 指針變量怎麼定義

上面一節我們已經講到指針變量的使用。那麼,它是如何定義呢?或者定義格式是怎麼樣的呢?上一節中的程序10.1中第5行,已經使用了指針變量的定義。那麼現在讓我們看看如何定義使用。

類型名 *指針變量名;

或者

類型名* 指針變量名;

或者

類型名 * 指針變量名;

比如程序10.1中的第5行:

int *point1, *point2;

或者

int* point1;

或者

int * point1;

 

當然我們還是可以定義爲如下:

char *ch;

float *f;

double *d;

這些我們將會在下面的章節中介紹的。同時在程序10.1中的第6~7行,就是指針變量的賦值。

point1 = &a;

point2 = &b;

或者我們改寫成定義時初始化:

int *point1 = &a;

int *point2 = &b;

 

 

10.2 指針和基礎數據類型的關係

10.2.1 整型指針的使用

之前我們已經大概介紹並使用了整型指針,接着我們還會通過一個具體的實際例子來講解指針變量的相關使用。

我們做過普通變量的交換,它是通過變量的賦值實現的交換,現在我們用指針如何實現類似的交換呢?請看實例10.2。

【例10.2】指針實現程序中數據交換。

解題思路:定義兩個指向普通變量的指針變量,然後交換指針變量中地址對應的值。最後輸出交換之後的結果。

編寫程序:

#include <stdio.h>
int main()
{
	int a=2, b=3;
	int *point1 = &a, *point2 = &b;
	int tmp=0;
	printf("a,b交換前的結果:\n");
	printf("a = %d, b = %d\n", a, b);
	tmp = *point1;
	*point1 = *point2;
	*point2 = tmp;
	printf("a,b交換後的結果:\n");
	printf("a = %d, b = %d\n", a, b);
	return 0;
}

運行結果:

a,b交換前的結果:

a = 2, b = 3

a,b交換後的結果:

a = 3, b = 2

Press any key to continue

 

程序分析:程序第5行就是指針變量的初始化,把point1指向a的地址,point2指向b的地址。在程序第9~11行實現了數據的交換,就是使用point1和point2實現a和b之間的數據交換,這和使用a,b實現數據交換是等價的,因爲point1就是a的地址,point2就是b的地址。如圖10.2爲point1與a,point2與b之間的關係結構。

圖10.2 point1與a,point2與b之間的關係結構

 

10.2.2 浮點型指針

浮點型指針和整型指針的用法是一致的,此處之所以再次寫出浮點型指針,其主要目的就是爲了深化對指針的理解,同樣廢話不多說,直接上例題。

 

 

10.2.3 雙精度型指針

 

 

 

10.2.4 字符型指針的使用

之前我們已經知道了指針就是地址,如果把字符定義成指針,該字符指針就會指向一個地址,該地址可以是指向一個字符,也可以是指向一個字符串。所以也稱爲指針引用字符串。

首先我們介紹通過指針引用一個字符的方式。之前我們講過字符的輸出形式,以%c的格式輸出結果。那麼針對字符串中的數據我們也是可以通過這樣的輸出格式輸出字符串中需要的一個字符。

【例10.3】通過指針輸出字符格式數據。

解題思路:主要定義一個字符變量和一個字符串變量,通過字符指針指向對應的數據,然後觀察字符指針的輸出形式。

編寫程序:

#include <stdio.h>
int main()
{
	char c1='S';
	char c2[]="I am a good student.";
	char *p=NULL;
	p = &c1;
	printf("%c\n", *p);
	p = c2;
	printf("%c\n", c2[3]);
	printf("%c\n", p[3]);
	printf("%c\n", *(p+3));
	return 0;
}

運行結果:

S

m

m

m

Press any key to continue

 

程序分析:程序第7行用字符指針p指向字符變量c1,輸出時就需要使用*p,表示地址p指向存儲單元存放的數據,輸出結果如第8行所示。第9行用字符指針p指向字符串變量c2,由於數組名c2可表示數組的首地址,和c2都是表示的地址,就可以用指針p直接指向字符串地址c2。通過printf函數中輸出方式的比較發現,c2和p的用法是一樣的,當然他們兩者還有其他的輸出方法,比如第12行*(p+3)。

 

那麼如何用指針來輸出字符串呢?

我們知道字符以%c的格式輸出結果。字符串的輸出格式是%s。結合上一個程序的講解,聰明的同學應該已經知道怎麼輸出了。我們看上一個例子中p=c2表示此時兩者是等價的,那麼p不就可以按照c2的方式輸出字符串了嗎?那麼接下來讓我們見證奇蹟。哈哈哈。

【例10.4】通過指針變量引用字符串。

解題思路:主要定義兩個變量,一個字符串變量,一個字符指針變量,讓指針變量指向字符串變量,然後把指針變量按照字符串變量的形式輸出。

編寫程序:

#include <stdio.h>
int main()
{
	char c2[]="I am a good student.";
	char *p=NULL;
	p = c2;
	printf("%s\n", c2);
	printf("%s\n", p);
	printf("%s\n", (c2+5));
	printf("%s\n", (p+5));
	return 0;
}

運行結果:

I am a good student.

I am a good student.

a good student.

a good student.

Press any key to continue

 

程序分析:程序第7~8行可以看出用指針輸出字符串變量的形式,如果我們想要輸出一個字符串的從某個位置到最後的那部分,就可以通過程序第9~10行輸出結果。

 

幾點注意事項:

  1. C語言中只有字符變量,沒有字符串變量。

例如:

char *p = “I am a good student.”;

等價於:

char *p=NULL;

p = “I am a good student.”;//把字符串第一個字符賦給p。

 

p被定義爲一個字符型指針變量,它指向的是一個字符地址,就是字符”I am a good student.”的字符’I’的地址,也就是該字符串的首地址。我們也知道如果要知道一個字符串,那麼只需要它的首地址即可,所以通過指針變量p是可以輸出該字符串的。

但是,它不等價於如下兩行代碼:

char *p=NULL;

*p = “I am a good student.”;//錯誤的方式,

 

此處把字符串賦值給*p是不正確的,*p代表一個字符數據,而此處卻把一個字符串賦值給*p,明顯不能成功。

當我們輸出指針變量p指向的字符串“I am a good student.”,首先是輸出字符’I’,然後p地址加1,指向下一個字符地址,然後輸出空格,這樣依次所有的數據直到遇到字符’\0’結束輸出。

 

  1. 通過數組名或字符指針變量可以輸出一個字符串,但是對於數值型數組是不能通過數組名輸出它的全部元素的。

比如,下面兩行代碼輸出的結果並不是整個數組的值:

int a[5]={1,2,3,4,5};

printf(“%d\n”, a);

 

這樣輸出的結果爲a的地址,並且是十進制輸出。如果以”%p”輸出,這是按照地址輸出。

 

【例10.5】一個字符串數據複製到另一個字符串中。

解題思路:我們定義兩個字符數組,然後把一個存放數據的字符數組中的數據通過指針複製到另一個字符數組中。需要注意的是空字符數組存儲空間需要大於存放字符的數組存儲空間的大小。

編寫程序:

#include <stdio.h>
int main()
{
	char a[] = "I am a good student.", b[25];
	char *p1 = a, *p2 = b;
	while(*p1 != '\0')
	{
		*(p2++) = *(p1++);
	}
	*p2 = '\0';
	printf("%s\n",b);
	return 0;
}

運行結果:

I am a good student.

Press any key to continue

 

程序分析:在程序第4行,我們定義兩個字符數組a和b,在定義時需要考慮b的數組長度要大於等於a的數組長度,不然放不下整個a數組中的數據。程序第5行我們定義兩個指針變量,分別指向數組a和數組b。然後在6~9行的while循環中依次把a中的數據放到b中,每次移動是靠指針的改變,也就是地址改變,由上一個地址指向下一個地址。程序第10行是爲了把最後一個字符’\0’賦值給*p2,爲了避免b輸出時出現亂碼,因爲字符輸出時遇見’\0’就會結束輸出,而a數組中的’\0’並沒有賦值給*p2。

 

10.3 指針和數組的關係

10.3.1 指針與一維數組的關係

我們在之前的章節中,已經講解了一維數組的定義,一個數組包含若干元素,通過數組下標可以訪問到任何一個元素,那麼現在我們如何通過指向數組的指針來訪問每一個數組中的數據呢?

圖10.3 指向數組的指針

比如:

int a[]={1,2,3,4,5,6};

int *p = a;

上面兩行我們都清楚,表示p指向數組a的指針,如圖10.3是兩者在內存中的存儲方式。那麼我們如何指向某個具體的元素呢?

p = &a[0];

p = &a[3];

p = &a[5];

上面三行表示通過指針變量p指向數組中的第1,4,6個元素,這樣就可以訪問數組中任何一個元素了。事實上,p = &a[0]與p = a表示的是同一個地址。之前我們訪問數組是通過下標,那麼現在將會由下標轉換成指針,如果指針自加(p++),表示指針的指向由上一個存儲單元的地址轉到下一個存儲地址。

注意:數組名不代表整個數組,只表示數組首元素的地址。但是如果我們把數組名作爲指針使用,那麼是可以訪問整數數組中的元素的。

比如:

for(i=0; i<6; i++){ printf(“%d ”, a[i]); }

for(i=0; i<6; i++){ printf(“%d ”, *(a + i)); }

for(i=0, p = a; i<6; i++){ printf(“%d ”, *(p+i) ); }

for(i=0, p = a; i<6; i++){ printf(“%d ”, *(p++) ); }

這四行實現了相同的功能,都是輸出數組a中的所有元素。

 

【例10.6】學生C語言成績排序。

解題思路:首先定義一個浮點型數組用於存放學生的C語言成績,然後定義一個指向該數組的指針變量,通過該指針變量對學生成績進行排序,並且求出成績最高和最低的學生成績。

編寫程序:

#include <stdio.h>
int main()
{
	float score[10];
	float *p = score;
	int i=0, j=0;
	printf("請輸入10個學生的C語言成績:\n");
	for (i=0; i<10; i++)
	{
		scanf("%f", (p+i));
	}
	printf("排序中......\n");
	for (i=0; i<10; i++)
	{
		for(j=0; j<9-i; j++)
		{
			if (*(p+j) < *(p+j+1))
			{
				float temp = *(p+j);
				*(p+j) = *(p+j+1);
				*(p+j+1) = temp;
			}
		}
	}
	printf("排序結果爲:\n");
	for (i=0; i<10; i++)
	{
		printf("%.1f ", *(score+i));
	}
	printf("\n");
	printf("C語言最高成績爲:%.1f\n", *p);
	printf("C語言最低成績爲:%.1f\n", *(p+9));
	return 0;
}

運行結果:

請輸入10個學生的C語言成績:

87 90 67 92.5 98 61 57 79.5 86 88

排序中......

排序結果爲:

98.0 92.5 90.0 88.0 87.0 86.0 79.5 67.0 61.0 57.0

C語言最高成績爲:98.0

C語言最低成績爲:57.0

Press any key to continue

 

程序分析:程序第5行定義了一個指向浮點型數組的指針變量p。程序第8~11行表示通過指針來接受輸入的值,指針變量本身就是地址,所以我們直接使用p作爲地址來接受輸入的結果。程序中我們使用的是p+i來實現地址的不斷增加,數據的不斷輸入,我們還可以使用另一個輸入方式就是p++,但是我們需要在第11行後面添加一行代碼:p=score,因爲p++地址增加後會把指針指向score的最後一個內存單元之後的地址。程序13~24行是整個程序的核心算法,通過指針p實現了數據的從大到小排序。內層循環每完成一次,就會實現把最小的數據放到最下面。由於我們已經把最小的數據放到了最下面,所以下次執行的時候就會比上一次少比較一次,也就是程序第15行中的i<9-i。程序19~21行,就是數據交行的方式,首先定義一個局部變量temp,然後p的基址加上j獲取相應地址,通過地址獲取對應地址的數據。每次都是相鄰兩個進行比較的。程序26~29行是輸出最終的排序結果,在28行我們發現,輸出是使用的是score+i對應地址的值,事實上score和p可以等價使用的,此處的功能和p一樣。最後程序31~32行輸出最高成績和最低成績,因爲我們之前已經把成績按照從大到小的順序排列完成,那麼第一個絕對是最高的,最後的就是最低的。

 

10.3.2 指針與二維數組的關係

上一節中,已經講解了指針與一維數組的關係,這一節我們討論指針與二維數組的關係。

圖10.4 指向數組的指針

 

之前我們已經知道二維數組就是相當於一維數組中的每個元素表示一個數組。

比如:

int a[3][2]={{1,2},{3,4},{5,6}};

a是一個二維數組,如圖10.是數組a在內存中的存儲方式。如果在一維數組中,a[0]表示下標爲0的這個元素,但是在二維數組中a[0]表示下標爲0的一個數組。另外,C語言中數組是按照行存儲的,並且數組名就是該數組的首地址,所以a就是該數組的首地址,我們要訪問其中的某個元素可以通過下標訪問,比如:訪問第一個元素,就是a[0][0]。第二個元素就是a[0][1]。,,,。最後一個元素就是a[2][1]。那麼我們有沒有其他的方法訪問該數組中的元素呢?當然可以,比如訪問元素a[1][1],我們還可以寫成形式*(a[1]+1)或者*(*(a+1)+1),而他對應的地址則爲(a[1]+1)或者(*(a+1)+1)。針對數組和指針的更深關係,大家可以自己深入學習。那麼讓我們來看個程序吧,通過程序的實際運行及結果,會使大家更加深入的理解。

 

【例10.7】用數組名作爲指針輸出二維數組的中數據和地址。

解題思路:該程序我們主要通過定義一個二維數組,通過該二維數組名輸出相應的地址和數據,來更詳細的理解相關相關操作結果。

編寫程序:

#include <stdio.h>
int main()
{
	int a[3][2]={{1,2},{3,4},{5,6}};
	printf("%d, %d\n", a, *a);//0行首地址和0行0列元素地址
	printf("%d, %d\n", a[0], *(a+0));//0行0列元素地址
	printf("%d, %d\n", &a[0], &a[0][0]);//0行首地址和0行0列元素地址
	printf("%d, %d\n", a[1], a+1);//1行0列元素地址和1行首地址
	printf("%d, %d\n", &a[1][0], *(a+1)+0);//1行0列元素地址
	printf("%d, %d\n", a[2], *(a+2));//2行0列元素地址
	printf("%d, %d\n", &a[2], a+2);//2行首地址
	printf("%d, %d\n", a[1][0], *(*(a+1)+0));//1行0列元素的值
	printf("%d, %d\n", *a[2], *(*(a+2)+0));//1行0列元素的值
	return 0;
}

運行結果:

1244976, 1244976

1244976, 1244976

1244976, 1244976

1244984, 1244984

1244984, 1244984

1244992, 1244992

1244992, 1244992

3, 3

5, 5

Press any key to continue

 

程序分析:首先我們地址是以十進制表示的,如果要表示成十六進制只需要把格式控制換成“%p”即可。由於不同的計算機,不同的編譯環境,不同的運行時間運行這個程序時,由於內存分配情況不同,所顯示的地址可能是不同的,但是上面顯示的地址有共同的規律。比如程序第5~7行表示相同的內存地址,但是他們表示的含義是不同的,程序第8~9行以及第10~11行表示相同的含義。但是他們的地址卻發生了變換。由之前的1244976變成1244984再變成1244992。前兩者之間的差值爲8個字節(一行有兩個整型元素,每個整型元素佔4個字節)。後兩者之間也是相同的道理。

 

上面我們講解了二維數組名作爲參數輸出相應的數據,那麼我們如何定義一個指向二維數組的指針呢?且看下面兩行。

int a[3][2]={{1,2},{3,4},{5,6}};

int (*p)[2] = a;

 

這兩行就是如何定義一個指向二維數組的指針變量。(*p)後面的“[]”中的數值需要與該指針指向的數組列數相同。具體讓我們看看如何實現指向二維數組的指針操作。

 

【例10.8】輸入n名學生語文,數學,英語,輸出總成績(指向二維數組的指針實現)。

解題思路:首先定義一個二維數組,然後定義一個指向二維數組的指針,通過指針操作數據,實現功能。

編寫程序:

#include <stdio.h>
int main()
{
	int StuNum=0;
	double score[100][3]={0};
	double (*p)[3] = score;
	int i=0;
	printf("請輸入學生的人數(n<100):");
	scanf("%d", &StuNum);
	for (i=0; i<StuNum; ++i)
	{
		printf("請輸入第%d名學生的語文,數學和英語的成績(以空格分隔):", i+1);
		scanf("%lf%lf%lf", &score[i][0], &score[i][1], &score[i][2]);
	}

	for (i=0; i<StuNum; i++)
	{
		double sum = (*p)[0]+(*p)[1]+(*p)[2];
		printf("第%d名學生的總成績爲:%.1lf\n", i+1, sum);
		p++;
	}
	return 0;
}

運行結果:

請輸入學生的人數(n<100):7

請輸入第1名學生的語文,數學和英語的成績(以空格分隔):78 80 65

請輸入第2名學生的語文,數學和英語的成績(以空格分隔):90 97 87

請輸入第3名學生的語文,數學和英語的成績(以空格分隔):67 91 83

請輸入第4名學生的語文,數學和英語的成績(以空格分隔):62 76 90

請輸入第5名學生的語文,數學和英語的成績(以空格分隔):86 94 88

請輸入第6名學生的語文,數學和英語的成績(以空格分隔):95 86 90

請輸入第7名學生的語文,數學和英語的成績(以空格分隔):93 89 77

第1名學生的總成績爲:223.0

第2名學生的總成績爲:274.0

第3名學生的總成績爲:241.0

第4名學生的總成績爲:228.0

第5名學生的總成績爲:268.0

第6名學生的總成績爲:271.0

第7名學生的總成績爲:259.0

Press any key to continue

 

程序分析:改程序中主要有兩個知識點,其一是程序第6行,定義一個指向二維數組的指針。其二是程序第17行的計算總成績,當i=0時,(*p)[0]就等價於score[0][0],(*p)[1]就等價與score[0][1]。而(*p)就相當於指向了score[i]。

 

上面簡要的介紹了一下指向二維數組的指針,算是開胃菜,我們來看下面一個程序。

【例10.9】在一個班中,找到有成績不及格科目所有學生以及第n個學生的成績。

解題思路:針對此題,對於一個班的學生的成績我們可以直接初始化,然後依次尋找學生的各個科目是否合格,如果不合格就輸出這個學生所有的成績。然後輸入第n個學生,輸出該名學生的成績。此題中我們設定學生一共有6名。

編寫程序:

#include <stdio.h>
int main()
{
	float score[6][3]={ {89, 76, 56},
						{90, 100, 78},
						{88, 45, 96},
						{67, 45, 89},
						{79, 91, 60},
						{59, 99, 76}};
	int i=0, j=0;
	float (*p)[3] = score;
	int n=0;
	printf("所有學生的成績爲:\n");
	for (i=0; i<6; ++i)
	{
		printf("第%d名學生成績:%.1f %.1f %.1f\n", i+1, *(*(p+i)+0), *(*(p+i)+1), *(*(p+i)+2));
	}
	printf("科目不及格學生有:\n");
	for (i=0; i<6; ++i)
	{
		for (j=0; j<3; ++j)
		{
			if (*(*(p+i)+j) < 60)
			{
				printf("第%d名學生成績:%.1f %.1f %.1f\n", i+1, *(*(p+i)+0), *(*(p+i)+1), *(*(p+i)+2));
				break;
			}
		}
	}
	printf("請輸入你要查找的學生的名次:");
	scanf("%d", &n);
	if (n<0 || n>=6)
	{
		printf("班級不存在這名學生。\n");
		return -1;
	}
	printf("第%d名學生成績:%.1f %.1f %.1f\n", n, *(*(p+n-1)+0), *(*(p+n-1)+1), *(*(p+n-1)+2));
	return 0;
}

運行結果:

所有學生的成績爲:

第1名學生成績:89.0 76.0 56.0

第2名學生成績:90.0 100.0 78.0

第3名學生成績:88.0 45.0 96.0

第4名學生成績:67.0 45.0 89.0

第5名學生成績:79.0 91.0 60.0

第6名學生成績:59.0 99.0 76.0

科目不及格學生有:

第1名學生成績:89.0 76.0 56.0

第3名學生成績:88.0 45.0 96.0

第4名學生成績:67.0 45.0 89.0

第6名學生成績:59.0 99.0 76.0

請輸入你要查找的學生的名次:3

第3名學生成績:88.0 45.0 96.0

Press any key to continue

 

程序分析:本程序是使用指針的另一個用法,我們對比程序10.8和10.9會發現之前對於三維數組的輸出我們使用的是“(*p)[i]”的形式,而此後使用的是“*(*(p+i)+j)”的形式,兩者都是指向二維數組的指針的輸出形式。本程序中i表示行,j表示列。二位數組可以理解爲行和列的關係。

 

 

針對上面的定義指向二維數組的指針,我們使用的都是這樣的形式:

int a[3][2]={{1,2},{3,4},{5,6}};

int (*p)[2] = a;

 

把*p用一個“()”給括起來,那麼如果不添加這個“()”又是什麼呢?又是如何定義呢?那麼如果不添加,那麼“(*p)[2] ”就變成了“*p[2] ”,他表示指向一個二維數組的地址。具體的定義如下:

int a[3][2]={{1,2},{3,4},{5,6}};

int *p[2];

p[0] = a[0];

p[1] = a[1];

p[2] = a[2];

 

此時,p指向的是對於二維數組a的行元素的首地址。

當然對於指針還有其他定義方法和使用方法,具體更加詳細的瞭解,大家可以自己深入學習。

 

 

10.3.3 指針與多維數組的關係

上一節我們講解了如何使用指針指向二維數組,那麼如何使用指針指向三維甚至跟多維的數組呢?

我們首先觀察如何定義一個指向三維數組的指針,看過之後,聰明的你一定會看出點規律。

 

int a[3][2][2] = {  {{1,2},{3,4}},

{{5,6},{7,8}},

{{9,10},{11,12}}};

int (*p)[2][2] = a;

 

我們觀察三維指針的定義會發現,其實只是把三維數組中的a[3]換成了(*p)而已,其他的都保持不變即可。沒錯就是這麼定義,很簡單。

接下來我們還是舉一個實際的例子大家看看結果,就能夠更加深入的理解指向多維數組的指針了。

【例10.10】指向多維數組的指針的使用。

解題思路:定義一個三維數組和一個指向三維數組的指針變量,通過輸出三維數組中已經初始化完成的數據,查看學習如何使用指向多維數組的指針。

編寫程序:

#include<stdio.h>
void main()
{
	int a[3][2][2] = {  {{1,2},{3,4}},
						{{5,6},{7,8}},
						{{9,10},{11,12}}};
	int (*p)[2][2] = a;
	int i, j, k;
	for (i=0; i<3; ++i)
	{
		for (j=0; j<2; ++j)
		{
			for (k=0; k<2; ++k)
			{
				printf("%d ", *(*(*(p+i)+j)+k));
			}
		}
	}
	printf("\n");
}

運行結果:

1 2 3 4 5 6 7 8 9 10 11 12

Press any key to continue

 

程序分析:此處我們只需要注意第4行三維數組的定義方法,當然這樣的定義方法雖然比較清晰,但是在給他們歸類的時候容易出現錯誤,那麼我們可以只使用一個花括號括起來所有數據即可不需要多餘的花括號,比如:int a[3][2][2] = { {{1,2},{3,4}}, {{5,6},{7,8}}, {{9,10},{11,12}}}; 。然後一個需要注意的點在第15行,觀察對比和指向二維數組的指針輸出數據的格式關係,會發現,此處只不過多了一維而已,那麼四維,五維等也是同理。

 

 

10.4 指針變量作爲函數參數

之前章節中我們講解了函數參數的傳遞,此處我們將會講解指針變量作爲函數參數傳遞數據。我們將會針對函數數據交換寫兩個程序進行對比,一個是普通的變量傳遞,一個是指針變量傳遞。

【例10.11】普通變量作爲參數傳遞給數組。

編寫程序:

#include <stdio.h>
void swap(int m, int n)
{
	int temp = m;
	m = n;
	n = temp;
}
int main()
{	
	int a=4, b=6;
	printf("交換前:a = %d,b = %d\n", a, b);
	swap(a, b);
	printf("交換後:a = %d,b = %d\n", a, b);
	return 0;
}

運行結果:

交換前:a = 4,b = 6

交換後:a = 4,b = 6

Press any key to continue

 

 

【例10.12】指針變量作爲參數傳遞給數組。

編寫程序:

#include <stdio.h>
void swap(int *m, int *n)
{
	int temp = *m;
	*m = *n;
	*n = temp;
}
int main()
{	
	int a=4, b=6;
	int *p = &a, *q = &b;
	printf("交換前:a = %d,b = %d\n", a, b);
	swap( p, q );
	printf("交換後:a = %d,b = %d\n", a, b);
	return 0;
}

運行結果:

交換前:a = 4,b = 6

交換後:a = 6,b = 4

Press any key to continue

 

兩個程序分析:通過兩個程序運行結果的對比我們發現,第一個程序調用交換函數後,a,b並沒有實現數據數值的交換,而第二個程序調用函數後實現了a,b數值的交換,這是爲什麼呢?通過對比交換函數我們發現,在第一個程序的交換函數我們傳遞的是數值,而第二個交換函數我們傳遞的是地址。第一個交換函數中,把a傳給局部變量m(形參),把b傳給局部變量n(形參),在該函數中實現的是m和n的交換,這和a,b是沒有任何關係的,相當於另外兩個變量的數據交換,而這兩個變量的值只是由a,b分別賦給的而已。第二個交換函數中,是把a和b的地址分別賦值給指針m,n,如果交換m和n的數據相當於把a的地址給b,b的地址給a,那麼a中存放的數值由之前的4變成了6,b中存放的數值由之前的6變成4,圖10.2就是指針交換的示意圖,講的就是這樣的意思。

 

上面兩個函數通過對比的方式講解了指針如何作爲函數參數進行數據的傳遞。事實上,一維數組名可以作爲函數參數,多維數組名也可以作爲函數參數。用指針變量做形參,以接受實參數組名傳遞來的地址。

上面這兩個實例是通過對比來展現指針變量作爲函數形參的數據傳遞,那麼一維數組,多維數組是怎麼用指針變量作爲形參表示呢?

實際上,普通變量和一維數組的形參都可以類似於程序10.12那樣的形式傳遞。下面我們來看一個實際的例子。

【10.13】通過指向一維數組的指針實現一維數組中數據的逆序。

解題思路:數據的逆序就是把第一個數據和最後一個交換位置,第二個和倒數第二個交換位置,以此類推。

編寫程序:

#include <stdio.h>
int main()
{
	void swap(int *arr, const int n);
	void showResult(int arr[], const int n);
	int score[10]={1,2,3,4,5,6};
	printf("數據交換前:\n");
	showResult(score, 6);
	printf("\n數據交換中...\n");
	swap(score, 6);
	printf("\n數據交換後:\n");
	showResult(score, 6);
	return 0;
}
void swap(int *arr, const int n)
{
	int i=0, temp=0;
	while (i<=n/2)
	{
		temp = *(arr+i);
		*(arr+i) = *(arr+n-1-i);
		*(arr+n-1-i) = temp;
		i++;
	}
}
void showResult(int arr[], const int n)
{
	int i;
	for (i=0; i<n; ++i)
	{
		printf("第%d名學生成績:%d\n", i+1, *arr++);
	}
}

運行結果:

數據交換前:

第1名學生成績:1

第2名學生成績:2

第3名學生成績:3

第4名學生成績:4

第5名學生成績:5

第6名學生成績:6

 

數據交換中...

 

數據交換後:

第1名學生成績:6

第2名學生成績:5

第3名學生成績:3

第4名學生成績:4

第5名學生成績:2

第6名學生成績:1

Press any key to continue

 

程序分析:觀察交換函數的輸出函數的形參,發現一個是以指針的形式接受實參傳遞的數據,一個是以數組形式接受實參傳遞的數據。兩者在使用上沒有太大差別。但是通過對比10.12的swap函數和本程序的swap函數,會發現10.12中是一個變量傳遞給指針類型的形參,而本程序的swap函數則是一個整型數組傳遞給指針類型的形參,這兩者有什麼區別嗎?表面上看是不一樣的,因爲一個是變量,一個是數組。然而實際上他們都是指向了對應實參的地址而已。從地址角度出發,其實並沒有什麼不同。我們觀察輸出函數則是以一個數組的形式接受實參傳遞來的數據,這個方式也是初學者最喜歡使用的方式,因爲它能夠避免一些錯誤的出現。

 

上面講解了指向一維數組的指針變量,那麼二維指針變量如何使用了,那麼廢話不多說我們直接上題。

【例10.14】指向二維數組指針變量作爲函數參數進行數據傳遞。

解題思路:此處我們使用指向二維數組的指針作爲函數參數直接輸出初試數據。大家關鍵觀察二維數組是如何傳遞的。

編寫程序:

#include <stdio.h>
int main()
{
	void showResult(float (*p)[3], int m, int n);
	float score[6][3]={ {89, 76, 56},
						{90, 100, 78},
						{88, 45, 96},
						{67, 45, 89},
						{79, 91, 60},
						{59, 99, 76}};
	
	printf("科目不及格學生有:\n");
	showResult(score, 6, 3);
	return 0;
}
void showResult(float (*p)[3], int m, int n)
{
	int i, j;
	for (i=0; i<m; ++i)
	{
		for (j=0; j<n; ++j)
		{
			if (*(*(p+i)+j) < 60)
			{
				printf("第%d名學生成績:%.1f %.1f %.1f\n", i+1, *(*(p+i)+0), *(*(p+i)+1), *(*(p+i)+2));
				break;
			}
		}
	}
}

運行結果:

科目不及格學生有:

第1名學生成績:89.0 76.0 56.0

第3名學生成績:88.0 45.0 96.0

第4名學生成績:67.0 45.0 89.0

第6名學生成績:59.0 99.0 76.0

Press any key to continue

 

程序分析:觀察showResult函數中接受二維數組的形參格式。我們會發現其實這和定義一個指向二維數組的指針形式是一樣的。最後只不過把所有的數據通過函數輸出結果而已。

 

 

10.5 指向函數的指針

10.5.1 函數指針

什麼是函數指針?要是理解函數指針,首先要知道函數。我們知道函數調用的本質就是在內存中找到該函數的地址,那麼函數指針就會指向這個函數的起始地址,以此實現函數的調用。

那麼如何定義呢?我們需要首先定義一個函數,然後定義一個指向該函數的指針,指向該函數的起始地址。這樣該指針就具備了交換函數的功能。說起來很簡單,具體怎麼實現呢?讓我們通過例子來更加詳細的瞭解指向函數指針的定義。

比如:

void swap(int *a, int *b);

void (*p)(int *a, int *b);

p = swap;

 

上面這三段表示了一個指向函數指針的定義形式。用p指向函數swap的起始地址,它的參數是兩個指針類型的整型,通過對比發現指向函數指針的定義很簡單,只不過把之前的swap改變成(*p),而其他的保持不變,然後讓p指向swap即可。

通過這個例子大家已經基本上理解了如何定義一個指向函數的指針變量,那麼我們現在拿一個具體的實例來觀察,加深理解和掌握。

【10.15】通過指向函數的指針實現兩個數據的交換。

解題思路:我們首先定義一個交換函數,然後定義一個指向函數的指針變量,讓該指針指向交換函數,即這個指針就具備了交換函數的功能。

編寫程序:

#include <stdio.h>
int main()
{
	void swap(int *a, int *b);
	void (*p)(int *a, int *b);
	int m = 10, n= 15;
	printf("交換前的數據:%d, %d\n", m, n);
	p = swap;
	p(&m, &n);
	printf("交換後的數據:%d, %d\n", m, n);
	return 0;
}
void swap(int *a, int *b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

運行結果:

交換前的數據:10, 15

交換後的數據:15, 10

Press any key to continue

 

程序分析:第4~5行以及第8行,我們之前已經講過,現在我們最重要的是講解第九行,查看第9行它的使用和swap函數是一樣的,假如我們此處使用的是swap函數,那麼它的使用是swap(&m, &n)。那麼它的使用只不過換一個名字而已。這和普通指向變量的指針使用方式類似。

 

我們已經講解了一個實例,那麼定義指向函數的指針變量的一般形式我們總結如下:

類型名 (*p)(函數參數列表);

如之前說過的“void (*p)(int *a, int *b);”這裏的類型名是指函數返回值的類型。

 

10.5.2 函數指針調用函數

關於函數指針調用函數,我們上一節的例10.15已經涉及,這個程序是通過一個實例來講解怎麼使用函數指針調用函數,現在大家應該已經有一個初步的瞭解了。基本的使用也應該沒有問題了,那麼問題來了,我們爲什麼要用函數指針調用函數呢?直接使用這個函數不就行了嗎?還要多此一舉。

那麼我現在舉一個例子,大家觀察和上一個例子的區別,同時這個例子也是改寫程序10.13,大家觀察要寫這個程序有什麼好處?

【例10.16】用函數指針調用函數實現數據的逆序和輸出結果。

解題思路:我們定義兩個函數,一個是輸出函數,一個是逆序函數,然後定義一個函數指針,根據需求分別指向這兩個函數,讓函數指針實現這兩個功能。

編寫程序:

#include <stdio.h>
int main()
{
	void swap(int *arr, const int n);
	void showResult(int arr[], const int n);
	void (*p)(int *arr, const int n);
	int score[10]={1,2,3,4,5,6};
	printf("數據交換前:\n");
	p = showResult;
	p(score, 6);
	printf("\n數據交換中...\n");
	p = swap;
	p(score, 6);
	printf("\n數據交換後:\n");
	p = showResult;
	p(score, 6);
	return 0;
}
void swap(int *arr, const int n)
{
	int i=0, temp=0;
	while (i<=n/2)
	{
		temp = *(arr+i);
		*(arr+i) = *(arr+n-1-i);
		*(arr+n-1-i) = temp;
		i++;
	}
}
void showResult(int arr[], const int n)
{
	int i;
	for (i=0; i<n; ++i)
	{
		printf("第%d名學生成績:%d\n", i+1, *arr++);
	}
}

運行結果:

數據交換前:

第1名學生成績:1

第2名學生成績:2

第3名學生成績:3

第4名學生成績:4

第5名學生成績:5

第6名學生成績:6

 

數據交換中...

 

數據交換後:

第1名學生成績:6

第2名學生成績:5

第3名學生成績:3

第4名學生成績:4

第5名學生成績:2

第6名學生成績:1

Press any key to continue

 

程序分析:通過觀察我們發現這個程序中無論是輸出函數showResult還是函數swap,只需要一個函數指針p,就可以實現兩者的功能了,這樣豈不是非常方便。再比如我們寫了兩個函數,分別是求最大值的max函數和最小值的min函數,他們的參數列表一樣,我們通過輸入數字來決定求最大值還是最小值。如下:

scanf(“%d”, num);

switch( num ){

case 1: p = max; break;

case 2: p = min; break;

default: printf(“輸入信息不匹配。\n”); return -1;

}

p(a, b);//a,b爲要比較的兩個數值。

這樣我們就不用管最大值函數和最小值函數了,直接使用p就可以求出我們的所有的結果。

 

那麼我們再舉一個例子,讓大家能夠更加詳細的瞭解和使用指向函數得指針。

【例10.17】指向函數指針的再次使用。

解題思路:通過指向函數的指針實現下列功能:最大值,最小值,兩個數相加,相減,相乘,相除,數的求餘。最後返回要求的結果。

編寫程序:

#include <stdio.h>
int main()
{
	int max(int a, int b);
	int min(int a, int b);
	int add(int a, int b);
	int sub(int a, int b);
	int mult(int a, int b);
	int div(int a, int b);
	int cpl(int a, int b);//求餘
	int (*p)(int a, int b);
	int num;
	int val1,val2, result;
	printf("請輸入兩個整數: \n");
	scanf("%d%d", &val1, &val2);
	fprintf(stderr, 
		   "你將執行如下那種操作?\n"
		   "*****************\n"
		   "*   1. 最大值   *\n"
		   "*   2. 最小值   *\n"
		   "*   3. 相加     *\n"
		   "*   4. 相減     *\n"
		   "*   5. 相乘     *\n"
		   "*   6. 相除     *\n"
		   "*   7. 求餘     *\n"
		   "*****************\n"
		   ">>>");
	scanf("%d", &num);
	switch(num)
	{
	case 1: p = max; break;
	case 2: p = min; break;
	case 3: p = add; break;
	case 4: p = sub; break;
	case 5: p = mult; break;
	case 6: 
		while (0 == val2)
		{
			printf("您輸入的除數不合法,請更改除數:");
			scanf("%d", &val2);
		}
		printf("更改成功。\n");
		p = div; 
		break;
	case 7: 
		while (0 == val2)
		{
			printf("您輸入的除數不合法,請更改除數:");
			scanf("%d", &val2);
		}
		printf("更改成功。\n");
		p = cpl; 
		break;
	default:
		fprintf(stderr, "輸入的數據錯誤。\n"); return 2;
	}
	result = p(val1, val2);
	printf("您的結果是:%d\n", result);
	return 0;
}
int max(int a, int b)
{
	return a>b?a:b;
}
int min(int a, int b)
{
	return a<b?a:b;
}
int add(int a, int b)
{
	return (a+b);
}
int sub(int a, int b)
{
	return (a-b);
}
int mult(int a, int b)
{
	return (a*b);
}
int div(int a, int b)
{
	return a/b;
}
int cpl(int a, int b)//取餘
{
	return a%b;
}

第一次運行結果:

請輸入兩個整數:

12 8

你將執行如下那種操作?

*****************

*   1. 最大值   *

*   2. 最小值   *

*   3. 相加     *

*   4. 相減     *

*   5. 相乘     *

*   6. 相除     *

*   7. 求餘     *

*****************

>>>1

您的結果是:12

Press any key to continue

 

第二次運行結果:

請輸入兩個整數:

12 8

你將執行如下那種操作?

*****************

*   1. 最大值   *

*   2. 最小值   *

*   3. 相加     *

*   4. 相減     *

*   5. 相乘     *

*   6. 相除     *

*   7. 求餘     *

*****************

>>>4

您的結果是:4

Press any key to continue

 

第三次運行結果:

請輸入兩個整數:

12 0

你將執行如下那種操作?

*****************

*   1. 最大值   *

*   2. 最小值   *

*   3. 相加     *

*   4. 相減     *

*   5. 相乘     *

*   6. 相除     *

*   7. 求餘     *

*****************

>>6

您輸入的除數不合法,請更改除數:8

更改成功。

您的結果是:1

press any key to continue

 

程序分析:這個程序我們就可以很清楚地知道如何使用指向函數的指針了,並且也會明白爲什麼這樣定義。在程序中我們只需要使用p就能夠實現所有指定的功能。大家可以多做幾個程序去嘗試嘗試。

 

10.5.3 指向函數的指針作爲函數參數

上一節中我們講解了函數指針調用函數,這一節我們講解函數指針作爲一個參數傳遞給另一個函數。一個函數用函數指針作爲參數,那麼意味着這個函數的一部分工作需要通過函數指針調用另外的函數來完成,這被稱爲“回調”。

比如我們有如下定義:

int func(int a, int b, int (*p)(int , int ));

這個就是指向函數的指針作爲函數參數,函數func中有作爲參數的函數指針*p。再調用時就可以直接把函數作爲參數傳遞給func函數。

下面讓我們舉一個實際的例子來講解說明。

【例10.18】指向函數指針的作爲函數參數。

解題思路:在例10.17中,我們通過指向函數的指針實現下列功能:最大值,最小值,兩個數相加,相減,相乘,相除,數的求餘。最後返回要求的結果。此處呢我們實現相同的功能,但是使用的方法是:指向函數的指針作爲函數參數,這個例子將是例10.17的更改版。

編寫程序:

#include <stdio.h>
int main()
{
	int max(int a, int b);
	int min(int a, int b);
	int add(int a, int b);
	int sub(int a, int b);
	int mult(int a, int b);
	int div(int a, int b);
	int cpl(int a, int b);//求餘
	void func(int a, int b, int (*p)(int, int));
	int num;
	int val1,val2;
	printf("請輸入兩個整數: \n");
	scanf("%d%d", &val1, &val2);
	fprintf(stderr, 
		   "你將執行如下那種操作?\n"
		   "*****************\n"
		   "*   1. 最大值   *\n"
		   "*   2. 最小值   *\n"
		   "*   3. 相加     *\n"
		   "*   4. 相減     *\n"
		   "*   5. 相乘     *\n"
		   "*   6. 相除     *\n"
		   "*   7. 求餘     *\n"
		   "*****************\n"
		   ">>>");
	scanf("%d", &num);
	switch(num)
	{
	case 1: func(val1, val2, max); break;
	case 2: func(val1, val2, min); break;
	case 3: func(val1, val2, add); break;
	case 4: func(val1, val2, sub); break;
	case 5: func(val1, val2, mult); break;
	case 6: 
		while (0 == val2)
		{
			printf("您輸入的除數不合法,請更改除數:");
			scanf("%d", &val2);
		}
		printf("更改成功。\n");
		func(val1, val2, div); 
		break;
	case 7: 
		while (0 == val2)
		{
			printf("您輸入的除數不合法,請更改除數:");
			scanf("%d", &val2);
		}
		printf("更改成功。\n");
		func(val1, val2, cpl); 
		break;
	default:
		fprintf(stderr, "輸入的數據錯誤。\n"); return 2;
	}
	return 0;
}
int max(int a, int b)
{
	return a>b?a:b;
}
int min(int a, int b)
{
	return a<b?a:b;
}
int add(int a, int b)
{
	return (a+b);
}
int sub(int a, int b)
{
	return (a-b);
}
int mult(int a, int b)
{
	return (a*b);
}
int div(int a, int b)
{
	return a/b;
}
int cpl(int a, int b)//取餘
{
	return a%b;
}
void func(int a, int b, int (*p)(int, int))
{
	int result = (*p)(a, b);
	printf("您的結果是:%d\n", result);
}

運行結果:

第一次運行結果:

請輸入兩個整數:

12 8

你將執行如下那種操作?

*****************

*   1. 最大值   *

*   2. 最小值   *

*   3. 相加     *

*   4. 相減     *

*   5. 相乘     *

*   6. 相除     *

*   7. 求餘     *

*****************

>>>1

您的結果是:12

Press any key to continue

 

第二次運行結果:

請輸入兩個整數:

12 8

你將執行如下那種操作?

*****************

*   1. 最大值   *

*   2. 最小值   *

*   3. 相加     *

*   4. 相減     *

*   5. 相乘     *

*   6. 相除     *

*   7. 求餘     *

*****************

>>>4

您的結果是:4

Press any key to continue

 

第三次運行結果:

請輸入兩個整數:

12 0

你將執行如下那種操作?

*****************

*   1. 最大值   *

*   2. 最小值   *

*   3. 相加     *

*   4. 相減     *

*   5. 相乘     *

*   6. 相除     *

*   7. 求餘     *

*****************

>>6

您輸入的除數不合法,請更改除數:8

更改成功。

您的結果是:1

press any key to continue

 

程序分析:程序第11行爲我們新添加的一行,代替之前的指向函數的指針。而指向函數的指針我們作爲函數參數傳遞進入函數中,這樣同樣實現計算兩個數的最大值,最小值等功能。大家通過對比例10.17和例10.18就會發現之間的不同及他們各自的使用方法。

事實上,當你學習win32編程的時候,你會發現許多API函數都是用函數指針作爲參數的,特別是處理圖形用戶接口時,因爲創建顯示風格的工作可以由這些函數本身完成,但確定顯示內容的工作需要由應用程序完成。

 

10.6 通過指針實現動態內存分配

本節中我們將會講解幾個內存分配和釋放函數,內存分配函數分別是:malloc函數,calloc函數,realloc函數。內存釋放函數是:free函數。它們都需要頭文件:#include <malloc.h>或者#include <stdlib.h>。

10.6.1 什麼是內存的動態分配

內存的動態分配就是指程序執行過程中動態地分配或者回收存儲空間的分配內存的方法。動態內存分配不像數組等靜態內存分配方法那樣需要預先分配存儲空間,而是由系統根據程序的需要來隨時分配,且分配大小就是程序要求的大小。

C/C++中定義了4個內存區間:代碼區,全局變量與靜態變量區,局部變量區即棧區,動態存儲區即堆區。那麼我們動態內存分配的區間就是堆區。

 

10.6.2 malloc函數與free函數

首先malloc函數是動態申請內存空間,free函數是動態釋放內存空間。那麼這兩者的函數原型是什麼呢?如下:

void *malloc(unsigned int size);

其作用是在內存的動態存儲區中分配一個長度爲size的連續空間。形參size的類型定爲無符號類型,因爲申請的內存空間不可能爲負數。返回類型是void*類型,表示未確定類型的指針,因爲void*類型可以通過類型強制轉換爲任何其他類型的指針。返回的指針指向該分配域的開頭位置。比如:

malloc(1024);

它表示開闢1024個字節的臨時分配域,函數值爲其第1個字節的地址。當然這是成功分配內存地址的情況,當分配內存地址失敗時返回值爲NULL。假如我們要分配1024個整型存儲單元,該怎麼寫代碼呢?如下:

int *p = (int*)malloc(1024*sizeof(int));

如果要分配1024個字符型存儲單元,代碼如下:

char *p = (char*)malloc(1024*sizeof(char));

 

以上是malloc函數的基本語法和基本使用,那麼free函數如何使用了,下面是free函數的函數原型:

void free(void *p);

它的作用是釋放指針變量p所指向的動態空間,使之前申請的動態空間可能再次被其他變量使用。p指向的是malloc,calloc或者realloc函數返回值。比如我們之前通過mallo函數申請的內存空間,現在通過free函數來釋放:

int *p = (int*)malloc(1024*sizeof(int));

free(p);

或者:

char *p = (char*)malloc(1024*sizeof(char));

free(p);

 

上面是通過理論來講解這兩個函數的使用,那麼下面讓我們通過實際的例子來加深兩個函數的理解吧。

【10.19】通過malloc函數申請存儲空間並使用free函數釋放。

解題思路:我們通過malloc函數申請一個整型的內存單元,然後操作該存儲單元,最後使用完成用free函數釋放內存空間。

編寫程序:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define N 4
int main(void)
{
	int *p, i;
	p = (int*)malloc(N*sizeof(int));
	for (i=0; i<N; ++i)
	{
		*(p+i) = i+1;
	}
	
	for (i=0; i<N; ++i)
	{
		printf("*(p+%d) = %d\n", i, *(p+i));
	}
	
	free(p);
	p=NULL;
	return EXIT_SUCCESS;
}

運行結果:

*(p+0) = 1

*(p+1) = 2

*(p+2) = 3

*(p+3) = 4

Press any key to continue

 

程序分析:首先使用malloc函數和free函數我們需要頭文件#include <stdlib.h> 或者 #include <malloc.h>,但是此處兩個頭文件都寫的原因是:#include <stdlib.h> 作爲EXIT_SUCCESS的頭文件,而#include <malloc.h>作爲malloc函數和free函數的頭文件。當然此處我們可以只寫#include <stdlib.h>作爲頭文件。程序第4行,我們宏定義了N爲4,表示我們要申請整型內存空間的個數。程序第8行表示申請4個整型內存空間。程序9~12行表示對申請的內存空間賦值。程序第14~17行爲輸出程序的賦值結果。我們在程序19行釋放指針p申請的內存空間。程序20行最後讓p指向NULL是爲了程序的安全性。

 

以上程序我們申請的是N=4個整型大小的內存單元,如果我們申請的N=1那麼就是表示申請1個整型字節大小的內存單元,那麼這就類似於定義一個整型變量,但是我們需要注意雖然類似,但是卻有本質的區別,比如:他們放置的位置不一樣,局部變量放置在棧區,而動態申請的內存放置在堆區。大家需要注意。

 

10.6.3 calloc函數與free函數

上面一節我們講解了malloc函數和free函數的聲明與使用。這一節我們講解和malloc函數類似的動態內存分配函數calloc函數。之所以要加上free函數,這是因爲動態內存申請後一定要記得釋放,否則會出現內存泄漏,而釋放的函數就是free函數,他們都是成對出現的。

那麼這calloc函數的原型是什麼呢?如下:

void *calloc(unsigned int n, unsigned int size);

其作用是在內存的動態存儲區中分配n個長度爲size的連續空間。這個空間很大足以放下一個數組。其中形參n和形參size的類型定爲無符號類型,因爲申請的內存空間不可能爲負數。返回類型是void*類型,表示未確定類型的指針,因爲void*類型可以通過類型強制轉換爲任何其他類型的指針。返回的指針指向該分配域的開頭位置。這和malloc函數的作用和使用方法類似。比如:

calloc(256, 4);

它表示開闢256個4字節的臨時分配域。當calloc函數成功分配內存地址的情況下,函數返回值爲其第1個字節的地址。而當分配內存地址失敗時返回值爲NULL。下面讓我們看看如何使用calloc函數分配空間:

int *p = (int*)calloc(256, 4);

 

以上是calloc函數的基本語法和基本使用,free函數上一節已經介紹,此處不再贅述。現在讓我們看看如何讓calloc函數和free函數的統一使用吧。

int *p = (int*)calloc(256, 4);

free(p);

 

同樣讓我們舉一個例子來詳細的理解這個函數的使用吧!

【例10.20】使用calloc函數實現數據的動態存儲,並從數據中找到最大值,最後使用free函數釋放動態空間。

解題思路:我們使用calloc函數分配n個整型字節的空間大小,然後把輸入的數據存放進該動態空間中,接着找到最大的數據,最後使用free函數釋放掉申請的動態內存空間。

編寫程序:

#include <stdio.h>
#include <stdlib.h>
int main ()
{
  int i,n;
  int * pData;
  int max = -65535;
  printf ("您要申請幾個整型空間: ");
  scanf ("%d",&i);
  pData = (int*) calloc (i,sizeof(int));
  if (pData==NULL) exit (1);
  printf("callo函數默認的初始化數據爲:\n");
  for (n=0;n<i;n++)
  {
	  printf("%d%c",pData[n], (n==i-1?'\n':' '));
  }
  for (n=0;n<i;n++)
  {
    printf ("請輸入第%d個數據: ",n+1);
    scanf ("%d",&pData[n]);
	max = (max<pData[n])?pData[n]:max;
  }
  printf ("\n你已經輸入的數據: \n");
  for (n=0;n<i;n++) printf ("%d ",pData[n]);
  printf("\n其中最大的是:%d\n", max);
  free (pData);
  pData = NULL;
  return 0;
}

運行結果:

您要申請幾個整型空間: 5

callo函數默認的初始化數據爲:

0 0 0 0 0

請輸入第1個數據: 34

請輸入第2個數據: 67

請輸入第3個數據: 12

請輸入第4個數據: 89

請輸入第5個數據: 10

 

你已經輸入的數據:

34 67 12 89 10

其中最大的是:89

Press any key to continue

 

程序分析:在此處頭文件我們使用的是stdlib,在程序第10行我們申請i*sizeof(4)字節大小的動態內存空間。程序第13~16行表明我們動態申請的內存空間已經被0初始化。程序第17~22行我們對動態申請的內存空間賦值,並把最大的值用max保存。最後需要在程序第26行使用free函數釋放申請的動態內存空間。

 

10.6.4 realloc函數與free函數

上面兩節我們講解了malloc函數與free函數,calloc函數與free函數的聲明與使用。這一節我們講解另外一個動態內存分配函數realloc函數。同樣加上free函數的目的就是提醒大家動態內存申請後一定要釋放,否則會出現內存泄漏。

那麼realloc函數的原型如下:

void *realloc(void *p, unsigned int size);

其作用是先判斷當前指針是否有足夠的連續空間,如果有,擴大指針p指向內存地址,並且將指針p返回。如果空間不夠,先按照size指定的大小分配空間,將原來的數據從頭到尾拷貝到新分配的內存區域,而後釋放原來指針p所指向的內存區域(注:原來指針是自動釋放,不需要使用free),同時返回新分配的內存區域的首地址。即重新分配存儲器塊的地址。針對返回值,如果重新分配成功則返回指向該分配內存開頭位置的地址指針,否則返回空指針。申請動態內存的方式如下:

p = (int *)malloc(10*sizeof(int));

q = (int *)realloc(p , 10*sizeof(int));

...

free(q);

它表示首先使用malloc函數申請10個整型大小的內存空間,但是申請完後後悔了,因爲申請的內存空間不足,所有又使用realloc函數申請了10個整型大小的內存空間。如果p指向的內存空間足夠,那麼就會在p申請的空間之後接着開闢內存空間,但是如果p指向的內存空間不夠,那麼就會更換一塊足夠的內存空間,把p之前申請的內存空間拷貝到新的內存空間,之後把先前的動態空間刪除。如果整臺電腦的內存空間都不夠的話,那麼就會返回NULL。最後不要忘了使用free函數釋放realloc申請的內存空間。

 

上面是通過理論來講解realloc函數的使用,那麼下面讓我們通過實際的例子來加深它的理解吧。

【例10.21】函數realloc的使用。

解題思路:通過簡單的使用realloc函數實現輸入輸出,使大家瞭解realloc函數的基本使用。

編寫程序:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int i;
	int *p = (int*)malloc(5*sizeof(int));
	printf("malloc: %p\n", p);
	for (i=0; i<5; ++i)
	{
		p[i] = i;
	}
	p = (int *)realloc(p, 10*sizeof(int));
	printf("realloc: %p\n", p);
	for (i=5; i<10; ++i)
	{
		p[i] = i;
	}
	for (i=0; i<10; ++i)
	{
		printf("%3d", p[i]);
	}
	printf("\n");
	free(p);
	p = NULL;
	return 0;
}

運行結果:

malloc: 00590F08

realloc: 00590F08

  0  1  2  3  4  5  6  7  8  9

Press any key to continue

 

程序分析:程序第6行我們使用malloc函數申請了5個整型長度的內存空間。然後在程序第8~11行我們爲剛剛申請的動態內存空間賦值。賦值完成之後,我們突然想存放的數據不夠,打算再申請5個整型大小的內存空間,所以在程序第12行我們使用realloc函數在之前申請的空間的基礎上又申請5個整型大小的內存空間,就是10*sizeof(int)大小的存儲空間。新申請的內存空間要存儲數據,那麼就有了14~17行的操作。爲了查看我們輸入的數據到底如何,程序第18~21行輸出結果。最後,使用完了一定要記得釋放指針p,這就是程序第23行。

 

之前我們針對realloc函數說過:先判斷當前指針是否有足夠的連續空間,如果有,擴大指針p指向的內存地址,並且將指針p返回。如果空間不夠,先按照size指定的大小分配空間,將原來的數據從頭到尾拷貝到新分配的內存區域,而後釋放原來指針p所指向的內存區域,同時返回新分配的內存區域的首地址。即重新分配存儲器塊的地址。

那麼現在讓我們通過程序來查看這種情況吧!這樣大家就會能夠更加理解realloc函數真正的含義了。

【例10.22】使用realloc重新申請內存空間,內存空間足夠的情況。

編寫程序:

#include <stdio.h>
#include <malloc.h>
int main()
{
	char *p, *q;
	p = (char*)malloc(10);
	q = p;
	p = (char*)realloc(p, 10);
	printf("p=0x%x\n", p);
	printf("q=0x%x\n", q);
	free(p);
	p = NULL;
	q = NULL;
	return 0;
}

運行結果:

p=0x5f0f08

q=0x5f0f08

Press any key to continue

 

程序分析:這個程序我們一共分配了10個字節的內存空間,所以在內存中是非常有可能存在連續的10個字節的存儲空間,所以此次我們發現程序是在原來地址的基礎上有分配的,所以p和q的地址一樣。我們看下一個程序。

 

【例10.23】使用realloc重新申請內存空間,內存空間不足的情況。

編寫程序:

#include <stdio.h>
#include <malloc.h>
int main()
{
	char *p, *q;
	p = (char*)malloc(10);
	q = p;
	p = (char*)realloc(p, 1000);
	printf("p=0x%x\n", p);
	printf("q=0x%x\n", q);
	free(p);
	p = NULL;
	q = NULL;
	return 0;
}

運行結果:

p=0x272478

q=0x270f08

Press any key to continue


程序分析:這個程序中我們連續申請了1000個字節的內存空間,由於存在1000個連續的空閒的內存空間的可能性比較小,所以就出現了空間不夠,按照1000指定的大小分配空間,將原來的數據從頭到尾拷貝到新分配的內存區域,而後釋放原來指針p所指向的內存區域,同時返回新分配的內存區域的首地址,這就出現p和q指向的地址不一樣的原因,q指向原來的地址,p指向新地址。

 

此處需要注意的是:你們自己的電腦運行結果可能與我的不一樣,即地址可能不一樣,以及你們可連續分配的字節大小空間也是不一樣的,我的1010個字節空間不連續,那麼你們的就有可能連續,當然也有可能不連續。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章