文章目錄
- 前言
- 一、結構體
- 1、結構體型別的宣告
- 2、結構體的自參考
- 3、結構體變數的定義和初始化
- 4、結構體記憶體對齊
- 5、結構體傳參
- 二、位段
- 1、位段的定義
- 2、位段的記憶體分配
- 3、位段的應用
- 三、列舉
- 1、列舉型別的定義
- 2、列舉的優點
- 3、列舉的使用
- 四、聯合體(共用體)
- 1、聯合體的定義
- 2、聯合體的特點
- 3、聯合體的大小計算
前言

一、結構體
1、結構體型別的宣告
當我們想要描述一個復雜變數——學生,可以這樣宣告,
??代碼展示:
struct Stu
{
char name[20];//名字
int age;//年齡
char sex[5];//性別
char id[20];//學號
}s1;//分號不能丟
int main()
{
struct Stu s2;
return 0;
}
🔖解釋說明:
- struct是結構體的關鍵字
- Stu是結構體標簽名
- struct Stu是結構體的型別
- 大括號內包圍的是結構體成員變數的串列
- 變數s1是型別為struct Stu的全域變數,變數s2是該型別的區域變數
在宣告結構時,也有特殊的宣告,比如不完全宣告——匿名結構體型別,省略掉了結構體標簽,
??代碼展示:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
那么,此時,問題來了!
在上面的代碼基礎上,p = &x,這樣的代碼合理嗎?

而且,像這樣的匿名結構體型別只能使用一次,因為沒有標簽名,
2、結構體的自參考
眾所周知,函式可以自己呼叫自己,叫做函式的遞回,那么結構體是否也有自己參考自己呢?如果有又是如何實作的呢?
??代碼展示:
//代碼一:
struct N
{
int data;
struct N next;
};
//代碼二:
struct Node
{
int data;
struct Node* next;
};
//代碼三:
typedef struct
{
int data;
Node* next;
}Node;
//代碼四:
typedef struct Node
{
int data;
struct Node* next;
}Node;
🔖解釋說明:
代碼一:
這樣自參考是不正確的,當想要計算struct N型別所占空間大小時,就會出現瘋狂套娃現象,無法計算結果,因此是不可取的
代碼二:
這才是自參考的正確打開方式,data中存放的資料,next中存放著下一個struct Node型別資料的地址
代碼三:
該代碼想要實作匿名結構體的自參考,但這樣做是不可取的,因為需要完整的定義了該結構體才可以重新命名為Node,然而定義的成員串列中又有Node*,先后問題產生了,
代碼四:
可以通過這種重定義方式實作自參考,
3、結構體變數的定義和初始化
既然已經有了結構體型別,那么對其定義和初始化就變得非常的簡單
??代碼展示:
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};//結構體嵌套初始化
4、結構體記憶體對齊
掌握了結構體的基本使用,還應當重點了解結構體記憶體對齊問題從而計算結構體的大小,這是一個關于結構體的重點考點

結構體的對齊規則:
- 第一個成員在與結構體變數偏移量為0的地址處,
- 其他成員變數需要對齊到對齊數的整數倍的地址處,
對齊數 = 編譯器默認的一個對齊數與該成員大小的較小值,
VS中默認的值為8,Linux沒有默認對齊數- 結構體總大小為最大對齊數的整數倍,
- 當嵌套結構體時,嵌套的結構體對齊需要到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數的整數倍(包含嵌套結構體的對齊數),
??代碼展示:
//練習1
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
//練習2
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
//練習3
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
//練習4-結構體嵌套問題
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
👁效果展示:

🔖解釋說明:
結構體型別struct S1和struct S2兩者的成員組成是一樣的,但是定義順序有所差別,后者與前者相比將占用空間小的變數集中在了一起,導致兩者在遵循結構體對齊條件下,所占記憶體大小不一樣,做個對比吧!

結構體型別struct S3和struct S4是另外兩個典型例子,后者嵌套前者,

簡而言之,該做法就是為了拿空間換取時間
如果,,,

另外,,,
結構在對齊方式不合適的時候,我么可以自己更改默認對齊數,
這里我們將使用預處理指令#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;
}
👁效果展示:

🔖解釋說明:

5、結構體傳參
??代碼展示:
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->data[2]);
}
int main()
{
print1(s); //傳結構體
print2(&s); //傳地址
return 0;
}
👁效果展示:

🔖解釋說明:
函式傳參的時候,引數是需要壓堆疊,會有時間和空間上的系統開銷,
如果傳遞一個結構體物件的時候,結構體過大,引數壓堆疊的的系統開銷比較大,導致性能的下降,比如在這里,如果直接傳值s的話,由于結構體中創建了一個很大的陣列data,導致結構體過大,傳參時浪費的記憶體空間很大,效率低下,但是如果傳址&s的話,作為一個指標,占四個位元組,極大提高了運行效率,
簡而言之,結構體傳參時,傳結構體的地址更好
二、位段
1、位段的定義
位段,C語言允許在一個結構體中以位為單位來指定其成員所占記憶體長度,這種以位為單位的成員稱為“位段”或稱“位域” ,利用位段能夠用較少的位數存盤資料,
位段的宣告和結構是類似的,有兩個不同:
- 位段的成員必須是 int、unsigned int 、signed int、char ,
- 位段的成員名后邊有一個冒號和一個數字(指該成員占的位元位),
??代碼展示:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
2、位段的記憶體分配
位段的記憶體分配規則:
- 位段的成員可以是 int、unsigned int、signed int或者char (屬于整形家族)型別
- 位段的空間上是按照需要以==4個位元組( int )或者1個位元組( char )==的方式來開辟的,
- 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程式應該避免使用位段,
??代碼展示:
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;
🔖解釋說明:
在VS編譯器中開辟了空間以后,先使用低地址再使用高地址,并且剩余的位元位不夠下一個變數存盤時,那這一片空間將會被浪費,

簡而言之,跟結構相比,位段可以達到同樣的效果,但是可以很好的節省空間,但是有跨平臺的問題存在,
3、位段的應用

🔖解釋說明:
上圖是網路上IP資料包的格式,當你想要在網路上發一條訊息給你的好友,資訊是需要進行分裝的,訊息作為資料只是傳輸的一部分,還有一部分傳輸的是分裝中的其他資訊,比如4位版本號,4位首部長度,這些資訊只需要4個bit,如若不使用位段,直接每個部分一個整形的給空間,就會造成空間的大量浪費,
三、列舉
1、列舉型別的定義
在數學和計算機科學理論中,一個集的列舉是列出某些有窮序列集的所有成員的程式,或者是一種特定型別物件的計數,這兩種型別經常(但不總是)重疊,列舉在日常生活中很常見,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一個列舉,
2、列舉的優點
列舉的優點:
- 代碼的可讀性變高和可維護性變強
- 和#define定義的識別符號相比較列舉更加嚴謹,因為有型別檢查,
- 防止命名污染的現象
- 方便除錯,且使用方便,可以一下子定義很多常量
3、列舉的使用
列舉的說明與結構和聯合相似, 其形式為:
enum 列舉名
{
識別符號[=整型常數],
識別符號[=整型常數],
...
識別符號[=整型常數]
} 列舉變數;
如果列舉沒有初始化,即省掉"=整型常數"時, 則從第一個識別符號開始,順次賦給識別符號0, 1, 2, …但當列舉中的某個成員賦值后,其后的成員按依次加1的規則確定其值,
??代碼展示:
//代碼1
enum Num1
{
x1,
x2,
x3,
x4
}x;
//代碼2
enum Num2
{
y1,
y2 = 0,
y3 = 50,
y4
};
int main()
{
printf("%d %d %d %d\n", x1, x2, x3, x4);
printf("%d %d %d %d\n", y1, y2, y3, y4);
return 0;
}
👁效果展示:

注意:
列舉中每個成員(識別符號)結束符是==","== 不是";", 最后一個成員可省略",",
初始化時可以賦負數, 以后的識別符號仍依次加1,
列舉變數只能取列舉說明結構中的某個識別符號常量,
列舉值是常量,不是變數,不能在程式中用賦值陳述句再對它賦值(比如上面的代碼出現y3 = 3; ?),
只能把列舉值賦予列舉變數,不能把元素的數值直接賦予列舉變數,除非進行了強制型別轉換(比如上面的代碼出現x = x2?? x = 1?x = (enum Num1)1??)
四、聯合體(共用體)
1、聯合體的定義
需要使幾種不同型別的變數存放到同一段記憶體單元中,也就是使用覆寫技術,幾個變數互相覆寫,這種幾個不同的變數共同占用一段記憶體的結構,在C語言中,被稱作“共用體”型別結構,簡稱共用體,也叫聯合體,
2、聯合體的特點
聯合的成員是共用同一塊記憶體空間的,一個聯合變數的大小,至少是最大成員的大小(因為聯合至少得有能力保存最大的那個成員)
??代碼展示:
//聯合型別的宣告
union Un
{
char c;
int i;
};
//聯合變數的定義
union Un un;
int main()
{
//例①
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
//例②
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
👁效果展示:

🔖解釋說明:
通過例①的結果,我們可以直觀發現成員變數c和成員變數i共用地址
例②更加證實這一點,由于大小端存盤,變數i是以44 33 22 11這樣的順序存盤的,因為變數c與其公用地址,因此55將44覆寫,在記憶體中變數i為55 33 22 11,列印出來為11 22 33 55
聯合體的相關應用:
在之前我們已經學會了判斷計算機大小端的方法,這里可以通過共用體的特點來實作
#include <stdio.h>
union Un
{
char c;
int i;
}num;
int main()
{
num.i = 1;
if(num.c == 1)
{
printf("小端存盤")
}
else
{
printf("大端存盤")
}
return 0;
}
向成員變數i中存放一個1,查看成員變數c的值,由于該變數是char型別,因此只訪問了第一個位元組,
3、聯合體的大小計算
聯合體大小計算規則:
- 聯合的大小至少是最大成員的大小,
- 當最大成員大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍,
??代碼展示:
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}
👁效果展示:

完!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/295696.html
標籤:其他
上一篇:對話譚浩強第四章
下一篇:小鼠是如何找出毒藥的?
