國立屏東大學 資訊工程學系 程式設計(二)
指標(Pointers)往往是許多同學在C語言的學習過程中,所遇到的第一個巨大的障礙。其實,只要將觀念建構清楚,指標其實一點都不困難。本章將就指標的基本觀念與相關語法進行說明。
要學習指標就必須先瞭解記憶體與變數的關係。在開始之前,先讓我們回顧一下在第5章所談到的變數與記憶體位址的概念:
<note>
</note>
再回顧一下,在第6章中,我們針對變數與記憶體的關係,所進行的說明。
<note>
</note>
指標變數,顧名思義即為一個變數,但其所儲存的不是數值(value)而是某個記憶體的位址(memory address),或者說其所儲存的值為某個記憶體的位址。其宣告方法等同一般的變數宣告,但在變數名稱前,須加入一個“*”號。
int *p; int * p; int* p;
以上這三個宣告的結果都相同,都會建立一個“應該會”指向某一個儲存整數的記憶體位址。C語言也允許我們混合一般的變數宣告與指標變數宣告,請參考下面的這些宣告:
int *x, y; // x是指標變數, y是一般變數 int i, *j; // i是一般變數, j是指標變數 int* i,j, k; // i是指標變數, j與k則是一般變數
在前述的例子中,指標變數都被宣告為“應該會”指向儲存整數(int)的記憶體位址,所以上述的宣告可視為宣告了一些“整數的指標變數”。一般而言,我們常將指標變數的型態稱為參考型態(referenced type),C語言允許我們將指標宣告為各種型態,換言之,C語言在指標的參考型態上並無限制,例如下面這些都是可行的宣告:
double *p; char *p; float *p;
請先執行下列程式,以確認你的系統上的記憶體使用情形:
int main() { printf("The size of an int is %d.\n", sizeof(int)); printf("The size of an int-pointer is %d.\n", sizeof(int *)); return 0; }
假設我們所得到的執行結果分別為4與8,由於其單位為byte,表示一個int的整數佔用32位元的記憶體空間,而一個整數指標佔用64位元。
<note tip> 以64位元進行定址的系統,其可使用的記憶體空間最大支援16EB (註1000GB = 1TB, 1000TB = 1PB, 1000PB = 1EB) (EB = Exabyte, 唸做艾克薩 - 拜) </note>
圖figure 1顯示了一個int整數x與整數指標p(它是由 int *p加以宣告),假設整數變數x是儲存於記憶體位址中的0x7ffff34fff00 - 0x7ffff34fff03 (32位元的整數佔四個bytes),其值為38,且假設整數指標p儲存於0x7ffff34fff68-0x7ffff34fff6f (8 bytes=64 bits),其值為0x7ffff34fff00。
若不考慮記憶體位址的細節,我們可以將figure 1表示為figure 2。
在圖中p是一個指向0x7ffff34fff00位址的指標,因為當初我們有宣告p的參考型態為int,所以C語言的compiler會知道,p所指向的是位於0x7ffff34fff00 - 0x7ffff34fff03的四個bytes。從抽象角度來看,由於p所儲存的值,正好是x所在的位址,因此我們更常用figure 3來加以表達這樣的一個關係。
還記得我們在前面的章節中,曾提過一個變數可以使用“&“運算子來取得其所再的記憶體位址嗎?我們可以透過下面的程式碼,來將整數變數x的位址,指定給整數指標p,如此一來,就如同figure 3一樣,p成為了指向整數變數x的指標。
int x=38; int *p; p=&x;
C語言提供”&“運算子,來取得變數所在的記憶體位址,以及”*”來間接存取指標變數所指向的記憶體位址中的值。例如,
int x=38; int *p; printf("x is located at %p.\n", &x);
其結果會輸出x所在的記憶體位址,且下面的程式碼,會讓p指向x所在的位址,並且將其值輸出。
p=&x; // 使p指向x所在的位址 printf("The value of *p is %d.\n", *p);
當我們以 p = &x; 將x的所在位址指派給p時,p就好比是x的別名一樣,我們可以在程式碼中使用x或是透過p來存取同一個記憶體位址的值。思考下面的程式碼片段,其執行結果為何?
int x, *p; p = &x; x = 6; printf("%d %d\n", x, *p); *p = 8; printf("%d %d\n", x, *p);
再思考下列的程式碼:
int x,y=5; x = *&y; y = &x; x = *y;
C語言允許兩個相同參考型態的指標,彼此間進行值的指派,當然其值所代表的是記憶體位址。考慮下面的程式碼,其執行結果為何?
int x=5, y=8, *p, *q; p = &x; q = &y; printf("*p=%d *q=%d\n", *p, *q);
再考慮以下的程式,其執行結果又為何?
int x=5, y=8, *p, *q; p = &x; q = &y; printf("x=%d y=%d *p=%d *q=%d\n", x, y, *p, *q); *p = *q; printf("x=%d y=%d *p=%d *q=%d\n", x, y, *p, *q);
再考慮以下的程式,其執行結果又為何?
int x=5, y=8, *p, *q; p = &x; q = &y; // 請參考Fig. 4 printf("x=%d y=%d *p=%d *q=%d\n", x, y, *p, *q); p = q; // 請參考Fig. 5 printf("x=%d y=%d *p=%d *q=%d\n", x, y, *p, *q);
在一般的情況下,一個C語言的函式(function)可以接受多個引數(arguments),並經計算後傳回一個單一的值,但若要讓函式傳回多個數值,就無法做到了。假設我們需要設計一個函式,接受一個double數值做為引數,並將其整數與小數部份分別傳回,則可以考慮以下的做法:
void decompose(double val, long *int_part, double *frac_part) { *int_part = (long) val; *frac_part = val - ((long)val); }
這個函式decompose並沒有任何的傳回值,可是它接受了兩個指標做為其引數,並在其計算過程中,透過*間接存取運算子進行相關的計算,所以其計算的結果過直接存放在這兩個指標所指向的記憶體位址內。如此一來,就可以做到讓一個函式可以傳回(事實上並沒有傳回的動作了)一個以上的數值。
要注意的是,函式的原型可以使用下列兩者之一進行宣告:
void decompose(double val, long *int_part, double *frac_part); void decompose(double, long *, double *);
下面的程式碼展示了呼叫decompose的方法:
double d = 3.1415; int i; double f; decompose(d, &i, &f);
除了可以使用指標做為函式的引數,我們也可以使用指標做為函式的傳回值,請參考以下的範例:
int *max(int *a, int *b) { if( *a > *b) return a; else return b; }
在呼叫時,要注意必須要以一個整數的指標來接收函式的傳回值:
int x, y; int *p; p = max( &x, &y);
或者,以下列的方法取回函式執行後傳回的指標,並透過間接存取,直接存取該指標所指向的位址裡的值。請參考下面的程式:
#include <stdio.h> int *max(int *a, int *b) { if(*a > *b) return a; else return b; } int main() { int x=5, y=10; int *p; p = max(&x, &y); printf("The maximum value is %d.\n", *p); *max(&x, &y)=100; printf("The values of x and y are %d and %d.\n", x, y); return 0; }
所謂的Call by Value是指當呼叫函式時,所傳入的是數值;同理,若傳入的是記憶體位址就叫Call by Address。下面兩個程式分別就Call by Value與Call by Address做一示範:
void swap(int x, int y) { int temp=x; x=y; y=temp; } int main() { int a=5, b=10; swap(a,b); printf("a=%d b=%d\n", a, b); }
void swap(int *x, int *y) { int temp=*x; *x=*y; *y=temp; } int main() { int a=5, b=10; swap(&a,&b); printf("a=%d b=%d\n", a, b); }