【C/C++基礎】11_使用者自定義資料型別
1. 結構體型別
1.1 結構體型別定義的一般形式
在實際問題中,一組資料往往具有不同的資料型別。例如,在學生登記表中,姓名應為字元型;學號可為整型或字元型;年齡應為整型;性別應為字元型;成績可為整型或實型。顯然不能用一個數組來存放這一組資料。因為陣列中各元素的型別和長度都必須一致,以便於編譯系統處理。為了解決這個問題,C語言中給出了另一種構造資料型別——“結構(structure)”或叫“結構體”。 它相當於其它高階語言中的記錄。“結構”是一種構造型別,它是由若干“成員”組成的。每一個成員可以是一個基本資料型別或者又是一個構造型別。結構既是一種“構造”而成的資料型別,那麼在說明和使用之前必須先定義它,也就是構造它。如同在說明和呼叫函式之前要先定義函式一樣。
定義一個結構的一般形式為:
struct結構名
{成員表列};
成員表列由若干個成員組成,每個成員都是該結構的一個組成部分。對每個成員也必須作型別說明,其形式為:
型別說明符 成員名;
每一個成員也稱為結構體的一個域。成員表又稱為域表。成員名的命名應符合識別符號的書寫規定。例如:
struct stu
{
int num;
char name[20];
char sex;
float score;
};
在這個結構定義中,結構名為stu,該結構由4個成員組成。第一個成員為num,整型變數;第二個成員為name,字元陣列;第三個成員為sex,字元變數;第四個成員為score,實型變數。應注意在括號後的分號是不可少的。結構定義之後,即可進行變數說明。凡說明為結構stu的變數都由上述4個成員組成。由此可見, 結構是一種複雜的資料型別,是數目固定,型別不同的若干有序變數的集合。
在C語言中,結構體的成語只能是資料。C++加以了擴充,結構體的成員既可以是資料成員,又可以是函式成員,以適應面向物件的程式設計。由於C++提供了類型別,一般情況下,不必使用帶函式成員的結構體。
1.2 結構型別變數的定義
說明結構變數有以下三種方法。以上面定義的stu為例來加以說明。
1. 先定義結構,再說明結構變數。
如:
struct stu
{
int num;
char name[20];
char sex;
float score;
};
struct stu boy1,boy2;
說明了兩個變數boy1和boy2為stu結構型別。也可以用巨集定義使一個符號常量來表示一個結構型別。
例如:
#define STUstruct stu
STU
{
int num;
char name[20];
char sex;
float score;
};
STU boy1,boy2;
2. 在定義結構型別的同時說明結構變數。
例如:
struct stu
{
int num;
char name[20];
char sex;
float score;
}boy1,boy2;
這種形式的說明的一般形式為:
struct結構名
{
成員表列
}變數名錶列;
3. 直接說明結構變數。
例如:
struct
{
int num;
char name[20];
char sex;
float score;
}boy1,boy2;
這種形式的說明的一般形式為:
struct
{
成員表列
}變數名錶列;
第三種方法與第二種方法的區別在於第三種方法中省去了結構名,而直接給出結構變數。三種方法中說明的boy1,boy2變數都具有下圖所示的結構。
說明了boy1,boy2變數為stu型別後,即可向這兩個變數中的各個成員賦值。在上述stu結構定義中,所有的成員都是基本資料型別或陣列型別。
4. 說明:
(1)結構體型別的結構可以根據需要進行設計出許多不同的結構體型別。
(2)型別與變數是不同的概念。只能對結構體變數中的成員賦值,對在編譯時型別不分配儲存空間。
(3)結構體中的成員可以單獨使用,他的作用相當於普通變數。
(4)成員也可以又是一個結構,即構成了巢狀的結構。例如,下圖給出了另一個數據結構。
按圖可給出以下結構定義:
struct date
{
int month;
int day;
int year;
};
struct{
int num;
char name[20];
char sex;
struct date birthday;
float score;
}boy1,boy2;
首先定義一個結構date,由month(月)、day(日)、year(年) 三個成員組成。在定義並說明變數 boy1 和 boy2 時,其中的成員birthday被說明為data結構型別。成員名可與程式中其它變數同名,互不干擾。
1.3 結構體型別變數的初始化
(1)和其他型別變數一樣,對結構變數可以在定義時進行初始化賦值。
【例11.1】對結構變數初始化。
main()
{
struct stu /*定義結構*/
{
int num;
char *name;
char sex;
float score;
}boy2,boy1={102,"Zhangping",'M',78.5};
boy2=boy1;
printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
}
(2)可以採用宣告型別與定義變數分開的形式,在定義變數時進行初始化。
如:stu student2={10002,"Wang Li", 'F',20};
1.4 引用結構體變數
在程式中使用結構變數時,往往不把它作為一個整體來使用。在ANSI C中除了允許具有相同型別的結構變數相互賦值以外,一般對結構變數的使用,包括賦值、輸入、輸出、運算等都是通過結構變數的成員來實現的。
表示結構變數成員的一般形式是:
結構變數名.成員名
例如:
boy1.num 即第一個人的學號
boy2.sex 即第二個人的性別
如果成員本身又是一個結構則必須逐級找到最低階的成員才能使用。
例如:
boy1.birthday.month
即第一個人出生的月份成員可以在程式中單獨使用,與普通變數完全相同。(1)可以將一個結構體變數的值賦給另一個具有相同結構的結構體變數。
(2)可以引用一個結構體變數中的一個成員的值。
(3)如果成員本身也是一個結構體型別,則要用若干個成員運算子,一級一級地遭到最低一級的成員。
(4)不能將一個結構體變數作為整體進行輸入和輸出。
(5)對結構體變數的成員可以向普通變數一樣進行有關的運算。
(4)可以引用結構體變數成員的地址,也可以引用結構體變數的地址。結構體變數的地址主要用作函式引數,將結構體變數的地址傳遞給形參。
1.5 結構體陣列
陣列的元素也可以是結構型別的。因此可以構成結構型陣列。結構陣列的每一個元素都是具有相同結構型別的下標結構變數。在實際應用中,經常用結構陣列來表示具有相同資料結構的一個群體。如一個班的學生檔案,一個車間職工的工資表等。方法和結構變數相似,只需說明它為陣列型別即可。
1. 定義結構體陣列
例如:
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5];
定義了一個結構陣列boy,共有5個元素,boy[0]~boy[4]。每個陣列元素都具有struct stu的結構形式。對結構陣列可以作初始化賦值。
2. 結構體陣列的初始化
例如:
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Liping","M",45},
{102,"Zhangping","M",62.5},
{103,"Hefang","F",92.5},
{104,"Chengling","F",87},
{105,"Wangming","M",58};
}
當對全部元素作初始化賦值時,也可不給出陣列長度。
如: stu students[]={{ },{ },...,{ }};
宣告結構體型別和定義結構體陣列機器初始化的操作也可以分開進行。
1.6 結構指標變數的說明和使用
1. 指向結構體變數的指標
一個指標變數當用來指向一個結構變數時,稱之為結構指標變數。結構指標變數中的值是所指向的結構變數的首地址。通過結構指標即可訪問該結構變數,這與陣列指標和函式指標的情況是相同的。
結構指標變數說明的一般形式為:
struct結構名 *結構指標變數名
例如,在前面的例題中定義了stu這個結構,如要說明一個指向stu的指標變數pstu,可寫為:
struct stu *pstu;
當然也可在定義stu結構時同時說明pstu。與前面討論的各類指標變數相同,結構指標變數也必須要先賦值後才能使用。
賦值是把結構變數的首地址賦予該指標變數,不能把結構名賦予該指標變數。如果boy是被說明為stu型別的結構變數,則:
pstu=&boy
是正確的,而:pstu=&stu是錯誤的。
結構名和結構變數是兩個不同的概念,不能混淆。結構名只能表示一個結構形式,編譯系統並不對它分配記憶體空間。只有當某變數被說明為這種型別的結構時,才對該變數分配儲存空間。因此上面&stu這種寫法是錯誤的,不可能去取一個結構名的首地址。有了結構指標變數,就能更方便地訪問結構變數的各個成員。
其訪問的一般形式為:
(*結構指標變數).成員名
或為:
結構指標變數->成員名
和 結構體變數名.成員名 三者等價。
例如:
(*pstu).num
或者:
pstu->num
應該注意(*pstu)兩側的括號不可少,因為成員符“.”的優先順序高於“*”。如去掉括號寫作*pstu.num則等效於*(pstu.num),這樣,意義就完全不對了。
2. 指向結構陣列的指標
指標變數可以指向一個結構陣列,這時結構指標變數的值是整個結構陣列的首地址。結構指標變數也可指向結構陣列的一個元素,這時結構指標變數的值是該結構陣列元素的首地址。
設ps為指向結構陣列的指標變數,則ps也指向該結構陣列的0號元素,ps+1指向1號元素,ps+i則指向i號元素。這與普通陣列的情況是一致的。
【例11.2】用指標變數輸出結構陣列。
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Zhou ping",'M',45},
{102,"Zhang ping",'M',62.5},
{103,"Liou fang",'F',92.5},
{104,"Cheng ling",'F',87},
{105,"Wang ming",'M',58},
};
void main()
{
struct stu *ps;
printf("No\tName\t\t\tSex\tScore\t\n");
for(ps=boy;ps<boy+5;ps++)
printf("%d\t%s\t\t%c\t%f\t\n",ps->num,ps->name,ps->sex,ps->score);
}
應該注意的是,一個結構指標變數雖然可以用來訪問結構變數或結構陣列元素的成員,但是,不能使它指向一個成員。也就是說不允許取一個成員的地址來賦予它。因此,下面的賦值是錯誤的。
ps=&boy[1].sex;
而只能是:
ps=boy;(賦予陣列首地址)
或者是:
ps=&boy[0];(賦予0號元素首地址)
3. 用結構體變數和指向結構體變數的指標構成連結串列
可在第一個結點的指標域記憶體入第二個結點的首地址,在第二個結點的指標域內又存放第三個結點的首地址,如此串連下去直到最後一個結點。最後一個結點因無後續結點連線,其指標域可賦為0。這樣一種連線方式,在資料結構中稱為“連結串列”。下圖為最一簡單鏈表的示意圖。
圖中,第0個結點稱為頭結點,它存放有第一個結點的首地址,它沒有資料,只是一個指標變數。以下的每個結點都分為兩個域,一個是資料域,存放各種實際的資料,如學號num,姓名name,性別sex和成績score等。另一個域為指標域,存放下一結點的首地址。連結串列中的每一個結點都是同一種結構型別。
例如,一個存放學生學號和成績的結點應為以下結構:
struct stu
{ int num;
int score;
struct stu *next;
}
前兩個成員項組成資料域,後一個成員項next構成指標域,它是一個指向stu型別結構的指標變數。
連結串列的基本操作對連結串列的主要操作有以下幾種:
1. 建立連結串列;
2. 結構的查詢與輸出;
3. 插入一個結點;
4. 刪除一個結點;
【例11.3】靜態連結串列的建立
#include<iostream>
using namespace std;
struct student{
int num;
float score;
struct student * next;
};
int main()
{
Student a,b,c;
Student *p,*head;
a.num=10001;a.score=90;
b.num=10002;b.score=91;
c.num=10003;c.score=92;
head=&a;
a.next=&b;
b.next=&c;
c.next=NULL;
p=head;
do{
cout<<p->num<<" "<<p->score<<endl;
p=p->next;
}while(p!=NULL);
return 0;
}
1.7 結構體型別資料作為函式引數
(1)結構體變數名作函式引數:用結構體變數作實參時,採取的是“值傳遞”的方式。
(2)用指向結構體變數的指標作實參,將結構體變數的地址傳給形參。
(3)用結構體變數的引用作函式形參,它就成為實參的別名。引用主要用作函式引數,它可以提高效率,而且保持程式良好的可讀性。
2. 動態儲存分配
在陣列一章中,曾介紹過陣列的長度是預先定義好的,在整個程式中固定不變。C語言中不允許動態陣列型別。
例如:
int n;
scanf("%d",&n);
int a[n];
用變量表示長度,想對陣列的大小作動態說明,這是錯誤的。但是在實際的程式設計中,往往會發生這種情況,即所需的記憶體空間取決於實際輸入的資料,而無法預先確定。對於這種問題,用陣列的辦法很難解決。為了解決上述問題,C語言提供了一些記憶體管理函式,這些記憶體管理函式可以按需要動態地分配記憶體空間,也可把不再使用的空間回收待用,為有效地利用記憶體資源提供了手段。
2.1 C語言常用的記憶體管理函式
以下四個函式的宣告在stdlib.h標頭檔案。
1. 分配記憶體空間函式malloc
呼叫形式:
(型別說明符*)malloc(size)
功能:在記憶體的動態儲存區中分配一塊長度為"size"位元組的連續區域。函式的返回值為該區域的首地址。
“型別說明符”表示把該區域用於何種資料型別。
(型別說明符*)表示把返回值強制轉換為該型別指標。
“size”是一個無符號數。
例如:
pc=(char*)malloc(100);
表示分配100個位元組的記憶體空間,並強制轉換為字元陣列型別,函式的返回值為指向該字元陣列的指標,把該指標賦予指標變數pc。
2. 分配記憶體空間函式 calloc
calloc 也用於分配記憶體空間。
呼叫形式:
(型別說明符*)calloc(n,size)
功能:在記憶體動態儲存區中分配n塊長度為“size”位元組的連續區域。函式的返回值為該區域的首地址。
(型別說明符*)用於強制型別轉換。
calloc函式與malloc 函式的區別僅在於一次可以分配n塊區域。
例如:
ps=(struet stu*)calloc(2,sizeof(structstu));
其中的sizeof(struct stu)是求stu的結構長度。因此該語句的意思是:按stu的長度分配2塊連續區域,強制轉換為stu型別,並把其首地址賦予指標變數ps。
3. 釋放記憶體空間函式free
呼叫形式:
free(void*ptr);
功能:釋放ptr所指向的一塊記憶體空間,ptr是一個任意型別的指標變數,它指向被釋放區域的首地址。被釋放區應是由malloc或calloc函式所分配的區域。
4. 使用realloc函式
函式原型:
void *realloc(void *p, unsigned int size);
功能:如果已經通過malloc函式或calloc函式獲得了動態空間,想要改變其大小,可以使用realloc函式重新分配。用realloc函式將p指向的動態空間的大小改為size,p的值不變。如果重新分配不成功,返回NULL。
【例11.4】分配一塊區域,輸入一個學生資料。
void main()
{
struct stu
{
int num;
char *name;
char sex;
float score;
} *ps;
ps=(struct stu*)malloc(sizeof(struct stu));
ps->num=102;
ps->name="Zhang ping";
ps->sex='M';
ps->score=62.5;
printf("Number=%d\nName=%s\n",ps->num,ps->name);
printf("Sex=%c\nScore=%f\n",ps->sex,ps->score);
free(ps);
}
2.2 C++用new和delete運算子進行動態分配和撤銷儲存空間
注意:new和delete是運算子,執行效率高。
new運算子使用的一般格式:
new 型別[初值];
分配不成功,會返回一個空指標NULL.用new分配陣列空間時不能指定初值。
delete運算子使用的一般格式:
delete 指標變數;(對變數)
delete[] 指標變數;(對陣列)
3. 共用體型別
共用體也是一種構造資料型別,它是將不同型別的變數存放在同一記憶體區域內。共用體也稱為聯合(union)。共用體的型別定義、變數定義及引用方式與結構相似,但它們有著本質的區別:結構變數的各成員佔用連續的不同儲存空間,而共用體變數的各成員佔用同一個儲存區域。
3.1 共用體變數的定義
共用體變數的定義與結構變數定義相似,首先,必須構造一個共用體資料型別,再定義具有這種型別的變數。
共用體型別定義的一般方法:
union 共用體名
{ 共用體成員表 } ;
其中,共用體成員表是對各成員的定義,形式為:型別說明符成員名;
與定義結構變數一樣,定義共用體變數的方法有以下三種:
(1)先定義共用體型別,再定義該型別資料
例如:
union data {
char n[10];
int a;
double f;};
union data x,y[10];
(2)在定義共用體型別的同時定義該型別變數
例如:
union data {
char n[10];
int a; double f; }x,y[10];
(3)不定義共用體型別名,直接定義共用體變數
例如:
union Data{
char n[10];
int a;
double f; }x,y[10];
定義了共用體變數後,系統就給它分配記憶體空間。因共用體變數中的各成員佔用同一儲存空間,所以,系統給共用體變數所分配的記憶體空間為其成員中所佔用記憶體空間最多的成員的單元數。共用體變數中各成員從第一個單元開始分配儲存空間,所以各成員的記憶體地址是相同的。
3.2 共用體變數的引用
定義了共用體變數後,即可使用它。其引用方式與結構體相似,使用成員運算子 “ . ”。若需對共用體變數初始化,只能對它的第一個成員賦初始值。例如:union Data x={"zhangsan"};是正確的,而union data x={"zhangsan",12,40000, 78,5};是錯誤的。
雖然共用體資料可以在同一記憶體空間中存放多個不同型別的成員,但在某一時刻只能存放其中的一個成員,起作用的是最後存放的成員資料,其他成員不起作用,如引用其他成員,則資料無意義。
例如,對data型別共用體變數,有以下語句:
x.a=100; strcpy(x.n,"zhangsan"); x.f=90.5;
則只有x.f是有效的,x.a與x.n目前資料是無意義的,因後面的賦值語句將前面共用體資料覆蓋了。
3.3 共用體的特點
(1)同一段記憶體段可以用來存放幾種不同型別的成員,但在每一瞬時只能存放其中的一個成員,而不是同時存放幾個。因為在同一瞬時儲存單元只能有唯一的內容,即在共用體變數中只能存放一個值。
(2)可以對共用體變數初始化,但初始化表中只能有一個常量。
(3)共用體變數中起作用的成員是最後一次賦值的成員,在對共用體變數中的一個成員賦值後,原有變數儲存單元的值就被取代。
(4)共用體變數的地址和它的各成員地址都是同一地址。如 &a.i, &a.ch, &a.f 都是同一值。
(5)不能對共用體變數名賦值,也不能企圖引用變數名來得到一個值。
(6)共用體型別可以出現在結構體型別中,也可以定義共用體陣列。
(7) 需要對同一段空間安排不同的用途,這時使用共用體型別比較方便。
4. 列舉型別
在實際問題中,有些變數的取值被限定在一個有限的範圍內。例如,一個星期內只有七天,一年只有十二個月,一個班每週有六門課程等等。如果把這些量說明為整型,字元型或其它型別顯然是不妥當的。為此,C語言提供了一種稱為“列舉”的型別。在“列舉”型別的定義中列舉出所有可能的取值,被說明為該“列舉”型別的變數取值不能超過定義的範圍。應該說明的是,列舉型別是一種基本資料型別,而不是一種構造型別,因為它不能再分解為任何基本型別。
4.1 列舉型別的定義和列舉變數的說明
1. 列舉的定義列舉型別定義的一般形式為:
enum列舉名{ 列舉值表 };
在列舉值表中應羅列出所有可用值。這些值也稱為列舉元素。
例如:
enum weekday{ sun,mou,tue,wed,thu,fri,sat };
該列舉名為weekday,列舉值共有7個,即一週中的七天。凡被說明為weekday型別變數的取值只能是七天中的某一天。
C++允許不寫關鍵字enum,但保留了C的用法。
2. 列舉變數的說明
如同結構和聯合一樣,列舉變數也可用不同的方式說明,即先定義後說明,同時定義說明或直接說明。
設有變數a,b,c被說明為上述的weekday,可採用下述任一種方式:
enum weekday{ sun,mou,tue,wed,thu,fri,sat };
enum weekday a,b,c;
或者為:
enum weekday{ sun,mou,tue,wed,thu,fri,sat }a,b,c;
或者為:
enum { sun,mou,tue,wed,thu,fri,sat }a,b,c;
4.2 列舉型別變數的賦值和使用
列舉型別在使用中有以下規定:
1. 列舉值是常量,不是變數。不能在程式中用賦值語句再對它賦值。
例如對列舉weekday的元素再作以下賦值:
sun=5;
mon=2;
sun=mon;
都是錯誤的。
2. 列舉元素本身由系統定義了一個表示序號的數值,從0開始順序定義為0,1,2…。如在weekday中,sun值為0,mon值為1,…,sat值為6。
【例11.5】
main(){
enum weekday
{ sun,mon,tue,wed,thu,fri,sat } a,b,c;
a=sun;
b=mon;
c=tue;
printf("%d,%d,%d",a,b,c);
}
說明:
(1)只能把列舉值賦予列舉變數,不能把元素的數值直接賦予列舉變數。如:
a=sum;
b=mon;
是正確的。而:
a=0;
b=1;
是錯誤的。
(2)如一定要把數值賦予列舉變數,則必須用強制型別轉換。
如:
a=(enum weekday)2;
其意義是將順序號為2的列舉元素賦予列舉變數a,相當於:
a=tue;
還應該說明的是列舉元素不是字元常量也不是字串常量,使用時不要加單、雙引號。
(3)列舉值可以用來做比較判斷,按整數的規則進行比較。
5. 型別定義符typedef
5.1 用typedef宣告新的型別名
C語言不僅提供了豐富的資料型別,而且還允許由使用者自己定義型別說明符,也就是說允許由使用者為資料型別取“別名”。型別定義符typedef即可用來完成此功能。例如,有整型量a,b,其說明如下:
int a,b;
其中int是整型變數的型別說明符。int的完整寫法為integer,為了增加程式的可讀性,可把整型說明符用typedef定義為:
typedef int INTEGER
這以後就可用INTEGER來代替int作整型變數的型別說明了。
例如:
INTEGER a,b;
它等效於:
int a,b;
用typedef定義陣列、指標、結構等型別將帶來很大的方便,不僅使程式書寫簡單而且使意義更為明確,因而增強了可讀性。
例如:
typedef char NAME[20]; 表示NAME是字元陣列型別,陣列長度為20。然後可用NAME 說明變數,如:
NAME a1,a2,s1,s2;
完全等效於:
char a1[20],a2[20],s1[20],s2[20]
又如:
typedef struct stu
{ char name[20];
int age;
char sex;
} STU;
定義STU表示stu的結構型別,然後可用STU來說明結構變數:
STU body1,body2;
typedef定義的一般形式為:
typedef 原型別名 新型別名
其中原型別名中含有定義部分,新型別名一般用大寫表示,以便於區別。
有時也可用巨集定義來代替typedef的功能,但是巨集定義是由預處理完成的,而typedef則是在編譯時完成的,後者更為靈活方便。
5.2 說明
(1)用typedef只是對已經存在的型別增加一個型別名,而沒有創造新的型別;
(2)不可以用typedef來定義變數;
(3)用typedef宣告陣列型別、字串型別和指標型別,使用比較方便;
(4)typedef有利於程式的通用與移植。
6. 記憶體對齊
對於大部分程式設計師來說,“記憶體對齊”對他們來說都應該是“透明的”。“記憶體對齊”應該是編譯器的 “管轄範圍”。編譯器為程式中的每個“資料單元”安排在適當的位置上。但是C語言的一個特點就是太靈活,太強大,它允許你干預“記憶體對齊”。
6.1 記憶體對齊的原因
1、平臺原因(移植原因):不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。2、效能原因:資料結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問;而對齊的記憶體訪問僅需要一次訪問。
6.2 對齊規則
每個特定平臺上的編譯器都有自己的預設“對齊係數”(也叫對齊模數)。程式設計師可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊係數”。 有虛擬函式的話就有虛表,虛表儲存虛擬函式地址,一個地址佔用的長度根據編譯器不同有可能不同,vs裡面是8個位元組,在devc++裡面是4個位元組。類和結構體的對齊方式相同,有兩條規則
1、資料成員對齊規則:結構(struct)(或聯合(union))的資料成員,第一個資料成員放在offset為0的地方,以後每個資料成員的對齊按照#pragma pack指定的數值和這個資料成員自身長度中,比較小的那個進行。
2、結構(或聯合)的整體對齊規則:在資料成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大資料成員長度中,比較小的那個進行
3、結合1、2推斷:當#pragma pack的n值等於或超過所有資料成員長度的時候,這個n值的大小將不產生任何效果。
6.3 試驗
我們將用典型的struct對齊來說明。首先我們定義一個struct:#pragma pack(n) /* n = 1, 2, 4, 8, 16 */
struct test_t {
int a;
char b;
short c;
char d;
};
#pragma pack(n)
首先我們首先確認在試驗平臺上的各個型別的size,經驗證兩個編譯器的輸出均為:
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
我們的試驗過程如下:通過#pragma pack(n)改變“對齊係數”,然後察看sizeof(struct test_t)的值。
(1)、1位元組對齊(#pragma pack(1))
輸出結果:sizeof(struct test_t) = 8 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(1)
struct test_t {
int a; /* 長度4 < 1 按1對齊;起始offset=0 0%1=0;存放位置區間[0,3] */
char b; /* 長度1 = 1 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 > 1 按1對齊;起始offset=5 5%1=0;存放位置區間[5,6] */
char d; /* 長度1 = 1 按1對齊;起始offset=7 7%1=0;存放位置區間[7] */
};
#pragma pack()
成員總大小=8
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 1) = 1
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 8 /* 8%1=0 */
什麼是“圓整”?
舉例說明:如上面的8位元組對齊中的“整體對齊”,整體大小=9 按 4 圓整 = 12
圓整的過程:從9開始每次加一,看是否能被4整除,這裡9,10,11均不能被4整除,到12時可以,則圓整結束。
輸出結果:sizeof(struct test_t) = 10 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(2)
struct test_t {
int a; /* 長度4 > 2 按2對齊;起始offset=0 0%2=0;存放位置區間[0,3] */
char b; /* 長度1 < 2 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 = 2 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d; /* 長度1 < 2 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 2) = 2
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 10 /* 10%2=0 */
(3)、4位元組對齊(#pragma pack(4))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(4)
struct test_t {
int a; /* 長度4 = 4 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
char b; /* 長度1 < 4 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 < 4 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d; /* 長度1 < 4 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 4) = 4
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 12 /* 12%4=0 */
(4)、8位元組對齊(#pragma pack(8))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(8)
struct test_t {
int a; /* 長度4 < 8 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
char b; /* 長度1 < 8 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 < 8 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d; /* 長度1 < 8 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 8) = 4
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 12 /* 12%4=0 */
(5)、16位元組對齊(#pragma pack(16))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(16)
struct test_t {
int a; /* 長度4 < 16 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
char b; /* 長度1 < 16 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 < 16 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d; /* 長度1 < 16 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 16) = 4
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 12 /* 12%4=0 */
(6)、結論
8位元組和16位元組對齊試驗證明了“規則”的第3點:“當#pragma pack的n值等於或超過所有資料成員長度的時候,這個n值的大小將不產生任何效果”。