C之指針與數組組合(二十六)

        咱們在前面講到數組的本質是一段連續的內存空間,那麼它的大小爲 sizeof(array_type) * array_size,同時數組名可看作指向數組第一個元素的常量指針。那麼問題來了,數組 a + 1 的意義是什麼呢?結果又是怎樣呢?指針運算的意義又是什麼?結果呢?下來咱們看個示例代碼,代碼以下面試

#include <stdio.h>

int main()
{
    int a[5] = {0};
    int* p = NULL;
    
    printf("a = 0x%X\n", (unsigned int)(a));
    printf("a + 1 = 0x%X\n", (unsigned int)(a + 1));
    
    printf("p = 0x%X\n", (unsigned int)(p));
    printf("p + 1 = 0x%X\n", (unsigned int)(p + 1));
    
    return 0;
}

        編譯結果以下數組

圖片.png

        咱們看到數組 a 至關於一個常量指針,而它便指向的首元素的地址,a + 1 即是首元素的地址加 4,也就是數組第二個元素的地址。由於指針 p int 型,因此  p + 1 至關於加 4。ide

        指針是一種特殊的變量,它與整數的運算規則爲 p + n <==> (unsigned int)p + n*sizeof(*p);那麼即是當指針指向同一類型的數組的元素時:p + 1 將指向當前元素的下一個元素;p - 1 將指向當前元素的上一個元素。指針之間只支持減法運算,而且參與減法運算的指針類型必須相同。p1 - p2 <==> ((unsigned int)p1 - (unsigned int)p2)/sizeof(type);注意:a> 只有當兩個指針指向同一個數組中的元素時,指針相減纔有意義,其意義爲指針所指元素的下標差;b> 當兩個指針指向的元素不在同一個數組中時,結果爲定義函數

        指針也能夠進行關係運算(<, <=, >, >=),指針關係運算的前提是同時指向同一個數組中的元素;任意兩個指針之間的比較運算(==,!=),參與比較運算的指針類型必須相同。學習

        下來咱們來看個示例代碼,代碼以下優化

#include <stdio.h>

#define DIM(a) (sizeof(a) / sizeof(*a))

int main()
{
    char s[] = {'H', 'e', 'l', 'l', 'o'};
    char* pBegin = s;
    char* pEnd = s + DIM(s); // Key point
    char* p = NULL;
    
    printf("pBegin = %p\n", pBegin);
    printf("pEnd = %p\n", pEnd);
    
    printf("Size: %d\n", pEnd - pBegin);
    
    for(p=pBegin; p<pEnd; p++)
    {
        printf("%c", *p);
    }
    
    printf("\n");
   
    return 0;
}

        咱們在第3行定義的宏是求這個數組元素的個數,在第9行定義的指針 pEnd 爲數組首元素的地址加上數組元素個數,那麼它恰好指向數組最後一個元素的臨界。這是 C 語言中的灰色地帶,在 C 語言中是合法的。咱們來看看編譯結果spa

圖片.png

        咱們看到結果是如咱們所想的那,由於是 char 類型的數組,因此 pEnd = pBegin + 5。3d

        數組名能夠當作常量指針使用,那麼指針是否也能夠當作數組名來使用呢?咱們日後接着說,在數組中的訪問方式有兩種:一、如下標的形式訪問數組中的元素;二、以指針的形式訪問數組中的元素。那麼這兩種方式有何區別呢?當指針以固定增量在數組中移動時,效率要高於下標形式。尤爲是指針增量爲 1 且硬件具備硬件增量模型時效率更高。下標形式與指針形式之間還會相互轉換:a[n] <==> *(a + n) <==> *(n + a) <==> n[a]。這種表示法是否是很奇怪?但通過理論推導徹底是成立的,下面咱們就來看看是否支持這種寫法指針

#include <stdio.h>

int main()
{
    int a[5] = {0};
    int* p = a;
    int i = 0;
    
    for(i=0; i<5; i++)
    {
        p[i] = i + 1;
    }
    
    for(i=0; i<5; i++)
    {
        printf("a[%d] = %d\n", i, *(a + i));
    }
    
    printf("\n");
    
    for(i=0; i<5; i++)
    {
        i[a] = i + 10;
    }
    
    for(i=0; i<5; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }
    
    return 0;
}

        咱們看到在程序第6行定義指針 p 並將它指向數組 a,接下來就是咱們以前說的到底指針是否也能夠當作數組名來使用呢,若是能夠第11行便不會報錯。在第16行咱們以指針的形式來打印數組 a 中的值,第23行則驗證咱們上面 i[a] 這種寫法是否正確,第28行則經過下標形式來訪問數組。我麼來看看編譯結果blog

圖片.png

        咱們看到程序沒報錯而且完美執行,這就回答了咱們上面的問題和疑問。可是得注意:在現代編譯器中,生成代碼優化率已大大提升,在固定增量時,下標形式的效率已經和指針形式至關了;但從代碼的可讀性和維護的角度來看,下標形式更優秀,這就是爲何咱們平時見到的代碼中的數組都是如下標形式訪問的啦。

        咱們下來再作個實驗,看看數組和指針的區別

test.c 代碼

#include <stdio.h>

int main()
{
    extern int a[];
    
    printf("&a = %p\n", &a);
    printf("a = %p\n", a);
    printf("*a = %d\n", *a);

    
    return 0;
}


ext.c 代碼

int a[] = {1, 2, 3, 4, 5};

        咱們看到在 ext.c 中定義了一個數組,咱們先以數組的方式在 test.c 中訪問,看看打印結果

圖片.png

        咱們看到的結果和咱們想的是一致的,&a 就表明數組的地址,a 就表明數組首元素的地址,兩個是相同的。*a 的值即是數組中第一個元素的值啦。咱們再來將 test.c 中的第5行改爲 extern int* a; 這樣呢,咱們來看看編譯結果

圖片.png

        咱們看到發生段錯誤了,這是什麼狀況呢?數組 a 的值爲 1,*a 發生段錯誤了,在內存中,數組的值是這樣存儲的 0001 0002 ... 0005(大端機器)。那麼 a 天然也就爲 1了,計算機中的 1 地址處爲內核態,用戶態的程序想要訪問內核態的地址,計算機固然會報錯。

        那麼 a 和 &a 有何區別呢? a 爲數組首元素的地址,&a 爲整個數組的地址。a 和 &a 的區別在於指針運算。a + 1 ==> (unsigned int)a + sizeof(*a);&a + 1 ==> (unsigned int)(&a) + sizeof(*&a) ==> (unsigned int)(&a) + sizeof(a);

        下來咱們來看個經典的指針運算問題,同時也是一道筆試面試題

#include <stdio.h>

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int* p1 = (int*)(&a + 1); 
    int* p2 = (int*)((int)a + 1);
    int* p3 = (int*)(a + 1);
    
    printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
    
    return 0;
}

        咱們看下編譯結果

圖片.png

        咱們來分析下,第一個 p1[-1] ==> p1[&a +1 - 1] =>p1[&a],天然它的值也就爲 5 了。p3[1] ==> (a + 1 +2) ==> (a + 2),天然也就爲 3 啦。第二個的數感受是隨機數,但咱們仔細分析下,它是首地址加 1,也就是日後移一位。這個數組在小端系統中,就是 1000 2000 ... 5000這樣分佈的,後移一位就變成了 0002,即是 0x02000000 轉成十進制即是 33554432 啦。

        數組做爲函數參數時,編譯器將其編譯成對應的指針。如:void f(int a[]) <==> void f(int* a);void f(int a[5]) <==> void f(int* a);在通常狀況下,當定義的函數中有數組參數時,須要定義另外一個參數來標定數組的大小。

        咱們來看個示例代碼

#include <stdio.h>

void func1(char a[5])
{
    printf("In func1: sizeof(a) = %d\n", sizeof(a));
    
    *a = 'a';
    
    a = NULL;
}

void func2(char b[])
{
    printf("In func2: sizeof(b) = %d\n", sizeof(b));
    
    *b = 'b';
    
    b = NULL;
}

int main()
{
    char array[10] = {0};
    
    func1(array);
    
    printf("array[0] = %c\n", array[0]);
    
    func2(array);
    
    printf("array[0] = %c\n", array[0]);
    
    return 0;
}

        咱們在 func1 中打印它的參數大小,而且以指針方式進行賦值和指向 NULL,若是是數組的話便會報錯。咱們來看看編譯結果

圖片.png

        咱們發現兩函數的數組參數都被當成指針來處理了。 經過本節對指針和數組的學習,總結以下:一、數組聲明時編譯器自動分配一片連續的內存空間,指針聲明時只分配了用於容納地址值的4字節空間;二、指針和整數能夠進行運算,其結果爲指針。指針之間只支持減法運算,其結果爲數組元素下標差;三、指針之間支持比較運算,其類型必須相同;四、數組名和指針僅使用方式相同,數組名的本質不是指針,指針的本質不是數組;五、數組名並非數組的地址,而是數組首元素的地址;六、函數的數組參數化爲指針。


        歡迎你們一塊兒來學習 C 語言,能夠加我QQ:243343083

相關文章
相關標籤/搜索