指標與運算
變數除了它儲存的數值以外,還有記憶體位址,下面是示範程式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | #include <stdio.h>
 int main(void) {
 
 int var = 10;
 
 printf("var 的值:%d\n", var);
 printf("var 的記憶體位址:%X\n", &var);
 
 return 0;
 
 }
 
 | 
執行結果
var 的值:10
var 的記憶體位址:22FE4C
一般變數提供對記憶體空間的直接存取,指標變數則提供間接存取
指標可以指向特定的記憶體位址而不直接操作到變數
指標變數的宣告語法如下
指標變數可以透過取址運算子&來指定,下面是示範程式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | #include <stdio.h>
 int main(void) {
 
 int var = 10;
 int *ptr = &var;
 
 printf("var 的位址:%X\n", &var);
 printf("var 的值:%d\n", var);
 printf("ptr 指向的位址:%X\n", ptr);
 printf("ptr 指向的位址的值:%d\n", *ptr);
 
 
 return 0;
 
 }
 
 | 
執行結果
var 的位址:22FE44
var 的值:10
ptr 指向的位址:22FE44
ptr 指向的位址的值:10
當指標指向每個記憶體位址之後,如果你指定某個值給*ptr,被指向的值也會被改變
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | #include <stdio.h>
 int main(void) {
 
 int var = 100;
 int *ptr = &var ;
 
 printf("var = %d\n", var);
 printf("*ptr = %d\n", *ptr);
 
 *ptr = 200;
 
 printf("var = %d\n", var);
 printf("*ptr = %d\n", *ptr);
 
 return 0;
 
 }
 
 | 
執行結果
var = 100
*ptr = 100
var = 200
*ptr = 200
指標如果指向不明的記憶體位址是危險的,有可能造成程式爆炸,所以指定初始值是安全的作法
NULL是透過#define定義為0的常數
#define會在之後的章節提到
另外新手可能會以為以下語法是宣告兩個指標變數
而實際上要宣告兩個指標變數要這樣寫
如果只打算拿記憶體位址做運算,不會牽涉到型態問題,可以使用void指標
void *ptr;
因為void指標只儲存位址資訊,所以不能直接提取資料,必須先轉換型態,例如:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | #include <stdio.h>
 int main(void) {
 
 int num = 10;
 void *ptr = &num ;
 
 
 
 
 
 int *iptr = (int*) ptr;
 printf("%d\n", *iptr);
 
 return 0;
 
 }
 
 | 
執行結果
10
除了指定運算子、取址運算子與取值運算子,指標還可以使用+、-、++、–、+=與-=等運算子
指標的加減法與一般數值的加減法不同,指標加1代表往後一個資料型態的長度
例如int指標加1表示記憶體位址加4位元組,double指標則加8位元組
減法則是往前N個資料型態的長度,下面是double指標的範例程式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | #include <stdio.h>
 int main(void) {
 
 double num = 10;
 double *ptr = #
 
 printf("ptr 位置:%d\n", ptr);
 printf("ptr + 1:%d\n", ptr + 1);
 printf("ptr + 2:%d\n", ptr + 2);
 
 return 0;
 
 }
 
 | 
執行結果
ptr 位置:2293312
ptr + 1:2293320
ptr + 2:2293328
傳指標給函數
C語言的函數呼叫都是傳值呼叫(Call by value),也就是每次呼叫時都複製一份給函數
但是因為區域變數的關係,變數離開當前區域就失去效力了
如果想要傳參數給函數並且真正修改到變數的值的話,就必須靠傳址呼叫(Call by address)了
下面將透過交換兩個數字的函數說明傳值呼叫
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | #include <stdio.h>
 int swap(int,int);
 
 int main(void) {
 
 int num1 = 10,num2 = 20;
 
 printf("交換前:%d %d\n",num1,num2);
 swap(num1,num2);
 printf("交換後:%d %d\n",num1,num2);
 
 return 0;
 
 }
 
 int swap(int a,int b){
 int c = a;
 a = b;
 b = c;
 }
 
 | 
執行結果
交換前:10 20
交換後:10 20
下面將透過交換兩個數字的函數說明傳址呼叫
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | #include <stdio.h>
 int swap(int*,int*);
 
 int main(void) {
 
 int num1 = 10,num2 = 20;
 
 printf("交換前:%d %d\n",num1,num2);
 swap(&num1,&num2);
 printf("交換後:%d %d\n",num1,num2);
 
 return 0;
 
 }
 
 int swap(int *a,int *b){
 int c = *a;
 *a = *b;
 *b = c;
 }
 
 | 
執行結果
交換前:10 20
交換後:20 10
指標與陣列
其實陣列就是指標的應用,陣列名稱就是指向陣列第一個元素的指標,以下是範例程式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | #include <stdio.h>
 int main(void) {
 
 int arr[10] = {0};
 
 printf("arr :\t\t%X\n", arr);
 printf("&arr[0] :\t%X\n", &arr[0]);
 
 return 0;
 
 }
 
 | 
執行結果
arr :           22FE20
&arr[0] :       22FE20
陣列的索引值其實是相對於開頭的位移量,所以第一個元素是0,表示位移量是0
以下是索引值的範例程式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | #include <stdio.h>
 int main(void) {
 
 int arr[10] = {0};
 int *ptr = arr;
 int i;
 
 for(i = 0; i < 10; i++) {
 printf("&arr[%d]: %X", i ,&arr[i]);
 printf("\t\tptr + %d: %X\n", i, ptr + i);
 }
 
 return 0;
 
 }
 
 | 
執行結果
&arr[0]: 22FE10         ptr + 0: 22FE10
&arr[1]: 22FE14         ptr + 1: 22FE14
&arr[2]: 22FE18         ptr + 2: 22FE18
&arr[3]: 22FE1C         ptr + 3: 22FE1C
&arr[4]: 22FE20         ptr + 4: 22FE20
&arr[5]: 22FE24         ptr + 5: 22FE24
&arr[6]: 22FE28         ptr + 6: 22FE28
&arr[7]: 22FE2C         ptr + 7: 22FE2C
&arr[8]: 22FE30         ptr + 8: 22FE30
&arr[9]: 22FE34         ptr + 9: 22FE34
以下範例程式用來說明陣列跟指標的關係
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 
 | #include <stdio.h>
 int main(void) {
 
 int arr[5] = {10, 20, 30, 40, 50};
 int *ptr = arr;
 int i;
 
 
 for(i = 0; i < 5; i++) {
 printf("*(ptr + %d): %d\n", i , *(ptr + i));
 }
 puts("");
 
 
 for(i = 0; i < 5; i++) {
 printf("ptr[%d]: %d\n", i, ptr[i]);
 }
 puts("");
 
 
 for(i = 0; i < 5; i++) {
 printf("*(arr + %d): %d\n", i , *(arr + i));
 }
 puts("");
 
 
 for(i = 0; i < 5; i++) {
 printf("arr[%d]: %d\n", i, arr[i]);
 }
 puts("");
 
 return 0;
 
 }
 
 | 
執行結果
*(ptr + 0): 10
*(ptr + 1): 20
*(ptr + 2): 30
*(ptr + 3): 40
*(ptr + 4): 50
ptr[0]: 10
ptr[1]: 20
ptr[2]: 30
ptr[3]: 40
ptr[4]: 50
*(arr + 0): 10
*(arr + 1): 20
*(arr + 2): 30
*(arr + 3): 40
*(arr + 4): 50
arr[0]: 10
arr[1]: 20
arr[2]: 30
arr[3]: 40
arr[4]: 50
指標與字串
字元指標可以指向一個字串常數,該字串常數會佔有一個記憶體空間,以下是示範程式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | #include <stdio.h>
 int main(void) {
 
 char *str = "hell";
 char *add = 0;
 
 add = str;
 printf("%s\t%X\n", str, add);
 
 str = "world";
 add = str;
 printf("%s\t%X\n", str, add);
 
 return 0;
 
 }
 
 | 
執行結果
hell    404000
world    40400C
可以看到兩個字串不同記憶體位址
但是要注意的是,如果使用陣列的方式宣告字串,是無法使用=指定運算子的
宣告陣列使用的空間是固定的,但是指標指定的字串長度可以不固定
動態記憶體配置
到目前為止,我們使用的都是先宣告好要使用什麼變數,程式執行時就配置好記憶體
但是有時候要程式執行到一半才知道需要多少記憶體,這時候就要使用malloc函數
下面這個語法會以動態的方式配置一個int型態大小的記憶體給ptr變數
| 1
 | int *ptr = malloc(sizeof(int));
 | 
但是這語法只配置空間,不會初始化變數的值
而動態配置的記憶體在程式結束前並不會自動歸還空間,所以必須需要使用free函數歸還記憶體
以下是陣列動態配置的範例程式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 | #include <stdio.h>#include <stdlib.h> // malloc跟free都定義在這
 
 int main(void) {
 
 int size = 0;
 
 printf("請輸入陣列長度:");
 scanf("%d", &size);
 int *arr = malloc(size * sizeof(int));
 
 int i;
 
 printf("顯示元素值:\n");
 for(i = 0; i < size; i++) {
 printf("arr[%d] = %d\n", i, *(arr+i));
 }
 
 printf("指定元素值:\n");
 for(i = 0; i < size; i++) {
 printf("arr[%d] = ", i);
 scanf("%d" , arr + i);
 }
 
 printf("顯示元素值:\n");
 for(i = 0; i < size; i++) {
 printf("arr[%d] = %d\n", i, *(arr+i));
 }
 
 free(arr);
 
 return 0;
 
 }
 
 | 
執行結果
請輸入陣列長度:5
顯示元素值:
arr[0] = 7952432
arr[1] = 0
arr[2] = 7930200
arr[3] = 0
arr[4] = 0
指定元素值:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
顯示元素值:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
雙重指標
雙重指標簡單講就是指向指標的指標,指標指向一般變數,雙重指標則指向指標變數
以下是範例程式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | #include <stdio.h>
 int main(void) {
 
 int p = 10;
 int *ptr1 = &p;
 int **ptr2 = &ptr1;
 
 printf("p\t%d\n", p);
 printf("&p\t%X\n\n", &p);
 
 printf("*ptr1\t%d\n", *ptr1);
 printf("ptr1\t%X\n", ptr1);
 printf("&ptr1\t%X\n\n", &ptr1);
 
 printf("**ptr2\t%d\n", **ptr2);
 printf("*ptr2\t%X\n", *ptr2);
 printf("ptr2\t%X\n", ptr2);
 printf("&ptr2\t%X\n\n", &ptr2);
 
 return 0;
 
 }
 
 | 
執行結果
p       10
&p      22FE4C
*ptr1   10
ptr1    22FE4C
&ptr1   22FE40
**ptr2  10
*ptr2   22FE4C
ptr2    22FE40
&ptr2   22FE38
可以看得出來,ptr1指向了p的位址,而ptr2指向了ptr1的位址
雙重指標的使用時機通常用於傳回指標
例如想將函數裡面配置的記憶體空間取出來,以下是範例程式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 
 | #include <stdio.h>#include <stdlib.h>
 
 int new_int_array(int,int**);
 
 int main(void) {
 
 int* arr_ptr = NULL;
 
 if(new_int_array(5,&arr_ptr)){
 
 int i;
 for(i = 0; i < 5; i++)
 arr_ptr[i] = i+1;
 
 for(i = 0; i < 5; i++)
 printf("%d ",arr_ptr[i]);
 puts("");
 }
 
 return 0;
 
 }
 
 int new_int_array(int length, int** arr_ptr){
 
 int* pData = (int*)malloc(sizeof(int)*length);
 
 if (pData == NULL){
 
 *arr_ptr = NULL;
 return 0;
 
 }
 *arr_ptr = pData;
 return 1;
 
 }
 
 | 
執行結果
1 2 3 4 5
函數指標
程式在執行時,函數本身也在記憶體中佔有一段空間,而函數名稱則指向該記憶體空間
當呼叫函數的時候,程式就會去執行所指向的記憶體空間中的指令
指標也可以指向函數,語法如下
回傳值型態 (*指標名稱)(參數列);
函數型態由回傳值型態與參數列決定,不包括函式名稱
一個函數指標可指向具有相同型態的函式,也就是具有相同回傳值型態和參數列的函數
下面示範程式裡面,ptr指向func函數,可以看到ptr跟func有相同功效
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | #include <stdio.h>
 int func(int);
 
 int main(void) {
 int (*ptr)() = 0;
 
 ptr = func;
 
 func(5);
 ptr(5);
 
 printf("address of foo:%X\n", (int) func);
 printf("address of foo:%X\n", (int) ptr);
 
 return 0;
 
 }
 
 int func(int num) {
 
 printf("Print:%d\n",num);
 return 0;
 
 }
 
 | 
執行結果
Print:5
Print:5
address of foo:401597
address of foo:401597
函數指標的優點在於彈性
例如以下程式碼使用了函數指標,省去了switch的麻煩
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 
 | #include <stdio.h>
 void func1();
 void func2();
 void func3();
 void func4();
 void func5();
 
 int main(void) {
 
 void (*key[5])() = {0};
 
 key[0] = func1;
 key[1] = func2;
 key[2] = func3;
 key[3] = func4;
 key[4] = func5;
 
 while(1){
 char c = getch();
 if(c>='1'&&c<='5') key[c-'1']();
 }
 
 return 0;
 }
 
 void func1(){
 puts("你按了1");
 }
 void func2(){
 puts("你按了2");
 }
 void func3(){
 puts("你按了3");
 }
 void func4(){
 puts("你按了4");
 }
 void func5(){
 puts("你按了5");
 }
 
 | 
參考
- C語言
- 指標與記憶體位址
- 指標的運算
- 指標與陣列
- 指標與字串
- malloc()、free()、calloc() 與 realloc()
- 雙重指標
- 陣 列 與 指 標