目錄
結構體
1.結構體型別宣告
1.1 結構的基礎知識
1.2 結構的宣告
1.3 特殊的宣告
1.4 結構的自參考
1.5 結構體變數的定義和初始化
1.6 結構體記憶體對齊
1.7 修改默認對齊數
1.8 結構體傳參
2. 位段
2.1 什么是位段
2.2 位段的記憶體分配
2.3 位段的跨平臺問題
2.4 位段的應用
3. 列舉
3.1 列舉型別的定義
3.2 列舉的優點
3.3 列舉的使用
4. 聯合(共用體)
4.1 聯合型別的定義
4.2 聯合的特點
4.3 聯合大小的計算
結構體
1.結構體型別宣告
1.1 結構的基礎知識
結構是一些值的集合,這些值稱為成員變數,結構的每個成員可以是不同型別的變數1.2 結構的宣告
struct tag { member-list; }variable-list;
例如描述一個學生:struct Stu { char name[20];//名字 int age;//年齡 char sex[5];//性別 char id[20];//學號 };//分號不能丟1.3 特殊的宣告
在宣告結構的時候,可以不完全的宣告,
比如
//匿名結構體型別 struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], *p;上面的兩個結構在宣告的時候省略掉了結構體標簽(tag),
那么問題來了?//在上面代碼的基礎上,下面的代碼合法嗎? p = &x;警告:
編譯器會把上面的兩個宣告當成完全不同的兩個型別,
所以是非法的,1.4 結構的自參考
在結構中包含一個型別為該結構本身的成員是否可以呢?
//代碼1 struct Node { int data; struct Node next; }; //可行否?這樣當然不可以了,跟遞回類似,自己中無線包含自己,是不是有點無窮盡也的意味了,
正確的自參考方式:
//代碼2 struct Node { int data; struct Node* next; };敲重點,注意了,
//代碼3 typedef struct { int data; Node* next; }Node; //這樣寫代碼,可行否?咋一看好像沒啥問題是吧,但小心了,這里我們的Node*next在定義之前就使用了
正確的定義方式當然是下面的了//解決方案: typedef struct Node { int data; struct Node* next; }Node;1.5 結構體變數的定義和初始化
有了結構體型別,那如何定義變數,其實很簡單,
struct Point { int x; int y; }p1; //宣告型別的同時定義變數p1 struct Point p2; //定義結構體變數p2 //初始化:定義變數的同時賦初值, struct Point p3 = {x, y}; struct Stu //型別宣告 { char name[15];//名字 int age; //年齡 }; struct Stu s = {"zhangsan", 20};//初始化 struct Node { int data; struct Point p; struct Node* next; }n1 = {10, {4,5}, NULL}; //結構體嵌套初始化 struct Node n2 = {20, {5, 6}, NULL};//結構體嵌套初始化1.6 結構體記憶體對齊
結構體記憶體對齊也算是結構體復雜點之一吧, 其實理解了結構體記憶體對齊規則,問題就迎刃而解了.
首先得掌握結構體的對齊規則:
1. 第一個成員在與結構體變數偏移量為0的地址處,
2. 其他成員變數要對齊到某個數字(對齊數)的整數倍的地址處,
對齊數 = 編譯器默認的一個對齊數 與 該成員大小的較小值, VS中默認的值為8
3. 結構體總大小為最大對齊數(每個成員變數都有一個對齊數)的整數倍,
4. 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整
體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍,我們用圖片來更好理解
我們舉個簡單的例子
//例如: struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; };
我們再來分析上面兩個例子
首先我們來看S1
我們再來看S2
因為char型別的對齊數是1,int型別是4
但同時我們也要特別注意
并不是缺多少都能在空里面補多少
例如下面這種
為什么存在記憶體對齊?
大部分的參考資料都是如是說的:
1. 平臺原因(移植原因):
不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特
定型別的資料,否則拋出硬體例外,
2. 性能原因:
資料結構(尤其是堆疊)應該盡可能地在自然邊界上對齊,
原因在于,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問;而對齊的記憶體訪問僅需要一次訪
問,
總體來說:
結構體的記憶體對齊是拿空間來換取時間的做法,
那在設計結構體的時候,我們既要滿足對齊,又要節省空間,如何做到:讓占用空間小的成員盡量集中在一起,
通過上面的例子我們已經知道原因了.//例如: struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; }; //S1和S2型別的成員一模一樣,但是S1和S2所占空間的大小有了一些區別, //這里當然是第二種更省空間了1.7 修改默認對齊數
之前我們見過了 #pragma 這個預處理指令,這里我們再次使用,可以改變我們的默認對齊數,
#include <stdio.h> #pragma pack(8)//設定默認對齊數為8 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消設定的默認對齊數,還原為默認 #pragma pack(1)//設定默認對齊數為1 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消設定的默認對齊數,還原為默認 int main() { //輸出的結果是什么? printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }對齊數為1,所以可以 一個接一個儲存
結論:
結構在對齊方式不合適的時候,我么可以自己更改默認對齊數,1.8 結構體傳參
直接看代碼
struct S { int data[1000]; int num; }; struct S s = {{1,2,3,4}, 1000}; //結構體傳參 void print1(struct S s) { printf("%d\n", s.num); } //結構體地址傳參 void print2(struct S* ps) { printf("%d\n", ps->num); } int main() { print1(s); //傳結構體 print2(&s); //傳地址 return 0; }上面的 print1 和 print2 函式哪個好些?
答案是:首選print2函式,
原因:
函式傳參的時候,引數是需要壓堆疊,會有時間和空間上的系統開銷,
如果傳遞一個結構體物件的時候,結構體過大,引數壓堆疊的的系統開銷比較大,所以會導致性能
的下降結論:
結構體傳參的時候,要傳結構體的地址
2. 位段
2.1 什么是位段
位段的宣告和結構是類似的,有兩個不同:
1.位段的成員必須是 int、unsigned int 或signed int ,
2.位段的成員名后邊有一個冒號和一個數字,比如:
struct A { int _a:2; int _b:5; int _c:10; int _d:30; };A就是一個位段型別,
那位段A的大小是多少?
2.2 位段的記憶體分配
1. 位段的成員可以是 int unsigned int signed int 或者是 char (屬于整形家族)型別
2. 位段的空間上是按照需要以4個位元組( int )或者1個位元組( char )的方式來開辟的,
3. 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程式應該避免使用位段,
//一個例子 struct S { char a:3; char b:4; char c:5; char d:4; }; struct S s = {0}; s.a = 10; s.b = 12; s.c = 3; s.d = 4; //空間是如何開辟的?
2.3 位段的跨平臺問題
1. int 位段被當成有符號數還是無符號數是不確定的,
2. 位段中最大位的數目不能確定,(16位機器最大16,32位機器最大32,寫成27,在16位機
器會出問題,
3. 位段中的成員在記憶體中從左向右分配,還是從右向左分配標準尚未定義,
4. 當一個結構包含兩個位段,第二個位段成員比較大,無法容納于第一個位段剩余的位時,是
舍棄剩余的位還是利用,這是不確定的總結:
跟結構相比,位段可以達到同樣的效果,但是可以很好的節省空間,但是有跨平臺的問題存在,2.4 位段的應用
3. 列舉
列舉顧名思義就是一一列舉,
把可能的取值一一列舉,
比如我們現實生活中:
一周的星期一到星期日是有限的7天,可以一一列舉,
性別有:男、女、保密,也可以一一列舉,
月份有12個月,也可以一一列舉
這里就可以使用列舉了
3.1 列舉型別的定義
enum Day//星期 { Mon, Tues, Wed, Thur, Fri, Sat, Sun }; enum Sex//性別 { MALE, FEMALE, SECRET }; enum Color//顏色 { RED, GREEN, BLUE };以上定義的 enum Day , enum Sex , enum Color 都是列舉型別,
{}中的內容是列舉型別的可能取值,也叫 列舉常量
這些可能取值都是有值的,默認從0開始,一次遞增1,當然在定義的時候也可以賦初值.例如:
enum Color//顏色 { RED=1, GREEN=2, BLUE=4 };3.2 列舉的優點
為什么使用列舉?
我們可以使用 #define 定義常量,為什么非要使用列舉?
列舉的優點:
1. 增加代碼的可讀性和可維護性
2. 和#define定義的識別符號比較列舉有型別檢查,更加嚴謹,
3. 防止了命名污染(封裝)
4. 便于除錯
5. 使用方便,一次可以定義多個常量3.3 列舉的使用
enum Color//顏色 { RED=1, GREEN=2, BLUE=4 }; enum Color clr = GREEN;//只能拿列舉常量給列舉變數賦值,才不會出現型別的差異, clr = 5; //這種不允許相關操作
列舉變數的值只能取列舉常量表中所列的值,就是整型數的一個子集,
列舉變數占用記憶體的大小與整型數相同,
列舉變數只能參與賦值和關系運算以及輸出操作,參與運算時用其本身的整數值,例如,設有定義:
enum color_set1 {RED, BLUE, WHITE, BLACK} color1, color2;
enum color_set2 { GREEN, RED, YELLOW, WHITE} color3, color4;
則允許的賦值操作如下:
color3=RED; //將列舉常量值賦給列舉變數
color4=color3; //相同型別的列舉變數賦值,color4的值為RED
int i=color3; //將列舉變數賦給整型變數,i的值為1
int j=GREEN; //將列舉常量賦給整型變數,j的值為0
允許的關系運算有:==、<、>、<=、>=、!=等例如:
//比較同型別列舉變數color3,color4是否相等
if (color3==color4) cout<<”相等”;
//輸出的是變數color3與WHITE的比較結果,結果為1
cout<< color3<WHITE;
列舉變數可以直接輸出,輸出的是變數的整數值,例如:
cout<< color3; //輸出的是color3的整數值,即RED的整數值
4. 聯合(共用體)
4.1 聯合型別的定義
聯合體union的定義方式與結構體一樣,但是二者有根本區別,
在結構中各成員有各自的記憶體空間,一個結構變數的總長度是各成員長度之和,
而在“聯合”中,各成員共享一段記憶體空間,一個聯合變數的長度等于各成員中最長的長度,
聯合也是一種特殊的自定義型別
這種型別定義的變數也包含一系列的成員,特征是這些成員公用同一塊空間(所以聯合也叫共用體),
比如:
//聯合型別的宣告 union Un { char c; int i; }; //聯合變數的定義 union Un un; //計算連個變數的大小 printf("%d\n", sizeof(un));
4.2 聯合的特點
聯合的成員是共用同一塊記憶體空間的,這樣一個聯合變數的大小,至少是最大成員的大小(因為
聯合至少得有能力保存最大的那個成員)union Un { int i; char c; }; union Un un; // 下面輸出的結果是一樣的嗎 printf("%d\n", &(un.i)); printf("%d\n", &(un.c)); //下面輸出的結果是什么? un.i = 0x11223344; un.c = 0x55; printf("%x\n", un.i);
從以上可以看出 是同一塊空間
4.3 聯合大小的計算
在The C Programming Language里面講述union記憶體分配的原話是
1)聯合體就是一個結構
2)聯合體的所有成員相對于基地址的偏移量為0
3)此結構空間要大到總夠容納最“寬”的成員
4)并且,其對齊方式要適合于聯合體中所有型別的成員
大概意思:
聯合的大小至少是最大成員的大小,
當最大成員大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍.這里我們簡單看個例子.至于對齊的規則上文已經給出,這里就不多加解釋了.
以上就是筆者對自定義型別的總結,由于筆者水平有限,文章若有不足之處,還請大家在評論區留言.
同時感謝大家的支持.
覺得文章寫得不錯的,能不能給個筆者一鍵三連呢?
謝謝大家!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/301813.html
標籤:其他















