數(shù)組簡介
數(shù)組是我們編程中經(jīng)常遇到的一個類型量愧。多維數(shù)組是如何在內(nèi)存中存儲的呢?數(shù)組名到底代表什么呢溃槐?
數(shù)組是相同類型數(shù)據(jù)的一個集合匣砖,在內(nèi)存中連續(xù)存儲。
數(shù)組可以有一維昏滴、二維猴鲫、三維甚至更多維。多維的概念是存在于C/C++語言層面谣殊,對于編譯后的匯編和二進制拂共,只有一維的概念。
一維數(shù)組
一維數(shù)組使用如下形式進行定義:
data_type array_name[array_size]
比如
int scores[4];
定義一個數(shù)組姻几,該數(shù)組的每個元素的類型的是int, 該數(shù)組一共有4個元素匣缘。
當(dāng)然了,我們也可以在定義的時候給定初始值
int scores[4] = {90, 95, 99, 89};
這時候四個值在內(nèi)存中的排布為:
那如果我們沒有給定足夠的值呢鲜棠?比如
int scores[4] = {90, 95};
這時候后面兩個元素的值是啥肌厨?
其實這時候,后面兩個值會被填充0豁陆,得到的值為
我們可以寫一個簡單的小程序分析一下
// maian.cpp
int main()
{
int scores[4] = {90, 95};
return 0;
}
然后用clang++ -S main.cpp
生成匯編柑爸,我們看一下生成的匯編
這里我們不會詳細(xì)介紹x86匯編,只介紹一點我們需要用到的匯編的盒音。
moveq src, dst
也就是x86匯編里面表鳍,源在前面,目標(biāo)在后面祥诽。
a(%register)
表示register的值加上a這個偏移量指向的地址譬圣。所以上面
movq %rax, -24(%rbp)
相當(dāng)于
*(rbp - 24) = rax
。如果源是常數(shù)雄坪,那么直接使用一個90```就表示常數(shù)90。
如果我們把數(shù)組的大小變大一點维哈,比如下面的代碼
int main()
{
int scores[40] = {90, 95};
return 0;
}
反匯編我們可以看到
那也許有的人會問绳姨,是不是默認(rèn)就會清零?我們也可以驗證一下阔挠,把代碼修改為
int main()
{
int scores[40];
return 0;
}
反匯編我們可以看到
從匯編代碼我們可以看到是完全沒有默認(rèn)清零的飘庄。
最后值得一提的是,如果我們有初始值购撼,也可以不指定數(shù)組大小跪削,系統(tǒng)會根據(jù)初始值計算出大小谴仙。
int main()
{
int scores[] = {95,98,99,100};
return 0;
}
系統(tǒng)會自動根據(jù)初始值推斷出scores數(shù)組大小為4。
二維和多維數(shù)組
二維數(shù)組可如如下定義
data_type array_name[size_1][size_2];
比如
int scores[2][4];
三維以及多維數(shù)組的定義類似
data_type array_name[size_1][size_2]...[size_n];
比如三維數(shù)組可以定義為
int scores[2][3][4];
跟一維數(shù)組一樣碾盐,多維數(shù)組也可以在定義的時候給定初值
int scores[2][2][3] = {
{{1,2,3},{4,5,6}},
{{7,8,9},{10,11,12}}
};
它在內(nèi)存中的排布為
我們給定下面一段代碼
int main()
{
int scores[2][2][3] = {
{{1,2,3},{4,5,6}},
{{7,8,9},{10,11,12}}
};
scores[1][1][0] = 95;
return 0;
}
反匯編的代碼如下
從上面反匯編我們可以很容易看出狞甚,編譯之后并沒有多維的概念,都是一維的廓旬,多維的概念只存在于語言層面哼审。但是,這并不影響我們按照概念使用孕豹。從上面的匯編更多可以容易看出多維數(shù)組的值是怎么排布的涩盾。
可變大小的數(shù)組
可變大小數(shù)組也被稱為運行時大小數(shù)組。不同于在定義的時候指定常量大小励背,可變大小數(shù)組可以根據(jù)參數(shù)來定義大小春霍。
比如
void f(int size)
{
int scores[size];
...
}
這樣我們就可以在運行時根據(jù)需要傳入需要的大小來分配數(shù)組。
可變大小數(shù)組是C99標(biāo)準(zhǔn)里面的叶眉,所以C語言程序如果標(biāo)準(zhǔn)設(shè)為C99以及以上是一定可以使用的址儒。但是C++11之前是一定不支持可變大小數(shù)組的,C++11中提及數(shù)組大小的時候也說size是一個常量表達式衅疙,那么也就說明C++11從標(biāo)準(zhǔn)里面也是不支持可變大小數(shù)組的莲趣。但是GCC和Clang提供了擴展來支持可變大小數(shù)組,所以上面的代碼使用GCC和Clang是可以編譯的饱溢。
這里面就有兩個問題需要我們注意:
- 可變參數(shù)數(shù)組是一個很好的特性喧伞,但是如果我們想要代碼可移植性強,最好不要在C++中使用它绩郎。當(dāng)然了潘鲫,純C程序可以放心使用;
- C++并不是C的嚴(yán)格超集肋杖,C中的一些東西在C++中是不能使用的溉仑。
數(shù)組元素的訪問
常見的用數(shù)組名加上下標(biāo)訪問的方式我們已經(jīng)見過了,就是類似
int a[3] ={};
a[2] = 10;
但是在有的地方我們可以看見如下寫法
2[a] = 11
這種寫法是很不常見的状植,只是作為一種語法進行說明浊竟。更多的是a[-1]
這種寫法,其中a是一個指針浅萧。這里不詳細(xì)說明逐沙,后面在聊指針和內(nèi)存的時候會詳細(xì)說明哲思。
我們有下面一段代碼
int main()
{
int a[3] = {};
a[2] = 10;
1[a] = 11;
return 0;
}
這段代碼是完全可以編譯和運行的洼畅,在執(zhí)行到return 0;
的時候,a[0]=0, a[1]=11, a[2]=10
棚赔。從反匯編我們也可以看一下
我們可以看到無論是a[2]
還是1[a]
最后都是基指(也就是rbp寄存器的值)加上一個偏移量帝簇。其實t[b]
可以認(rèn)為都是*(t+b)
徘郭,所以t和b哪一個是數(shù)組名,哪一個是偏移量并沒有關(guān)系丧肴。
數(shù)組類型
對一個多維數(shù)組残揉,每一維的類型到底是什么?
為了解答這個問題芋浮,我們首先寫一個打印類型的工具函數(shù)
template <typename T>
void print_type()
{
#if _MSC_VER
const char *sig = __FUNCSIG__;
printf("%s\n", sig);
return;
#else
constexpr int skip_begin = 23;
constexpr int skip_end = 1;
const char* sig = __PRETTY_FUNCTION__;
#endif
char *result = new char[strlen(sig) - skip_begin - skip_end + 1]{};
memcpy(result, sig+skip_begin, strlen(sig) - skip_begin - skip_end);
printf("%s\n", result);
}
這個函數(shù)主要使用函數(shù)的簽名中含有參數(shù)名的原理來提取我們的類型名抱环,比如print_type<int>()
可以得到
void print_type() [T = int]
這里面是不是就有類型int
名在里面,我們?nèi)サ?code>int前面的字符串和后面的]就可以得到最終的類型纸巷。這就是上面函數(shù)的原理镇草。
手上沒有Visual Studio,所以Windows版本并沒有調(diào)試skip_begin和skip_end的值瘤旨,需要的人可以自行調(diào)試梯啤。Clang版本是測試過的,值為上面的23和1存哲,可以直接使用因宇。
我們現(xiàn)在可以寫個小程序打印不同維度的類型:
int main()
{
int scores[2][3][4];
print_type<decltype(&scores)>();
print_type<decltype(scores)>();
print_type<decltype(scores[0])>();
print_type<decltype(scores[0][0])>();
print_type<decltype(scores[0][0][0])>();
return 0;
}
可以得到如下輸出
int (*)[2][3][4]
int[2][3][4]
int (&)[3][4]
int (&)[4]
int &
也就是說
- 對數(shù)組名取地址,得到的是一個指向數(shù)組類型的指針類型祟偷;
- 數(shù)組的類型就是數(shù)組定義中去掉數(shù)組名之后得到的類型察滑;
- 數(shù)組的第一維元素是第二維和第三維組成的數(shù)組;
- 數(shù)組的第二維元素是一個一維數(shù)組修肠;
- 數(shù)組的第三維元素是一個整形杭棵。
另外,數(shù)組的第一維氛赐、第二維魂爪、第三維都是常規(guī)的引用類型,所以在類型里面都有&艰管。
這里有一個有意思的小問題就跟類型息息相關(guān):v的地址假設(shè)是a滓侍,那么v+1的地址用a表示是多少?
比如我們假設(shè)&scores的地址值是a牲芋,那么&scores+1的地址值是多少撩笆?
首先我們需要知道,對于地址的加減法缸浦,中1的量綱是夕冲。也就是1的大小用字節(jié)表示是所指對象的大小。
&scores+1
中的1用字節(jié)表示就是
所以&scores+1
的地址用a表示就是裂逐。
同理我們可以計算scores + 1
歹鱼,&scores[0] + 1
, &scores[0][0]+1
, &scores[0][0][0]+1
的地址的值。
復(fù)合字面量
經(jīng)常我們都會提到C++是C的超集卜高,C中可以用的C++都可以弥姻。但是今天我們就可以遇到第二個在兩門語言中表現(xiàn)不一樣的:復(fù)合字面量南片。
我們上面的多維數(shù)組是規(guī)則的,那我們?nèi)绻胍粋€二維數(shù)組庭敦,它的每個維度的大小不一樣疼进,是可以實現(xiàn)的嗎?
答案是當(dāng)然可以秧廉,而且方法不止一種伞广。
第一種常規(guī)的就是數(shù)組的成員是指針,然后讓指針指向不同大小的內(nèi)存疼电;
第二種方法就是使用復(fù)合字面量赔癌。那什么事復(fù)合字面量呢?這也是一個C99標(biāo)準(zhǔn)里面的東西澜沟。簡單來說
(int [2]){1,2};
就是一個復(fù)合字面量灾票。
我們可以使用復(fù)合字面量得到一個大小不同的數(shù)組
int main()
{
int (*scores[]) = {
(int[]){1,2},
(int[]){3,4,5},
(int[]){6, 7, 8, 9}
};
return 0;
}
這部分代碼,在Xcode中使用main.c是這樣的
在main.cpp就成了
本質(zhì)上這是一個C99的標(biāo)準(zhǔn)而不是一個C++的標(biāo)準(zhǔn)茫虽。在C++中的時候也需要謹(jǐn)慎謹(jǐn)慎再謹(jǐn)慎刊苍。
數(shù)組的分享就到這里了,還有一部分濒析,比如數(shù)組名是左值還是右值正什,需要分享了對應(yīng)的概念之后我們再回頭來討論。
更多文章及時發(fā)布在公眾號“探知軒”号杏,歡迎關(guān)注更及時看到文章婴氮。