目錄表

國立屏東大學 資訊工程學系 程式設計(二)

13. 指標(Pointers)


指標(Pointers)往往是許多同學在C語言的學習過程中,所遇到的第一個巨大的障礙。其實,只要將觀念建構清楚,指標其實一點都不困難。本章將就指標的基本觀念與相關語法進行說明。

13.1 基本觀念

要學習指標就必須先瞭解記憶體與變數的關係。在開始之前,先讓我們回顧一下在第5章所談到的變數與記憶體位址的概念:

<note>

摘錄第5章部份內容

</note>

再回顧一下,在第6章中,我們針對變數與記憶體的關係,所進行的說明。

<note>

摘錄第6章部份內容

</note>

13.2 指標變數(Pointer Variables)

指標變數,顧名思義即為一個變數,但其所儲存的不是數值(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。

Fig. 1

若不考慮記憶體位址的細節,我們可以將figure 1表示為figure 2

Fig. 2

在圖中p是一個指向0x7ffff34fff00位址的指標,因為當初我們有宣告p的參考型態為int,所以C語言的compiler會知道,p所指向的是位於0x7ffff34fff00 - 0x7ffff34fff03的四個bytes。從抽象角度來看,由於p所儲存的值,正好是x所在的位址,因此我們更常用figure 3來加以表達這樣的一個關係。

Fig. 3

還記得我們在前面的章節中,曾提過一個變數可以使用“&“運算子來取得其所再的記憶體位址嗎?我們可以透過下面的程式碼,來將整數變數x的位址,指定給整數指標p,如此一來,就如同figure 3一樣,p成為了指向整數變數x的指標。

int x=38;
int *p;

p=&x;

13.3 記憶體位址與間接存取運算子

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; 

13.4 指標指派(Pointer Assignment)

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);

Fig. 4

Fig. 5

13.5 指標與函式

13.5.1 以指標做為函式引數

在一般的情況下,一個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);

13.5.2 以指標做為函式的傳回值

除了可以使用指標做為函式的引數,我們也可以使用指標做為函式的傳回值,請參考以下的範例:

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;
}

13.6 Call by Value與Call by Address

所謂的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);
}