[Mac-10.7.1 Lion Intel-based, gcc 4.2.1]
Q: 指標到底是什嗎?
A: 有一天,你初到合肥,要到一個地方,但是路線不熟悉。遂問了一個路人,請問烏邦托在哪裡?路人答曰:向南走即可。這個回答就像指標一樣,告訴你一個方向。但是,到底向南多少,這就像是指標類型決定的大小。
Q: 看到那個const修飾指標的時候,老是搞不清楚到底是指標是常量還是指標指向的變數是常量?
A: 其實很簡單,採用從右向左的讀法即可搞定這些。如下例子:
#include <stdio.h>int main (int argc, const char * argv[]){ int i = 100; const int *pi = &i; *pi = 200; return 0;}
儲存為const.c, 使用gcc -o const const.c編譯:
可以看到編譯錯誤,表明pi是個不可更改其指向資料的指標。按照上面的從右讀的原則即為:
const int * pi
常量 整形 指向 pi
讀為: pi指向整形常量,即pi指向什麼變數可以改變,但是指向的變數的值不可改變。
當然,使用類型方法,const int *pi和 int const *pi的含義是一致的。
如下代碼就是ok的:
#include <stdio.h>int main(){ int i = 100, j = 200; const int *pi = &i; pi = &j; return 0;}
編譯ok.
另外一種情況:
#include <stdio.h>int main(){ int i = 100, j = 200; int *const pi = &i; pi = &j; return 0;}
編譯:
可以看到,編譯錯誤表示pi是唯讀,不可以更改pi的值。再使用從右向左的讀法:
int * const pi
整形 指向 常 pi
讀為: pi常指向整形
這也意味著,pi的值不能更改,但是沒有意味著pi指向的資料不可以更改。
#include <stdio.h>int main(){ int i = 100, j = 200; int *const pi = &i; *pi = j; return 0;}
如上代碼,編譯ok.
Q: 看過很多代碼中含有函數指標,它的本質是什嗎?
A: 它的本質即為一個指標,理論上函數的地址在編譯期即可計算得到(當然在連結或者運行時還可能有重新置放)。先看一個簡單的例子:
#include <stdio.h>#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));int add(int a, int b){ return a + b;}int main(){ int (*func)(int, int) = add; PRINT_D(func(1, 2)) return 0;}
儲存為func_ptr.c, 編譯運行:
分析下代碼:
int (*func)(int, int)表示聲明一個函數指標,它的參數是2個整形,傳回值為1個整形;什麼地方能體現出是函數指標呢?就在於func前面的*號。
= add; 表示此指標指向add函數。c語言的編譯原則是函數編譯之後可以確定當前編譯狀態的地址。為了確定,我們查看下彙編代碼:
gcc -S func_ptr.c得到彙編(部分):
_add:Leh_func_begin1:pushq%rbpLtmp0:movq%rsp, %rbpLtmp1:movl%edi, -4(%rbp)movl%esi, -8(%rbp)movl-4(%rbp), %eaxmovl-8(%rbp), %ecxaddl%ecx, %eaxmovl%eax, -16(%rbp)movl-16(%rbp), %eaxmovl%eax, -12(%rbp)movl-12(%rbp), %eaxpopq%rbpretLeh_func_end1:.globl_main.align4, 0x90_main:Leh_func_begin2:pushq%rbpLtmp2:movq%rsp, %rbpLtmp3:subq$16, %rspLtmp4:leaq_add(%rip), %raxmovq%rax, -16(%rbp)movq-16(%rbp), %raxmovl$1, %ecxmovl$2, %edxmovl%ecx, %edimovl%edx, %esicallq*%rax
可以看到_add在彙編代碼中是一個標號,標號就意味著是一個地址。callq *%rax可以看到,這是調用add函數。
Q: 用add賦值給func的時候,為什麼不用&add, 不是要取函數的地址嗎?
A: 是的, 這樣也可以,不過函數本身就可以看成地址,它們效果一樣的。
#include <stdio.h>#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));#define PRINT_P(ptr) printf("%10s is %p\n", (#ptr), (ptr));typedef int (*func)(int, int);int add(int a, int b){ return a + b;}int main(){ PRINT_P(add) PRINT_P(&add) return 0;}
運行:
Q: int (*func)(int, int)這種寫法有點複雜吧。
A: 是的,避免寫很多這種代碼,可以使用typedef來定義一個函數指標。
#include <stdio.h>#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));typedef int (*func)(int, int);int add(int a, int b){ return a + b;}int main(){ func add_func = add; PRINT_D(add_func(1, 2)) return 0;}
Q: 結構體裡面不也是可以含有函數指標的嗎?這裡的函數指標的作用是什麼呢?
A: 是的,可以含有。在結構體的函數指標一般為結構體資料服務的,它的實現可以類比物件導向語言的類的功能。實際上,正因為有了指標,整個編程世界才變得很精彩,很多類比方式都是通過指標達到的。
#include <stdio.h>#include <string.h>#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));#define PRINT_S(str) printf(#str" is %s\n", (str));typedef struct Student{ int age; char name[32]; int (*get_age)(struct Student *s); void (*set_age)(struct Student *s, int new_age); char *(*get_name)(struct Student *s); void (*set_name)(struct Student *s, char *new_name);}Student;int student_get_age(struct Student *s){ return s->age;}void student_set_age(struct Student *s, int new_age){ s->age = new_age;}char *student_get_name(struct Student *s){ return s->name;}void student_set_name(struct Student *s, char *new_name){ memset(s->name, 0, sizeof(s->name)); strncpy(s->name, new_name, sizeof(s->name));}int main(){ Student s; s.get_age = student_get_age; s.set_age = student_set_age; s.get_name = student_get_name; s.set_name = student_set_name; student_set_age(&s, 25); student_set_name(&s, "xichen"); PRINT_D(student_get_age(&s)) PRINT_S(student_get_name(&s)) return 0;
編譯運行:
Q: 好像這個數組和指標的聯絡還是挺多的,下面的代碼為什麼輸出的值不對?
#include <stdio.h>#include <string.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));void print_arr_size(int arr[3]){ PRINT_D(sizeof(arr))}int main(){ int arr[3] = {1, 2, 3}; print_arr_size(arr); return 0;}
運行結果:
A: 這是因為數組形式作為參數會被退化成指標導致的,也就是說print_arr_size函數的參數int arr[3]其實等同於int *arr, 所以sizeof(arr)的值是指標的大小(筆者的平台指標大小為8)。
Q: 常常看到函數的原型中有void *, 它到底代表什嗎?
A: 比如,
void*malloc(size_t);
申請空間,返回對應的指標;但是到底是什麼類型的指標,此函數不能確定,是需要外部調用者來確定;所以,void *可以看成通用型指標,其它類型的指標均可以賦值給void *類型指標;而且,void *類型指標都可以通過強制轉換變成需要的指標;用物件導向的思想來解釋,void *是基類, char *, int *等都是子類。
char *p = (char *)malloc(32);
其它類型指標轉換成void *的例子:
#include <stdio.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));int main(){ int i = 1; void *p = &i; PRINT_D(*(int *)p) return 0;}
當然,void *只是表示一種通用指標,到底此指標是什麼類型的不確定,所以不能直接對void *類型變數的資料進行直接操作。