我们知道数组可以存放很多数据,但是一个数组只能存放一种同类型的数据,为了解决这个问题,就出现了结构体,结构体可以存放多种类型的数据。
结构体的声明及特殊声明
struct tag // tag是结构体类型名,根据自己的需要自定义
{
number-list;//结构体的成员列表
}variable-list; //variable-list表示变量列表,可有可无
示例:
struct Student
{
//成员变量
char name[20];
int age;
float score;
};
可以看到结构体确实可以存放许多不同类型的数据
对结构体进行特殊声明
- 也叫不完全声明(不写结构体类型名)
示例:
struct
{
char name[20];
int age;
}
像上面这样的结构体类型只能用一次(一般用于较小的工程中)
结构体变量的创建和初始化
以上面的示例为例子,对结构体变量进行创建和初始化:
#include <stdio.h>
struct Student
{
//成员变量
char name[20];
int age;
float score;
}s3,s4;//在这里也可以创建结构体变量
int main()
{
//创建结构体变量
struct Student s1 = 0;//struct Student 代表s1的类型,说明了s1是结构体变量
//对结构体变量进行初始化
s1 = {"lisi",19,90}; //这是按着成员变量的顺序进行初始化
struct Student s2 = 0;
// s2.age = 100;//只对s2这个变量的成绩进行初始化
//下面这条语句是不按成员变量的顺序进行初始化
s2 = {.score = 98,.name = "wangwu",.age = 20};
return 0;
}
访问结构体的成员
访问结构体成员,需要用到两个操作符,第一个操作符是“ . ”操作符,第二个操作符是“->”操作符
点(“ . ”)操作符:结构体变量 . 成员变量名
箭头(“ -> ”)操作符:结构体变量的地址 -> 成员变量名
示例:
#include <stdio.h>
struct Student
{
char name[20];
int age;
}s1,s2;
int main()
{
s1 = {"lisi",19};
s2 = {"wangwu",20};
//运用点操作符访问
printf("%s %d\n",s1.name,s2.age);
//运通箭头操作符访问
struct Student *ps2 = &s2;
printf("%s %d\n",ps2 -> name,ps2 -> age);
//用数组进行访问
struct Student s[2] = {s1,s2};
for(int i = 0; i < 2;i++)
{
printf("%s %d\n",s[i].name,s[i].age);
}
return 0;
}
结构体的自引用
用一个例子来讲解,如:定义一个链表,链表中有多个节点,如何对每个链表的节点进行访问
先用画图板给大家画一个链表:
struct Node
{
int data;
struct Node next;// next代表下一个节点
};
上面的代码其实是错误的,一个结构体中包含一个同类型的结构体,会使结构体成员变量的大小变得无穷大,一直访问下去,sizeof(struct Node)是多少是不可知的,所以这样的结构体自引用是不合理的。
正确的自引用:
struct Node
{
int data;//数据域
struct Node *next;//指针域
};
我们可以通过存放下一个结点的地址,来找到下一个节点,这样的sizeof(struct Node)是可知的。
结构体内存对齐(计算结构体大小)
定义一个结构体,计算该结构体的大小:
#include <stdio.h>
struct S1
{
char c1;
char c2;
int n;
}s1;
int main()
{
s1 = {0};
//打印结构体的大小
printf("%zd\n",sizeof(struct S1));
return 0;
}
大家是否会认为该结构体的大小是:1+1+4=6
其实输出的结果是:8
出现这样的结果是因为,结构体成员在内存中是存在对齐现象的。
offsetof函数
在讲对齐现象之前,先给大家介绍一下offsetof函数,该函数是计算结构体成员相较于结构体变量起始位置的偏移量,可通过该函数来计算结构体的大小
示例:计算上面代码中结构体成员的偏移量
#include <stdio.h>
#include <stddef.h>//offsetof函数的头文件
struct S1
{
char c1;
char c2;
int n;
};
int main()
{
//打印结构体成员的偏移量
//打印的是无符号整数,所以用%zd打印
printf("%zd\n",offsetof(struct S1,c1));//偏移量是0
printf("%zd\n",offsetof(struct S1,c2));//偏移量是1
printf("%zd\n",offsetof(struct S1,n));//偏移量是4
return 0;
}
输出的结果是:0 1 4
通过上面offsetof函数算出的c1,c2,n的偏移量,可得出结构体struct S1的大小
画一块内存帮助大家理解
可知,得到结构体变量的偏移量可算出结构体的大小
回到结构体内存对齐
结构体成员在内存中的对齐是有规则的,规则如下:
- 结构体的第1个成员要对齐到起始位置偏移量为0的地址处
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数与该成员变量的大小比较,谁小谁就是对齐数
vs中默认的对齐数是8
linux中gcc没有默认对齐数,对齐数就是成员自身的大小- 结构体的总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的那个)的整数倍
- 如果嵌套了结构体情况,嵌套的结构体成员对齐到自己成员中最大的对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍
举例:
还是用刚刚struct S1的代码进行举例,该结构体的大小是8
#include <stdio.h>
struct S1
{
char c1;
char c2;
int n;
}s1;
int main()
{
s1 = {0};
//打印结构体的大小
printf("%zd\n",sizeof(struct S1));
return 0;
}
按规则来,第一条规则:结构体的第1个成员要对齐到起始位置偏移量为0的地址处
第二条规则:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处,对齐数 = 编译器默认的一个对齐数与该成员变量的大小比较,谁小谁就是对齐数,vs中默认的对齐数是8。
第三条规则:结构体的总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的那个)的整数倍
第四条规则:如果嵌套了结构体情况,嵌套的结构体成员对齐到自己成员中最大的对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍
//先定义一个嵌套结构体
struct S1
{
char c1;
char c2;
int n;
};
struct S2
{
char c3;
struct S1 s1;
};
struct S2中嵌套了struct S1,由刚刚可知,struct S1的大小为8,还是一样借助画图板来分析struct S2的大小
要注意的是,当得出的结构体大小不是最大对齐数的整数倍时,需要继续增加字节数,达到整数倍的即可
关于结构体的知识就讲到这,还有不懂的或者错误的欢迎提出,一起加油!