一、结构体
在初识结构体一文中,我们对结构体已经有所了解.
1.结构体的声明,
2.结构体变量的定义与初始化,
3.结构体传参.
其实结构体是一个很重要的内容,在数据结构中,应用十分广泛.所以学好结构体也是十分重要的!
1.1 匿名结构体
匿名结构体是一种省略struct后面的类型名的一种结构体类型.
这种情况好少见,是我们需要使用这个结构体一次时,后面不会再用这个结构体时使用.(个人感觉用处不大).
是一种不完全结构体的声明.
struct { int age;//年龄 char name[10];//姓名 float grade;//成绩 }s1 = { 18,"初阶牛",80.0f }; int main() { printf("%d %s %f\n", s1.age,s1.name,s1.grade); return 0; }
注意匿名结构体只有在声明结构体时的后面定义变量,否则对于一个没有名字的结构体,下次想使用就很难找到它了.
补充知识:
两个拥有相同成员变量的结构体,他们是同一类型的结构体吗?
对于两个拥有相同成员变量的结构体,编译器并不会将他们视作同一结构体类型.
//匿名结构体类型 struct s1 { int age;//年龄 char name[10];//姓名 double grade;//成绩 }s1 = { 18,"初阶牛",50 }; struct s2 { int age;//年龄 char name[10];//姓名 double grade;//成绩 } *p; int main() { p = &s1; printf("%d %s %lf\n", p->age, p->name, p->grade); return 0; }
运行结果:
18 初阶牛 50.000000
这里之所以能打印出来是因为,在c语言中会发生强制转换,p会指向s1的地址,刚好偏移量又一样,所以才会打印出来,但是这并不意味着这两个结构体类型相同.
1.2 结构体的自引用
之前我们见过结构体的嵌套定义.
如:
#include <stdio.h> typedef struct teacher { char name[20];//名字 char subject[20];//科目 }ter;//将struct teacher重命名为ter,使得类型名更加简洁 typedef struct student { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 ter t1;//嵌套定义,一个结构体中包含令一个结构体. }stu;//同理struct student重命名为stu int main() { //嵌套结构体的初始化 stu s1 = { "初阶牛",20,"男","20216666",{"pengge","c语言"} }; //嵌套结构体的打印 printf("%-6s %-2d %s %s %s %s\n", s1.name, s1.age, s1.sex, s1.id, s1.t1.name, s1.t1.subject); return 0; }
嵌套定义示例图解:
那么可以自己引用自己(自引用)吗?
我们试着尝试写一下代码:
错误示例1:
typedef struct student { char name[20];//名字 int age;//年龄 char sex[5];//性别 stu s1; }stu;
原因:
虽然我们对struct student类型进行了重命名为stu,但是在结构体内部还没有生效,因为这个结构体类型的定义还没结束,结构体本身并不认识stu,只有声明结束后才可以使用.
typedef struct student { char name[20];//名字 int age;//年龄 char sex[5];//性别 struct student s1; }stu;
原因:
虽然我们这里正确的使用了结构体的类型名,但是,如果一个结构体中引用了自己,那么这个结构体的大小是多少?这不就无限递归了吗?
这样?
正确的自引用方法是:
typedef struct student { char name[20];//名字 int age;//年龄 char sex[5];//性别 struct student* next;//一个结构体指针,用于指向与自己类型相同的结构体 }stu;
这种结构体自引用的情况在数据结构的链表中就有应用.
我们可以简单了解一下,后续在数据结构中会详细讲解.
这只是申请了局部变量作为链表的成员,仅仅只是为了介绍结构体自引用的应用.
不需要过分研究.
typedef struct student { char name[20];//名字 int age;//年龄 char sex[5];//性别 struct student* next; }stu; int main() { stu s1 = { "学生1",18,"男",NULL };//暂时将最后一个成员结构体指针置空 stu s2 = { "学生2",19,"女",NULL }; stu s3 = { "学生3",20,"男",NULL }; //将这些结构体都连接起来 s1.next = &s2; s2.next = &s3; //创建一个头指针来访问他们 stu* head = &s1; //打印 while(head != NULL)//最后一个结构体的next是NULL { printf("%-8s %-5d%s\n", head->name, head->age, head->sex); head = head->next;//往后找下一个结构体 } return 0; }
运行结果:
学生1 18 男
学生2 19 女
学生3 20 男
链表的简单了解:
抽象图:
内存中的存储图解:
1.3 结构体内存大小的计算
结构体大小的计算是一个重要知识点.
试着猜一下结构体stu的大小是多少?
示例1:
struct student { int a; char b; int c; }stu; int main() { printf("%zd", sizeof(stu)); return 0; }
12
不知道友友,们有没有听过内存对齐.
其实结构体可是一个纨绔子弟,"富哥"都是很奢侈的,他经常浪费内存!!!
结构体大小计算方法:
内存对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处。从偏移量为0的地址处向后使用.
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 =编译器的默认对齐数与变量成员大小中的较小值.(在VS中默认对齐数是8)
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的成员的对齐数)的整数倍。
还是举例子更好理解对吧?
🌰栗子
示例1:普通结构体
示例1答案:
首先我们将首地址作为偏移量为0的地址.
第一个元素是整形,占4个字节,默认对齐数是8字节,较少者是4字节,则对齐数就是4,故偏移量0-3分配给a变量.
第二个元素是char类型,占1个字节,min(1,8),则对齐数是1,故偏移量4位置分配给b变量.
第三个元素是int型,同理,默认对齐数是4,则偏移量5-7都不能使用,是的你没有听错,这三个字节都被浪费掉了,从偏移量为8开始,8-11偏移的地址分配给变量c.
最后,此时占用了12个字节,最大的成员对齐数是4,刚好12是4的倍数,所以整个结构体的大小就是占12个字节.
图解:
示例2:包含double类型成员的结构体
struct S4 { double d; char c; };
运行结果:
16
原因:
double占八个字节,则0-7的偏移量分配给d
char占一个字节,则偏移量8的位置分配给c
总字节数为9,但是成员最大对齐数是8,9不是8 的倍数,所以需要内存对齐,故最后占16字节.
图解:
将结构体改成如下结构,一样是占用16字节.
struct S4 { double d;//0-7 char c;//8 int i;//12-15 };
示例3:嵌套结构体的内存大小计算
struct S3 { int a; char c1; int i; }; struct S4 { char c1; struct S3 s3; int d; }; int main() { printf("%d\n", sizeof(struct S4)); return 0; }
运行结果:
20
图解:
示例四:包含数组的结构体
#include <stdio.h> typedef struct student { char a; int arr[6]; char b; }stu; int main() { printf("%d", sizeof(stu)); return 0; }
数组就将其看成该元素类型的多个成员变量即可,即对齐数是4,就是6个int型的变量。
图解:
练习题1:
#include <stdio.h> struct S2 { char c1; //0 int i; //4-7 char c2; //8 }; struct S3 { char c1; //0 char c2; //1 int i; //4-7 }; int main() { struct S2 a = { 'a',1,'b' }; struct S3 b = { 'a','b',1}; printf("%d\n", sizeof(struct S2)); //12 printf("%d\n", sizeof(struct S3)); //8 return 0; }
当我们拿捏不定时,我们可以通过调试,在内存窗口观察其中的值.
补充知识:cc是系统分配空间时初始化的值,我们就理解为未知值(未被使用)
字符a的ASCII码值是97(十进制)---->61(16进制).
字符b的ASCII码值是98(十进制)---->62(16进制).
s2在内存中:
s3在内存中
练习2:
struct S3 { int a;//0-3 char c1;//4 int i;//8-11 double b;//16-23 }; struct S4 { char c1;//1 struct S3 s3;//8-31 int d;//32-35 }; int main() { printf("%d\n", sizeof(struct S4)); return 0; }
答案:40