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。這樣一來,arr 和 p 在訪問數組元素時可以説是等價的。
既然可以使用 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] 則訪問該一維數組中的具體元素。
上述代碼模擬了二維數組的效果,但實際上並非真正的二維數組,因為每一行的存儲空間並不連續。