Download presentation
Presentation is loading. Please wait.
1
第八章 指標 (Pointer)
2
「指標就是位址」,這句話可以說是本章最重要的基本觀念。
8-1 指標的基本語法 「指標就是位址」,這句話可以說是本章最重要的基本觀念。 指標 是C語言用來表示位址的一種語法,透過指標可以直接存取到任何的記憶體位址,我們把指標指向欲處理的資料位址,就可以方便地對那個資料進行操作。如果那個位址儲存的是一堆相關的資料,例如一個陣列資料,只須使用一個指標就可以讀取或寫入全部的資料。
3
8-1-1 指標與位址 「指標就是位址」,意思是說指標資料型態的值就是某個記憶體位址。 例如我們宣告 int a; int *p=&a;
指標與位址 「指標就是位址」,意思是說指標資料型態的值就是某個記憶體位址。 例如我們宣告 int a; int *p=&a; 表示 a 是一個整數變數的名稱,其資料型態是 int,而變數 a 是存在記憶體的某個地方,它的內容是一個"整數值",&a 就是這個地方的位址。 p 是一個整數指標變數(通常簡稱為整數指標)的名稱,其資料型態是 int *,既然是變數所以 p 也是存在記憶體的某個地方,但是它的內容是一個"記憶體位址",請注意 p 前面有個星號 *。我們宣告初值 p=&a 就是把 a 的位址,存入指標變數 p,換句話說就是 p 指向 a
4
因為 p 是指標變數,當然 &p 也表示這個變數的位址,只不過很少用到就是了。
*p 則是表示 “p所指資料的內容”,就本例而言,因為 p 指向 a,其實 *p 就是 a 的內容值。 變數名稱 (內容) 記憶體位址 a x1A20 p x1A x1A22
5
就是宣告字元變數 c 與字元指標 cp,而 cp = &c 就是把 c 的位址,存入指標變數 p,換句話說就是 "p 指向 c"。
同理 char c, *cp; cp = &c; 就是宣告字元變數 c 與字元指標 cp,而 cp = &c 就是把 c 的位址,存入指標變數 p,換句話說就是 "p 指向 c"。 c x1000 cp x1001 ‘X’ 0x1000
6
指標宣告的語法 type *name; type 是任何資料型態如 int , char, float等 name 是指標變數的名稱
7
8-1-2 取址運算&與間接運算* 指標的基本用法有兩個相關的運算符號:取址運算 & 與間接運算 * 先來看看 & 取址運算:
取址運算&與間接運算* 指標的基本用法有兩個相關的運算符號:取址運算 & 與間接運算 * 先來看看 & 取址運算: 宣告 int a; a是整數變數 &a是a的位址 這個 & 運算符號的意義,就是取得變數的位址,所以稱為"取址運算"。 其實我們用來輸入整數的 scanf("%d",&a); 就是這個 & 運算符號
8
而間接運算 * 是用取得“指標所指向的變數值”,變數資料不是直接取得而是經由指標來間接(indirect)存取。
宣告 int a, *p; p=&a; a是整數變數 &a是a的位址 p是指標 *p是指標所指的內容值
9
因為 p 指向 a,所以 *p 就是 a 的內容,如果
*p = 100; 則 a 的值就會變成 100, 而 printf("%d %d",a, *p); 就會印出 的結果。
10
【語法練習 8-1-1】 (1) 試宣告一個整數變數 x ________________________
(2) 試宣告一個整數指標 p ________________________ (3) 將 p 指向 x ________________________ (4) 印出 p 所指的內容 _______________________ (5) 印出 x 的位址 ________________________
11
指標常數與指標變數 指標常數是指直接指定的記憶體位址,例如 0xA L 是 IBM PC VGA 卡顯示區的記憶體位址。 有一個常常用到的指標常數是 NULL,稱為"虛指標",代表不指向任何資料。 通常我們用 NULL 指標來做一連串資料的結束,或是表示所指向的資料是不存在的。 指標變數是指一個變數的內容是指標,也就是儲存指向某資料的位址;那個位址存的是整數,這個指標就是整數指標;那個位址存的是字元,這個指標就是字元指標。
12
其實不管是整數指標還是字元指標,對指標的內容而言,都一樣是位址,所差異的只是存取指標所指的內容時,一次是取一個整數為單位,或是一次取一個字元。因此我們可以用型態轉換的方式,直接變換指標的型態。例如:
int a, *ap; char c, *cp; a = 258; ap = &a; cp = (char *) ap; c = *cp;
13
有時候我們會遇到不指定資料型態的指標,寫成 void
有時候我們會遇到不指定資料型態的指標,寫成 void * 的宣告型態,表示暫時不指定資料型態,由程式設計者自己用型態轉換來指定,視當時的需要再決定。通常都是用在 malloc 或 calloc 等記憶体配置函數的宣告。 指標型態的列印,最好使用 printf 的 %p 格式,這是列印出完整的“位址”。如果編譯程式不是提供 %p 的格式,也可以用 %u 的格式來印。 int num=18, *p=# printf("*p=%d p=%p &p=%p", *p, p, &p);
14
【語法練習 8-1-2】 (1) 試宣告一個字元變數 x ________________________
(2) 試宣告一個字元指標 p ________________________ (3) 將 p 指向 x ________________________ (4) 印出 p 所指的內容 _______________________ (5) 印出 p 所佔的記憶體位元組數 ________________________
15
指標的加減運算 指標的型態宣告會指定所指向的資料,一次存取的單位。整數指標一次取一個整數,而字元指標一次取一個字元為單位。因此,指標的加法和減法運算,也是每次加一個單位或減一個單位的位址。對整數指標而言,加一個單位就是增加 2 個位元組的位址。 如果 p 是整數指標 int *p,其位址是 1000, 則 p+1 就是位 1002,p+2 就是位址 1004, 而 p+i 是 p 往後的第 i 個,位址為 1000+i*sizeof(int) = 1000+i*2
16
指標的遞加運算 ++ 就是每次指向下一個單位的資料,遞減運算 -- 就是指向前一個單位的資料,通常是處理陣列型態的資料時,才會派上用場。
17
【範例 8-1-3】用指標存取陣列的元素 7 int a[5]={9,8,7,6,5}; 8 int *ip; 11 ip=a;
printf("*ip = %d\n",*ip); printf("*(ip+1) = %d\n",*(ip+1)); printf("*(ip+2) = %d\n",*(ip+2)); printf("*(ip+3) = %d\n",*(ip+3)); printf("*(ip+4) = %d\n",*(ip+4));
18
8-1-5 指標的用途 指標是C語言功能很強的語法,通常有下列的用途: 1‧傳遞函數的引數 2‧有效率地存取陣列資料 3‧直接記憶體定址
指標的用途 指標是C語言功能很強的語法,通常有下列的用途: 1‧傳遞函數的引數 2‧有效率地存取陣列資料 3‧直接記憶體定址 4‧複雜的資料型態 5‧動態記憶體配置
19
1‧傳遞函數的引數 由於函數內部無法直接存取函數外的資料,可以將資料的位址以指標型態傳入函數內,這樣就可以間接存取到函數外的資料,這就是所謂的傳址呼叫 (call by address) 的觀念。 我們可以用這種方式來傳回多個函數內部的執行結果,也可以把資料處理完後,將其位址以指標型態來傳回。
20
2‧有效率地存取陣列資料 當指標指向一個陣列時,陣列元素的存取就可用指標間接運算來進行,由於指標是直接操作記憶体位址,可用比較接近機器指令的方式來運算,所以執行的速度較快,效率較高。此外,指標也用來將陣列的起始位址傳入函數,而不用傳入整個陣列的資料,簡化函數呼叫的執行工作。
21
3‧直接記憶體定址 用指標直接指向記憶體位址,就可以直接控制 VGA 螢幕或其他硬體裝置,這是比較接近低階語言的用法。此外,也可以用指標傳遞系統呼叫 (system call) 的各項參數。
22
4‧複雜的資料型態 許多資料結構所使用的複雜資料型態,例如鏈結串列 (linked list)、樹狀結構 (tree structure) 等,就需要用指標型態才能達成。
23
5‧動態記憶體配置 當程式執行時,視需要向系統要求配置記憶體空間,來存放資料,就是所謂的動態記憶體配置 (dynamic memory allocation)。等到資料處理完後,程式釋放記憶體還給系統,下次有需求時再要求配置,這樣可以充份利用系統所剩餘的記憶體空間,讓程式可以處理更大量的資料。
24
8-2 指標與函數 指標常見的用途之一,就是傳遞函數的引數,以及傳回函數執行的結果。本節討論指標與函數間的相關應用。
25
8-2-1 傳址呼叫--用指標傳入函數 C語言程式使用函數呼叫時,會把實際引數的值複製一份傳給型式引數,例如:
傳址呼叫--用指標傳入函數 C語言程式使用函數呼叫時,會把實際引數的值複製一份傳給型式引數,例如: int sum_sq (int,int); main() { int a=3,b=4,c; c=sum_sq(a,b); printf("sum of square %d %d is %d",a,b,c); } int sum_sq (int p,int q) int r; r=p*p+q*q; return r;
26
主程式呼叫 sum_sq 函數時,實際引數 a,b 的值會分別拷貝給函數內的型式引數 p,q 。這種複製引數值的函數呼叫,稱為傳值呼叫 (call by value) 。函數內的 p 與 q 的值即使有所改變,也不會影響到函數外 a,b 的值。這是因為變數 a,b 的勢力範圍只有在主程式中,而變數 p,q 的勢力範圍只在函數中。 但是有時候,我們想在函數內直接改變函數外的資料時,這種方式就無法達成目的。解決的方法就是將變數的位址傳入函數內,這樣函數內藉由位址來間接存取這兩個變數,就可以實際改變其內容。這種將位址傳入出數的方式,就稱為傳址呼叫 ( call by address)。傳址呼叫時,將實際引數的位址帶入,而函數內用指標變數來承接。
27
【範例 8-2-1】 用傳值呼叫無法將兩變數的值互換
【範例 8-2-1】 用傳值呼叫無法將兩變數的值互換 4 void swap (int,int); 6 main() 7 { int a=100,b=99; printf("before swap(): a=%d b=%d\n",a,b); swap(a,b); printf("after swap(): a=%d b=%d\n",a,b); 13 } 15 void swap (int p, int q) 16 { int t; printf("before exchange: p=%d q=%d\n",p,q); t=p; p=q; q=t; printf("after exchange: p=%d q=%d\n",p,q); 24 }
28
【範例 8-2-2】 用傳址呼叫來將兩個變數的值互換
【範例 8-2-2】 用傳址呼叫來將兩個變數的值互換 4 void swap (int *,int *); 6 main() 7 { int a=100,b=99; printf("before swap(): a=%d b=%d\n",a,b); swap(&a,&b); printf("after swap(): a=%d b=%d\n",a,b); 13 } 15 void swap (int *p, int *q) 16 { int t; printf("before exchange: *p=%d *q=%d\n",*p,*q); t = *p; *p=*q; *q=t; printf("after exchange: *p=%d *q=%d\n",*p,*q); 24 }
29
8-2-2 用指標傳回多個傳回值 一個函數只能用 return 來傳回一個傳回值。如果函數內運算的結果,得到一個以上的數值,就必須另覓他法。
用指標傳回多個傳回值 一個函數只能用 return 來傳回一個傳回值。如果函數內運算的結果,得到一個以上的數值,就必須另覓他法。 一種方法是採用全域變數,定義在所有函數內的外面。函數內的所有運算結果,可以直接存入全域變數,欲傳入出數內的引數值,也可以先存入全域變數,然後在函數內直接存取。
30
【範例 8-2-3】用全域變數來傳回多個運算結果
用全域變數來傳回多個運算結果--商與餘數 4 int quo, rem; /* global variables */ 6 main() 7 { void divide2(int,int); int a=7,b=3; divide2(a,b); printf("quotient of %d divided by %d is %d\n",a,b,quo); printf("remainder of %d divided by %d is %d\n",a,b,rem); 14 } 16 void divide2 (int p, int q) 17 { quo = p/q; rem = p%q; 20 }
31
用全域變數來傳函數的傳回值,雖然很方便,但是要付出一些代價。其一是全域變數是所有函數都存取得到,非屬本函數專用,可能會被其他函數所更動,導致錯誤的結果。其二是程式如果使用太多全域變數,管理起來較費事,而且函數內部有函數外部的資料,會破壞函數的獨立性。此外,全域變數所佔用的記憶體空間無法釋放,不像函數的引數是在堆疊中,呼叫時才佔用記憶體,函數返回時,就會釋放。 用指標來傳回多個傳回值,也是一種可行的方法。這樣讓傳遞資料的呼叫者,與函數內部的執行結果,直接對映,不會受到其他程式片段或函數的影響。
32
【範例 8-2-4】 用指標來傳回多個運算結果 用指標來傳回多個運算結果--商與餘數 4 main() 5 {
5 { void divide2(int,int,int *,int *); int a=7,b=3,quo,rem; 8 divide2(a,b,&quo,&rem); printf("quotient of %d divided by %d is %d\n",a,b,quo); printf("remainder of %d divided by %d is %d\n",a,b,rem); 12 } 14 void divide2 (int p, int q, int *qval, int *rval) 15 { *qval = p/q; *rval = p%q; 18 }
33
指標型態的傳回值 有時候函數運算的結果是一項位址,例如一個字串的位址,一個陣列的起始位址等,這時就必須使用指標型態的傳回值。
34
【範例 8-2-5】 設計一個函數輸入一個字串,傳回字串中第一個空白符號 ( ‘ ’、‘\t 或 ‘\n’ ) 出現的位址,若找不到則傳回虛指標 NULL。字串是以 ‘\0’ 字元做為結束。 5 char *get_white (char *); 7 main() 8 { char msg1[20]="Merry Xmas"; char msg2[20]="line1\nline2\n"; char msg3[20]="ChenHongShan"; char *s1,*s2,*s3; s1=get_white(msg1); printf("msg1 : %s s1 : [%s]\n",msg1,s1); s2=get_white(msg2); printf("msg2 : %s s2 : [%s]\n",msg2,s2); s3=get_white(msg3); printf("msg3 : %s s3 : [%s]\n",msg3,s3); 20 }
35
22 char *get_white (char *s)
23 { while (*s != '\0') { 25 if (*s == ' ' || *s == '\t' || *s == '\n') return s; 27 s++; }; return NULL; 30 }
36
執行結果: msg1 : Merry Xmas s1 : [ Xmas] msg2 : line1 line2 s2 : [ ] msg3 : ChenHongShan s3 : [(null)]
37
8-3 指標與陣列 指標與陣列的使用,關係相當密切。陣列是一連串相同型態資料的集合,配置在連續的記憶體位址,用索引值來存取某個特定的元素。例如 int a[6],i; for (i=0;i<6;i++) a[i]=0; 將 a 整數陣列的 6 個元素都填成 0,其中 a[i] 表示 a 的第 i 個元素。若 a 的起始位址是 1000,則 &a[i] 是 a 的第 i 個元素的位址,就是 1000+i*sizeof(int),可以依此算出。
38
如果我們將指標指向一個陣列,那麼就可用指標的加減運算來存取陣列的某特定元素。特別是對陣列元素進行連續的存取時,例如列印陣列內容,存入陣列資料等,執行速度較快,因為指標是以記憶體位址來直接存取資料,所以效率較高。這就是為什麼會使用指標來存取陣列資料的原因。上面的例子可改用指標來達成: int a[6],i,*p; p=a; for (i=0;i<6;i++) *(p+i) = 0; 請注意 a 是陣列名稱,可代表陣列的起始位址,所以 p=a; 和 p=&a[0]; 是相同的效果,都是將p指向a陣列的開頭。而 p+i 是 p 加 i 個單位的位址,*(p+i) 就是那個元素的內容,所以 *(p+i) 就相當於 a[i],我們也可以直接寫成 p[i]。
40
其實上述範例用指標來做,最有效率的寫法如下:
int a[b],i,*p; p=a; for (i=0;i<b;i++) *p++ = 0; 注意 *p++ 的作用是先將 p 所指的元素設定為 0 後,再將 p 指標的內容遞增一個單位,使 p 指向下一個元素。
41
指向陣列的指標 當一個指標變數指向一個陣列時,概念上就可以把指標當成是陣列來用,不管這個陣列型態是整數、字元或是其它更複雜的資料型態。例如: int score[10],*p=score; 則我們可以用 p[i] 或 *(p+i) 來當成 score[i],其實也可以用 *(score+i) 來表示,只不過這種寫法沒有什麼意義,因為 score 本來就是一個陣列。
42
如果指標指向一個字串,通常會用如下宣告:
char *p="Help"; 其實就是 p 指標指向一個以 '\0' 字元為結束的字元陣列,p[i] 或 *(p+i) 都是表示字串的第 i 個元素,列印整個字串可以用 printf("%s",p); 來表示,詳細的字串用法將在第九章完整地介紹。
43
【語法練習 8-3-1】 (1) 試宣告一個整數陣列 x ________________________
(2) 試宣告一個整數指標 p ________________________ (3) 將 p 指向 x 陣列 ________________________ (4) 印出 p 所指的第 0 個元素的內容 _______________________ (5) 用for 迴圈印出 p 所指的所有元素的內容 ________________________________________
44
【語法練習 8-3-2】 (1) 試宣告一個字元陣列 x ________________________
(2) 試宣告一個字元指標 p ________________________ (3) 將 p 指向 x 陣列 ________________________ (4) 印出 p 所指的第 3 個元素的內容 _______________________ (5) 用for 迴圈印出 p 所指的所有元素的內容 ________________________________________
45
【範例 8-3-1】 列印指標所指向的陣列 4 #define N 6 6 main() 7 {
7 { int a[N]={1,3,5,7,9,11}; int i, *p; 10 p=a; for (i=0; i<N; i++) printf("i=%d: %d, %d\n",i,a[i],*p++); 14 }
46
用指標傳陣列資料入函數 需要將一個陣列傳入函數時,不必將整個陣列的所有元素全部傳入,只須將陣列名稱(也就是陣列的起始位址)當成指標傳入函數即可,函數內部可藉由指標,直接存取陣列的各個元素。
47
【範例 8-3-2】使用指標來印出傳入函數的陣列資料
4 void print_array (int *,int); 6 main() 7 { int a[ ]={1,3,5,7,9,11}; int n=sizeof(a)/sizeof(int); print_array(a,n); 12 } 13 void print_array (int *p, int cnt) 14 { int i; for (i=0; i<cnt; i++) printf("%dth element : %d\n",i,*p++); 19 }
48
【範例 8-3-4】 設計一函數,傳入一維整數陣列,及其元素個數,用指標方式,求陣列元素的最大值,並設計一程式來加以驗証。
4 int get_max (int *,int); 5 6 main() 7 { int a[]={1,-3,5,-7,9,-11}; int n=sizeof(a)/sizeof(int); 10 printf("max value = %d\n",get_max(a,n)); 12 }
49
13 int get_max (int *p, int cnt)
14 { int max,i; 16 max=p[0]; for (i=1; i<cnt; i++) if (p[i] > max) max=p[i]; return max; 21 }
50
【範例 8-3-3】 設計一函數,傳入一維整數陣列,及其元素個數,用指標方式來求陣列的總和,並設計一程式來加以驗証。
4 int sum_array (int *,int); 5 6 main() 7 { int a[]={1,3,5,7,9,11}; int n=sizeof(a)/sizeof(int); 10 printf("total = %d\n",sum_array(a,n)); 12 }
51
13 int sum_array (int *p, int cnt)
14 { int i,sum; 16 sum=0; for (i=0; i<cnt; i++) sum += *p++; return sum; 21 }
52
用指標傳回陣列資料 如果函數的輸出是一個陣列,可以直接傳回指向陣列開頭的指標。不過要注意陣列的記憶體空間是否在函數外事先配置好,否則應指定陣列為 static 方可傳回,因為 static 宣告會將陣列配置在靜態變數區,資料才能保存。如果用自動變數,函數一結束,陣列空間就被全部回收了!
53
【範例 8-3-6】 設計一函數 get_str 由鍵盤輸入一連串字元,直到 Enter 鍵為止,將其變成字串後,傳回指向字串的指標。
3 char *get_str(); 4 5 main() 6 { char *msg; 8 msg=get_str(); printf("%s",msg); 11 }
54
13 char *get_str() 14 { static char str[80]; /* allocate static data */ int i=0; char *p=str; 18 *p=getchar(); while (*p != '\n') { p++; *p=getchar(); } *p='\0'; /* make string */ return str; 26 }
Similar presentations