C/C++陣列詳解(一維和二維)
陣列這東西,說說都懂,但是似乎並沒有完全吃透,導致很多地方有疑惑。所以再梳理一遍。
陣列定義
陣列是存放型別相同的物件的容器,這些物件本身沒有名字,需要通過其所在位置訪問。
從定義中可以看出,陣列存放的是物件且型別相同。所以不存在引用的物件(引用不是物件)
陣列的侷限性:陣列的大小確定不變,不能隨意向陣列中增加元素。如果不確定元素的確切個數,使用vector。
定義和初始化內建陣列
陣列的宣告形如a[d],其中a是陣列的名字,d是陣列的維度。維度說明陣列中的元素的個數,因此必須大於0。陣列中元素的個數也屬於陣列型別的一部分,編譯的時候維度應該也是已知的
unsigned int cnt = 42; //不是常量表達式
int arr[10]; //含有10個整數的陣列 // 10是字面值常量
const int sz = 42; //常量表達式
constexpr int sz = 42; //C++11,常量
const int sz = get_size(); //不是常量表達式,sz的值只有在執行時才會獲取
int arr['a'];//‘a’是字元字面值常量,
預設情況下,陣列的元素被預設初始化
和內建型別的變數一樣,如果在函式內部定義了某種內建型別的陣列,那麼預設初始化會令陣列含有未定義的值。
定義陣列的時候必須指定陣列的型別,不允許使用auto關鍵字由初始化的列表推斷型別。
顯式初始化陣列
可以對陣列的元素進行列表初始化,此時可以忽略陣列的維度。如果在宣告時候沒有指定陣列的維度,編譯器可以根據初始值的數量計算並推測出來;相反,如果指明瞭維度,那麼初始值的總數量不應該超過指定的大小。如果維度比提供的初始值數量大,則用提供的初始值初始化靠前的元素,剩下的元素被初始化成預設值。(如int中的0,string中的“”)
constexpr int sz = 3;
int ia1[sz] = {0,1,2};
int a2[] = {0,1,2};
int a3[5] = {1,2,3}; //等價於a3[5]={1,2,3,0,0}
srting a4[3] = {"hi","bye"}; //等價於a4[3]={"hi","byr",""}
int a[2] = {1,2,3}; //錯誤,初始值過多
字元陣列的特殊性
字元陣列有一種額外的初始化形式,我們可以用字串字面值對此類陣列進行初始化。當使用這種方式初始化時,一定要注意字串字面值的結尾處還有一個空字元,這個空字元也會像字串其他字元一樣被拷貝到字元陣列中去。
char a1[] = {'C', '+', '+'}; //列表初始化,沒有空字元
char a2[] = {'C', '+', '+', '\0'};
char a2[] = "C++"; //自動新增表示字串結束的空字元
const char a4[6] = "Daniel"; //錯誤:沒有空間可以存放空字元
注意:有時候在Linux和Windows下不太一樣,Windows要求嚴格(出錯直接報錯,而Linux並不會,記憶體管理機制不一樣)
注意:特別是在拷貝的時候,注意結尾處有沒有加上'\0'
不允許拷貝和賦值
不能將陣列的內容拷貝給其他陣列作為初始值,也不能用陣列為其他陣列賦值:
int a[] = {0,1,2};
int a2[] = a; //錯誤:不允許使用一個數組初始化另一個數組
a2 = a; //錯誤:不能把一個數組直接賦值給另一個數組
理解複雜的陣列宣告
陣列能存放大多數型別的物件。例如,可以定義一個存放指標的陣列。又因為陣列本身就是物件,所以允許定義陣列的指標及陣列的引用。
①:int *ptrs[10]; //ptrs是含有10個整型指標的陣列
預設情況下,型別修飾符從右向左依次繫結,所以ptrs首先是一個大小為10的陣列,它的名字是ptrs,然後陣列中存放的是指向int的指標。
②:int &refs[10] = /* ? */; //錯誤:不錯在引用的陣列
因為陣列存放的是物件,而引用不是物件。
拓展:這裡還要注意一點:引用在定義的時候必須初始化
例1:int &ref; //錯誤
例2:class A{
public:
A::A();
~A::A();
int &ref; //正確
};
解釋:引用在定義的時候必須初始化,而什麼叫定義,凡是有記憶體佔用行為的就是定義,否則就是宣告。
③:int (*Parray)[10] = &arr; //Parray指向一個含有10個整數的陣列
其實這裡很好理解,就是按照運算子優先順序理解,首先()與[]優先順序一樣,所以按照結合,先執行()再[],()括號裡表明Parray是一個指標,然後在執行右邊的[],所以得知Parray是一個指向大小為10的陣列的指標,然後左邊的int表明陣列型別為int。
注意:等號右邊是對整個陣列取地址
知識點:陣列名在什麼時候退化為指標
以下情況均不能將陣列名s視為指標:
(1) sizeof(s)
(2) &s;
(3) 用陣列對陣列的引用進行初始化時
④:int (&arrRef)[10] = arr; //arrRef引用一個含有10個整數的陣列
這個與③一樣。只不過arrRef是大小10,型別為int的陣列的引用。因為這裡是對陣列的引用,所以右邊arr理解為陣列而不是首元素地址。
多維陣列
嚴格來說,C++語言中沒有多維陣列,通常說的多維陣列其實是陣列的陣列。
int a[2][3]; //大小為2的陣列,每個元素是含有3個整數的陣列
int b[10][20][30] = {0}; //大小為10的陣列,每個元素是含有20個數組的陣列,然後這些陣列是含有30個整數的陣列
對於二維陣列來說,常把第一個維度稱作行,第二個維度稱作列。
多維陣列的初始化
int ia[3][4] = { //三個元素,每個元素都是大小為4的陣列
{0,1,2,3}, // 第一行的初始值
{4,5,6,7}, // 第二行的初始值
{8,9,10,11} // 第三行的初始值
};
也可以是:
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
也可以顯示初始化每行的首元素:
int ia[3][4] = {{ 0 },{ 4 },{ 8 }}; //其他未列出的元素執行預設值初始化,但是若省去花括號就不一樣了:
int ia[3][4] = {0,4,8}; //這裡顯示初始化第一行前3個元素,後面的元素預設初始化
理解如下宣告:
int ia[3][4] = { 0 };
int (&row)[4] = ia[1]; //把row繫結到ia的第二個4元素陣列上
解釋:首先二維陣列和一維陣列一樣,陣列名也是指向陣列首元素的指標。
但是:這裡一定要注意二維陣列的元素其實是陣列,
而上面說過陣列引用初始化時,陣列不褪化為指標,所以ia[1]代表第二個元素,也就是一個大小為4的陣列。