前言
我對C指針的理解一直停留在:指針本身是一塊內存,它保存了一塊內存的地址,可以引用,但是最近在讀代碼的時候,各種指針的聲明搞得我異常苦惱,趕緊去學習了一番,也只是明白了最基本的使用,總結如下。
基本知識
指針的基本使用正如前言中說的,先看這樣一段代碼:
[cpp]
#include <stdio.h>
int main()
{
int i = 10;
int * p;
printf("p的地址:%d\n",&p);
printf("未初始化時p的內容:%d\n",p);
// printf("未初始化訪問p指向的內存:%d\n",*p); // 這行代碼訪問了個野指針,必然發生段錯誤
p = &i;
printf("--初始化p完畢--\n");
printf("p裡面保存的地址:%d\n",p);
printf("p指向的內存的內容:%d\n",*p);
printf("p的大小:%d\n",sizeof(p));
printf("p指向的內存大小:%d\n",sizeof(*p));
return 0;
}
#include <stdio.h>
int main()
{
int i = 10;
int * p;
printf("p的地址:%d\n",&p);
printf("未初始化時p的內容:%d\n",p);
// printf("未初始化訪問p指向的內存:%d\n",*p); // 這行代碼訪問了個野指針,必然發生段錯誤
p = &i;
printf("--初始化p完畢--\n");
printf("p裡面保存的地址:%d\n",p);
printf("p指向的內存的內容:%d\n",*p);
printf("p的大小:%d\n",sizeof(p));
printf("p指向的內存大小:%d\n",sizeof(*p));
return 0;
}
輸出結果為:[plain] view plaincopyprint?p的地址:1439276008
未初始化時p的內容:0
--初始化p完畢--
p裡面保存的地址:1439276020
p指向的內存的內容:10
p的大小:8
p指向的內存大小:4
p的地址:1439276008
未初始化時p的內容:0
--初始化p完畢--
p裡面保存的地址:1439276020
p指向的內存的內容:10
p的大小:8
p指向的內存大小:4
這就是指針的基本使用,可用下圖來說明:
指針與數組
首先看這兩個聲明語句:
[cpp]
char (*a) [100];
char* a [100];
char (*a) [100];
char* a [100];
第一個是聲明了一個指向有100個char元素的數組的指針(注意和指向數組首地址的char型指針分開);第二個是聲明了一個有100個char*元素的數組,數組裡面裝的是char *。
為了理解,我們來看這樣一段代碼:
[cpp]
#include <stdio.h>
int main()
{
int arr[10][100];
printf("sizeof(arr[0]) = %lu\n", sizeof(arr[0]));
printf("sizeof(arr[0][0]) = %lu\n", sizeof(arr[0][0]));
int *p;
int (*q)[100];
p = &arr[0][0];
q = &arr[0];
printf("p = %d\n",p);
printf("q = %d\n",q);
printf("sizeof((*p)) = %lu\n", sizeof((*p)));
printf("sizeof((*q)) = %lu\n", sizeof((*q)));
p++;
q++;
printf("after add 1, p = %d\n", p);
printf("after add 1, q = %d\n", q);
return 0;
}
#include <stdio.h>
int main()
{
int arr[10][100];
printf("sizeof(arr[0]) = %lu\n", sizeof(arr[0]));
printf("sizeof(arr[0][0]) = %lu\n", sizeof(arr[0][0]));
int *p;
int (*q)[100];
p = &arr[0][0];
q = &arr[0];
printf("p = %d\n",p);
printf("q = %d\n",q);
printf("sizeof((*p)) = %lu\n", sizeof((*p)));
printf("sizeof((*q)) = %lu\n", sizeof((*q)));
p++;
q++;
printf("after add 1, p = %d\n", p);
printf("after add 1, q = %d\n", q);
return 0;
}
這端代碼運行後結果如下:
[plain]
sizeof(arr[0]) = 400
sizeof(arr[0][0]) = 4
p = 1411443800
q = 1411443800
sizeof((*p)) = 4
sizeof((*q)) = 400
after add 1, p = 1411443804
after add 1, q = 1411444200
sizeof(arr[0]) = 400
sizeof(arr[0][0]) = 4
p = 1411443800
q = 1411443800
sizeof((*p)) = 4
sizeof((*q)) = 400
after add 1, p = 1411443804
after add 1, q = 1411444200
因為內存是線性的,C中所謂的二維數組不過是數組的數組,arr這個數組有10個元素,每個元素是一個長度為100的數組,在程序員的腦子裡面,arr是一個有10行100列的二維數組。
代碼裡的p是一個指向int型的指針,q是一個指向“有100個int的int數組”的指針。所以p和q的初始化方式是不同的,但是開始的時候他們都指向了arr這個數組的數組的首地址(初始時是相等的),但是到後面分別執行自增操作之後,因為它們的類型不同,因此根據指針自增運算的含義,他們移動的步長也不相同,p移動了sizeof(int)個字節,而q移動了sizeof(int[100])個字節,於是它們的值也大不相同,可以用下圖來說明:
另外要注意的就是字符二維數組的聲明:
[cpp]
include <stdio.h>
int main()
{
char* str[2] = {"liushuai","kobe"};
printf("%s %s\n",str[0],str[1]);
return 0;
}
#include <stdio.h>
int main()
{
char* str[2] = {"liushuai","kobe"};
printf("%s %s\n",str[0],str[1]);
return 0;
}
輸出結果顯然:
[plain]
liushuai kobe
liushuai kobe
以上是合法的字符二維數組的聲明,str是一個有兩個元素的數組,每個元素的類型是一個char*,結合上面所講的,應該不難理解。
返回指針的函數和函數指針
來看下面兩個聲明語句:
[cpp]
int* foo(int i);
int* foo(int i);
這個應該比較好理解,類比著裝有指針的數組的聲明char* a[100],這是個函數聲明,聲明了一個名字為foo的函數,這個函數接受一個類型為int的參數,返回一個指向int型的指針。
再看下面的聲明:
[cpp]
void (*bar)();
void (*bar)();
類比著數組的聲明,這個語句聲明了一個指向函數的指針bar,它指向的函數要求返回值為void,且不接受任何參數。這是一個比較簡單的函數的函數指針的聲明。
函數既然可以返回一個指針,那麼一個函數能不能返回一個指向函數的指針呢?答案是肯定的,看,指針是多麼靈活。剛剛接觸可能會有點不適應,我們來看一個例子:
[cpp]
int (*foo(int)) (double*,char);
int (*foo(int)) (double*,char);
類比著上面的講解,我們知道,這個語句聲明了一個函數foo,它接受一個int類型的參數,返回一個指向函數的指針,要求指向的函數具有這樣的形式:接受一個double類型的指針和char型的變量作為參數,返回一個int類型的值。
我們可以用C中的typedef簡化這個聲明:
[cpp]
typedef int (*ptf) (double*, char);
ptf foo(int );
typedef int (*ptf) (double*, char);
ptf foo(int );
注意:typedef和#define是不同的,typedef是給“這樣”的指針起了一個別名ptf,而不是簡單的進行宏替換。
好吧,我們接著來個更變態的,如果一個函數的參數和返回值都是函數指針,那麼聲明就會更復雜,例如:
[cpp] view plaincopyprint?void (*signal (int sig, void (*func) (int siga)) ) ( int siga );
void (*signal (int sig, void (*func) (int siga)) ) ( int siga );
其實慢點分析也不難,我們可以用typedef來簡化:
[cpp]
typedef void (*p_sig) (int);
p_sig signal(int sig, p_sig func);
typedef void (*p_sig) (int);
p_sig signal(int sig, p_sig func);
signal這個函數的參數func是一個函數指針,返回了一個函數指針,且兩種指針要求指向的函數具有同一種形式(接受一個int型的參數,返回空值)。
通過函數指針調用函數
還是通過一個例子來說明問題:
[cpp]
#include <stdio.h>
void printMyName();
int main()
{
void (*f)();
f = printMyName;
f();
f = &printMyName;
f();
return 0;
}
void printMyName()
{
printf("liushuaikobe\n");
}
#include <stdio.h>
void printMyName();
int main()
{
void (*f)();
f = printMyName;
f();
f = &printMyName;
f();
return 0;
}
void printMyName()
{
printf("liushuaikobe\n");
}
是不是很容易呢。注意用“&函數名”和“函數名”初始化一個函數指針都是合法的,因為C中函數名會被轉換為指向這個函數的指針。
指針真是充滿智慧的產物,通過函數指針,可以輕松實現面向對象語言中多態等一些高級特性(例如Java的接口,C++的虛函數),真的太美妙了。
對於大神,這些東西可能都是小兒科,但是本人C真的沒怎麼用過,搞懂了這些,我也很高興了。
最後送大家一句話:
不要因為走得太遠,就忘了自己當初為什麼出發。