C语言|C语言--自定义类型详解(结构体+枚举+联合)

自定义类型详解(结构体+枚举+联合) 结构体 结构体类型的声明

struct tag { member-list }variable-list; struct Book { char name[20]; char author[20]; }s1; struct Book s2; int main() { struct Book s3; }

特殊声明-匿名结构体
struct //匿名结构体类型 { int a; char c; double d; }s1,s2; ///声明的时候创建对象struct { int a; char c; double d; }* ps int main() { ps=&s1; ///waring:编译器认为不兼容 }

typedef struct { int data; Node* next; }Node; ///error typedef struct Node { int data; struct Node* next; }Node; ///把这个结构体struct Node --->Node ///typedef xxx Node; //中间的正常结构体命名就是xxx

结构的自引用
数据结构:描述了数据在内存中存储的结构
自己能够找到自己类型的数据
struct Node { int num; struct Node* next; }

结构体变量的定义和初始化
  • 初始化用{}
struct P{ int x,y; }p3={5,6},p4={7,8}; struct Point p2={1,2}; struct S { double d; struct Point p; char name[20]; int data[20]; }; int main() { struct Point p1={3,4}; struct S s={3.14,{1,5},"zhangsan",{1,2,3}}; printf("%lf\n",s.d); printf("%d %d\n",s.p.x,s.p.x); printf("%s\n",s.name); }

结构体的内存对齐(计算结构体的大小)
热门考点
//练习1 struct S1 { char c1; int a; char c2; }; struct S2 { char c1; char c2; int a; }; int main() { struct S1 s={'x',100,'y'}; printf("%d",sizeof(struct S1)); printf("%d",sizoef(struct S2)); return 0; }

  • 结构体内存对齐的规则
    • 第一个成员在与结构体变量偏移量为0的地址处。
    • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    • 每次对齐数 = 编译器默认的一个对齐数 与 该变量大小的较小值。VS中默认的值为8.GCC中没有规定
    • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
    • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数(内部结构体的对齐数))的整数倍。
  • 结构体对齐的意义
    • 平台原因(移植原因)
      • 不是所有的硬件平台都能访问任意地址上的数据,某些硬件平台只能在地址处访问特定类型的数据(比如4的倍数),不然会抛出硬件异常
    • 性能原因
      • 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
      • 比如一次只能访问4个字节,char+int一次就访问不完,还要接着读取下面的int的一个字节
总的来说,结构体的内存对齐就是空间换时间
  • 使用的时候我们尽可能满足内存对齐,原则是让占用空间小的成员尽量集中在一起 packi
    • struct S1 { char c1; int i; char c2; struct S2 { char c1; char c2; int i; };

    • #pragma pack(1) struct S1 { char c1; //1 1 1 int i; //4 1 1 char c2; //1 1 1 }; #pragma pack() int main() { struct S1 s; printf("%d\n",sizeof(s)); //6 }

百度笔试题:
写一个宏,计算结构体中某变量相对于首地址的偏移。并给出说明。
考察:offset宏的实现
#include struct S1{ char c1; int i; char c2; }; int main(){ printf("%d\n",offsetof(struct S1,c1)); printf("%d\n",offsetof(struct S1,i)) ; }

修改默认对齐数
VS的默认对齐数是8。
#pragma预处理指令可以改变默认对齐数字
#include #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; }

结构体的传参
struct S{ int data[1000]; int num; }; void print1(struct S tmp){ int i=0; for(i=0; i<10; i++){ printf("%d ",tmp.data[i]); } printf("\nnum=%d\n",tmp.num); } void print2(const struct S* ps)//优先考虑使用这种形式比较优秀 { int i=0; for(int i=0; i<10; i++){ printf("%d ",ps->data[i]); } printf("%d\n",ps->num); } int main(){ struct S s={ {1,2,3,4,5,6,7,8,9,10},100}; print1(s); print2(&s); }

位段
讲的书比较少,C和指针提及。
【C语言|C语言--自定义类型详解(结构体+枚举+联合)】位段的声明和结构是类似的,有两个不同。
  1. 位段的成员必须是int,unsigned in,signed int
  2. 位段的成员名后边有一个冒号和一个数字
位段–二进制位
eg.性别 男 女 保密
? 00 01 10 11
//位段是可以节省空间的 struct A{ int _a:2; // 2 bit位 只给2位,截断 int _b:5; // 5 bit位 int _c:10; // 10 bit位 int _d:30; //30 bit 位}; //47 bit ---6 byte //8byte //4byte -->32bit2+5+10 此时不够30bit给_d //+4byte -->32bit此时_d用哪里的bit呢?标准没有规定。但 //是此时知道够用了整体为8字节 int main(){ printf("%d\n",sizeof(struct A)); //8 }

struct S{ char a:3; char b:4; char c:5; char d:4; }; int main(){ struct S s={0}; //0 0 0 0 0 0 0 0 //先左边3个bit还是右边3个,C没有交代。假设右边开始 s.a=10; s.b=12; s.c=3; s.d=4; }

位段的内存分配
  1. 位段的成员可以是int,unsigned int,signed int,或者char(属于整型家族)类型
  2. 位段的空间上是按照需要以4个字节(int)或者1个字节(char)来一个个开辟
  3. 位段涉及很多不确定因素,位段不跨平台的。注意可移植程序避免。
  4. VS下一个一个给,剩下不够扔掉。从右边开始。C标准没规定。
C语言|C语言--自定义类型详解(结构体+枚举+联合)
文章图片

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

枚举 枚举类型的定义
把可能的取值能一一列举。
enum Day{ Mon, Thes, Wed, Thur, Fri, Sat, Sun }; //枚举常量//字面常量,const常量,define常量,枚举常量 enum Color{ RED=5, GREEN,//6 BLUE//7}; //默认从0开始,向下递增,从最后一个赋值好的,剩余没初始化的递增 int main(){ enum Color c=GREEN; if(c==GREEN) { printf("绿色\n"); } }

枚举的优点
我们可以使用#define来定义,为什么要枚举
  1. 增加代码的可读性和标识性
  2. 枚举有类型,和#define比起有类型检查,更加严谨
  3. 防止了命名污染(封装)
  4. 便于调试
    1. 会显示是枚举类型
  5. 使用方便,一次可以定义多个变量
每个是整型变量,大小固定是整型字节。
枚举的使用
enum OPTION{ EXIT, ADD, SUB, MUL, DIV} void menu(){} int main(){ int input=0; do { menu(); printf("请选择:"); scanf("%d",&input); switch(input) { case ADD: break; case SUB: break; } } }

联合(共用体) 这种类型定义的变量包含一系列的成员,特征是这些成员公用同一块空间。
联合类型的定义
union Un{ char c; int i; } int main(){ Union Un u={0}; printf("%d\n",sizeof(u)); ///4个字节 printf("%p\n",&u); printf("%p\n",&(u.c)); printf("%p\n",&(u.i)); }

i和c同一时间只能用一个。改i也会改c,改c也会改i。
联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为 联合至少得有能力保存最大的那个成员)。
面试题:判断大小端
Union U{ char c; int i; }u; int main(){ u.i=1; if(u.c==1) { printf("小端"); } else{ printf("大端"); } }

联合大小的计算
  • 联合的大小至少是最大成员的大小
  • 当最大成员大小不是最大对齐数的整数倍的时候,要整数对齐
union Un1{ char c[5]; //5 1 int i; //4 4 } Union Un2{ short c[7]; //14 2 int i; //4 8 4 }

练习–通讯录
  1. 存放1000个人的信息
    1. 信息:名字+性别+年龄+电话+住址
  2. 增加联系人
  3. 删除联系人
  4. 修改联系人
  5. 查找联系人
  6. 排序联系人
  7. 存文件
##练习
下面代码的结果是( )
int main() { unsigned char puc[4]; struct tagPIM { unsigned char ucPim1; unsigned char ucData0 : 1; unsigned char ucData1 : 2; unsigned char ucData2 : 3; }*pstPimData; pstPimData = https://www.it610.com/article/(struct tagPIM*)puc; memset(puc,0,4); pstPimData->ucPim1 = 2; pstPimData->ucData0 = 3; pstPimData->ucData1 = 4; pstPimData->ucData2 = 5; printf("%02x %02x %02x %02x\n",puc[0], puc[1], puc[2], puc[3]); return 0; }

VS下的位段是右边开始赋值的。同时要注意,整个是一个字节内,所以这种情况下算值是
1 2 4 8 16 32…
填充序列是类似小端的低地址在低位,所以排列顺序是00 101 00 1。也就是0010 1001,即0x29。
因为为02 29 00 00
C语言|C语言--自定义类型详解(结构体+枚举+联合)
文章图片

下面代码的结果是:
#include 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; }

注意这是小端机子。
所以s->i[0]=0x39。放在左边(即低地址)
s->i[1]=0x38。放在右边(即高地址)
两个字节合起来读。高地址的数字在高位。所以是3839
C语言|C语言--自定义类型详解(结构体+枚举+联合)
文章图片

    推荐阅读