结构体struct
- 定义:
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
结构体的声明
- 普通声明:
struct tag { member-list; }variable-list;
- 特殊的声明:不完全的声明(匿名结构体类型)
- 示例:
struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], *p;
- 注意:
对于匿名结构在声明的时候省略掉了结构体标签(tag),也就是只能在声明的时候进行操作(声明外再次使用无法进行调用(没有名称))
- 示例:
//在上面代码的基础上,下面的代码为err p = &x; //编译器会把上面的两个声明当成完全不同的两个类型
结构的自引用
在链表中我们需要用到的就是结构的自引用
- 示例:
//创建链表节点 struct Node { int data; struct Node* next; };
- 易错点:
typedef struct { int data; Node* next; }Node; //只有在重命名后才能使用重命名名 //正确写法: typedef struct Node { int data; struct Node* next; }Node;
结构体变量的定义和初始化
- 示例1:单类型结构体
struct Point { int x; int y; }p1; //声明类型的同时定义变量p1 struct Point p2; //定义结构体变量p2 //初始化:定义变量的同时赋初值。 struct Point p3 = {x, y};
- 示例2:多类型结构体
struct Stu //类型声明 { char name[15];//名字 int age; //年龄 }; struct Stu s = {"zhangsan", 20};//初始化
- 示例3:嵌套型结构体
struct Node { int data; struct Point p; struct Node* next; }n1 = {10, {4,5}, NULL}; //结构体嵌套初始化 struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
空结构体大小
- 示例:
struct student { }stu; int main (void) { printf ("sizeof (stu) = %d\n", sizeof (stu)); return 0; }
输出结果: 在C中, sizeof (stu) = 0 在C++中, sizeof (stu) = 1
结论:
对于空结构体不同编译器理解不同,所以大小不一(可能0或者1(作为占位符))
结构体内存对齐
定义:
struct中的各成员变量的存储地址有一套对齐的机制(让CPU能够更舒服地访问变量)
总体来说:
结构体的内存对齐是拿空间来换取时间的做法
原因:
平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常
性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问
对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数为编译器默认的一个对齐数与该成员大小的较小值。(VS中默认的值为8)
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍,如果不满足,在最后一个成员后面填充
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
示例1:
struct S1 { char c1; int i; char c2; }; printf("%d\n", sizeof(struct S1)); //输出结果:12
- 解释:
- 第一个成员c1在与结构体变量偏移量为0的地址处
- 对于c2它的对齐数为4(int大小为4,小于平台默认值8),该变量要对齐到偏移量为4的倍数处,即从偏移量为4的位置开始存放
- 对于c3(char类型的对齐数为1,正数都为1的倍数),从偏移量为9的位置开始放
- 该结构体的总大小须为最大对齐数(每个成员变量都有一个对齐数)(这里也就是4)的整数倍,故为12(已经占用了9个字节)
示例2:
struct S2 { char c1; char c2; int i; }; printf("%d\n", sizeof(struct S2)); //输出结果:8
- 解释:
- 第一个成员c1在与结构体变量偏移量为0的地址处
- c2放在偏移量为1的地址处
- i放在偏移量为4的地址处(对齐到偏移量为对齐数4的倍数处)
- 示例3:
struct S3 { double d; char c; int i; }; printf("%d\n", sizeof(struct S3)); //输出结果:16
- 解释:
- 第一个成员d在与结构体变量偏移量为0的地址处
- c放在偏移量为8的地址处
- i放在偏移量为12的地址处(对齐到偏移量为对齐数4的倍数处)
- 示例4:
struct S4 { char c1; struct S3 s3; double d; }; printf("%d\n", sizeof(struct S4)); //输出结果:48
解释:
- 第一个成员c1在与结构体变量偏移量为0的地址处
- s3放在偏移量为8的地址处(s3最大对齐数为8)
- d放在偏移量为24的地址处(对齐到偏移量为对齐数8的倍数处)
- 总大小为成员变量最大对齐数的倍数(也就是16的倍数)即大小为32
结论:
尽量让占用空间小的成员尽量集中在一起(既满足对齐,又节省空间)(如示例1与示例2)
修改默认对齐数
使用#pragma 这个预处理指令来改变我们的默认对齐数
示例:
#include <stdio.h> #pragma pack(8)//设置默认对齐数为8 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 #pragma pack(1)//设置默认对齐数为1 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 int main() { //输出的结果是什么? printf("%d\t", sizeof(struct S1)); printf("%d\t", sizeof(struct S2)); return 0; } //输出结果:12 6
- 结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数
宏offsetof
- 作用:
计算结构体中某变量相对于首地址的偏移,并给出说明
#include<stdio.h> #include<stddef.h> struct s { char c; int i; double d; }; int main() { // offsetof其实是一个宏,用来表示成员相对于结构体的偏移量 //而且offsetof的参数传的是一个类型,更加说了offsetof是一个宏 printf("%d\n", offsetof(struct s, c));// 0 printf("%d\n", offsetof(struct s, i));// 4 printf("%d\n", offsetof(struct s, d));// 8 return 0; }
结构体传参
- 示例:
struct S { int data[1000]; int num; }; struct S s = {{1,2,3,4}, 1000}; //结构体传参 //创建临时结构体来接收(消耗空间) void print1(struct S s) { printf("%d\n", s.num); } //结构体地址传参 //指针接收 void print2(struct S* ps) { printf("%d\n", ps->num); } int main() { print1(s); //传结构体 print2(&s); //传地址 return 0; }
- 结论:
其实效果都一样,两者都可以选择,但是推荐结构体传址
- 原因:
- 函数传参的时候,参数是需要压栈的,压栈会占用空间
- 如果传递一个结构体对象的时候,结构体过大的话,那么参数压栈的的系统开销比较大,会导致性能下降
柔性数组
定义:
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少一个其他成员
使用:
- sizeof 返回的这种结构大小不包括柔性数组的内存
- 用malloc()函数进行内存动态分配,分配的内存应该大于结构的大小,以适应柔性数组的预期大小
- 用malloc函数分配了内存,肯定就需要用free函数来释放内存
示例:
#include<stdio.h> #include<string.h> #include<stdlib.h> typedef struct data { int len; //一般用来表示字符数组的字符个数 char name[];//空间大小为0 }S; int main(void) { S s; printf("sizeof(s)=%d\n",sizeof(s));//输出为4,即是int类型大小 int len = 10; //申请空间 struct data *p =(struct data*)malloc(sizeof(s)+sizeof(char)*len); //判断是否申请成功&请空处理 p->len = len; strcpy(p->name,"xxxxxx"); //字符串赋值需要用strcpy printf("%s\n",p->name); //释放指针p free(p); return 0; } //输出结果:xxxxxx
struct与class的区别
在C++里struct关键字与class关键字一般可以通用
- 只有一个很小的区别:
struct的成员默认情况下属性是public的,而class成员却是private的