C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】

前言:本章主要内容是C语言自定义类型中的结构体、枚举和联合。
【C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】】
文章目录

  • 结构体
      • 结构
        • 结构的声明
        • 例如描述一个学生:
        • 特殊的声明(声明结构也可以不完全声明)
          • 例如(省略结构体标签tag):
      • 结构的自引用
          • 例如:
      • 结构体变量的定义和初始化
          • 1.声明类型的同时定义变量p1
          • 2.定义结构体变量p2
          • 3.初始化:定义变量的同时赋初值。
            • 3.1
            • 3.2
          • 4.结构体嵌套初始化
            • 4.1
            • 4.2
      • 结构体内存对齐
        • 如何计算?首先得掌握结构体的对齐规则:
          • 例题,计算下列结构体的大小。
        • 为什么存在内存对齐?
        • 那在设计结构体的时候,我们既要满足内存对齐,又想节省空间,如何做到?
          • 例题:
        • 修改默认对齐数
          • 1.设置默认对齐数
          • 2.取消设置默认对齐数,还原为默认
          • 例子
        • 结构体传参
          • 例题(结构体传参和结构体地址传参)
      • 位段
        • 什么是位段?
          • 比如:
        • 位段的内存分配原则
          • 例子:
          • 思考下上例中空间是如何开辟的?
        • 位段的跨平台问题
        • 位段的应用
  • 枚举
      • 枚举类型的定义
        • 星期、性别、颜色枚举类型举例
        • 枚举常量都是有值得,默认从0开始,一次递增1,当然也可以在定义的时候赋初值。例如:
        • 枚举的优点(明明已经有#define定义常量,为什么非要使用枚举?)
        • 枚举的使用
          • 例子1:这样给clr赋值5可以吗?
          • 例子2:更加直观的显示所要实现算法的功能,而非简单的1、2、3、4.
  • 联合(共用体)
        • 联合类型的声明、定义和联合类型变量大小计算:
        • 联合的特点
        • 判断大小端存储
          • 例题:若位顺序类似小端,低地址在低处,所以39是低地址,在低位,38在高位,结果就是3839;若类似大端存储,则结果是3938
        • 联合大小的计算
          • 例题:

结构体 结构
定义:结构是一些值的集合,这些值成为成员变量。结构的每个成员可以是不同类型的变量。
结构的声明
struct tag { member - list; }variable-list;

例如描述一个学生:
struct stu { char name[30]; //名字 int age; //年龄 char sex[5]; //性别 };

特殊的声明(声明结构也可以不完全声明) 例如(省略结构体标签tag):
struct { int a; char b; float c; }a; struct { int a; char b; float c; }a[30],*p;

注意:编译器会把上面两个声明当成完全不同的两个类型,所以使用 p=&x 非法。
结构的自引用
例如:
struct Node { int data; struct Node* next; };

结构体变量的定义和初始化
1.声明类型的同时定义变量p1
struct Point { int x; int y; }p1;

2.定义结构体变量p2
struct Point p2;

3.初始化:定义变量的同时赋初值。 3.1
struct Point p3 = { x , y };

3.2
struct Stu { char name[20]; int age; }; struct Stu s = { "zhangsan",20 };

4.结构体嵌套初始化 4.1
struct Node { int data; struct Point p; struct Node* nest; }n1 = { 10,{4,5},NULL };

4.2
struct Node n2 = { 20,{5,6},NULL };

结构体内存对齐
掌握了结构体的基本使用后,接下来介绍下如何计算结构体的大小,这就涉及到了一个热门考点:结构体内存对齐。
如何计算?首先得掌握结构体的对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的一个对齐数与该成员大小的较小值。
VS中的默认值是8
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
例题,计算下列结构体的大小。
struct S { char c1; int i; char c2; }; struct S2 { char c1; int i; double d; //8 }; struct S3 { char c1; char c2; int i; }; struct S4 { double d; char c; int i; }; struct S5 { char c1; struct S4 s4; double d; }; #include int main() { struct S s = {0}; struct S2 s2 = { 0 }; struct S3 s3 = { 0 }; struct S4 s4 = { 0 }; struct S5 s5 = { 0 }; printf("%d\n", sizeof(s5)); //32 printf("%d\n", sizeof(s)); //12 printf("%d\n", sizeof(s2)); //16 printf("%d\n", sizeof(s3)); //8 printf("%d\n", sizeof(s4)); //16 return 0; }

C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】
文章图片

注释:
以S为例,画下S的图示。其余类似,大家可以自己动手画图分析一波!
C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】
文章图片

S的c1占用一个字节,浪费三个字节,int从下标4(第五个)开始,占用四个字节,c2占用一个字节,浪费3个字节,累计1+3+4+1+3=12;
S2的c1占用一个字节,浪费三个字节,int从下标4(第五个)开始,占用四个字节,d占用8个字节,累计1+3+4+8=16;
S3的c1占用一个字节,c2占用一个字节,然后浪费两个字节,int从下标4(第五个)开始,占用四个字节,累计1+1+2+4=8;
S4的d占用8个字节,c占用一个字节,然后浪费三个字节,int从下标12(第13个)开始,占用四个字节,累计8+1+3+4=16;
S5的c1占用1个字节,然后浪费7个字节,struct s4从下标8(第九个)开始(此时默认对齐数是8),占用16个字节,double d从下标24(第25个)开始,占用8个字节,累计1+7+16+8=32;
为什么存在内存对齐? C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】
文章图片

总结:结构体的内存对齐是拿空间换取时间
那在设计结构体的时候,我们既要满足内存对齐,又想节省空间,如何做到?
答:让占用空间小的成员尽量集中在一起
例题:
struct s1 { char c1; int i; char c2; }; struct s2 { char c1; char c2; int i; };

注释:上例中s1和s2类型的成员一模一样,但是s1占了12字节,s2占8字节,s2更具备优势。
修改默认对齐数 1.设置默认对齐数
#pragma pack(n)//默认对齐数为n

2.取消设置默认对齐数,还原为默认
#pragma pack()//取消默认对齐数

例子
#pragma pack(2)//默认对齐数为2 struct S { char c1; //0 int i; // char c2; // }; #pragma pack()//取消默认对齐数#pragma pack(1)//默认对齐数为1 struct S { char c1; //1 1 1 int i; //4 1 1 char c2; //1 1 1 }; #pragma pack()//取消默认对齐数int main() { printf("%d\n", sizeof(struct S)); return 0; }

注释:结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。
结构体传参 例题(结构体传参和结构体地址传参)
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函数,print1的缺点很明显:在函数传参过程中,参数需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,会导致性能下降。
总的来说,结构体传参的时候,传结构体的地址(指针大小固定4/8)。
位段
什么是位段? 位段的声明和结构是类似的,注意两点不同:
1.位段的成员必须是int、unsigned int或signed int。
2.位段的成员名后边有一个冒号和一个数字。
比如:
struct A { //4个字节 - 32bit int _a : 2; //_a 成员占2个bit位 int _b : 5; //_b 成员占5个bit位 int _c : 10; //_c 成员占10个bit位 //剩余15个bit //4个字节 - 32bit int _d : 30; //_b 成员占30个bit位 }; int main() { printf("%d\n", sizeof(struct A)); //8 return 0; }

注释:A就是一个位段类型,大小是8字节。
位段的内存分配原则
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; }; int main() { struct S s = { 0 }; s.a = 10; s.b = 12; s.c = 3; s.d = 4; return 0; }

思考下上例中空间是如何开辟的? 答:
C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】
文章图片

位段的跨平台问题
1.int位段被当成有符号数还是无符号数是不确定的。
2.位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)。
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余位段还是继续利用剩余位段,这点不确定。
总结:
跟结构相比,位段可以达到同样的效果,还可以很好的节省空间,但是存在跨平台的问题。
位段的应用
在计算机网络的信息传输过程中,减小数据包的大小。
C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】
文章图片

枚举
顾名思义就是把可能的取值一一列举。
比如:
月份有12个月,可以一一列举。
一周有七天,可以一一列举。
性别有男、女,可以一一列举。
颜色也可以一一列举。
这些例子里就可以使用枚举。
枚举类型的定义
星期、性别、颜色枚举类型举例
enum Day { Mon, Tues, Wed, Thur, Fri, Sat, Sun }; enum Sex { MALE, FEMALE, SECRET }; enum Color { RED, GREEN, BLUE };

注释:以上定义的enmu Day,enum Sex,enum Color都是枚举类型。{}中的内容是枚举类型的可能取值,也叫枚举常量。
枚举常量都是有值得,默认从0开始,一次递增1,当然也可以在定义的时候赋初值。例如:
enum Color { RED=1, GREEN=2, BLUE=4 };

枚举的优点(明明已经有#define定义常量,为什么非要使用枚举?)
1.增加代码的可读性和可维护性
2.和#define定义的标识符相比枚举有类型检查,更严谨
3.防止命名污染(封装)
4.便于调试
5.使用方便,一次可以定义多个变量
枚举的使用 例子1:这样给clr赋值5可以吗?
enum Color { RED=1, GREEN=2, BLUE=4 }; enum Color clr = GREEN; clr = 5;

答:不可以,枚举变量一经过定义赋值,不可再改变。
例子2:更加直观的显示所要实现算法的功能,而非简单的1、2、3、4.
void menu() { printf("*****************************\n"); printf("****1. add2. sub*****\n"); printf("****3. mul4. div*****\n"); printf("****0. exit*****\n"); printf("*****************************\n"); }enum Option { EXIT,//0 ADD,//1 SUB,//2 MUL,//3 DIV//4 }; int main() { int input = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case ADD: break; case SUB: break; case MUL: break; case DIV: break; case EXIT: break; default: break; } } while (input); return 0; }

联合(共用体)
联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以叫做共用体).
联合类型的声明、定义和联合类型变量大小计算:
union Un { short s[7]; int n; }; Union Un un; int main() { printf("%d\n", sizeof(union Un)); return 0; }

注释:结构体向int对齐,7个short一共是14字节,对齐后是16字节。n是单独的4字节,由于是union,所以n与s共用空间,只取最长的元素,故占用16字节。
联合的特点
联合的成员是共用同一块内存空间,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
判断大小端存储 例题:若位顺序类似小端,低地址在低处,所以39是低地址,在低位,38在高位,结果就是3839;若类似大端存储,则结果是3938
int main() { union { short k; char i[2]; }*s, a; s = &a; s->i[0] = 0x39; s->i[1] = 0x38; printf("%x\n", a.k); return 0; }

C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】
文章图片

注释:由输出结果可知位顺序类似小端,低地址在低处,所以39是低地址,在低位,38在高位,所以是3839。
联合大小的计算
1.联合的大小至少是最大成员的大小。
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
例题:
int main() { union Un1 { char c[5]; int i; }; union Un2 { short c[7]; int i; }; printf("%d\n", sizeof(union Un1)); printf("%d\n", sizeof(union Un2)); return 0; }

C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】
文章图片

注释:Un1中,char c[5]至少占用6个字节,最大对齐数4,取最大对齐数的整数倍,结果为8;Un2中,short c[7]至少占用16个字节,最大对齐数4,取最大对齐数的整数倍,恰好结果为16.
自定义类型(结构体+枚举+联合)部分到此介绍结束了,感谢您的阅读!!!如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。

    推荐阅读