指標,溫柔又危險—-小話c語言(7)

來源:互聯網
上載者:User

[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 *類型變數的資料進行直接操作。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.