深圳幻海软件技术有限公司 欢迎您!

c语言结构体看这篇文章就够啦(详细介绍结构体)

2023-03-31

前言:    c语言两大重要点,一个是指针,另一个就是结构体啦,这篇文章我将全面的介绍一下结构体,和他的使用,相信大家看完这篇以后定能对结构体有个深入的理解,并且会正确的使用它。 💞💞   欢迎来到小马学习代码博客!

前言:

        c语言两大重要点,一个是指针,另一个就是结构体啦,这篇文章我将全面的介绍一下结构体,和他的使用,相信大家看完这篇以后定能对结构体有个深入的理解,并且会正确的使用它。

 💞 💞    欢迎来到小马学习代码博客!!!

 现在已经入冬了吧,小马想问一下大家那里都下雪了嘛,我们这还没有下,但是在家里真的好冷啊,根本不想出门😭 

思维导图:

 

 

目录

一、结构体的认识

1.1结构体存在的意义:

1.2结构体的声明和定义:

1.3结构体的特殊声明:

1.4结构体的访问:

1.5结构体的初始化:

1.6结构体的传参:

2、结构体内存对齐

2.1结构体对齐的意义:

2.2结构体内存对齐的规则:

2.3代码演示:

2.4默认对齐数的修改: 

3、结构体类型

3.1结构体数组:

3.2结构体指针:

总结:


 

一、结构体的认识

1.1结构体存在的意义:

         结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
要知道数组存放的是相同类型的数据,而在日常生活中描述一个东西就不能用单单一个数据,例如描述一个学生,则需要姓名,性别,年龄,身高等等,这些就里就不能用数组来实现,固然需要我们用到结构体来完成。 结构体不仅可以记录不同类型的数据,而且使得数据结构是“高内聚,低耦合”的,更利于程序的阅读理解和移植,而且结构体的存储方式可以提高CPU对内存的访问速度。

1.2结构体的声明和定义:

  1. struct tag
  2. {
  3. member-list;
  4. }variable-list;
例如一个学生的信息:
1.2.1声明了之后定义
  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. char sex[5];//性别
  6. char id[20];//学号
  7. }; //分号不能丢
  8. struct Student stu1; //定义结构体变量

1.2.2声明的同时直接定义

  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. char sex[5];//性别
  6. char id[20];//学号
  7. }stu1; //分号不能丢 //声明的同时定义

1.3结构体的特殊声明:

  1. //匿名结构体类型
  2. struct
  3. {
  4. int a;
  5. char b;
  6. float c;
  7. }x;

匿名结构体类型不好的一点就是定义之后不能在定义新的结构体变量了。

1.4结构体的访问:

        在数组中我们是通过数组的下标来访问的,但是在结构体中,每个元素的类型都不是相同的,所以没办办法通过下表的方式来进行访问,故通过结构变量的成员是通过点操作符(.)访问的。

例如:

  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. char sex[5];//性别
  6. char id[20];//学号
  7. }stu1; //分号不能丢

stu1.name 表示这个学生的姓名,stu1.age表示这个学生的年龄等等。

要是结构体里面嵌套一个结构体:

  1. struct Birthday{
  2. int year;
  3. int month;
  4. int day;
  5. };
  6. struct Student{
  7. char name[20];
  8. int age ;
  9. char sex[20];
  10. char id[20];
  11. struct Birthday birthday;
  12. }stu1;

stu1.name.month 表示这个学生的生日的月份,stu1.name.year 表示生日的年份

结构体指针访问指向变量的成员
  1. struct Stu
  2. {
  3. char name[20];
  4. int age;
  5. };
  6. void print(struct Stu* ps) {
  7. printf("name = %s   age = %d\n", (*ps).name, (*ps).age);
  8.    //使用结构体指针访问指向对象的成员
  9. printf("name = %s   age = %d\n", ps->name, ps->age);
  10. }
  11. int main()
  12. {
  13.    struct Stu s = {"zhangsan", 20}; //结构体的赋值
  14.    print(&s);//结构体地址传参
  15.    return 0;
  16. }

这里是通过箭头来访问的

1.5结构体的初始化:

1.5.1在定义后在进行成员逐步赋值:

  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. char sex[5];//性别
  6. char id[20];//学号
  7. }; //分号不能丢
  8. int main()
  9. {
  10. struct Student stu1;
  11. strcpy(stu1.name,"xiaoma"); //strcpy进行赋值
  12. stu1.age =18;
  13. strcpy(stu1.sex,"nan");
  14. strcpy(stu1.id,"12345667");
  15. }

1.5.2在定义后进行整体赋值:

  1. struct Student{
  2. char name[20];
  3. int age ;
  4. char sex[20];
  5. char id[20];
  6. };
  7. int main()
  8. {
  9. struct Student stu2;
  10. stu2=(struct Student){"xiaoli",18,"nan","1234567"};
  11. //这里要进行强制类型转换 因为数组的赋值也是用的{},你要把它转换为结构体的赋值
  12. }

1.5.3在定义的同时进行整体赋值 :

  1. struct Student{
  2. char name[20];
  3. int age ;
  4. char sex[20];
  5. char id[20];
  6. }stu1={"xiaoma",18,"nan","12345667"}; //这是一种定义的同时进行赋值
  7. int main()
  8. {
  9. struct Student stu2={"xiaoli",18,"nan","1234567"}; //这是第二种定义后同时进行赋值
  10. }
  11. //要注意赋值的顺序要和你声明的元素顺行相同

1.5.4在定义的同时进行部分赋值:

  1. struct Student{
  2. char name[20];
  3. int age ;
  4. char sex[20];
  5. char id[20];
  6. }stu1={.name="xiaoma"};
  7. int main()
  8. {
  9. struct Student stu2={.name="xiaoli"}; //这里只是给姓名进行赋值
  10. }

1.5.5通过结构体进行赋值:

  1. struct Student{
  2. char name[20];
  3. int age ;
  4. char sex[20];
  5. char id[20];
  6. }stu1={.name="xiaoma"};
  7. int main()
  8. {
  9. struct Student stu2={.name="xiaoli"};
  10. stu2=stu1;
  11. }

这里是把结构体stu1赋值给结构体stu2(这里只是成员间进行赋值,并不改变结构体的地址

我们分别打印一下结构体的stu1和结构体stu2的地址:

发现只是将值进行的赋值,但地址并没有进行改变 。

1.6结构体的传参:

  1. struct S
  2. {
  3. int data[1000];
  4. int num;
  5. };
  6. struct S s = {{1,2,3,4}, 1000};
  7. //结构体传参
  8. void print1(struct S s) {
  9. printf("%d\n", s.num);
  10. }
  11. //结构体地址传参
  12. void print2(struct S* ps) {
  13. printf("%d\n", ps->num);
  14. }
  15. int main()
  16. {
  17. print1(s);  //传结构体
  18. print2(&s); //传地址
  19. return 0; }

这里我们有两种传参方法,一种是进行整体结构体传参,另一个是进行地址传参,这两种哪一种比较好呢?传地址是比较好的 因为:函数传参的时候,参数是需要压栈的。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

2、结构体内存对齐

2.1结构体对齐的意义:

      内存是以字节为单位编号的,某些硬件平台对特定类型的数据的内存要求从特定的地址开始,如果数据的存放不符合其平台的要求,就会影响到访问效率。所以在内存中各类型的数据按照一定的规则在内存中存放,就是对齐问题。而结构体所占用的内存空间就是每个成员对齐后存放时所占用的字节数之和。

1. 平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特

定类型的数据,否则抛出硬件异常。
 
2. 性能原因
数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。

2.2结构体内存对齐的规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

对齐数:在编译器中都存在一个默认对齐数 VS编译器的对齐数是8 成员大小就是所占字节的大小例如 int的是4,char是1等等

2.3代码演示:

  1. struct S1
  2. {
  3. char c1;
  4. int i;
  5. char c2;
  6. };
  7. int main()
  8. {
  9. printf("%lu\n", sizeof(struct S1));
  10. return 0;
  11. }

例如这个结构体 我们算的他所占字节是12 而结构体S1中就一个两个char类型和一个int类型不应该字节数为6吗 这里就是因为结构体内存对齐。

这里解释了为什么是12:

我们也可以进行对偏移量的打印来确认一下

  1. #include<stdio.h>
  2. #include<stddef.h> //运用offsetof这个宏需要引用这个头文件
  3. struct S1
  4. {
  5. char c1;
  6. int i;
  7. char c2;
  8. };
  9. int main()
  10. {
  11. printf("%lu\n", sizeof(struct S1));
  12. printf("%lu\n",offsetof(struct S1,c1));
  13. printf("%lu\n",offsetof(struct S1,i));
  14. printf("%lu\n",offsetof(struct S1,c2));
  15. return 0;
  16. }

这里我们打印结果可以证明内存对齐存在

2.4默认对齐数的修改: 

  1. #pragma pack(4) //这里把默认对齐数修改成了4
  2. struct S1
  3. {
  4. char c1;
  5. int i;
  6. char c2;
  7. };
  8. #pragma pack() //这里又回到了最初的默认对齐数

3、结构体类型

3.1结构体数组:

   结构体数组一般应用还比较多 例如:在完成通讯录的时候,一个人员信息用结构体但通讯录并不是一个成员,则需要应用到结构体数组,在进行同学信息统计也需要应用结构体数组。结构体数组的定义和初始化其实都和上面差不多

3.1.1进行声明的同时定义:

  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. char sex[5];//性别
  6. char id[20];//学号
  7. }stu[5]; //分号不能丢

3.1.2声明后定义:

  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. char sex[5];//性别
  6. char id[20];//学号
  7. }; //分号不能丢
  8. struct Student stu[5];

3.1.3定义结构体数组同时进行初始化:

  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. }; //分号不能丢
  6. struct Student stu[2]={{"xiaoma",18},{"xiaoli",18};

3.1.4定义后在进行初始化:

  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. }; //分号不能丢
  6. struct Student stu[2];
  7. stu[1]=(struct Student){"xiaoma",18};

3.1.5将每个成员逐步初始化:

  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. }; //分号不能丢
  6. struct Student stu[2];
  7. strcpy(stu[2].name,"xiaoma");
  8. stu[2].age=18;

3.2结构体指针:

结构体指针应用在数据结构中比较多:例如应用在单链表,带头双向循环链表中用来指向下一个结构体,同时进行结构体传参的时候运用指针也比较好。例子我就不举了,大家可以看一下我的单向循环链表,和带头双向循环链表进行深入的了解一下结构体指针。

  1. struct Student
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. char sex[5];//性别
  6. char id[20];//学号
  7. }; //分号不能丢
  8. struct Student *pstu;

总结:

        以上就是结构体的全部内容了,从结构体的认识,结构体存在的意义是什么,他和数组的区别,同时也有结构体的声明定义和初始化,在之后也讲述了结构体内存对齐的原理和为什么要进行内存对齐,在之后结构体类型中只是讲述了结构体数组,和结构体指针,结构体函数传参(在上面),结构体传参为什么有地址进行传参,而不是整体的进行传他的好处,这里已经尽小马所能把结构体能想到的都给大家全部呈现出来啦!

 最后小马码文不易,如果觉得有帮助就多多支持哈!!!^ _ ^

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览42577 人正在系统学习中