> 文档中心 > struct结构体你了解多少,希望认真的6个小时,可以换来各位看官你的回眸

struct结构体你了解多少,希望认真的6个小时,可以换来各位看官你的回眸

今天重点说下结稍微构体相关知识,今天内容有点多,荤素搭配,希望各位看官耐心看完,相信会有收获的 。
今天也要努力学习啊。 ღ( ´・ᴗ・` )
今天也要努力学习呀!

1.什么是结构

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

2. 结构体都有“名字”吗

一般结构体就是匿名和非匿名,下面看看他们的区别吧
例 1:

struct stu{int age;char ID[20];char name[20];}n1,n2,*n3;

这是正常结构体,内部是结构体成员,n1,n2是结构体创建的变量,n3时结构体变量指针,stu是结构体名称,也就是结构体名字。

那如果没有名字呢?
例 2:

struct{int age;char ID[20]; char name[20];}n4;

这里的结构体就没有名字,只有一个结构体创建的变量n4,这叫匿名结构体。

那么有个问题来了,n4和n1或n2的类型相同吗?
请看下面代码
例 3:

struct stu{int age;char ID[20];char name[20];}n1,n2,*n3;struct{int age;char ID[20]; char name[20];}n4;int main(){n3 = &n4;// err:“=”: 从“*”到“stu *”的类型不兼容return 0;}

这样来看是不对的,虽然结构体成员一样,但类型是不同的。

3.结构体的自引用(类似链表)

一个结构体可以自己引用自己吗?
例 4:

struct stu//err{int num;struct stu next;};

这样定义结构体,然后自引用可以吗?
答案是不行,这样会有一个著名的先有鸡还是先有蛋的问题。

正确的方法还是通过指针进行自引用。

struct stu{int num;struct stu* next;};

这样通过指针的方式进行访问是可以

例 5:

typedef struct//err{ int data; Node* next;}Node;

typedef对类型重命名后的也不行哦
这样时不可以的,还是存在鸡蛋悖论。

应该改为这样

typedef struct Node{ int data; struct Node* next;}Node;

4.结构体的初始化

结构体初始化问题,一般来说,定义结构体变量时就要初始化了,这样方便快捷,否则就要一个个成员去一点点的进行初始化。
例 6:
在这里插入图片描述
这里用调试的方案来展现初始化的两种方法。分别是n1的先定义在逐步初始化,这里注意的是数组初始化要用strcpy函数进行字符串拷贝。n2是直接进行定义并初始化。比较来看,n2比n1要简单,快捷。各有各的优劣吧,怎样初始化相信各位看官已经明白了吧。

还有就是结构体嵌套初始化,起始大同小异。
例 :7
在这里插入图片描述

这样就可以进行嵌套结构体的初始化。
(同样你也可以通过访问一个个结构体成员变量的形式进行初始化)。

5.如何访问结构体成员

访问结构体成员一般有两种方式
1.通过 ‘ . ’点操作符进行访问,下面打印下例7,来更深刻认识下点操作符。
例 8:

struct date{char name[20];int age;}n6;struct stu{int class;int grade;struct date n6;};int main(){struct stu n5 = { 1, 1, { "胡杨树下", 16 } };printf("%d\n", n5.class);printf("%d\n", n5.grade);printf("%s\n", n5.n6.name);printf("%d\n", n5.n6.age);return 0;}

通过 点操作符 来进行访问结构体变量进行打印,可以访问到每个成员。

2.通过 -> 指向指针的方式进行打印,也可以的
例 9:

struct date{char name[20];int age;}n6;struct stu{int class;int grade;struct date n6;};int main(){struct stu n5 = { 1, 1, { "胡杨树下", 16 } };struct stu *n7 = &n5;printf("%d", n7->class);printf("%d", n7->grade);printf("%d", n7->n6.age);printf("%d", n7->n6.name);return 0;}

这样通过指针的形式访问到了每个变量,也可进行打印。

6.结构体传参

那么各位读者大人,有想过为啥要有两种访问方法吗,一种不可以吗?
一种访问方式也不是不可以,只不过有时候为了效率更快,才用的两种方法,就比如说,一个结构体如果要传作为函数参数传递时,为了更快,更省空间,所以用传地址的形式,那么此时如果没有指针访问的形式,那么是不是很苦恼呢。
一般情况下结构体比正常定义的变量要大,传整体更费劲,更慢,更占空间所以一般传地址过去,这也就和上面说的指针访问结构体有联系,也回答了为什么有两种访问方法这一说了。

7.结构体内存对齐

为什么要有内存对齐呢?

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

内存对齐的规则

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

这个的详解可以看这位大佬的,很详细啦,就不在赘述了。
(最大对齐数是可以修改的)

#pragma pack(1)//在这之间的默认对齐数是1.#pragma pack()//取消设置的默认对齐数,还原为默认

结构体内存对齐数

8.空结构体大小问题

空结构体是多大,这个要看编译器,不同编译器不同效果。
在VS2013中,不允许定义的。
例 10:

struct stu{};int main(){int ret = sizeof(struct stu);//error要求一个结构或联合至少有一个成员return 0;}

在VS2013中结构体没有成员是不可以定义的。
在linux环境中,空结构体可以定义的,空结构体大小为0。

9 .结构体中的柔性数组概念(C99引入的)

柔性数组一般是在结构体中,用动态开辟的,一般柔性数组前必须要有一个有效的结构体成员,并且柔性数组成员是结构体最后一个成员。
例 11

struct str{int num;int arr[0];};int main(){struct str *p = (struct str*)malloc \(sizeof(struct str) + sizeof(int)*10);return 0;}

这里数组的数组,写成int arr[0] 或int arr[ ]都可以。
柔性数组的大小不算结构体的大小.
例 12:

struct str{int num;int arr[0];};int main(){int ret  = sizeof(struct str);//结果是4return 0;}

怎样访问柔性数组?
例 13:

struct str{int num;int arr[0];};int main(){struct str *p = (struct str*)malloc \(sizeof(struct str) + sizeof(int)*10);for (int i = 0; i < 10; i++){p->arr[i] = i;}for (int i = 0; i < 10; i++){printf("%d ", p->arr[i]);}free(p);p = NULL;return 0;}

运行结果
在这里插入图片描述

同样的结构体中的柔性数组会内存对齐吗?

实际上柔性数组,不完全算是结构体成员,它只是把地址交给结构体,让结构体使用,所以没有内存对齐这一说。

10.补充知识 : 位段

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

看个代码,了解下位段

例 14:

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

在这里插入图片描述

从这里可以看处这上面的位段占用三个字节。

在这里插入图片描述
下面的进行的是赋值。这是在内存中的数字。

关于位段还有很多不确定的因素。

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。所以位段跨平台性很差。

下期预告

typedef和其他存储类型关键词

欢迎回访,欢迎指正。

冰雪之城