C語言進階:第29課:指針和數組分析(下)

數組名可以當做常量指針使用,那麼指針是否也可以當做數組名來使用呢?

數組的訪問方式:
下標的形式訪問數組中的元素;
指針的形式訪問數組中的元素;

指針以固定增量在數組中移動時,效率高於下標形式,指針增量爲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;
}

注意:

現代編譯期的生成代碼優化率已經大大提高,在固定增量時,下標形式的效率已經和指針形式的效率相當,

但從可讀性和代碼維護的角度來看,下標的形式更優。

數組和指針的不同之處:

在ext.c中:

int a[] = {1, 2, 3, 4, 5};             //定義數組

在test.c中:

#include <stdio.h>

int main()
{
    extern int* a;                     //聲明指針
    
    printf("&a = %p\n", &a);           //a定義時的地址值
    printf("a = %p\n", a);             //在這個地址值上取第一個數作爲地址
    printf("*a = %d\n", *a);           //ox1,這個內存地址是預留給操作系統的,所以無法訪問
    
    return 0;
}

編譯爲報錯,運行:

./a.out
&a = 0x804a014
a = 0x1
段錯誤

但如果這樣聲明的時候:

#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

爲什麼會出現這種情況呢?數組名是不是指針呢?

首先必須明確,數組名並不是指針。

在外部.c文件中,定義了一個包含五個int類型的數組,所以編譯器會在內存中分配20個字節的空間,其地址如上所示。換句話說,標識符a在編譯後得到一個新的意義,代表了一個地址:0x804a014,接下來,當編譯text.c時,發現聲明瞭的extern int* a,編譯器在此時將默認爲a爲一個外部定義的指針,代表一個地址。

所以第一行打印就打印了a的地址值,第二行打印呢?a的值是什麼呢?a是一個指針變量,變量有四個字節,所以編譯器會在a的地址處讀取四個字節的地址,因爲此時a被認爲是一個指針,所以此時讀取的是在這個地址(0x804a014)上的0001這個數,即0x1。但是0x1這個地址是預留給操作系統的,但凡用戶態的程序要訪問這個地址(第三行打印),就會出現段錯誤。

a與&a的區別:

        a爲數組首元素的地址

        &a爲整個數組的地址

        a和&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;
}

上述代碼編譯運行輸出什麼呢?

// A. 數組下標不能是負數,程序無法運行
// B. p1[-1]將輸出隨機數,p2[0]輸出2, p3[1]輸出3
// C. p1[-1]將輸出亂碼, p2[0]和p3[1]輸出2
值得注意的是p2,在對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);
    
    printf("a = %p\n", a);
    printf("*(int*)((int)a) = %d\n", *(int*)((int)a));
    printf("*(int*)((int)a + 1) = %d\n", *(int*)((int)a + 1));
    
    int* p3 = (int*)(a + 1);
    
    printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
    
    return 0;
}

編譯運行:

~/will$ ./a.out
a = 0xbffe3780
*(int*)((int)a) = 1
*(int*)((int)a + 1) = 33554432
5, 33554432, 3

p2 = 0xbffe3780 + 1 =  0xbffe3781;     p2已經不是指針運算了,即在原地址處加1,

p2[0] = *p2 ; 即在0xbffe3781處的數據:

0002
1000 2000 3000 4000 5000//小端系統

*p2 ---> 0x0200 0000

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;  //此處的a是指針,不表示數組
}

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;
}

編譯運行:

~/will$ gcc test.c
~/will$ ./a.out
In func1: sizeof(a) = 4
array[0] = a
In func2: sizeof(b) = 4
array[0] = b

數組參數並不是數組了,而是一個指針。

小結:

    數組名和指針僅使用方式相同

        數組名的本質不是指針

        指針的本質不是數組

數組名並不是數組的地址,而是數組元素的地址(體現在指針運算)

函數的數組參數退化爲指針。

發佈了83 篇原創文章 · 獲贊 12 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章