目錄
什么是數組(Array)?
🔍為什么數組的下標要從 0 開始??
一、內存地址與偏移量的關系:從 0 開始是最自然的映射
二、指針的起點就是第 0 個元素的地址
三、歷史原因:BCPL → B → C → C++
數組的內存體現
?數組的聲明
數組的訪問方式?
什么是數組(Array)?
數組(Array)是 C++ 中的一種線性數據結構,用于存儲多個相同類型的變量,并且這些變量在內存中是連續排列的。
你可以把它想象成一個排好隊的儲物柜,每個柜子有編號(下標),每個柜子里放著一個值。例如:
int arr[5] = {10, 20, 30, 40, 50};
這表示我們聲明了一個包含 5 個 int
類型的數組,它依次存儲:
-
arr[0] = 10
-
arr[1] = 20
-
arr[2] = 30
-
arr[3] = 40
-
arr[4] = 50
注意:數組的下標從 0 開始,而不是 1。
🔍為什么數組的下標要從 0 開始??
雖然最初很多人覺得「從 1 開始」更符合直覺,但數組從 0 開始其實是有深刻的底層原因和效率考量,它與 指針、地址計算、語言設計哲學 有關。我們來系統解釋這個設計邏輯。?
一、內存地址與偏移量的關系:從 0 開始是最自然的映射
在 C/C++ 中,數組實際上是指針加偏移(pointer arithmetic)。
例子:
int A[4] = {10, 20, 30, 40};
假設數組 A
從地址 0x1000
開始,且每個 int
占 4 字節。
下標 i | 內存地址 | 數學計算 |
---|---|---|
A[0] | 0x1000 | A + 0 → 0x1000 + 0 * 4 |
A[1] | 0x1004 | A + 1 → 0x1000 + 1 * 4 |
A[2] | 0x1008 | A + 2 → 0x1000 + 2 * 4 |
?訪問 A[i]
實際上是計算:
*(A + i) // 指針 + 偏移量
?👉 如果下標從 1 開始,那就必須寫成:
*(A + (i - 1))
?這樣會多一個運算(減法),無論在運行效率還是語義上都不自然。
二、指針的起點就是第 0 個元素的地址
當你聲明:int A[5];
數組名 A
實際上是指向 A[0]
的地址。不是 A[1]
,不是別的起點。
所以,
*A == A[0]
*(A+1) == A[1]
*(A+2) == A[2]
?如果下標從 1 開始,就會出現“偏移一格”的矛盾,代碼會更難維護。
三、歷史原因:BCPL → B → C → C++
🧬 C語言起源于 B 和 BCPL
最早的語言 BCPL 和 B語言 中沒有數組的概念,只有“地址 + 偏移”。C 語言繼承了這種偏移訪問模型,所以自然地,數組從 0
開始偏移。
Dennis Ritchie(C 語言的設計者)就是遵循這個簡潔、底層直觀的設計哲學。
現代語言很多也從 0 開始
大多數現代語言也繼承了這個設計:
語言 | 數組是否從 0 開始 |
---|---|
C | ? 是 |
C++ | ? 是 |
Java | ? 是 |
Python | ? 是 |
JavaScript | ? 是 |
Rust | ? 是 |
雖然也有一些語言(如 Fortran、Lua)允許你從 1 開始索引,但這并不常見。
數組的內存體現
數組的核心特征是:所有元素在內存中是挨著排放的,沒有任何間隔。
我們用一個直觀的內存圖解來說明:
假設 int
類型占用 4 字節(常見情況),數組如下:
int arr[4] = {100, 200, 300, 400};
?如果 arr[0]
存儲在內存地址 0x1000
,那么在內存中是這樣的:
內存地址 值
0x1000 arr[0] = 100
0x1004 arr[1] = 200
0x1008 arr[2] = 300
0x100C arr[3] = 400
?? 特點總結:
-
每個元素都緊挨著上一個,偏移量是
sizeof(類型)
。 -
編譯器知道數組是連續的,所以可以通過起始地址和偏移快速定位任意元素:
arr[i]
等價于*(arr + i)
?數組的聲明
在 C++ 中,聲明數組就是告訴編譯器我們要創建一個連續內存區域,用于存儲多個相同類型的數據項。聲明時必須指定類型和元素數量。?
1. int A[5];
含義:
-
聲明一個整型數組
A
,包含 5 個元素。 -
未初始化,每個元素的值是未定義的垃圾值(在局部變量中)。
注意:
-
在函數內部聲明的數組(局部數組)不會自動清零。
-
在全局或靜態作用域中聲明的數組會被自動初始化為 0。
2. int A[5] = {2, 4, 6, 8, 10};
?含義:
-
聲明一個大小為 5 的整型數組,并完全初始化所有元素。
-
A[0] = 2
,A[1] = 4
, ...,A[4] = 10
特點:
-
初始化列表剛好填滿數組,無自動補零。
-
所有元素值由你控制。
3. int A[5] = {2, 4};
含義:
-
聲明一個大小為 5 的數組,只初始化前兩個元素。
-
剩下的元素會被自動補零。
?結果是:
A[0] = 2
A[1] = 4
A[2] = 0
A[3] = 0
A[4] = 0
4. int A[5] = {0};
含義:
-
聲明一個大小為 5 的數組,僅第一個元素初始化為 0。
-
其余元素也會被自動補零。
-
快速清零的技巧:用
{0}
初始化整個數組。
實際效果:
A[0] = 0
A[1] = 0
A[2] = 0
A[3] = 0
A[4] = 0
5. int A[] = {2, 4, 6, 8, 10};
含義:
-
不指定大小,由初始化列表的元素數量自動推斷大小為 5。
-
效果與
int A[5] = {2, 4, 6, 8, 10};
相同。
編譯器推斷出:int A[5]; ? ? // ← 實際等價形式
?更簡潔,特別是在你明確初始化所有元素的情況下。
?
數組的訪問方式?
通過索引訪問數組元素(Index)
這是最常見、最直接的方式。
A[index]
-
index
是整數類型,從0
開始。 -
索引值必須在合法范圍內:
0
到數組大小 - 1
。
用指針訪問數組元素?
數組名與指針的關系:
在大多數表達式中,數組名會自動退化為指向第一個元素的指針:
int A[5] = {1, 2, 3, 4, 5};
int* p = A; // A 就是 &A[0]
此時:
-
p
和A
都指向數組開頭 -
你可以用指針訪問元素
? 使用 *(pointer + index)
:?
int A[5] = {1, 2, 3, 4, 5};
int* p = A;cout << *(p + 0); // 輸出 1
cout << *(p + 3); // 輸出 4*(p + 2) = 100; // 修改 A[2] 為 100
🟰 等價關系:
表達式 | 含義 |
---|---|
A[i] | 訪問數組第 i 個元素 |
*(A + i) | 使用數組名當指針 |
*(p + i) | 使用指針訪問數組元素 |
p[i] | 指針變量也支持 [] 下標運算(語法糖) |