文章目录

  • 结构体
    • 结构体的声明
    • 结构体变量的定义
    • 结构体的特殊声明
    • 结构体的自引用
    • 结构体的初始化
    • 结构体内存对齐
    • 结构体传参
  • 位段
    • 位段的内存分配
    • 位段的跨平台问题
    • 位段的应用
  • 枚举
  • 联合(共用体)
    • 联合体的大小计算
    • 最后:文章有什么不对的地方或者有什么更好的写法欢迎大家在评论区指出

结构体

结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量

结构体的声明

下面的代码是结构体的基本框架

struct tag
{member-list;
}variable-list;

假设描述一个学生,学生的信息:姓名,年龄,性别,学号

struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}; //分号不能丢

结构体变量的定义

就以上面的学生为例,struct Stu属于结构体类型,下面定义的,s1,s2,s3,s4就是结构体变量
创建结构体类型的时候,并不开辟内存,只有定义完变量的时候才会开辟空间。

struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}s1,s2; //分号不能丢
int main()
{struct Stu s3;struct Stu s4;return 0;
}

结构体的特殊声明

结构体的特殊声明就是在创建结构体类型的时候,省略了结构体标签名,也是不完全声明,这也称为匿名结构体类型。

struct         //匿名结构体类型
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}; //分号不能丢

而我们在给匿名结构体类型定义变量的时候只能在结构体类型上,不能在函数内定义结构体变量

struct            //匿名结构体类型
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}s1,s2; //分号不能丢,应该在这里定义变量
int main()
{struct Stu s3;//在这里定义变量是errorstruct Stu s4;//在这里定义变量是errorreturn 0;
}

定义s3,s4变量就会报下面的错误,匿名结构体只能用一次,之后再想定义新的变量是不行的。

接下来下面有一个匿名结构体类型问题,来看一下

struct
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}a[20], *p;
//问题:在上面的代码基础上,下面的代码正确吗?
p=&x;

结论:因为是匿名结构体类型,所以编译器会把他们认定为两个不同的结构体类型,
所以p=&x,是错误的

结构体的自引用

结构体的自引用就是,在结构中包含一个为该结构体本身的成员

struct Node
{int data;struct Node next;//error
};

上面的这段代码是错误的,因为这样就类似于套娃,也就不能知道结构体类型的大小
应该改为下面这段代码

struct Node
{int data;struct Node* next;//true
};

如果嫌每次输入这个结构体类型太长,也可以选用typdef
下面的是正确写法

typedef struct Node
{int data;struct Node* next;
}Node;
int main()
{Node s1;return 0;
}

下面是错误示范

typedef struct Node
{int data;Node* next;//这里是不可以这样写的,error
}Node;
int main()
{Node s1;return 0;
}
typedef struct Node
{int data;struct Node* next;
}Node;//也就不能在这个地方定义变量了
int main()
{Node s1;//用了typdef是方便了很多,但是也有另外的一种声音就说//用完typdef之后并不能直观的知道这是什么类型return 0;
}

结构体的初始化

整体初始化

下面s1,s2,s3这两种初始化的方法是都可以的,而在定义s3时,可以顺序颠倒用这种方式

struct Stu //类型声明
{char name[15];//名字int age; //年龄
}s1={ "zhangsan",15 };
int main()
{struct Stu s2 = {"lisi",20};struct Stu s3={.age=25,.name="wangwu"};printf("%s %d",s1.name,s1.age);printf("%s %d",s2.name,s2.age);printf("%s %d",s3.name,s3.age);return 0;
}

单个初始化

字符串并不能直接初始化,需要借用到strcpy


struct Stu //类型声明
{char name[15];//名字int age;//年龄char a;
}s1;
int main()
{//struct Stu s2 = { "lisi",20 };//struct Stu s3 = { .age = 25,.name = "wangwu" };struct Stu s4;//s4.name = "lili";//errorstrcpy(s4.name, "lili");s4.age = 46;s4.a = 'a';printf("%s %d %c", s4.name, s4.age,s4.a);return 0;
}

结构体内存对齐

在开始之前我们先来了解一个宏offsetof

它是用来计算结构体成员相对于起始位置的偏移量

       #include <stddef.h>//头文件size_t offsetof(type, member);//type为结构体类型,member为结构体成员名
#include<stddef.h>
struct S1
{char a;int b;char c;
}s1;
int main()
{printf("%zd\n",offsetof(struct S1,a));//0printf("%zd\n", offsetof(struct S1, b));//4printf("%zd\n", offsetof(struct S1, c));//8return 0;
}

我们先来看一段代码,来看看这个结构体的内存大小
可能有人会猜结果是:6,但是输出的结果为:12

struct S1
{char a;int b;char c;
}s1;
int main()
{printf("%d",sizeof(struct S1));//12return 0;
}


结构体内存对齐的规则
1.结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处
2.从第二个成员开始,要对齐到某个【对齐数】的整数倍的偏移处
对齐数:结构体成员自身大小和默认对齐数的较小值
VS编译器的默认对齐数:8
Linux环境默认不设对齐数(对齐数是结构体成员的自身大小)
3.结构体的总大小,必须是最大对齐数的整数倍
每个结构体成员都有体格对齐数,其中最大对齐数就是【最大对齐数】
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

下面我们来看一段代码

struct S2
{double d;char c;int i;
};
struct S1
{char c1;struct S2 s2;double d;
};
int main()
{printf("%d",sizeof(struct S1));//结果:32return 0;
}

修改默认对齐数

//设置默认对齐数为1
#pragma pack(1)//恢复默认对齐数
#pragma pack()
struct s1
{char a;int b;char c;
}s;
int main()
{printf("%d",sizeof(struct s1));//6return 0;
}

为什么要内存对齐

  1. 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
    定类型的数据,否则抛出硬件异常。
  2. 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访

    总结:结构体的内存对齐是拿空间来换取时间的做法。

结构体传参

可以将结构体变量直接传过去,也可以将结构体地址传过去

struct s
{int a;int b;
};
void print(struct s s1)
{printf("%d",s1.a);
}
void print1(struct s* p)
{printf("%d",(*p).b);//这两种方式都可以printf("%d",p->b);//
}
int main()
{struct s s1 = {1,2};print(s1);print1(&s1);
}

传值和传址哪个好
传址好!
原因:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的
下降。

位段

位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字(数字的单位是比特位)

下面这段代码就属于位段

struct s
{int a : 2;int b : 10;int c : 20;
};

位段的内存分配

  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;
}


在这里我们也可以看出位段的内存是怎么分配的了,在VS当中每个字节的八个比特位是从右往左用的

位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。(不确定是否存在符号位)
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
    器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
    舍弃剩余的位还是利用,这是不确定的。
    总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在

可以参考一下下面的代码,同样都是存入1,2,3,4,而用的字节却不一样,这也就是位段的优势。

struct S
{char a : 1;char b : 2;char c : 3;char d : 4;
};
struct S1
{int _a;int _b;int _c;int _d;
};
int main()
{struct S s = { 0 };struct S1 s1;s.a = 1;s.b = 2;s.c = 3;s.d = 4;s1._a = 1;s1._b = 2;s1._c = 3;s1._d = 4;printf("%d\n",sizeof(struct S));//2printf("%d\n",sizeof(struct S1));//4return 0;
}

位段的应用

合理的使用空间

枚举

枚举的意思就是一一列举

enum Color//颜色
{RED,GREEN,BLUE
};
int main()
{enum Color color1 = RED;//trueenum Color color2 = 5;//在Vs当中是能通过的,c++是通不过的printf("%d",RED);//0printf("%d", GREEN);//1printf("%d", BLUE);//2return 0;
}

也可以对枚举类型里的成员进行定义
在后面还进行了对枚举类型内存大小进行了打印,结果是4,因为枚举类型在定义变量的时候,一次只能定义一个。

enum Color
{RED = 2,GREEN,BLUE = 5
};
int main()
{enum Color color1 = RED;printf("%d", RED);//2printf("%d", GREEN);//3printf("%d", BLUE);//5printf("%d",sizeof(enum Color));//4return 0;
}

通过这些代码我们也可以看出枚举和宏定义有一些类似
但是宏定义的内容并没有类型,就只是在预处理阶段全部替换了,并且如果数据变大之后,宏定义会很麻烦

#define RED 2
#define GREEN 3
#define BLUE 4

枚举的优点

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

联合(共用体)

联合体和结构体也有一些类似,联合体是只能存在一个成员,而结构体是要求每个成员必须都存在
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。

union Un
{char a;int b;double c;
};
int main()
{union Un un;printf("%d",sizeof(union Un));//8printf("%d",sizeof(un));//8return 0;
}
union Un
{char a;int b;double c;
};
int main()
{union Un un;printf("%p\n",&un);printf("%p\n", &un.a);printf("%p\n", &un.b);printf("%p\n", &un.c);return 0;
}


在这里可以用联合体来判断计算机的大小端


int ret()
{union Un{char a;int b;}un;un.b = 1;return un.a;
}

联合体的大小计算

联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍

union Un1
{char c[5];//自身对齐数是1,默认对齐数是8,它为最大成员5,//因为要取到最大对齐数的整数倍所以结果为8
int i;//自身对齐数是4,默认对齐数8,取较小值为4
};
union Un2
{short c[7];
int i;
};printf("%d\n", sizeof(union Un1));//8
printf("%d\n", sizeof(union Un2));//16

最后:文章有什么不对的地方或者有什么更好的写法欢迎大家在评论区指出

【C语言】自定义类型:结构体,位段,枚举,联合相关推荐

  1. 【C语言进阶】结构体 位段 枚举 联合体详解

    目录 1.结构体 1.1结构体的基础类型 1.2结构的声明 1.3特殊的声明 1.4结构体的自引用 1.5结构体变量的定义和初始化 1.6结构体内存对齐(计算结构体的大小) 1.6.1 结构体对齐规则 ...

  2. C语言--自定义类型——结构体

    结构体 建立结构体声明 定义结构变量 初始化结构 访问结构体成员 结构的初始化器 结构数组 指向结构的指针 用指针访问成员 向函数传递结构的信息 传递结构成员 传递结构的地址 传递结构 结构和结构指针 ...

  3. 结构体 位段 枚举 联合体

    结构体对齐规则: 1.第一个成员在结构体变量偏移量为0的地址处 2.其他成员变量要对齐到对齐数的整数倍的地址处 对齐数=编译器默认对起数 和 成员字节大小中的较小值 3.结构体总大小为最大对齐数的整数 ...

  4. c语言自定义的结构体,c语言定义多个结构体

    本文收集整理关于c语言定义多个结构体的相关议题,使用内容导航快速到达. 内容导航: Q1:C语言中定义一个结构体如何在不同的.C文件中使用. #include 然后就用啊 正式一点的,可以声明 ext ...

  5. 【C语言】自定义类型 结构体 枚举 联合

    结构体的基础知识 结构体是一些值的集合,这些值称为成员变量.结构体的每个成员可以是不同类型的变量. 结构体的声明 定义一个学生的结构体 struct Stu {char name[20];//名字in ...

  6. 自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合

    目录 一.结构体

  7. Mohican_4/22 结构体 typedef 枚举 联合 位段 内存对齐

    一.结构体 struct Test {int a;int b;int c; }; 关键字 struct  ,表示其是一个结构体,后面是一个可选的标记(Test) strcut Test Code st ...

  8. step2 . day5 C语言中的结构体和枚举

    最近几天交叉的学习C和Linux,知识梳理的不是很仔细,有很多还没有搞明白的问题,所有耽误了几天更新笔记,也是在细嚼慢咽中,做了一个规划表,现阶段先把C后面的知识学好,然后再梳理Linux系统相关知识 ...

  9. C语言——自定义类型(结构体,枚举,联合体,位段)

    目录 结构体 结构体的定义 匿名结构体 结构体的自引用 结构体大小计算 位段 枚举 枚举概念 枚举的声明与使用 枚举大小计算 枚举与宏的区别 联合体 联合体的概念 联合体的声明与使用 联合体大小计算 ...

  10. C语言自定义类型的介绍(结构体,枚举,联合体,位段)

    ⭐️前面的话⭐️ 大家好!在C语言中,有个叫"自定义类型"玩意,它究竟是什么呢?其实,就是字面意思,可以自己定义的类型就是自定义类型.具体说就是我们熟知的结构体,枚举,位段,联合体 ...

最新文章

  1. Technology Document Guide of TensorRT
  2. 兔子--html,js,php,ASP,ASP.NET,JSP的关系
  3. [arduino]-序言:面向仅有C语言基础之人的单片机开发板
  4. php针探,php针探代码,这款针探可以检测你机器的(1/7)
  5. 被脱库咋办?KMS 给你解决方案!
  6. python读取scv文件显示:OSError: Initializing from file failed
  7. android- activity,Application,activity渲染xml文件
  8. 计算机flash听课记录范文,听课记录范文.doc
  9. python列重命名
  10. 2018拼多多校招【最大乘积】Python解法
  11. uniapp 获取设备唯一标识(OAID、AAID、AndroidID、IMEI等)插件 Ba-IdCode
  12. VirusTotal智能搜索
  13. 2014年双11抢货宝典-双11促销商品价格对比清单---妈妈再也不用担心我成为剁手党啦
  14. 程序员都要学学任正非的坚强!这个老头,在逆境中崛起!
  15. XV6 Lab7:Locks
  16. 推荐个开源在线文档,助道友领悟 Django 之“道”
  17. shu_p64 n后问题
  18. 供应施耐德MT框架开关MT10 MT12 MT16 MT20 MT25 MT32 MT40 MT63
  19. Android studio3.6.1+ opencv3.4.1进行摄像头颜色识别的调试过程
  20. 华为研究院19级研究员几年心得终成趣谈网络协议文档,讲解

热门文章

  1. Win11文件夹拒绝访问怎么办?
  2. iOS开发(第三方使用)——讯飞语音SDK接入
  3. 35个电子商务购物网站界面设计欣赏
  4. STM32学习笔记——结构体
  5. excel拆分表格并保存为工作簿
  6. 【云原生 | Envoy 系列】--Envoy两种健康检测方式
  7. 简单的Web版计算器
  8. 如何从ie11降到ie9
  9. C++中复制构造函数与重载赋值操作符
  10. ECCV22 | 从单目RGB图像中进行类别级6D物体姿态估计的物体级深度重构