文章目录
前言
提示:这里可以添加本文要记录的大概内容:
c语言中允许我们自己创造一些类型,即自定义类型,如结构体、联合体、枚举类型
今天要讲的是结构体。
提示:以下是本篇文章正文内容,下面案例可供参考
一、结构体的声明和变量定义
结构是一些值的集合,这些值成为成员变量,结构的每个成员可以是不同类型的变量。
类比于数组,数组是一组相同类型的值的集合,而结构体是一组不同类型的值的集合,每个成员变量类型可以不同。
结构体关键字是struct。
下面是一个结构体的声明
struct tag
{
int a;
char arr[10];
//member_list;
}variable_list;
注意结构体最后的分号,这是不能缺少的。
此结构体类型为struct tag。
大括号内部为member_list,即成员变量。
代码最后的variable_list是创建的变量名,此处变量可创建也可不创建,不做要求。同时,此方法创建的变量是全局变量。
此外还有另一种创建变量的方式,这是在main函数内部创建变量,所创建的变量也当然是局部变量。
int main()
{
struct tag s1;
struct tag s2;
return 0;
}
同时,为了简便书写,可以利用typedef来重命名结构体类型。
typedef struct student
{
char arr[20];
int age;
}stu;
int main()
{
stu s;
return 0;
}
此时,struct student类型的结构体就被重命名成为stu,以后使用时直接用stu创建结构体变量即可
二、结构体变量的初始化
结构体变量的初始化需要使用大括号。如下:
typedef struct student
{
char arr[20];
int age;
}stu;
int main()
{
stu s = {"zhangsan",18};
return 0;
}
此外,还有结构体嵌套初始化,在大括号里用大括号来初始化嵌套的结构体。
struct Point
{
int x;
int y;
};
struct Node
{
int data;
struct Point p;
struct Node* next;
};
int main()
{
struct Node n = { 20,{5,6},NULL };//结构体嵌套初始化
return 0;
}
三、结构体的自引用
结构体的自引用类似于函数的递归,不过结构体的自引用需要使用结构体指针,否则便会如同死递归一样,一直循环,却没有结果。
下面是正确用例。
struct Node
{
int data;
struct Node* next;
};
四、特殊结构体声明
匿名结构体类型
在声明结构的时候,可以不完全声明
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}*p;
但是要注意的是,上面两个结构体是完全不同的类型,即编译器会把上面两个声明当成完全不同的类型。
五、结构体内存对齐
了解结构体在内存中的存储方式,以便我们计算结构体的大小。
看下面的代码,思考为什么是这个结果
struct S
{
int a;
char b;
float c;
}S;
int main()
{
printf("%d\n", sizeof(S));
return 0;
}
这是由于结构体内存对齐的原因。
1.结构体的第一个成员永远放在0偏移处
2.从第二个成员开始,以后每个成员都要对齐到某个对齐数的整数倍处,这个对齐数是:成员自身大小和默认对齐数的较小值。
备注:
VS环境下默认对齐数是8
gcc环境下 没有默认对齐数,此时对齐数就是成员自身大小
3.当成员全部存放后
结构体的总大小必须是,所有成员的对齐数中最大对齐数的整数倍。
如果不够,则浪费空间对齐。
值得一提的是,不止是最后浪费空间对齐,而是每次对齐的时候,如果不满足规则都要浪费空间对齐。
这个是上面例题的内存解释。
a占四个字节,从0到3,吧,b占一个字节,c由于要对齐,且本身大小为4个字节,故要从8开始,到12的前面,故最终内存大小为12。
此外,如果嵌套了结构体,则为另一种情况。
4.如果嵌套了结构体,嵌套的结构体成员要对齐到自己成员的最大对齐数的整数倍
整个结构体的大小,必须是最大对齐数的整数倍,最大对齐数包含嵌套的结构体成员中的对齐数。
如下所示
此外,c语言中有一个宏叫offsetof是用来计算偏移量的。
头文件是#include<stddef.h>
struct S
{
char c;
int a;
};
int main()
{
struct S s = { 0 };
printf("%d\n", offsetof(struct S, c));//0
printf("%d\n", offsetof(struct S, a));//4
return 0;
}
为什么存在内存对齐呢?
总的来说,内存对齐是拿空间换取时间的做法。
为了既要满足对齐,又要节省空间,我们应该:
让占用空间小的成员尽量集中在一起。
例如下面的两段代码
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
S1和S2类型的成员一模一样,但是前者所占空间更大。
我们还可以使用#pragma这个预处理指令,改变我们的默认对齐数。
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原成默认
六、结构体传参
结构体传参有两种情况,一种是直接传结构体,另一种是传结构体地址。
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4},1000 };
//结构体传参
void printf1(struct S s)
{
printf("%d\n", s.num);
};
//结构体地址传参
void printf2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
printf1(s);//传结构体
printf2(&s);//传地址
return 0;
}
实际应用中使用第二个函数,函数传参的时候,参数需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能下降。
即结构体传参时要传结构体地址。
七、结构体实现位段
位段是使结构体所占内存减少的一种结构。
位段的声明跟结构类似,有两个不同:
1.位段的成员必须是int、unsigned int、signed int等整形家族的成员。
2.位段的成员名后面有一个冒号和一个数字。
struct A
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
位段的位指的是二进制位,冒号后面的表示所占的比特位。
比如说一个int类型的数据很小,只需要2个比特位就可以完成存储,这个时候为了节约空间,我们就会用到位段。
位段的内存分配:
1.位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2.位段的空间是按照需要4个字节(int)或者一个字节(char)的方式来开辟的。
3.位段涉及很多不稳定因素,是不跨平台的,可移植程序应避免使用位段。
上面是VS处理位段的情况,别的编译器不一定。
位段的跨平台问题
总结
以上便是结构体的全部知识,希望对大家有所帮助。