动态

详情 返回 返回

深入理解指針Part3——指針與數組 - 动态 详情

1 數組名的理解

在C/C++中,數組名在表達式中使用時,通常會轉換為指向數組首元素的指針(區別數組指針)。此規則有兩個例外

  • 作為 sizeof 運算符的操作數:sizeof(arr) 返回的是整個數組所佔的字節大小,而非指針大小。
  • 作為 & 運算符的操作數:&arr 產生的是一個指向整個數組的指針(類型為 int(*)[10]),而非指向首元素的指針(類型為 int*)。

實例分析(假設有 int arr[10]):

arr&arr[0] 的類型是 int*。對它們 +1,地址增加一個 int 的大小(4字節)。

&arr 的類型是 int(*)[10]。對它 +1,地址會增加 10 * sizeof(int) = 40字節,即跳過整個數組。

2 使用指針訪問數組

有了前面知識的支持,再結合數組的特性,我們就可以很方便地使用指針來訪問數組了。

我們可以將數組名 arr(即數組首元素的地址)賦值給指針變量 p。這樣一來,arrp 在訪問數組元素時可以説是等價的。

既然可以使用 arr[i] 來訪問數組元素,那麼是否也能使用 p[i] 呢?答案是肯定的,將 *(p + i) 替換為 p[i] 後,程序仍然可以正常打印結果。這説明 p[i] 本質上等價於 *(p + i)。同理,arr[i] 也等價於 *(arr + i)

實際上,在編譯器處理數組元素的訪問時,也會將其轉換為“首元素地址 + 偏移量”的方式,先計算出元素的地址,再通過解引用來訪問該元素。

3 一維數組傳參的本質

我們已經學習過數組,也知道數組可以作為參數傳遞給函數。現在我們將探討數組傳參的本質。首先從一個問題開始:我們之前通常是在函數外部計算數組的元素個數,那麼是否可以在數組傳給函數之後,在函數內部求出它的元素個數呢?

實際測試發現,在函數內部並不能正確獲取數組的元素個數。這就引出了數組傳參的本質問題。

我們之前學過:數組名在大多數情況下表示數組首元素的地址。在數組傳參時,傳遞的正是數組名,也就是説,數組傳參本質上傳遞的是數組首元素的地址

因此,函數的形參部分實際上是用一個指針變量來接收這個首元素地址。在函數內部使用 sizeof(arr) 時,計算的是該指針變量的大小(以字節為單位),而不是整個數組的大小。正因為參數本質上是一個指針,所以在函數內部無法直接求出原數組的元素個數。

總結來説,一維數組傳參時,形參既可以寫成數組的形式,也可以寫成指針的形式,但它們的本質都是指針

4 冒泡排序

void Bubble_sort1(int* arr, size_t sz)
{
    for (int i = 0; i < sz - 1; i++)
    {
        for (int j = 0; j < sz - 1 - i; j++)
        {
            if (*(arr + j) > *(arr + j + 1))
            {
                int tmp = *(arr + j);
                *(arr + j) = *(arr + j + 1);
                *(arr + j + 1) = tmp;
            }
        }
    }
}

//A Better Version,在數組有序後就跳出循環
void Bubble_sort2(int* arr, size_t sz)
{
    for (int i = 0; i < sz - 1; i++)
    {
        int flag = 1;//假設這一趟已經有序了
        for (int j = 0; j < sz - 1 - i; j++)
        {
            if (*(arr + j) > *(arr + j + 1))
            {
                int tmp = *(arr + j);
                *(arr + j) = *(arr + j + 1);
                *(arr + j + 1) = tmp;
                flag = 0;
            }
        }
        if (flag)//這一趟沒交換就説明已經有序,不用再排了
            break;
    }
}

void Print(int* arr, size_t sz)
{
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", *(arr + i));
    }
    printf("\n");
}

int main()
{
    int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
    size_t sz = sizeof(arr) / sizeof(arr[0]);
    Print(arr, sz);
    Bubble_sort2(arr, sz);//從小到大
    Print(arr, sz);
    return 0;
}

5 二級指針

int main() 
{
    int a = 10;
    int* pa = &a;   // 一級指針,保存a的地址
    int** ppa = &pa; // 二級指針,保存pa的地址

    // 通過二級指針修改變量a的值
    **ppa = 30;     // 步驟1: *ppa 找到 pa
    // 步驟2: *(*ppa) 即 *pa 找到 a
    // 步驟3: 將a的值改為30

    printf("a = %d\n", a);  // 輸出: a = 30

    // 通過二級指針改變一級指針的指向
    int b = 50;
    *ppa = &b;      // 讓pa轉而指向b
    *pa = 100;      // 現在修改的是b的值

    printf("b = %d\n", b);  // 輸出: b = 100

    return 0;
}

6 指針數組

指針數組的本質是一個數組,其元素類型是指針類型。若一個數組的每個元素都是用來存放地址(指針)的,則該數組稱為指針數組。

例如,int* arr[5] 聲明瞭 arr 是一個包含5個元素的數組,每個元素都是一個 int*(整型指針)。

拆開看,int* [5] 是變量 arr 的類型,它是一個數組類型,包含了5個 int* 類型的元素。

特性:指針數組的每個元素本身是一個地址,而這個地址又可以作為入口,指向另一塊連續的內存區域(如另一個數組或一個數據結構)。這使得它成為管理多個指針、構建複雜數據結構(如字符串數組、二維動態數組)的基礎。

7 指針數組模擬二維數組

#include <stdio.h>

int main()
{
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[] = {2, 3, 4, 5, 6};
    int arr3[] = {3, 4, 5, 6, 7};
    
    // 數組名是首元素的地址,類型是int*,可以存放在parr數組中
    int* parr[3] = {arr1, arr2, arr3};
    int i = 0;
    int j = 0;
    
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 5; j++)
        {
            printf("%d ", parr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

數組名是數組首元素的地址,其類型為 int*,因此可以存放在 parr 這個指針數組中。

parr[i] 訪問的是 parr 數組的元素,該元素是一個指向整型一維數組的指針。parr[i][j] 則訪問該一維數組中的具體元素。

上述代碼模擬了二維數組的效果,但實際上並非真正的二維數組,因為每一行的存儲空間並不連續。


正文完

user avatar gushiio 头像 u_16231477 头像 didiaodekaishuiping 头像 jianghushinian 头像 hlinleanring 头像 wanzuqiudeshangba 头像 mougeyewan 头像 meirenlideshuizhurou 头像 dosswy 头像 piano 头像 shenjingwa_6545efd9181d1 头像 xcye 头像
点赞 21 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.