數(shù)組,存儲(chǔ)同類型的復(fù)合類型;結(jié)構(gòu)體,存儲(chǔ)不同類型的復(fù)合類型,用于自定義數(shù)據(jù)結(jié)構(gòu)。
成都創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比龍華網(wǎng)站開(kāi)發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式龍華網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋龍華地區(qū)。費(fèi)用合理售后完善,十載實(shí)體公司更值得信賴。
計(jì)算機(jī)中,針對(duì)存儲(chǔ)大量數(shù)據(jù)的集合,有著兩種方式,一種是以塊式集中存儲(chǔ)數(shù)據(jù),這就是數(shù)組的存儲(chǔ)方式,大量同類型的數(shù)據(jù)集中放在一塊;另外一種大量數(shù)據(jù)逐個(gè)分開(kāi),但其存儲(chǔ)的數(shù)據(jù)項(xiàng)就包括下一個(gè)數(shù)據(jù)的存儲(chǔ)地址,就像一個(gè)方向標(biāo),指向下一個(gè)數(shù)據(jù),整體來(lái)看就像連接起來(lái)的表格,所以這種結(jié)構(gòu)被稱為鏈表。
數(shù)組作為順序存儲(chǔ)的典型,存儲(chǔ)相同類型的值,以該類型存儲(chǔ)大小為單位劃分,其長(zhǎng)度就是其容量,可容納多少個(gè)該類型的值在聲明之初就定好了,數(shù)組內(nèi)元素的訪問(wèn)可通過(guò)下標(biāo)進(jìn)行訪問(wèn)(下標(biāo)就是整數(shù)索引,從0開(kāi)始)。
數(shù)組的常見(jiàn)聲明如下:
type array[size];
數(shù)組常見(jiàn)的聲明就是數(shù)據(jù)類型加,數(shù)組名后面中括號(hào)括起來(lái)數(shù)組大小。在程序中,數(shù)組往往用來(lái)存儲(chǔ)重要數(shù)據(jù),它們的使用往往需要先進(jìn)行初始化,比較直接的就是用大括號(hào)括起來(lái)的數(shù)值列表對(duì)數(shù)組進(jìn)行初始化(數(shù)值個(gè)數(shù)不得大于數(shù)組大小)。如下:
//完整的初始化
int nums1[4] = {1, 2, 3, 4};
//部分初始化
int nums2[4] = {1, 2};
//C99后支持的新特性,只初始化最后一個(gè)元素
int nums3[4] = {[3] = 4};
//不明確指定數(shù)組大小的聲明初始化
int nums4[] = {1, 2, 3, 4, 5};
//錯(cuò)誤示范
int nums5[4] = {1, 2, 3, 4, 5};
int nums6[4];
//下面這步是非法的
nums6 = {1, 2, 3, 4};
如上,數(shù)組的初始化是在聲明之初就該進(jìn)行了的,而且初始化接受部分的初始化,這種初始化是從數(shù)組第一個(gè)元素開(kāi)始初始化直到用完常量值,剩下的由編譯器自動(dòng)賦以類型零值。上面這種用大括號(hào)括起來(lái)的列表來(lái)對(duì)數(shù)組進(jìn)行初始化,在c++里面被稱為列表初始化,所以這里也這么稱呼吧,方便點(diǎn)。如上面的錯(cuò)誤例子,當(dāng)數(shù)組在初始化時(shí)初始化列表大于數(shù)組容量時(shí),編譯器直接報(bào)錯(cuò),而且聲明以后,在后面的語(yǔ)句進(jìn)行列表初始化也是非法的,同樣會(huì)報(bào)錯(cuò),但可以聲明固定大小的數(shù)組而不初始化。還有如上面nums4這樣不指定size的,聲明初始化以后,其大小就是初始化的列表長(zhǎng)度,也就是說(shuō)nums4的長(zhǎng)度是5。
數(shù)組是一個(gè)集合,數(shù)組的使用往往就是數(shù)組內(nèi)元素的讀取和寫入,而數(shù)組內(nèi)元素的調(diào)用可以通過(guò)數(shù)組下標(biāo)來(lái)索引該元素,而數(shù)組的下標(biāo)索引從0開(kāi)始。下面是幾個(gè)應(yīng)用方式:
//1
int nums[4] = {1, 2, 3, 4};
scanf("%d", &nums[0]); //"5"
printf("%d\n", nums[0]); //"5"
//2
int nums1[4] = {[3] = 10};
printf("%d, %d\n", nums1[0], nums1[3]); //"0, 10"
scanf("%d", &nums[0]); //"8"
printf("%d, %d\n", nums1[0], nums1[3]); //"8, 10"
//3
int nums2[4];
printf("%d, %d, %d, %d\n", nums2[0], nums2[1], nums2[2], nums2[3]); //"8, 0, 20, 0"
scanf("%d%d%d%d", &nums2[0], &nums2[1], &nums2[2], &nums2[3]); //"0 1 2 3"
printf("%d, %d, %d, %d\n", nums2[0], nums2[1], nums2[2], nums2[3]); //"0, 1, 2, 3"
如上面的三個(gè)例子所示,三個(gè)整型數(shù)組,三種狀態(tài)下(完全初始化、部分初始化,只聲明)的讀取和寫入。其實(shí),數(shù)組的使用,和for這種計(jì)數(shù)循環(huán)天然適配,如下:
int nums[4], i;
//寫入數(shù)據(jù)
for(i = 0;i < 4;i++)
scanf("%d", &nums[i]); //逐行輸入"0"、"1"、"2"、"3"
//讀取
for(i=0 ;i < 4; i++)
printf("%d, ", nums[i]);
//"0, 1, 2, 3,"
上面使用數(shù)組元素的方式是基于下標(biāo)來(lái)進(jìn)行,但也有另一種方式進(jìn)行調(diào)用。數(shù)組名的值,本身就是一個(gè)指針常量,它是數(shù)組第一個(gè)元素的地址,所以,數(shù)組元素的調(diào)用也可以用指針來(lái)進(jìn)行。如下:
int nums[4] = {0, 1, 2, 3};
printf("%d, %d\n", *nums, *(nums + 2)); //"0, 2"
int *p = nums, i;
for(i = 0;i < 4; i++)
printf("%d, ", *(p + i)); //"0, 1, 2, 3, "
如上使用都是可以的,不過(guò)使用指針要注意的就是*取值符和&取址符,指針本身是指向某地址的變量,而*取值符的作用就是用來(lái)取指針指向地址的值,而取址符對(duì)指針本身往往沒(méi)有多大使用,因?yàn)槭褂脠?chǎng)景往往更關(guān)注指針指向地址的值,這里是想提醒不能把指針當(dāng)做尋常變量那樣使用取址符&。
要注意的,數(shù)組的使用和指針有共通之初,但并非等同于指針。
在實(shí)際生活中,數(shù)據(jù)集有集合形式,也有矩陣形式,針對(duì)這種種數(shù)據(jù)的處理,C中往往都用數(shù)組進(jìn)行,只是數(shù)組的形式有所不同。集合列表用一維數(shù)組,矩陣用二維數(shù)組,乃至有三維數(shù)組、四維數(shù)組應(yīng)付更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。這部分進(jìn)行的就是對(duì)數(shù)組的學(xué)習(xí)解讀。
數(shù)組的所謂二維三維方面,在這里的體現(xiàn),用下標(biāo)展示會(huì)更加直觀,如下聲明定義一個(gè)二維數(shù)組:
//完全初始化
int matrix[2][3] = {
{0, 1, 2},
{3, 4, 5}
};
//部分初始化
int matrix1[2][3] = {
{0, 1},
{3, 4}
};
//不指定數(shù)組大小的聲明初始化
int matrix2[][3] = {
{0, 1, 2},
{3, 4, 5}
};
回顧一下,數(shù)組是相同類型元素的列表,一維數(shù)組是一個(gè)簡(jiǎn)單列表,里面存放著同類型的一個(gè)個(gè)常量值,那二維數(shù)組呢?它則是存放著一個(gè)個(gè)一維數(shù)組的另類列表,所以,不去深究數(shù)組內(nèi)的元素,其實(shí)二維數(shù)組和一維數(shù)組乃至多維數(shù)組都是一樣的,它們都是一個(gè)有序列表。
就著上面的結(jié)論來(lái)看上面的例子就簡(jiǎn)單多了,matrix是一個(gè)存放著兩個(gè)數(shù)組的列表,內(nèi)層數(shù)組則存放著三個(gè)整型數(shù)據(jù)。因此,可以在數(shù)組大小范圍已定的情況下,用不足其大小的列表去對(duì)其進(jìn)行初始化,比如matrix1,還有不明確外層數(shù)組數(shù)量的情況用符合內(nèi)層大小的一定個(gè)數(shù)的數(shù)組去對(duì)數(shù)組初始化,比如matrix2(還是有點(diǎn)拗口)。上面的例子也可以改成下面的樣子:
//完全初始化
int matrix[2][3] = { {0, 1, 2},{3, 4, 5} };
int matrix[2][3] = {0, 1, 2, 3, 4, 5};
//部分初始化
int matrix1[2][3] = { {0, 1},{3, 4} };
int matrix1[2][3] = {0, 1, 3, 4};
//不指定數(shù)組大小的聲明初始化
int matrix2[][3] = { {0, 1, 2},{3, 4, 5} };
int matrix2[][3] = {0, 1, 2, 3, 4, 5};
這樣來(lái)看,就比較直觀,實(shí)際上二維數(shù)組的元素也是順序存放的。針對(duì)二維數(shù)組元素的訪問(wèn),還是使用下標(biāo)的方式比較直觀,但使用指針的方式進(jìn)行訪問(wèn)也是可以的,不過(guò)相對(duì)來(lái)說(shuō),就形式來(lái)看,比較麻煩,一層疊一層的。
int matrix[2][3] = {0, 1, 2, 3, 4, 5};
int *p = matrix; //GNU中會(huì)有警告,沒(méi)有在vs嘗試
printf("%d\n", *(p+4)); //"4"
printf("%d\n", *((p+1)+1)); //"2",這里被計(jì)算器理解成p+2的取值
int (*p1)[3] = matrix; //定義一個(gè)int [3]類型的指針,初始化其值使其指向matrix
printf("%d, %d\n", *(*p1 + 1), *(*(p1+1)+1)); //讀取二維數(shù)組第一行第二列和第二行第二列的值,輸出"1, 4"
總的來(lái)說(shuō),二維數(shù)組的指針調(diào)用方式有兩種,一種是當(dāng)做一維數(shù)組的正常偏移調(diào)用,第二種就是聲明一個(gè)指向內(nèi)層數(shù)組類型的指針,并初始化為指向matrix指向地址。因?yàn)?運(yùn)算符的優(yōu)先度要低于[]運(yùn)算符,所以為了表明指針身份,需要把變量名和*運(yùn)算符括起來(lái);另外,需要重點(diǎn)說(shuō)明的是上面的p1是一個(gè)int [4]類型的指針,(指針,是指向某地址的變量,具體來(lái)說(shuō)是某種類型值的地址),所以,實(shí)際上類型對(duì)于指針就是束縛,防止它訪問(wèn)的存儲(chǔ)越界從而得到期望以外的值,而int [4]類型也是束縛,可以把這樣的4個(gè)整型連起來(lái)的存儲(chǔ)空間看做一個(gè)單位,現(xiàn)在的指針就指向這么一種單位的地址,當(dāng)它初始化成matrix的地址后進(jìn)行偏移,它實(shí)際上就是以matrix的內(nèi)層數(shù)組為單位進(jìn)行偏移。順便說(shuō)一句,有確定類型的指針也有空類型的指針。
和for循環(huán)配合使用的二維數(shù)組
因?yàn)槎S數(shù)組可以展開(kāi)成一維數(shù)組,所以用循環(huán)調(diào)用就有嵌套和不嵌套的使用,如下:
int matrix[2][3] = {
{0, 1, 2},
{3, 4, 5}
};
int i, j, *p = matrix;
//嵌套循環(huán)
for(i = 0;i< 2;i++) {
for(j = 0;j < 3;j++)
printf("%d, ", matrix[i][j]);
printf("\n");
}
//"0, 1, 2, "
//"3, 4, 5, "
//不嵌套循環(huán),編譯器報(bào)警系列
for(i = 0;i < 6; i++)
printf("%d, ", *(p + i));
//"0, 1, 2, 3, 4, 5, "
//嵌套循環(huán)
int (*p1)[3] = matrix;
for(i = 0;i<2;i++) {
for(j=0;j<3;j++)
printf("%d, ", *(*(p + i) + j));
printf("\n");
}
//"0, 1, 2, "
//"3, 4, 5, "
多維數(shù)組和二維數(shù)組共通,只不過(guò)是集合往更深一層嵌套,這里就不展開(kāi)了。
在日常應(yīng)用中,數(shù)組的長(zhǎng)度往往是固定的,固定的方式各不一,比較常見(jiàn)的,就是使用宏定義定下數(shù)組長(zhǎng)度,而這種數(shù)組往往存儲(chǔ)的就是一個(gè)個(gè)常量數(shù)據(jù),是日常生活中基本不動(dòng)的數(shù)據(jù),比如year數(shù)組就該有12個(gè)數(shù),每個(gè)數(shù)存放每個(gè)月的天數(shù);week數(shù)組就該存著周日到周一這么幾個(gè)數(shù)值,具體是字符串還是整型數(shù)就看需要了。如下:
#define MONTH 12
#define WEEK 7
//const限定符
const int leap_year[MONTH] = {31, 28, 31, 30, 31, 30, 31, 30, 31, 31, 30, 31};
const char week[WEEK] = {"Sunday", "Monday", "Tuesday", "Wedsday", "Thursday", "Friday", "Saturday"};
除此以外,還可以用整型變量或者整型表達(dá)式來(lái)確定數(shù)組長(zhǎng)度,在C99之前,規(guī)定的標(biāo)準(zhǔn)是以整形常量表達(dá)式為數(shù)組確定大小,而不是整型表達(dá)式,這是C99后添加的新特性。其實(shí)就個(gè)人來(lái)看,無(wú)非就是一句語(yǔ)句和兩句語(yǔ)句的區(qū)別。不過(guò)這里也說(shuō)明相對(duì)一詞的重要性,變量,不確定的,但在運(yùn)行程序中它是確定的,也就是相對(duì)程序運(yùn)行來(lái)說(shuō),它是固定的。這個(gè)就是變長(zhǎng)數(shù)組--VLA。
int sum, a, b;
//輸入a、b值
//c99之前
sum = a * b;
int num[sum];
//c99以后
int num[a*b];
相較于一開(kāi)始就用常量給定區(qū)域的數(shù)組而言,變長(zhǎng)數(shù)組也算是動(dòng)態(tài)數(shù)組了,這種數(shù)組長(zhǎng)度在程序運(yùn)行時(shí)確定的數(shù)組就是動(dòng)態(tài)數(shù)組,反過(guò)來(lái)說(shuō),運(yùn)行以前確定的,就是靜態(tài)數(shù)組了(因?yàn)槌A烤褪沁\(yùn)行前就確定了的)。除此以外,還有一種動(dòng)態(tài)數(shù)組,這種數(shù)組隨程序需要而確定大小,內(nèi)存空間也是自己申請(qǐng),使用完畢自己釋放。這種申請(qǐng)的內(nèi)存, 來(lái)自于堆,由于數(shù)組是可以逐層嵌套的,對(duì)于這種數(shù)組就需要自外向里,逐層創(chuàng)建,而釋放則是反過(guò)來(lái),由里向外逐層釋放。而這種,才是常說(shuō)的動(dòng)態(tài)數(shù)組。
堆區(qū)和棧區(qū)
對(duì)于一個(gè)運(yùn)行程序來(lái)說(shuō),內(nèi)存常被分為棧區(qū)、堆區(qū)、常量區(qū)、靜態(tài)區(qū)和代碼區(qū)。初始,程序以可執(zhí)行代碼的形式存放在磁盤中,操作系統(tǒng)在運(yùn)行程序的時(shí)候就會(huì)把代碼和靜態(tài)數(shù)據(jù)(如初始化變量)加載到內(nèi)存中,加載完畢后,分配內(nèi)存給運(yùn)行時(shí)棧(存放局部變量、函數(shù)參數(shù)和返回地址,main也是函數(shù)),除此以外,還有著堆內(nèi)存由程序顯式請(qǐng)求分配,對(duì)于數(shù)組、一些數(shù)據(jù)結(jié)構(gòu)(鏈表、散列表、樹(shù)等)都需要堆區(qū)存儲(chǔ),隨程序運(yùn)行的時(shí)候逐漸變化。
在C語(yǔ)言中,內(nèi)存的顯式請(qǐng)求和顯式釋放都有兩個(gè)專門的函數(shù)--malloc函數(shù)和free函數(shù),而c++中則是new運(yùn)算符和delete運(yùn)算符申請(qǐng)和釋放。上面兩函數(shù)原型如下:
#include <stdlib.h>
void *malloc(int num);
void free(void *address);
//另外幾個(gè)相關(guān)函數(shù)
void *calloc(int num, int size);
//在內(nèi)存中動(dòng)態(tài)地分配 num 個(gè)長(zhǎng)度為 size 的連續(xù)空間,并將每一個(gè)字節(jié)都初始化為 0。所以它的結(jié)果是分配了 num*size 個(gè)字節(jié)長(zhǎng)度的內(nèi)存空間,并且每個(gè)字節(jié)的值都是0。
void *realloc(void *address, int newsize);
//該函數(shù)重新分配內(nèi)存,把內(nèi)存擴(kuò)展到 newsize。
malloc函數(shù)的用法,申請(qǐng)num指定字節(jié)的內(nèi)存并返回指向該內(nèi)存的空指針,一般在賦值給特定指針前需要強(qiáng)制類型轉(zhuǎn)換;free則是把a(bǔ)ddress指向的堆內(nèi)存進(jìn)行釋放,沒(méi)有返回值。常見(jiàn)的配合數(shù)組的使用如下:
#include <stdlib.h>
int *p;
p = (int *)malloc(4* sizeof(int));
for(i = 0;i < 4; i++)
p[i] = i;
for(i = 0;i < 4;i++)
printf("%d, ", p[i]);
free(p);
如上,申請(qǐng)一個(gè)4整型大小的內(nèi)存空間,在強(qiáng)制類型轉(zhuǎn)換后賦值給整形指針p,這時(shí)候就可以針對(duì)p指針做數(shù)組操作了(指針操作也行),重點(diǎn),用完后記得free。
動(dòng)態(tài)二維數(shù)組
上面已經(jīng)有二維數(shù)組的學(xué)習(xí),所以這里主要是一個(gè)嵌套malloc和free的調(diào)用例子
#include <stdio.h>
#include <stdlib.h>
int main() {
int **p, i, j;
//定義一個(gè)指向指針數(shù)組的指針,并指向能存下3個(gè)整型指針的連續(xù)內(nèi)存
p = (int **)malloc(3*sizeof(int *));
//逐層分配空間并賦值
for (i = 0;i < 3; i++){
p[i] = (int *)malloc(4*sizeof(int));
for(j = 0;j < 4;j++)
p[i][j] = i * 3 + j + 1;
}
//從數(shù)組末開(kāi)始讀取值,一行讀取完畢就釋放那一行的內(nèi)存
for(i = 2;i >= 0;i--){
for(j=3;j>= 0;j--)
printf("%d, ", p[i][j]);
printf("\n");
free(p[i]);
}
//釋放最外層的指針存放內(nèi)存
free(p);
return 0;
}
/*輸出
10, 9, 8, 7,
7, 6, 5, 4,
4, 3, 2, 1,
*/
如上,針對(duì)動(dòng)態(tài)二維數(shù)組的內(nèi)存申請(qǐng)和釋放就更能體現(xiàn)二維數(shù)組的性質(zhì),外層數(shù)組存放內(nèi)層數(shù)組地址,通過(guò)地址訪問(wèn)到該數(shù)組內(nèi)容,而內(nèi)數(shù)組也用下標(biāo)把信息拆分得更加細(xì)致。嗯,雖說(shuō)上面的例子是從數(shù)組最深處開(kāi)始訪問(wèn)并逐漸向外面開(kāi)始釋放,但我正常從第一行內(nèi)層數(shù)組開(kāi)始釋放也沒(méi)有出現(xiàn)問(wèn)題,暫時(shí)沒(méi)有出現(xiàn)什么問(wèn)題,但還是能倒著釋放就倒著來(lái)吧。
指針就一個(gè)功能,指向某個(gè)地址,然后根據(jù)其指向的地址,它就有了不同的稱呼:
數(shù)組是持續(xù)內(nèi)存,在訪問(wèn)內(nèi)部元素的時(shí)候一旦越界,就會(huì)出現(xiàn)意料之外的行為。比如有一個(gè)元素長(zhǎng)度為4的數(shù)組a,但在訪問(wèn)a[4]這種行為的時(shí)候,就屬于明顯的越界,但編譯器并不會(huì)報(bào)錯(cuò),這種問(wèn)題的爆發(fā)會(huì)出現(xiàn)在程序運(yùn)行的時(shí)候,有時(shí)候會(huì)輸出期望值以外的值,有時(shí)候則會(huì)意外停止。所以這里是做一個(gè)警告,在進(jìn)行下標(biāo)訪問(wèn)的時(shí)候,要注意不能越界,另外像字符串這種字符數(shù)組,要習(xí)慣性地在數(shù)組末尾放置一個(gè)'\0'符。
- 從內(nèi)存存儲(chǔ)上看,數(shù)組是同類型元素的集合,是一大塊固定數(shù)量類型的存儲(chǔ);指針是存儲(chǔ)同類型地址的對(duì)象,根據(jù)系統(tǒng)的不同,其存儲(chǔ)也不同,但其不大于8字節(jié)內(nèi)存
- 從聲明定義上看,數(shù)組的整體定義要在聲明之時(shí),否則就只能逐一賦值,而指針可以拆開(kāi)進(jìn)行,而且也可以指向不同的地址
- 從使用上看,數(shù)組使用下標(biāo)進(jìn)行元素訪問(wèn),指針用*運(yùn)算符取值,配合數(shù)組可以進(jìn)行地址偏移
作為復(fù)合類型的重要組成,結(jié)構(gòu)體可以由其他各種數(shù)據(jù)類型組成,共同構(gòu)成一個(gè)表達(dá)特殊意義的數(shù)據(jù)結(jié)構(gòu),也是面向?qū)ο缶幊痰囊粋€(gè)重要出發(fā)點(diǎn)。場(chǎng)景切入,當(dāng)我們要描述一個(gè)對(duì)象的時(shí)候,比如一個(gè)學(xué)生,那我們可以用哪幾個(gè)數(shù)據(jù)?成績(jī)、身高、體重、年級(jí)、班級(jí),等等皆可,針對(duì)我們需求的場(chǎng)景,抽取重要屬性成結(jié)構(gòu)體,程序需要處理的,就是結(jié)構(gòu)體的屬性數(shù)據(jù)。當(dāng)我們需要做一個(gè)成績(jī)系統(tǒng),就需要學(xué)生的各科成績(jī),當(dāng)我們需要做健康檢測(cè),就需要學(xué)生的身高、體重、視力等數(shù)據(jù),需求不同,使用的屬性不同。當(dāng)我們確定好一個(gè)數(shù)據(jù)結(jié)構(gòu)以后,就是確定了一個(gè)自定義類型,我們可以用這個(gè)類型來(lái)聲明定義需要的變量。結(jié)構(gòu)體的通用形式如下:
struct tag {
member-list
member-list
member-list
...
} variable-list ;
上面上面中,member-list就是成員列表,用字符型、整型、浮點(diǎn)型來(lái)填充,其變量名就是結(jié)構(gòu)體tag的屬性名,variable-list就是成員列表,一般來(lái)說(shuō)如果不是聲明為函數(shù)內(nèi)局部變量,都不會(huì)在聲明結(jié)構(gòu)體時(shí)就聲明這種類型的變量,所以一般全局的tag這里都是空留一個(gè)分號(hào)收尾。
結(jié)構(gòu)體的聲明,其實(shí)是用戶自定義了一個(gè)類型,它告訴了編譯器這種類型由哪些數(shù)據(jù)構(gòu)成。
結(jié)構(gòu)體是不同類型的集合,數(shù)組是同類型的集合,但類型其實(shí)從內(nèi)存角度上來(lái)說(shuō),就是劃分字節(jié)和讀取規(guī)則不一樣,兩者都是一大塊內(nèi)存,所以數(shù)組的初始化,很多可以參考數(shù)組。
要初始化一個(gè)結(jié)構(gòu)體,先要聲明一個(gè)結(jié)構(gòu)體,現(xiàn)在就抽取學(xué)生這一對(duì)象作為一個(gè)結(jié)構(gòu)體,抽象其姓名、年齡和成績(jī)作為基本屬性,如下:
struct student {
char name[10];
int age;
float total_score;
};
struct student a = {"Xiao Ming", 14, 89.7};
如上為student結(jié)構(gòu)體的一個(gè)聲明(這個(gè)聲明一般放在所有函數(shù)外面作為全局變量使用),聲明student類型的a對(duì)象,用的也是括號(hào)初始化。需要注意,C和C++針對(duì)結(jié)構(gòu)體對(duì)象的聲明有點(diǎn)不同,C中進(jìn)行聲明需要添加struct突出結(jié)構(gòu)體類型,c++中則是可選項(xiàng),可加可不加。
當(dāng)然也可以進(jìn)行聲明,然后逐個(gè)元素進(jìn)行初始化,不過(guò)student的name屬性是數(shù)組,所以它只能在聲明之時(shí)初始化,或者使用strcpy等方法進(jìn)行賦值。
//只聲明不初始化
struct student temp;
temp.name = "Siri"; //error: assignment to expression with array type
strcpy(temp.name, "Siri"); //使用前要引入頭文件string.h
temp.age = 10; //合法
printf("%f\n", temp.total_score); //"0.000000",編譯器自動(dòng)初始化為float類型零值
另外初始化的方法還有:
struct student b = {.name = "Xiao Fei",
.age = 15,
.total_score = 98.6};
如上面這種方式就是C99和C11后為結(jié)構(gòu)體添加的指定初始化器(designated initializer),在大括號(hào)內(nèi)用點(diǎn)運(yùn)算符和屬性名標(biāo)識(shí)特定元素,等號(hào)賦值初始化。當(dāng)然也可以部分初始化,這樣的話,沒(méi)有初始化的部分就會(huì)被編譯器自動(dòng)初始化為類型零值。
目前,關(guān)于結(jié)構(gòu)體屬性的訪問(wèn),可以使用點(diǎn)運(yùn)算符來(lái)進(jìn)行,如上面的指定初始化器中的用法,點(diǎn)運(yùn)算符加屬性名就可以訪問(wèn)對(duì)應(yīng)屬性,這就有點(diǎn)類似數(shù)組下標(biāo)。實(shí)際上,一般的結(jié)構(gòu)體對(duì)象只有這種訪問(wèn)方式,而結(jié)構(gòu)體指針有著另外的方式來(lái)訪問(wèn)結(jié)構(gòu)體屬性。
struct student *p = &a;
printf("%f\n", p->total_score); //"98.6"
如上,作為指向結(jié)構(gòu)體的指針,適用的訪問(wèn)結(jié)構(gòu)體屬性的方法就是箭頭,突出指針的存在。
結(jié)構(gòu)體是自定義類型,c中任何類型都可以產(chǎn)生指針和數(shù)組,所以就也有結(jié)構(gòu)體數(shù)組和結(jié)構(gòu)體指針,它們的聲明定義和具體元素的訪問(wèn)沒(méi)有特殊之處,只不過(guò)湊在一起會(huì)讓人一下子有點(diǎn)不適。
struct student stu[3] = {
{"Xiao Ming", 14, 89.7},
{"Siri", 11, 80},
{"Xiao Fei", 88}
};
//錯(cuò)誤,這種聲明只能在結(jié)構(gòu)體類型聲明的時(shí)候進(jìn)行,如下
struct student {
char name[10];
int age;
float total_score;
} stu[3] = {
{"Xiao Ming", 14, 89.7},
{"Siri", 11, 80},
{"Xiao Fei", 8}
};
printf("%d\n", stu[2].age); //"8"
//只聲明,后續(xù)可以像數(shù)組只聲明那樣進(jìn)行類似的初始化
struct student stu[4];
上面就是結(jié)構(gòu)體數(shù)組的聲明定義,關(guān)于結(jié)構(gòu)體指針的聲明比較簡(jiǎn)單,就簡(jiǎn)單的類型加*標(biāo)識(shí)即可,然后用同類型普通變量的地址給予賦值就算初始化了。另外結(jié)構(gòu)體數(shù)組的元素調(diào)用用的是下標(biāo)訪問(wèn),而結(jié)構(gòu)體指針用的是箭頭訪問(wèn),但當(dāng)指針指向數(shù)組時(shí),它用的,還是下標(biāo)訪問(wèn)方法。
復(fù)合嵌套
上面從一開(kāi)始,結(jié)構(gòu)體的屬性就納入了數(shù)組這一復(fù)合類型,所以結(jié)構(gòu)體也是可以內(nèi)嵌指針的,而且把數(shù)組改換成指針,也會(huì)方便很多。當(dāng)然,在C語(yǔ)言中,方便與危險(xiǎn)等同,越是簡(jiǎn)單的地方,越容易出事,不過(guò)這里不討論。作為描述對(duì)象的結(jié)構(gòu)體,對(duì)象內(nèi)部也可以有對(duì)象屬性,所以結(jié)構(gòu)體同樣可以嵌套結(jié)構(gòu)體,比如給student拆解名字屬性,名字屬性就可以作為一個(gè)對(duì)象,內(nèi)部填充姓和名,如下:
struct Name {
char *surname;
char *name;
};
struct student {
struct Name name;
int age;
float total_score;
};
本來(lái)是想直接把student放前面,Name搞個(gè)前置聲明的,發(fā)現(xiàn)不行,而且結(jié)構(gòu)體的聲明也不能進(jìn)行初始化,還有各種各樣的特性。。。。。。還是c++用多了,總把c的struct當(dāng)做類class。
聲明還是比較簡(jiǎn)單的,下面介紹一下其初始化和使用:
//main內(nèi)
int i;
//普通student對(duì)象
struct student a = {{"Jack", "Chen"}, 25, 100.0};
//student數(shù)組
struct student stu[2] = {
{{"Hong", "Pan"}, 26, 88},
{{"Jin", "Jiang"}, 27, 98}
};
//student指針
struct student *p = &a;
printf("name: %s %s\n", a.name.surname, a.name.name);
for(i=0;i<2;i++)
printf("name: %s %s, score: %.2f\n", stu[i].name.surname, stu[i].name.name, stu[i].total_score);
printf("age: %d\n", p->age);
//聲明student對(duì)象但不初始化
struct student temp;
temp.name.surname="Siri";
temp.name.name="MI";
scanf("%d", &temp.age); //80
printf("age: %d\n", temp.age); //"80"
算是比較簡(jiǎn)單的了,具體的實(shí)驗(yàn)進(jìn)行,就看個(gè)人經(jīng)驗(yàn)了。當(dāng)這些個(gè)簡(jiǎn)單的概念組合在一起變得龐大的時(shí)候,就大而化之,關(guān)注最底層的那一部分的特性,往往能更好理解和使用。
上面有介紹過(guò)動(dòng)態(tài)數(shù)組,它是由malloc顯式向堆內(nèi)存申請(qǐng)?zhí)囟ù笮〉目臻g并由free顯式釋放的;同樣的,結(jié)構(gòu)體也可以,應(yīng)該說(shuō),它往往是這么使用的,當(dāng)它作為一個(gè)重要的數(shù)據(jù)結(jié)構(gòu)的時(shí)候。
#include <stdlib.h>
struct student *p;
p = (struct student *)malloc(2*sizeof(struct student));
/*一堆應(yīng)用操作*/
free(p);
其實(shí)這里并不復(fù)雜,但也只是一個(gè)概念的記錄,復(fù)雜的是引入數(shù)據(jù)結(jié)構(gòu)以后的事,這里篇幅也不夠,只做記錄。
先舉一個(gè)例:
struct Name {
char *surname;
char *name;
};
struct A {
int age;
struct Name name;
float total_score;
};
struct B {
int age;
float total_score;
struct Name name;
};
printf("A: %d, B: %d\n", sizeof(struct A), sizeof(struct B));
//輸出:A: 32, B: 24
printf("ptr: %d, int: %d, float: %d, Name: %d\n", sizeof(char *), sizeof(int), sizeof(float), sizeof(struct Name));
//輔助數(shù)據(jù):"ptr: 8, int: 4, float: 4, Name: 16"
上面這個(gè)例子,指的是有著同樣屬性的A、B兩個(gè)結(jié)構(gòu)體,卻因?yàn)閷傩缘南群舐暶黜樞虿灰粯?,使得sizeof得到的占用內(nèi)存不一樣。出現(xiàn)這種情況的原因,就是這里要介紹的內(nèi)容--字節(jié)對(duì)齊。
首先,說(shuō)明一下字節(jié)對(duì)齊是如何對(duì)齊。在C中,一個(gè)變量,一塊內(nèi)存,一個(gè)復(fù)合變量,一大塊內(nèi)存,比如數(shù)組,但結(jié)構(gòu)體就不一樣,因?yàn)樗娴氖遣煌愋偷淖兞?,不像?shù)組那樣內(nèi)部元素一致整齊。所以,字節(jié)對(duì)齊,就是針對(duì)結(jié)構(gòu)體而言的一種調(diào)整了。所謂對(duì)齊,就是結(jié)構(gòu)體中所有成員在分配內(nèi)存時(shí)都要向成員中最大那個(gè)對(duì)齊,最大的那個(gè)作為一個(gè)標(biāo)準(zhǔn),當(dāng)?shù)谝粋€(gè)成員沒(méi)有超出這個(gè)標(biāo)準(zhǔn),后面緊跟著的也沒(méi)超出這個(gè)標(biāo)準(zhǔn),就會(huì)加在一起,看看是否到了這個(gè)標(biāo)準(zhǔn),到了就可以新開(kāi)一塊最高標(biāo)準(zhǔn)的內(nèi)存,沒(méi)到就繼續(xù)疊,最后湊起來(lái)的內(nèi)存塊就是最大那塊的倍數(shù)(這里考慮的都是基本類型,不包括復(fù)合類型)。如下:
struct A {
char a;
int i;
};
struct B {
char a;
char b;
int i;
};
struct C {
char a;
char b;
char c;
char d;
char e;
int i;
};
struct D {
char a;
int i;
double db;
};
struct E {
char a;
int i;
int i2;
double db;
};
printf("char: %d, int: %d, double: %d\n", sizeof(char), sizeof(int), sizeof(double));
//"char: 1, int: 4, double: 8"
printf("A: %d, B: %d, C: %d, D: %d\n", sizeof(struct A), sizeof(struct B), sizeof(struct C), sizeof(struct D));
//"A: 8, B: 8, C: 12, D: 16, E: 24"
上面的例子的空間結(jié)構(gòu)大概可以參考如下:
struct A
a | 空 | 空 | 空 |
---|---|---|---|
i | i | i | i |
占了8字節(jié) | |||
struct B |
|||
a | b | 空 | 空 |
--- | --- | --- | --- |
i | i | i | i |
占了8字節(jié) | |||
struct C |
|||
a | b | c | e |
--- | --- | --- | --- |
e | 空 | 空 | 空 |
i | |||
占了12字節(jié) | |||
struct D |
|||
a | 空 | 空 | 空 |
---- | ---- | --- | --- |
db | |||
占了16個(gè)字節(jié) |
struct E
|a|空|空|空|i|i|i|i|
|----|----|---|---|---|-|-|-|-|-|
|i2|i2|i2|i2|空|空|空|空|
|db||||||||||
占了24個(gè)字節(jié)
上面都是針對(duì)結(jié)構(gòu)體由簡(jiǎn)單的基本類型進(jìn)行的解讀,如果是復(fù)合類型呢?加進(jìn)去數(shù)組或者其他結(jié)構(gòu)體呢?這種存在就比較特殊,屬于好好的內(nèi)存塊里面凸出去的大頭,就像臉上的粉刺,讓臉看得沒(méi)有那么整潔雅觀。
其實(shí)加進(jìn)去數(shù)組也沒(méi)什么,結(jié)構(gòu)體也是基本類型的集合,雖說(shuō)不至于拆開(kāi)數(shù)組留空,但填補(bǔ)空缺還是夠的,最要命的還是內(nèi)嵌結(jié)構(gòu)體。如下:
struct Name {
char *surname;
char *name;
};
struct A {
int a;
struct Name name;
float f;
}sa;
struct B {
int a;
float f;
struct Name name;
}sb;
struct C {
struct Name name;
int a;
float f;
}sc;
printf("ptr: %d, int: %d, float: %d, Name: %d\n", sizeof(char *), sizeof(int), sizeof(float), sizeof(struct Name));
//輔助信息:"ptr: 8, int: 4, float: 4, Name: 16"
printf("A: %d, B: %d, C: %d\n", sizeof(struct A), sizeof(struct B), sizeof(struct C));
//"A: 32, B: 24, C: 24"
printf("A: %p, %p\nB: %p, %p\nC: %p, %p\n", &sa.a, &sa.name, &sb.f, &sb.name, &sc.name, &sc.a);
/*輔助性地址信息
A: 00000000004079C0, 00000000004079C8
B: 00000000004079A4, 00000000004079A8
C: 0000000000, 0000000000
*/
上面的例子就是自定義了一個(gè)存放兩個(gè)指針的算是規(guī)整的結(jié)構(gòu)體,然后把它作為大頭放進(jìn)三個(gè)同樣存放基本類型的結(jié)構(gòu)體中,分不同順序來(lái)進(jìn)行實(shí)驗(yàn),直觀點(diǎn)的內(nèi)存結(jié)構(gòu)如下:
struct A
a | a | a | a | 空 | 空 | 空 | 空 |
---|---|---|---|---|---|---|---|
name | |||||||
name | |||||||
f | f | f | f | 空 | 空 | 空 | 空 |
占據(jù)32位字節(jié),name獨(dú)占16字節(jié),分成8字節(jié),a和f被name分開(kāi),所以各自占了8字節(jié),但類型限制,實(shí)占4字節(jié),其余字節(jié)位空置 |
struct B
a | a | a | a | f | f | f | f |
---|---|---|---|---|---|---|---|
name | |||||||
name | |||||||
占24字節(jié),a和f合在一起湊足8字節(jié),name獨(dú)占兩個(gè)8字節(jié),同理,struct C也是一樣構(gòu)造 |
struct C
name | |||||||
---|---|---|---|---|---|---|---|
name | |||||||
a | a | a | a | f | f | f | f |
和上面的大B同理。上面就很明白了,哪怕是復(fù)合類型如結(jié)構(gòu)體,也不是簡(jiǎn)單看其整體內(nèi)存,在這部分其實(shí)要有個(gè)上限,那就是系統(tǒng)限定的字節(jié)對(duì)齊最大單位,當(dāng)所有成員中的最大成員超出這個(gè)單位,那就按系統(tǒng)的來(lái)。64位系統(tǒng)限定最大對(duì)齊為8字節(jié),32則是限定為4字節(jié)。 |
字節(jié)對(duì)齊的意義
在沒(méi)有字節(jié)對(duì)齊的時(shí)候,結(jié)構(gòu)體的內(nèi)存分配和數(shù)組一樣,都貼在一起的,這樣一來(lái)就會(huì)出現(xiàn)那么幾個(gè)數(shù)據(jù)湊成奇數(shù)內(nèi)存塊,而CPU讀取數(shù)據(jù)是根據(jù)數(shù)據(jù)總線來(lái)讀取的,16位數(shù)據(jù)總線可以一次讀取兩字節(jié)的數(shù)據(jù),32位數(shù)據(jù)總線一次能讀取4字節(jié)數(shù)據(jù),64位系統(tǒng)則是8字節(jié)數(shù)據(jù)。當(dāng)char型成員和int型成員湊在一起時(shí)為5字節(jié)內(nèi)存,32位系統(tǒng)就沒(méi)辦法一次讀取正確,需要讀取第二次,所以拓展開(kāi),char成員占4字節(jié)內(nèi)存的一字節(jié),其余置空,這樣對(duì)于CPU就能每次讀取正確。這算是空間內(nèi)存換取時(shí)間的意思了。字節(jié)對(duì)齊的存在是針對(duì)CPU的讀取效率問(wèn)題。
手動(dòng)字節(jié)對(duì)齊
當(dāng)兩臺(tái)機(jī)器進(jìn)行通信,它們需要使用一種數(shù)據(jù)結(jié)構(gòu)傳輸信息,那它們采取的字節(jié)對(duì)齊規(guī)則一致就顯得很有必要,這種時(shí)候就可以進(jìn)行手動(dòng)的字節(jié)對(duì)齊規(guī)則調(diào)整。方法有兩種,一種是預(yù)編譯中設(shè)定規(guī)則,另一種就是數(shù)組聲明時(shí)設(shè)定對(duì)齊規(guī)則,如下:
//設(shè)定其后的代碼中字節(jié)對(duì)齊以n為準(zhǔn)
#pragma pack(n)
struct A {
...//balabala
};
//設(shè)定其后的代碼中字節(jié)對(duì)齊根據(jù)默認(rèn)進(jìn)行
#pragma pack()
struct B {
...
}__attribute__((packed));
//上面的__attribut__是一個(gè)給編譯器看的設(shè)置,重點(diǎn)關(guān)注里面的參數(shù)
//packed參數(shù),表示按照實(shí)際來(lái)進(jìn)行字節(jié)對(duì)齊,也就是不對(duì)齊
以上僅為個(gè)人參考書(shū)籍博客等資料而后實(shí)驗(yàn)所得,一家之言。
文章題目:c數(shù)組與結(jié)構(gòu)體
本文鏈接:http://chinadenli.net/article6/dsoieog.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)、搜索引擎優(yōu)化、響應(yīng)式網(wǎng)站、動(dòng)態(tài)網(wǎng)站、企業(yè)網(wǎng)站制作、外貿(mào)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)