定义与使用
1、定义
定义结构体:
定义结构体时,需要注意最后的分号必须加上。
定义结构体时,成员只去声明类型,不进行赋值。赋值在定义结构体变量时进行。
struct 结构体名{
结构体成员列表
}; //注意这里的分号
定义结构体变量:
方法1:正常定义结构体变量
struct 结构体名 结构体变量名;
方法2:定义结构体时,定义结构体变量
struct 结构体名{
结构体成员列表
}变量名1,变量名2; //直接在这里写变量名,变量名之间用,隔开
2、初始化结构体变量
情况1:全部成员赋值
全部成员赋值就是在定义结构体变量后直接加上成员值,各个成员值之间用" , "分隔。
struct 结构体名 结构体变量名 = {依次赋值的成员值};
情况2:部分成员赋值
部分成员赋值需要在赋值时指定成员并赋值。
struct 结构体名 结构体变量名 = {
.成员1 = 值,
.成员2 = 值
};
情况3:定义结构体时,定义结构体变量的初始化
struct 结构体名{
结构体成员列表
}变量名 = {成员值};
3、访问结构体变量
方式1:当为结构体变量时,用" . "来访问成员
方式2:当为结构体指针时,用" -> "来访问成员
结构体数组
结构体数组就是一个数组,它的成员是一个结构体变量。
定义与赋值:
定义与赋值的方法与一维数组完全一样。
struct 结构体名 数组名[数组大小] = {{第1个结构体成员初始化值},{第2个结构体成员初始化值}};
结构体嵌套结构体
结构体嵌套结构体就是结构体中成员的类型可以是结构体。
作用:
解决不同结构体中重复的、完全一样的部分
示例:
假设现在要创建两个结构体来管理学生、老师的信息。对于学生,有姓名、性别、学分这三个信息;对于老师,有姓名、性别、电话号码这三个信息。可以看到,在学生和老师的信息中姓名、性别这两个信息是重复的。因此可以建立一个结构体来封装它们,这样使得学生和老师的结构体定义变得更加简洁。
代码如下:
#include <stdio.h>
struct both{ //老师和学生共有的成员
char* name;
char* sex;
};
struct student{//学生信息结构体
struct both stu;
int score;
};
struct teacher{//老师信息结构体
struct both tea;
char* phone;
};
int main(){
//struct both tBothStu = {"stuName","沃尔玛购物袋"};
struct both tBothTea = {"teaName","纸扎投石车"};
struct student stu1 = {{"stuName","沃尔玛购物袋"},100};//可以直接赋值结构体
struct teacher tea1 = {tBothTea,"123124123"};//也可以通过定义的结构体进行初始化
printf("stu1:name=%s sex=%s score=%d\n",\
stu1.stu.name,\
stu1.stu.sex,\
stu1.score
);
printf("tea1:name=%s sex=%s phone=%s\n",\
tea1.tea.name,\
tea1.tea.sex,\
tea1.phone
);
return 0;
}
运行结果如下:
结构体的大小
字节对齐是什么:
对于结构体,编译器会自动对其成员变量进行对齐,以提高数据存取的效率。
结构体大小计算步骤:
相关名词:
- 自身对齐:数据类型的大小就是自身对齐的大小,例如char为1,int为4
- 默认对齐:与电脑位数有关。32位默认对齐为4,64位默认对齐为8
- 有效对齐:有效对齐=min(自身对齐,默认对齐),是结构体中实际占用空间的大小
- 存放地址:存放地址一定能够整除有效地址
计算步骤:(依次计算成员空间)
- 确定自己操作系统的位数,从而确定默认对齐方式
- 计算第一个成员,通过公式:" 有效对齐 = min(自身对齐,默认对齐) "计算有效对齐
- 查看当前的存放地址是否能够整除有效对齐字节数,不能整除需要补空间对齐。 注意:补其后能够整除的数值是下一个变量的起始地址,并没有存储数据 例如:补其后是12,那么实际的有数据的空间只到11
- 依次计算其他成员空间
- 查看最终存放地址是否能够整除" max(全部有效对齐) "
结构体大小手算示例:
假设有一个如下的结构体,在64位的操作系统下计算其结构体大小(假设起始地址为0x00)
struct test{
char a;
int b;
char c;
char d;
};
列出如下表格,依次计算存储地址:
变量名 | 类型 | 自身对齐 | 默认对齐 | 有效对齐 | 存储地址 |
a | char | 1 | 8(64位) | 1 | 0 1 2 3 |
b | int | 4 | 8(64位) | 4 | 4 5 6 7 |
c | char | 1 | 8(64位) | 1 | 8 |
d | char | 1 | 8(64位) | 1 | 9 10 11 |
计算a:可以计算出有效对齐为1,存储地址为0,可以整除,所以存储地址为0
计算b:可以计算出有效对齐为4,存储地址为1,不能整除,所以补齐到最小的可以整除4的地址,即:补齐到4。a的空间为0,1,2,3;b的空间为4,5,6,7这四个字节用于存储
计算c:可以计算出有效对齐为1,存储地址为8,可以整除,所以存储地址为8
计算d:可以计算出有效对齐为1,存储地址为9,可以整除,所以存储地址为9
整体大小计算:可以计算出max(全部有效对齐)=max(1,4,1,4)=4,存储地址9不能整除4,所以补齐到最小的可以整除4的地址,即:补齐到12。因此最终结构体大小为0~11,共12字节
位域
什么是位域:
位域就是把一个字节中的二进制位分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
例如:现在有一个字节的空间,可以让bit0~bit2表示信息a,让bit3~bit5表示信息b,让bit6~bit7表示信息c。这样一个字节分为了3个域,域名就是a,b,c;a域的位数为3,b域的位数为3,c域的位数为2。
位域的定义:
位域定义就是把结构体中的成员列表换成位域列表,格式为:<类型> <位域名>:<位域长度>
以例题为例,那么a,b,c这三个域的定义如下:
struct test{
char a:3;
char b:3;
char c:2;
};
对于上述定义,如果按照结构体成员来定义,计算出的大小应该为3字节。而按位域方法定义,计算出来的大小为1字节。这样就节省了空间。
补充练习:结构体大小手算例题
例题1:
假设有一个如下的结构体,在64位的操作系统下计算其结构体大小(假设起始地址为0x00)
struct test{
char a;
short b;
char c;
char d;
};
列出如下表格,依次计算存储地址:
变量名 | 类型 | 自身对齐 | 默认对齐 | 有效对齐 | 存储地址 |
a | char | 1 | 8(64位) | 1 | 0 1 |
b | short | 2 | 8(64位) | 2 | 2 3 |
c | char | 1 | 8(64位) | 1 | 4 |
d | char | 1 | 8(64位) | 1 | 5 |
最终补齐地址为6,实际存储空间为0~5,6字节
例题2:
假设有一个如下的结构体,在64位的操作系统下计算其结构体大小(假设起始地址为0x00)
struct test{
char a;
double b;
char c;
char d;
};
列出如下表格,依次计算存储地址:
变量名 | 类型 | 自身对齐 | 默认对齐 | 有效对齐 | 存储地址 |
a | char | 1 | 8(64位) | 1 | 0 1 2 3 4 5 6 7 |
b | double | 8 | 8(64位) | 8 | 8 9 10 11 12 13 14 15 |
c | char | 1 | 8(64位) | 1 | 16 |
d | char | 1 | 8(64位) | 1 | 17 18 19 20 21 22 23 |
最终补齐地址为24,实际存储空间为0~23,24字节