结构体是⼀些值的集合,这些值称为 成员变量。结构的每个成员可以是不同类型的变量。结构体(struct)是一种用户自定义的数据类型,它允许你将多个不同类型的数据组合成一个单独的类型。结构体允许你将相关的数据元素组合成一个单一的数据结构,从而方便数据的处理和管理。
一,结构体变量的创建和初始化
1,1结构体的创建与声明
创建一个结构体的关键字为:struct
演示:
#include <stdio.h>
// 定义一个名为Student的结构体
struct Student
{
char name[50]; // 学生的名字
int age; // 学生的年龄
char ID[20]; // 学生的学号
};
这样我们就定义了一个结构体数据类型,结构体的成员变量具体是什么数据类型需要我们根据创建的结构体所描述的对象来定义。例如这里创建了一个结构体用来存储关于学生的身份信息,所以成员变量包括了名字。年龄,学号。
定义完数据类型后,我们就可以创建结构体变量,与其他数据类型创建变量的方式没有什么非常大的差异。
结构体变量的创建方式有以下几种:
struct Student
{
char name[50]; // 学生的名字
int age; // 学生的年龄
char ID[20]; // 学生的学号
}b1,b2;//一,直接在结构体后创建变量,为全局变量
struct Student b3;//全局变量
int main()
{
struct Student b4;//局部变量
struct Student arr[5];//创建一个结构体数组
}
1,2结构体的初始化
我们在创建其它数据类型的变量时,通常会进行初始化,同样的,在创建结构体变量的时候,我们也可以对结构体变量进行初始化。
结构体的初始化有不同的方法:
#include <stdio.h>
struct Student
{
char name[50]; // 学生的名字
int age; // 学生的年龄
char ID[20]; // 学生的学号
};
int main()
{
struct Student s1 = { "zhangsan",18,"2024666478" };
//一,按照结构体成员的顺序初始化
struct Student s2 = { .age = 18, .name = "lisi", .ID = "20230818002" };
//二,按照指定的顺序初始化
//"."用于访问结构体的成员
//打印
printf("%s %d %s\n", s1.name, s1.age, s1.ID);
printf("%s %d %s\n", s2.name, s2.age, s2.ID);
return 0;
}
打印结果:
1,3结构体特殊声明
在声明结构的时候,可以不完全的声明。
例如:
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x={18,'a',3.14};//匿名结构体初始化
int main()
{
printf("%d %c %f\n",x.a,x.b,x.c);//打印
return 0;
}
匿名结构体智能够使用一次,因为没有标签,所以第二次使用时编译器无法识别。
匿名结构体使用时还有一个重要的注意事项,我们先来看一段代码:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}* px;
上面我们创建了一个匿名结构体变量和一个匿名结构体指针,在这个条件下,代码:
px = &x;
不合法,因为编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。
警告:
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。
二,结构体的大小
2,1内存对齐
在了解了结构体后,我们再来讨论一个问题,结构体在内存中所占用的空间大小是如何计算的。
我们先来看一段计算结构体大小的代码:
#include <stdio.h>
struct Stu
{
int a;
char b;
int c;
}s1;
int main()
{
printf("%d \n",sizeof(s1));
return 0;
}
我们知道,整型占4个字节,char占一个字节,那么这个结构体的大小是9个字节吗?
答案是占12个字节:
至于12个字节是怎么计算的,就要关于结构体内存对齐的知识了。
我们首先要知道结构体的对齐规则:
1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。
3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
下面我就使用VS来具体介绍一下(VS 中默认的值为 8)
首先,系统再给结构体分配空间时,每个结构体变量的空间是一整块连续的内存:
struct Stu
{
char b;//一个字节
int a;//四个字节
char c;//一个字节
}s1;
首先,第一个成员对⻬到和结构体变量起始位置偏移量为0的地址处,然后,我们计算每个成员变量的对齐地址,char类型大小为一个字节(1<8),所以对齐地址为1的整数倍,int类型占四个字节(4<8),所以对齐地址为4的整数倍,所以,我们就可以将该结构体在内存中变量所占的地址使用图的形式表示出来:
×表示结构体中浪费的空间,又因为结构体总⼤⼩为最⼤对⻬数的整数倍,在该结构体中,最大对其数为4,所以总大小为4的整数倍,所以总大小应该为12个字节。我们可以看到,这样安排成员变量浪费了许多空间,所以我们在创建结构体时应该规划好成员的顺序。
如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
三,用结构体实现位段
在C语言中,位段(也称为位域或位字段)是一种数据结构,它允许程序员在结构体中定义具有特定位数的成员。这可以用于节省内存,特别是在处理硬件寄存器或需要紧凑存储的数据结构时。位段在struct中定义,并指定每个成员所占用的位数。
下面是一个简单的位段示例:
#include <stdio.h>
struct packed_data
{
unsigned int a:3; // a占用3位
unsigned int b:5; // b占用5位
unsigned int c:8; // c占用8位
};
int main() {
struct packed_data data;
data.a = 7; // 3位可以表示的最大值是7 (2^3 - 1)
data.b = 31; // 5位可以表示的最大值是31 (2^5 - 1)
data.c = 255; // 8位可以表示的最大值是255 (2^8 - 1)
printf("a: %u, b: %u, c: %u\n", data.a, data.b, data.c);
return 0;
}
在上面的示例中,我们定义了一个名为packed_data的结构体,其中包含三个位段成员:a、b和c。它们分别占用3位、5位和8位。注意,位段的长度不能超过其基础类型的长度。例如,对于unsigned int,其长度通常是32位(这取决于具体的编译器和平台),所以你不能定义一个超过32位的位段。
虽然位段在某些情况下可以节省内存,但它们也有一些限制和注意事项:
跨平台问题:位段在不同的系统和编译器上的行为可能会有所不同。因此,在使用位段进行跨平台编程时,需要格外小心。
对齐和填充:编译器可能会在结构体成员之间插入填充字节,以确保正确的对齐。这可能会影响位段的布局和大小。
访问效率:访问位段可能比访问普通的结构体成员要慢一些,因为编译器可能需要执行额外的位操作来读取或写入位段的值。
因此,尽管位段在某些特定场景下很有用,但在一般的应用程序中,通常建议使用完整的数据类型(如int、float等)来定义结构体的成员。