登堂入室C++之?dāng)?shù)組

數(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)存中的排布為:

1d-array.png

那如果我們沒有給定足夠的值呢鲜棠?比如

int scores[4] = {90, 95};

這時候后面兩個元素的值是啥肌厨?

其實這時候,后面兩個值會被填充0豁陆,得到的值為

1d-array-pad.png

我們可以寫一個簡單的小程序分析一下

// maian.cpp
int main()
{
    int scores[4] = {90, 95};
    return 0;
}

然后用clang++ -S main.cpp生成匯編柑爸,我們看一下生成的匯編

1d-array-main.png

這里我們不會詳細(xì)介紹x86匯編,只介紹一點我們需要用到的匯編的盒音。

moveq src, dst 

也就是x86匯編里面表鳍,源在前面,目標(biāo)在后面祥诽。

a(%register)

表示register的值加上a這個偏移量指向的地址譬圣。所以上面

movq %rax, -24(%rbp)

相當(dāng)于*(rbp - 24) = rax

如果源是常數(shù)雄坪,那么直接使用一個符號加上這個常數(shù)就可以了厘熟,比如```90```就表示常數(shù)90。

如果我們把數(shù)組的大小變大一點维哈,比如下面的代碼

int main()
{
    int scores[40] = {90, 95};
    return 0;
}

反匯編我們可以看到

1d-array-main-2.jpg

那也許有的人會問绳姨,是不是默認(rèn)就會清零?我們也可以驗證一下阔挠,把代碼修改為

int main()
{
    int scores[40];
    return 0;
}

反匯編我們可以看到

1d-array-main-3.jpg

從匯編代碼我們可以看到是完全沒有默認(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)存中的排布為

2d-array.png

我們給定下面一段代碼

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;
}

反匯編的代碼如下

2d-array-as-1d.jpg

從上面反匯編我們可以很容易看出狞甚,編譯之后并沒有多維的概念,都是一維的廓旬,多維的概念只存在于語言層面哼审。但是,這并不影響我們按照概念使用孕豹。從上面的匯編更多可以容易看出多維數(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是可以編譯的饱溢。

這里面就有兩個問題需要我們注意:

  1. 可變參數(shù)數(shù)組是一個很好的特性喧伞,但是如果我們想要代碼可移植性強,最好不要在C++中使用它绩郎。當(dāng)然了潘鲫,純C程序可以放心使用;
  2. 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棚赔。從反匯編我們也可以看一下

array-ele-access.jpg

我們可以看到無論是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的地址值是多少撩笆?

首先我們需要知道,對于地址的加減法缸浦,v+1中1的量綱是sizeof(*v)夕冲。也就是1的大小用字節(jié)表示是v所指對象的大小。

&scores+1中的1用字節(jié)表示就是
\begin{aligned} sizeof(*\&scores)&=sizeof(scores)\\ &=2\times 3\times 4\times sizeof(int) \\ &= 96 \end{aligned}
所以&scores+1的地址用a表示就是a+96裂逐。

同理我們可以計算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是這樣的

var-array-c.jpg

在main.cpp就成了

var-array-cpp.jpg

本質(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)注更及時看到文章婴氮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市盾致,隨后出現(xiàn)的幾起案子主经,更是在濱河造成了極大的恐慌,老刑警劉巖庭惜,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罩驻,死亡現(xiàn)場離奇詭異硼控,居然都是意外死亡蝉仇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門拄养,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骏啰,“玉大人节吮,你說我怎么就攤上這事∨懈” “怎么了透绩?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我渺贤,道長雏胃,這世上最難降的妖魔是什么请毛? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任志鞍,我火速辦了婚禮,結(jié)果婚禮上方仿,老公的妹妹穿的比我還像新娘固棚。我一直安慰自己,他們只是感情好仙蚜,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布此洲。 她就那樣靜靜地躺著,像睡著了一般委粉。 火紅的嫁衣襯著肌膚如雪呜师。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天贾节,我揣著相機與錄音汁汗,去河邊找鬼。 笑死栗涂,一個胖子當(dāng)著我的面吹牛知牌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斤程,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼角寸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了忿墅?” 一聲冷哼從身側(cè)響起扁藕,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疚脐,沒想到半個月后纹磺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡亮曹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年橄杨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片照卦。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡式矫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出役耕,到底是詐尸還是另有隱情采转,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站故慈,受9級特大地震影響板熊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜察绷,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一干签、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拆撼,春花似錦容劳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至莺禁,卻和暖如春留量,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哟冬。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工楼熄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柒傻。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓孝赫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親红符。 傳聞我的和親對象是個殘疾皇子青柄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容