【C语言】结构体(struct)最全的讲解(万字干货)以及枚举类型

您所在的位置:网站首页 结构体创建函数 【C语言】结构体(struct)最全的讲解(万字干货)以及枚举类型

【C语言】结构体(struct)最全的讲解(万字干货)以及枚举类型

2024-07-17 20:06| 来源: 网络整理| 查看: 265

谢谢观看!希望以下内容帮助到了你,对你起到作用的话,可以一键三连加关注!你们的支持是我更新地动力。 因作者水平有限,有错误还请指出,多多包涵,谢谢!

目录 一、结构体类型的声明1.1结构体回顾1.1.1结构的声明1.1.2结构体变量的创建和初始化 1.2结构的特殊声明1.3结构的自引用 二、结构体内存对齐2.1对齐规则2.2为什么存在内存对齐?2.3修改默认对齐数 三、结构体传参四、结构体实现位段4.1什么是位段?4.2位段的内存分配4.3位段的跨平台问题4.4位段的应用4.5位段使用的注意事项 五、枚举类型5.1枚举类型的声明5.2枚举的优点

  在自定义的类型中一共有3种: 结构体、联合体、枚举   那么就先让我们了解一下结构体的细节和枚举类型吧。

一、结构体类型的声明 1.1结构体回顾

  结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是相同类型的变量,也可以是不同类型的变量。成员变量可以是一个或者多个。   数组也是一些值的集合。数组的每个元素只能是相同类型的数据。元素个数可以是一个或者多个。   注意区分数组和结构的不同点和相同点。   

1.1.1结构的声明 struct tag { member-list;//成员列表 }variable-list;//变量列表

  让我们通过代码来描述一个学生:一个学生具有的属性是年龄、名字、性别、学号等。

//结构体类型,类似于int,char,float等类型 //不同的是,这个是我们自己创建的类型,我们创建类型是为了什么?当然是为了创建变量了,类似于int a、 char c等 //类型名是struct Stu,和int 、char一样 struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }; //分号不能丢,重要的部分

  

1.1.2结构体变量的创建和初始化

结构体变量的创建有2种方式,但是有点小区别:

在声明的时候进行创建结构体变量。此时的变量是全局变量在main()函数中用结构体类型来创建结构体变量。此时的变量是此时的变量是局部变量 struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }s1,s2;结构体变量的创建第一种方法,创建了变量s1、s2,类型是struct Stu //s1、s2是全局变量 int main() { //结构体变量的创建第二种方法 struct Stu s3;//创建了变量s3,类型是struct Stu struct Stu s4;//创建了变量s4,类型是struct Stu struct Stu s5;//创建了变量s5,类型是struct Stu //s3、s4、s5是局部变量 struct Stu s6 = {"王五",18,"男","2024"};//在创建结构体变量是进行赋值,就是初始化 return 0; }

结构体变量的初始化有2种方式,但是也有区别:

在结构体变量的创建时,用{}进行初始化。此时必须严格按声明中成员变量的顺序来初始化用{.成员变量 = , .成员变量 = }(使用到.操作符)来初始化变量。 #include struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }; int main() { //按照结构体成员的顺序初始化 //在结构体变量的创建时,用`{}`进行初始化 struct Stu s = { "张三", 20, "男", "20230818001" }; printf("name: %s\n", s.name); printf("age : %d\n", s.age); printf("sex : %s\n", s.sex); printf("id : %s\n", s.id); //按照指定的顺序初始化 //用`{.成员变量 = , .成员变量 = }`(使用到`.`操作符)来初始化变量 struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥" }; printf("name: %s\n", s2.name); printf("age : %d\n", s2.age); printf("sex : %s\n", s2.sex); printf("id : %s\n", s2.id); return 0; }

  通过上面学习结构体的知识,让我们写个代码实现一下,创建和初始化,以及打印数据。

struct Book { char book_name[20];//书的名字 char author[20];//作者 float price;//定价 char id[19];//书号 }; int main() { struct Book b1 = { "C语言","王五",38.8f,"PG20240520" }; struct Book b2 = { .id="PG20240510",.book_name="c++",.author="李四",.price=55.5f }; printf("%s %s %f %s\n", b1.book_name, b1.author, b1.price, b1.id); printf("%s %s %f %s\n", b2.book_name, b2.author, b2.price, b2.id); return 0; }

在这里插入图片描述      补充知识:可以看到红色方框和红色箭头,我们发现此时打印的浮点数类型的数据居然有偏差。其实这就说明了一点:浮点数在内存中有可能是不能精确保存的。

  分析一下:对于5.5、9.0我们是可以准确的写成对应的2进制数1001.0、101.1,那么对于这样的浮点数,在内存中是可以准确存放相关的值(E、M、S),但是对于38.8,我们是不能完整地写成对应的2进制数据100110.110...,因为0.8=0.5+0.25+...会始终差一些才可以等于0.8,而由于对于float浮点数中的M相关值的存放限制在23位,那么就可能会舍弃掉一部分的位数,导致浮点数在内存中并不是准确保存的。   

在这里插入图片描述

  所以结论是:浮点数在内存中并不是准确保存的。那根据这一点,在我们对浮点数进行比较大小时,我们就不能简单地对2个数据进行 ==是否相等的判断,而是有一定的误差。

int main() { //这是错误的比较浮点数的大小,因为可能会导致一些误差 flaot f = 3.45; if(f == 3.45) { } //这是正确的比较浮点数的大小 if(fabs(f-3.45) num); } int main() { struct S s = {{1,2,3,4}, 1000}; print1(s); //传结构体 print2(&s); //传地址 return 0; }

在这里插入图片描述

四、结构体实现位段

  结构体讲完就得讲讲结构体实现位段的能力。

4.1什么是位段?

在这里插入图片描述

struct A1 { int _a:2;//数字部分表示变量只占多少个2进制位,也就是占多少比特位 int _b:5; int _c:10; int _d:30; int _e:50;//这一行是错误的,int类型的比特位数就只有32位,50超出了限制,会报错 }; struct A2 { int _a; int _b; int _c; int _d; }; int main() { printf("%zd\n",sizeof(struct A1));//结果为8 printf("%zd\n",sizeof(struct A2));//结果为16 return 0; }

在这里插入图片描述      通过代码可以看出,位段可以在一定程度上减少空间的浪费,因为假如一个变量age表示一个人的年龄,设定1-100岁,那么给int变量占4个字节就有点浪费空间了,那么我们可以限制一下。

4.2位段的内存分配

在这里插入图片描述

//⼀个例⼦ struct S { char a:3; char b:4; char c:5; char d:4; }; int main() { printf("%zd\n",sizeof(struct S));//结果为3字节, //说明此时位段占3个字节大小存放成员数据,还大概表明了位段存放数据的形式 //测试一下位段中是如何存放数据的,调试窗口内存中看 struct S s = {0}; s.a = 10; s.b = 12; s.c = 3; s.d = 4; return 0; }

在这里插入图片描述      上面的猜想和代码运行的结果大小为3个字节是一致的,可能猜想正确,我们还可以测试一下 在这里插入图片描述   

4.3位段的跨平台问题

在这里插入图片描述

4.4位段的应用

在这里插入图片描述

4.5位段使用的注意事项

在这里插入图片描述

struct A { int _a : 2; int _b : 5; int _c : 10; int _d : 30; }; int main() { struct A sa = {0}; scanf("%d", &sa._b);//这是错误的, //因为_b和_a共用了一个字节,而一个字节只有一个地址编号,那么直接对_b取地址操作是错误的 //正确的⽰范 int b = 0; scanf("%d", &b);//输入10 sa._b = b;//虽然不能取地址操作,但是可以赋值 printf("%d",sa._b);//结果为10 return 0; } 五、枚举类型 5.1枚举类型的声明

在这里插入图片描述

enum Sex//性别 { //该枚举类型的三种可能取值 //它们都是常量,被称为枚举常量,默认从0开始,递增+1 MALE,//0 FEMALE,//1 SECRET//2 }; int main() { enum Sex people1 = MALE;//表示第一个人的性别是男性 enum Sex people2 = FEMALE;//表示第二个人的性别是女性 printf("%d\n",MALE);//0 printf("%d\n",FEMALE);//1 printf("%d\n",SECRET);//2 return 0; } enum Sex//性别 { MALE=7, FEMALE=9, SECRET=6 }; int main() { printf("%d\n",MALE);//7 printf("%d\n",FEMALE);//9 printf("%d\n",SECRET);//6 return 0; } enum Sex//性别 { MALE, FEMALE=8, SECRET }; int main() { printf("%d\n",MALE);//0 printf("%d\n",FEMALE);//8 printf("%d\n",SECRET);//9 return 0; }

     结论:从修改了初始值的常量开始后面的枚举常量表示的数字都递增+1,前面的保持不变。

     

5.2枚举的优点

在这里插入图片描述 类型检查

  

//写一个计算器-完成整数的加法、减法、乘法、除法 //代码一 enum Option { EXIT,//0 ADD,//1 SUB, MUL, DIV }; void menu() { printf("**********************************\n"); printf("****** 1. add 2. sub ******\n"); printf("****** 3. mul 4. div ******\n"); printf("****** 0. exit ******\n"); printf("**********************************\n"); } int main() { int input = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case SUB: break; case ADD: break; case MUL: break; case DIV: break; case EXIT: printf("退出\n"); break; default: printf("选择错误,重新选择\n"); break; } } while (input); return 0; } void menu() { printf("**********************************\n"); printf("****** 1. add 2. sub ******\n"); printf("****** 3. mul 4. div ******\n"); printf("****** 0. exit ******\n"); printf("**********************************\n"); } int main() { int input = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: break; case 2: break; case 3: break; case 4: break; case 0: printf("退出\n"); break; default: printf("选择错误,重新选择\n"); break; } } while (input); return 0; }

     对比一下代码一和代码二,在我们看代码主函数main()中的分支语句switch,发现两个代码的可读性不一样,看代码一我们可以明确知道哪一部分要实现加减乘除具体的操作,而代码二中我们是根据打印菜单对应下来才写相应的操作的。



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3