<!--指針初步-->
聲明變量的實質是開闢內存空間,聲明變量的時候都須要爲其分配內存。數組
計算機中,每字節的內存都由惟一一個16進制數做爲標記,這個標記特定內存的數就叫作地址。函數
可是地址顯然和整數不一樣:把它看成「座標」去理解更爲貼切,而不是「數」。
spa
地址和指針的關係就比如常量和變量的關係。指針
所以在認識指針的做用以前,須要明白指針的本質是一種變量類型,即指針變量。code
正如int型變量用來存儲整數同樣,指針變量存放的內容是「其餘變量的地址」。索引
顯然變量一旦被聲明,它的地址就肯定了,所以「變量的地址」是一種常量。
int* p;
這個語句就聲明瞭一個指針變量,變量名爲p,它的類型是int*,即指向int的指針類型。
這個變量一樣能夠這麼申請:
int *p;
沒有任何區別,可是要注意變量的標識符爲p而不是*p,變量名稱只能由字母數字下劃線組成。
* 的做用有兩個:一個是在聲明指針變量時指明該變量是指針類型;另外一個是做爲一元操做符,對指針變量進行「取值」操做,得到其指向的變量本體。
int a;
p = &a;
&是取地址符,爲一元操做符,其後接某個變量,&a總體表示變量a的地址。內存
地址是常量,這個地址被存儲在指針變量p中。
double b;
p = &b; // WRONG
因爲p被聲明爲int*,不要將其餘類型變量的地址賦值給它,即便使用強制轉換也是沒有意義的。
整個過程
int* p;
int a;
p = &a; // 也能夠用一個指令寫出 int a, *p = &a; 效果徹底相同
這麼作以後,咱們便說:指針p指向變量a,*p就是a自己,給*p賦值就徹底等同於給a賦值。
*p = 5;
printf("%d\n", a);
結果會顯示a爲5。
<!--指針處理數組-->
指針的好處之一就是爲咱們利用函數處理整個數組提供了方便。
int s[] = {2, 1, 8, 7, 6, 2};
這是一個含有6個元素的數組,咱們以前只知道s[0]~s[5]每一個元素均可看成一個int變量去使用,從沒考慮過標識符 s 自己有什麼意義。
事實上,s就是數組第一個元素的地址,即s == &s[0]。
其實地址之間能夠進行一些運算,好比減法。可是大多狀況下,你將兩個不一樣變量的地址相減是無心義的,由於咱們並不知道那兩個變量被分配在了內存中的哪一個部分。
而數組的各個元素的內存必定是連續的,對於上述數組s,若是&s[0] == 0x10293243,那麼 &s[1] == 0x10293247。
s[0]是一個int變量,它佔用地址0x1029324三、0x1029324四、0x1029324五、0x10293246 。io
int型變量佔用4字節的內存,每一個字節都有惟一對應的地址,但用&對某個多字節變量取址,它只會返回「頭地址」。class
如今就能夠理解爲何&s[0] == 0x10293243,而&s[1] == 0x10293247了。變量
但有趣的是,當咱們用&s[1] - &s[0]時,獲得的倒是1而不是4,即數組元素的地址差,是元素之間的距離,而不是差了多少字節。
這樣,咱們便獲得了&s[1] == (&s[0] + 1) == (s + 1),同理 &s[i] == s + i。
或許你認爲&s[0] == 0x10293243,&s[1] == 0x10293247,而(&s[1] - &s[0] == 1)是荒謬的,可是請記住以前說的,地址是一種特殊的數據類型而不是整數,所以他們之間的運算必然符合新的規則。
咱們如今寫一個將數組中全部元素都變成相反數的程序,你們能夠編寫驗證:
#include <stdio.h>
void k(int* p, int n);
int main()
{
int s[] = {2, 1, 8, 7, 6, 2};
int len = sizeof(s) / sizeof(int);
int i;
k(s, len);
for(i = 0; i < len; i++)
printf("%d ", s[i]);
return 0;
}
void k(int* p, int n)
{
int i;
for(i = 0; i < n; i++)
*(p+i) = -*(p+i);
}
函數k也能夠這樣寫
void k(int p[], int n)
{
int i;
for(i = 0; i < n; i++)
p[i] = -p[i];
}
可是須要注意只有在處理數組時才能夠這麼作。
<!--二重指針-->
正如你所想的,因爲指針也是一個變量,因此它也有地址。
int* p;
那麼 &p即是指針變量p的地址。
可是咱們要把&p賦值給誰?
int* k;
k = &p; //WRONG
顯然不行,由於k只接受int型變量的地址,而p倒是(int*)型變量的地址。
正確的作法是這樣:
int* *k = &p;
這裏咱們將(int*)這個總體看成一個類型來看,即可以清楚理解,k是指向這個類型的指針。
也能夠寫做這樣:
int a; // a爲int型變量
int* p = &a; // p爲指向int型變量的指針
int** k = &p; // k是指向int型變量的指針 的指針 也叫作 int型二重指針
若是你以爲第三行難以理解,不妨採用上面的方法:
(int*) *k = &p;
將int*看做一個獨立的類型,k即是指向這個類型的指針。
那麼按照以前的邏輯,*p是a自己,*k就是p自己。
若是咱們再將*k看做總體,那麼*(*k)即是a自己,即(*(*k)) == a。
你能夠省略那兩層括號,**k == a也沒錯。
你不用擔憂這個比較語句是否會被電腦理解爲**(k == a),由於*的「結合性」遠遠強過 == 。
<!--操做符結合性簡述-->
這裏說一下操做符結合性的問題,規律十分簡單:
一元操做符的結合型強過二元操做符,而在一元操做符中,右側操做符(數組索引符[]、滯後自增符++、成員鏈接符.)的結合性強於左側操做符(反向取值符*、取地址符&、優先自增符++)。
這意味着int *p[5];是一個指針構成的數組,p[0]到p[4]的類型都是(int*):int *(p[5]); 。
<!--野指針-->
int *p; //沒有給p賦值
*p = 5; //WRONG
這麼作是錯誤的。
當聲明一個指針型變量的時候,請必定爲其初始化或賦值。
對於普通變量:
int a;
printf("a = %d\n", a);
當給a賦值前打印a的值的時候,a的值多是0,但大多數狀況是一個混亂的值,由於內存中的數據都是混亂不堪的01010010110...,這多是上一個佔用該內存區的變量的值,可是對a而言只是垃圾值。
所以,當咱們將內存中的某部分申請出來做爲一個變量時,這個值也是不肯定的,這對於指針也是同樣。
也就是說,上面的變量p中是某個不肯定的值。但因爲p是指針類型,它就會把這個不肯定的指看成地址去看待。
咱們若是改變了*p,就改變了一個不肯定的地址中的數據,這個地址多是以前已經申請的某個變量的地址,也多是更爲重要的內存區。
沒有被初始化的指針被稱爲野指針,使用它的後果是不肯定且危險的。
事實上還存在另一種野指針,可是這涉及到動態內存分配,不是這裏要說的。
若是實在不想在聲明的時候給p初始化,能夠這麼作:
int* p = NULL;
NULL叫作空指針,是一個特殊的指(事實上是0)。咱們這麼聲明就表示p不指向任何內存區。
就講到這裏啦,但願能幫上你們的忙。