1. 程式人生 > 實用技巧 >20 陣列和指標

20 陣列和指標

目錄

1 陣列的本質

  • 陣列是一段連續的記憶體空間

  • 陣列的空間大小為 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;
    }
    
    //輸出結果
    a = 0xBFF51B48
    a + 1 = 0xBFF51B4c
    p = 0x0
    p + 1 = 0x4
    

2 指標的運算

  • 指標是一種特殊的變數,與整數的運算規則為:p + n; => (unsigned int)p + n * sizeof(*p);

  • 當指標 p 指向一個同類型的陣列的元素時:p + 1 將指向當前元素的下一個元素;p - 1 將指向當前元素的上一個元素

  • 指標之間只支援減法運算,參與減法運算的指標型別必須相同:p1 - p2; => ((unsigned int)p1 - (unsigned int)p2) / sizeof(type);

  • 只有當兩個指標指向同一個陣列中的元素時,指標相減才有意義,其意義為指標所指元素的下標差

  • 當兩個指標指向的元素不在同一個陣列中時,結果未定義

3 指標的比較

  • 指標也可以進行關係運算(<,<=,>,>=)

  • 指標關係運算的前提是同時指向同一個陣列中的元素

  • 任意兩個指標之間的比較運算(==,!=)無限制

  • 參與比較運算的指標型別必須相同

  • 示例1

    #include <stdio.h>
    
    int main()
    {
        char s1[] = {'H', 'e', 'l', 'l', 'o'};
        int i = 0;
        char s2[] = {'W', 'o', 'r', 'l', 'd'};
        char* p0 = s1;
        char* p1 = &s1[3];
        char* p2 = s2;
        int* p = &i;
    	
        printf("%d\n", p0 - p1);  //-3
        printf("%d\n", p0 + p2);  //error
        printf("%d\n", p0 - p2);  //error
        printf("%d\n", p0 - p);   //error
        printf("%d\n", p0 * p2);  //error
        printf("%d\n", p0 / p2);  //error
    	
        return 0;
    }
    
  • 示例2

    #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;
    }
    //輸出結果
    pBegin = 0xbfac155f
    pEnd = 0xbfac1564
    size = 5
    Hello
    

4 陣列的訪問方式

  • 陣列名可以當作常量指標使用,那麼指標是否也可以當作陣列名來使用?

    • 可以!
  • 下標的形式訪問陣列中的元素

    int main()
    {
        int a[5] = {0};
        
        a[1] = 3;
        a[2] = 5;
        
        return 0;
    }
    
  • 指標的形式訪問陣列中的元素

    int main()
    {
        int a[5] = {0};
      
        //陣列名看作常量指標
        *(a + 1) = 3;
        *(a + 2) = 5;
        
        return 0;
    }
    
  • 下標形式 VS 指標形式

    • 指標以固定增量在陣列中移動時,效率高於下標形式
    • 指標增量為 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;
    }
    //輸出結果
    a[0] = 1
    a[1] = 2
    a[2] = 3
    a[3] = 4
    a[4] = 5
    
    p[0] = 10
    p[1] = 11
    p[2] = 12
    p[3] = 13
    p[4] = 14
    
  • 陣列和指標不同

    //ext.c
    int a[] = {1, 2, 3, 4, 5};
    
    //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;
    }
    
    • 執行結果

      &a = 0x804a014
      a = 0x804a014
      *a = 1
      
    • 修改:驗證陣列名與指標是否一樣。執行結果分析:

      • ext.c 中定義了一個數組,在記憶體為:1000 2000 ... 5000(Linux為小端系統) ,一共 20 個位元組
      • 該陣列在記憶體中的地址為:0x804a014 ,也就是說在編譯後,識別符號 a 的意義為一個地址:0x804a014
      • 在編譯 test.c 時,當編譯到 extern int* a; 時,發現識別符號 a 在別處定義
      • 當編譯到 printf("&a = %p\n", &a); 時,列印識別符號 a 的地址,即為:0x804a014
      • 當編譯到 printf("a = %p\n", a); 時,列印的是一個指標變數 a ,其值儲存的是一個地址,那麼取 4 個位元組的值即為:0x1
      • 當編譯到 printf("*a = %d\n", *a); 時,*a 是到地址 0x1 取值,而此地址為作業系統所使用,產生段錯誤
      //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;
      }
      //執行結果
      &a = 0x804a014
      a = 0x1
      段錯誤
      

4 a 和 &a 的區別

  • a 為陣列首元素的地址

  • &a 為整個陣列的地址

  • a 和 &a 的區別在於指標運算

    • a + 1 => (unsigned int)a + szieof(*a)
    • &a + 1 => (unsigned int)(&a) + sizeof(*&a) => (unsigned int)(&a) + sizeof(a)
    • 二者加 1 增加的步長不一樣
  • 指標運算示例

    #include <stdio.h>
    
    int main()
    {
        int a[5] = {1, 2, 3, 4, 5};
        printf("a = %p\n",a);
        int* p1 = (int*)(&a + 1);  //p1指向陣列a最後一個元素5後的下一個位置
        int* p2 = (int*)((int)a + 1);  //整數加1是數學運算,結果為整數,p2指向一個地址:0xbfa71990+1=0xbfa71991,=> p2 = 0x0200 0000 =>十進位制數:33554432
        int* p3 = (int*)(a + 1);  //p3指向第2個元素
        
        printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
        
        return 0;
    }
    //執行結果
    a = 0xbfa71990
    5,33554432,3
    

5 陣列引數

  • 陣列作為函式引數時,編譯器將其編譯成對應的指標

    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是陣列名的話會報錯:陣列名不可以賦值
        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;
    }
    
    // 執行結果
    In func2: sizeof(a) = 4
    array[0] = a
    In func2: sizeof(b) = 4
    array[0] = b