1.
只要是學過C或C++的人都知道其中指針的重要性,我們對它又愛又恨,愛的是他很強大,恨的是他過於強大以至於有那麼多的變化,令人防不勝防。
筆者現在總結一下c指針中那些令人迷糊的概念:
2.1.0 指針初始化問題
intmain(void)
{
int*p;
*p= 0;
}
我們知道,在堆棧上分配的變量初始值是不確定的,也就是說指針p所指向的內存地址是不確定的,後面用*p訪問不確定的地址就會導致不確定的後果,如果導致段錯誤還比較容易改正,如果意外改寫了數據而導致隨後的運行中出錯,就很難找到錯誤原因了。像這種指向不確定地址的指針稱爲“野指針”(Unbound Pointer),爲避免出現野指針。
2.1.1 指針引起的棧溢出
int main(void)
{
int*p = NULL;
...
*p= 0;
...
}
(解釋見前一博客)
2.2非常好用的空指針
void func(void *pv)
{
/**pv = 'A' is illegal */
char*pchar = pv;
*pchar= 'A';
}
int main(void)
{
charc;
func(&c);
printf("%c\n",c);
...
}
注意,只能定義void *指針,而不能定義void型的變量,因爲void *指針和別的指針一樣都佔4個字節,而如果定義void型變量(也就是類型暫時不確定的變量),編譯器不知道該分配幾個字節給變量。
2.3 指針與const
這個地方相信很多初學者都存在疑惑。
const int *a;
int const *a;
這兩種寫法是一樣的,a是一個指向const int型的指針,a所指向的內存單元不可改寫,所以(*a)++是不允許的,但a可以改寫,所以a++是允許的。
int * const a;
a是一個指向int型的const指針,*a是可以改寫的,但a不允許改寫。
int const * const a;
a是一個指向const int型的const指針,因此*a和a都不允許改寫。
2.3.1 字符型指針
int main(void)
{
char*p = "abcd";
...
*p= 'A';
...
}
p指向.rodata段,不允許改寫,但編譯器不會報錯,在運行時會出現段錯誤。(上一篇博客中有提到)
2.4 指向數組的指針&&指針數組
int (*a)[10];&&int *a [10]應該如何理解呢?
我們可以認爲[]比*有更高的優先級,如果a先和*結合則表示a是一個指針,如果a先和[]結合則表示a是一個數組。
int *a[10];這個定義可以拆成兩句:
typedef int *t;
t a[10];
t代表int *類型,a則是由這種類型的元素組成的數組。int (*a)[10];這個定義也可以拆成兩句:
typedef int t[10];
t *a;
t代表由10個int組成的數組類型,a則是指向這種類型的指針。
2.4.1 數組&&指針
int a[5][10];
int (*pa)[10] = &a[0];
兩者都表示一個二維數組。
我們可以把pa當成二維數組名來使用,pa[1][2]和a[1][2]取的也是同一個元素,而且pa比a用起來更靈活,數組名不支持賦值、自增等運算,而指針可以支持,pa++使pa跳過二維數組的一行(40個字節),指向a[1]的首地址。
2.5 函數指針&&指針函數
函數指針:
函數類型(*指針變量名)(形參列表);
“函數類型”說明函數的返回類型,由於“()”的優先級高於“*”,所以指針變量名外的括號必不可少,後面的“形參列表”表示指針變量指向的函數所帶的參數列表。
double (*ptr)(double x);
在定義函數指針時請注意:函數指針和它指向的函數的參數個數和類型都應該是—致的;
函數指針的類型和函數的返回值類型也必須是一致的。
指針函數:
個函數不僅可以帶回一個整型數據的值,字符類型值和實型類型的值,還可以帶回指針類型的數據,使其指向某個地址單元。
返回指針的函數,一般定義格式爲:
類型標識符 *函數名(參數表)