指標與運算
變數除了它儲存的數值以外,還有記憶體位址,下面是示範程式
1 2 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
一般變數提供對記憶體空間的直接存取,指標變數則提供間接存取
指標可以指向特定的記憶體位址而不直接操作到變數
指標變數的宣告語法如下
指標變數可以透過取址運算子&來指定,下面是示範程式
1 2 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,被指向的值也會被改變
1 2 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指標只儲存位址資訊,所以不能直接提取資料,必須先轉換型態,例如:
1 2 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指標的範例程式
1 2 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)了
下面將透過交換兩個數字的函數說明傳值呼叫
1 2 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
下面將透過交換兩個數字的函數說明傳址呼叫
1 2 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
指標與陣列
其實陣列就是指標的應用,陣列名稱就是指向陣列第一個元素的指標,以下是範例程式
1 2 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
以下是索引值的範例程式
1 2 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
以下範例程式用來說明陣列跟指標的關係
1 2 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
指標與字串
字元指標可以指向一個字串常數,該字串常數會佔有一個記憶體空間,以下是示範程式
1 2 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函數歸還記憶體
以下是陣列動態配置的範例程式
1 2 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
雙重指標
雙重指標簡單講就是指向指標的指標,指標指向一般變數,雙重指標則指向指標變數
以下是範例程式
1 2 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的位址
雙重指標的使用時機通常用於傳回指標
例如想將函數裡面配置的記憶體空間取出來,以下是範例程式
1 2 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有相同功效
1 2 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的麻煩
1 2 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()
- 雙重指標
- 陣 列 與 指 標