C語言之複合資料型別
複合資料型別
思維導圖
結構體基礎
-
結構體型別的定義與宣告
-
結構體型別變數定義和初始化
- 定義變數
struct stu s1;- 結構體變數初始化
struct stu s5 = { “小明”,10,15,5,98};- 使用,變數用 “.” 引用成員,指標用 “->” 引用成員
printf (“num = %d,name = %s\n”, s5.num, s5.name);
-
結構體型別變數賦值與使用
-
結構體陣列
-
結構體巢狀
-
結構體指標
• 結構體和結構體變數是不同的概念
• 結構體是一種資料型別,和我們的int float等是一樣的,編譯器不會為它分配記憶體
• 結構體變數才是實實在在的資料,才需要記憶體來儲存
struct student stu = {“xiaoming”, 18, 50}; // 結構體變數
struct student *pstu = &stu; // 結構體指標,指向一個結構體變數stu
struct student pstu = student; //錯誤的寫法
獲取結構體成員:
. 號的優先順序高於*,(*pstu)兩邊的括號是不能少的,如果去掉括號,
就變成 *pstu.name == *(pstu.name)
printf (“name = %s\n”, (*pstu).name);
->是一個新的運算子,一般叫做"箭頭",通過它結構體指標能直接取得結構體的成員
printf (“name = %s\n”, pstu->name);
printf (“age = %d\n”, pstu->age);
printf (“score = %.2f\n”, pstu->score);
結構體變數名代表的是整個集合本身,作為函式引數時傳遞的整個集合,也就是所有成員,而不是像陣列一樣被編譯器轉換成一個指標。如果結構體成員較多,尤其是成員為陣列時,傳送的時間和空間開銷會很大,影響程式的執行效率。所以最好的辦法就是使用結構體指標
結構體記憶體對齊模式
為什麼要進行記憶體對齊?
記憶體對齊是作業系統為了快速訪問記憶體而採取的一種策略,簡單來說,就是為了放置變數的二次訪問。作業系統在訪問記憶體 時,每次讀取一定的長度(這個長度就是作業系統的預設對齊係數,或者是預設對齊係數的整數倍)。如果沒有記憶體對齊時,為了讀取一個變數是,會產生匯流排的二 次訪問。
例如假設沒有記憶體對齊(預設對齊係數為8),結構體xx的變數位置會出現如下情況:
struct xx{
char b; //0xffbff5e8
int a; //0xffbff5e9
int c; //0xffbff5ed
char d; //0xffbff5f1
};
作業系統先讀取0xffbff5e8-0xffbff5ef的記憶體,然後在讀取0xffbff5f0-0xffbff5f8的記憶體,為了獲得值c,就需要將兩組記憶體合併,進行整合,這樣嚴重降低了記憶體的訪問效率。(這就涉及到了老生常談的問題,空間和效率哪個更重要?)。
這樣大家就能理解為什麼結構體的第一個變數,不管型別如何,都是能被8整除的吧(因為訪問記憶體是從8的整數倍開始的,為了增加讀取的效率)
記憶體對齊原則
1、 結構體成員的首地址要能被該成員的型別長度所整除
2、 結構體為成員分配空間原則
- 每次分配的時候按當前最大型別分配
- 如果當前空間夠用,則不再分配空間
- 如果當前型別大於系統預設對齊係數的時候,按系統係數標準進 行分配空間,否則按最大型別分配空間
系統預設對齊係數
每 個作業系統都有自己的預設記憶體對齊係數,如果是新版本的作業系統,預設對齊係數一般都是8,因為作業系統定義的最大型別儲存單元就是8個位元組,例如 long long,不存在超過8個位元組的型別(例如int是4,char是1,long在32位編譯時是4,64位編譯時是 8)。當作業系統的預設對齊係數與記憶體對齊的理論產生衝突時,以作業系統的對齊係數為基準
舉例說明:
#include<stdio.h>
struct test
{
char a;
int b;
long c;
long long d;
char e;
short f;
};
int main()
{
printf(" long size is %lu\n",sizeof(long));
printf(" short size is %lu\n",sizeof(short));
printf(" long size is %lu\n",sizeof(long));
printf("struct test size is %lu\n",sizeof(struct test));
return 0;
}
Ubuntu14.04 64bits 測試:
分析:
struct test 成員的最大型別長度是8位元組
Ubuntu14.04 64bits預設記憶體對齊係數也是8位元組
所以按8位元組進行分配空間
上面的成員排列會造成記憶體空間浪費
應該如下排列,資源最大化利用。
struct test
{
char a;
char e;
short f;
int b;
long c;
long long d;
};
結構體位域
- 概念:
有些資料在儲存時並不需要佔用一個完整的位元組,只需要佔用一個或幾個二進位制位即可。例如開關只有通電和斷電兩種狀態,用 0 和 1 表示足以,也就是用一個二進位。正是基於這種考慮,C語言又提供了一種叫做位域的資料結構。
在結構體定義時,我們可以指定某個成員變數所佔用的二進位制位數(Bit),這就是位域:
struct bs
{
unsigned m;
unsigned n: 4;
unsigned char ch: 6;
};
- 儲存規則:
• 相鄰成員的型別相同
當相鄰成員的型別相同時,如果它們的位寬之和小於型別的 sizeof 大小,那麼後面的成員緊鄰前一個成員儲存,直到不能容納為止;如果它們的位寬之和大於型別的 sizeof 大小,那麼後面的成員將從新的儲存單元開始,其偏移量為型別大小的整數倍。
struct bs{
unsigned m: 6;
unsigned n: 12;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
• 相鄰成員的型別不同
當相鄰成員的型別不同時,不同的編譯器有不同的實現方案,GCC 會壓縮儲存,而 VC/VS 不會
struct bs
{
unsigned m: 12;
unsigned char ch: 4;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
• 成員之間插著非位域成員
如果成員之間穿插著非位域成員,會視情況進行壓縮。例如對於下面的 bs:
struct bs
{
unsigned m: 12;
unsigned char ch;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
• 位域成員可以沒有名字,只給出資料型別和位寬
struct bs{
int m: 12;
int : 20; //該位域成員不能使用
int n: 4;
};
無名位域一般用來作填充或者調整成員位置。因為沒有名稱,無名位域不能使用。
上面的例子中,如果沒有位寬為 20 的無名成員,m、n 將會挨著儲存,sizeof(struct bs) 的結果為 4;有了這 20 位作為填充,m、n 將分開儲存,sizeof(struct bs) 的結果為 8。
共用體
共用體:
所用成員共用一段記憶體,共用體大小是成員中佔據最大的成員的大小
定義、初始化和使用:
union 共用體名
{
成員列表
};
結構體和共用體的區別在於:結構體的各個成員會佔用不同的記憶體,互相之間沒有影響;而共用體的所有成員佔用同一段記憶體,修改一個成員會影響其餘所有成員。
結構體佔用的記憶體大於等於所有成員佔用的記憶體的總和(成員之間可能會存在縫隙),共用體佔用的記憶體等於最長的成員佔用的記憶體。共用體使用了記憶體覆蓋技術,同一時刻只能儲存一個成員的值,如果對新的成員賦值,就會把原來成員的值覆蓋掉。
共用體的所有成員起始地址的是一樣的。
#include<stdio.h>
union Test
{
int a;
char c;
};
int main()
{
printf ("sizeof Test = %lu\n", sizeof(union Test));
union Test t;
t.a = 10;
t.c = 'A';
printf ("%d\n", t.a);
return 0;
}
大小端模式:
如果我們將0x1234abcd 寫入到以0x0000 開始的記憶體中,則Little endian和Big endian 模式的存放結果如下:
大端模式:字資料的高位元組儲存在低地址中,而字資料的低位元組則存放在高地址中。
小端模式:字資料的高位元組儲存在高地址中,而字資料的低位元組則存放在低地址中。
測試大小端
// 小端返回1 大端返回0
int func()
{
union
{
unsigned int a;
unsigned char c;
}t;
t.a = 0x12345678
return (t.c == 0x78);
}
列舉
enum typeName{ valueName1, valueName2, valueName3, ...... };
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
-
列舉列表中的 Mon、Tues、Wed 這些識別符號的作用範圍是全域性的,不能再定義與它們名字相同的變數。
-
Mon、Tues、Wed 等都是常量,不能對它們賦值,只能將它們的值賦給其他的變數
-
Mon、Tues、Wed 等都是常量, 初始化只能在定義的時候進行,如果沒有初始化,預設從0開始依次遞增1,如果只初始化部分,後面的也是一次遞增1
#include<stdio.h>
enum week{Mon, Tues, Wed=100, Thurs, Fri, Sat, Sun};
int main()
{
printf ("%d\n", Mon);
printf ("%d\n", Tues);
printf ("%d\n", Wed);
printf ("%d\n", Thurs);
printf ("%d\n", Fri);
printf ("%d\n", Sat);
printf ("%d\n", Sun);
return 0;
}
結果: