C指針概述

<!--指針初步-->

聲明變量的實質是開闢內存空間,聲明變量的時候都須要爲其分配內存。
數組

計算機中,每字節的內存都由惟一一個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不指向任何內存區。



就講到這裏啦,但願能幫上你們的忙。

相關文章
相關標籤/搜索