C语言为我们提供多个内置类型,例如我们平时最经常使用的int、char、double……就是我们常说的内置类型,但实际上内置类型并不能解决我们生活中所有的复杂问题。例如,我们需要一个学生类型,它包含班级、年级、姓名……,这便是一个内置类型所能做到的了,并且定义多个内置类型也过于混乱,这是我们所不愿看到的,于是结构体便应运而生了。
结构体定义
下面给出结构体定义:
struct student //student为结构体类型名
{
//以下为结构体所需的内置类型
char name[10];
int age;
char _class[12];
char grade[10];
};//结构体最后的分号不要忘记了
结构体关键字为struct,后面加上我们想要自定义的结构体类型名字,然后在花括号里定义我们所想要整合的内置类型,在定义的最后我们加上 ; (很容易忘记)就能够完成一个结构体的定义了。
结构体变量定义、初始化及打印
结构体实则是我们定义出来的自制类型,具有与int、char等内置类型近似的地方。
定义好了结构体,我们也需要像内置类型一样创建一个变量,这就与我们的内置类型很相似了。
int main()
{
struct student stu1;//C C++可通用
student stu2; //仅C++可用
return 0;
}
在main函数中,我们给出了两种定义结构体变量的方法,但一种是仅C++语法可用的,在.c文件中会直接报错(一般我们C语言编程所使用的文件后缀也是cpp,故后面直接使用C++语法定义来讲解)。
我们也能够跟在结构体后面进行定义变量,在下面我们也是直接定义了结构体变量stu
struct student //student为结构体类型名
{
//以下为结构体所需的内置类型
char name[10];
int age;
char _class[12];
char grade[10];
}stu;//结构体最后的分号不要忘记了
既然是一个变量,那么我们也能够对他进行初始化赋值了对吧。
student stu1 = { "caocao", 13, "一班", "初二"};
student stu2 = { "liubei", 14, "一班", "初三" };
student stu3 = { "sunquan", 17, "四班", "高二"};
初始化赋值我们也仅需要对各个结构体变量按照结构体定义中的顺序依次赋值便好。
如果我们没有对结构体变量进行初始化,里面各个成员的默认值便是随机的,若进行了一个以上的成员的初始化或给出花括号,那么除已初始元素,各元素的默认值为0
同时我们也能使用成员运算符 ‘ . ’,来对结构体变量的各个成员进行访问。
student stu1 = { "caocao", 13, "一班", "初二"};
student stu2 = { "liubei", 14, "一班", "初三" };
student stu3 = { "sunquan", 17, "四班", "高二"};
printf("学生1性名: %s ,年龄: %d ,班级: %s ,年级:%s\n", stu1.name, stu1.age, stu1._class, stu1.grade);
printf("学生2性名: %s ,年龄: %d ,班级: %s ,年级:%s\n", stu2.name, stu2.age, stu2._class, stu2.grade);
printf("学生3性名: %s ,年龄: %d ,班级: %s ,年级:%s\n", stu3.name, stu3.age, stu3._class, stu3.grade);
结构体指针
结构体与其他内置类型一样具备相应的指针类型。
方式与其他内置指针类型的定义相似。
student *stu = &stu1; //仅适用于C++语法
struct student* _stu = &stu2; //C C++均适用
我们也能够在跟在结构体定义之后进行定义
struct student //student为结构体类型名
{
//以下为结构体所需的内置类型
char name[10];
int age;
char _class[12];
char grade[10];
}stu,*Stu;//结构体最后的分号不要忘记了
我们也能够使用与内置类型一样的初始化方式对其进行赋值。
我们有了指针,我们也能够通过指针来访问一个变量了,在这里我们使用成员运算符‘ . ’和结构体指针运算符‘ -> ’两种方式来对其进行访问。
printf("学生1姓名: %s\n", (*stu).name);
printf("学生2姓名: %s\n",_stu->name);
第一种方式我们通过对stu指针进行解引用使之具象化为stu1结构体变量,然后通过成员运算符来对name进行访问,而通过指针运算符我们能够通过->直接找到_stu所指向变量的成员name。
结构体数组
如果我们需要大量的同类型的结构体变量,无疑,一个个定义是十分不方便的,而结构体与基本内置类型一样具有数组的定义形式来帮我们解决类似问题。
student StuArr[3] = { { "caocao", 13, "一班", "初二"} ,
{ "liubei", 14, "一班", "初三" } ,
{ "sunquan", 17, "四班", "高二"} };
我们数组各元素(student结构体变量)赋值,且每个结构体变量的数据我们使用花括号隔开,是不是与二维数组的形式十分的相似呢?
同时,我们的花括号是可以直接去掉的。
student StuArr[3] = { "caocao", 13, "一班", "初二" ,
"liubei", 14, "一班", "初三" ,
"sunquan", 17, "四班", "高二" };
与之前结构体变量的赋值一样,如果我们没有对结构体变量进行初始化,里面各个成员的默认值便是随机的,若进行了一个以上的成员的初始化或给出花括号,那么除已初始元素,各元素的默认值为0。
结构体对齐
既然已经知道了结构体的大致知识,那么你们认为一个结构体的大大小是多少呢?所有的内置类型的大小相加?
嗯?然而我们的事实貌似并不是如此。如果所有的内置类型大小相加那不应该就是6吗,为什么变成了8呢,这就涉及了结构体对齐的知识。
在谈及结构体对齐的知识之前我们需要关注一个特例:
给出一个空的结构体,他的大小为1,而不是为0。
我们首先需要理解:我们为什么需要结构体对齐?
上述例子,我们如果没有进行结构体对齐,那么我们便需要4个字节存储a,1个字节存储b,1个字节存储c,看似对空间进行了最大化利用,实则这对于计算机读取操作是十分不友好的 。
假定这是计算机读取数据的方式,那计算机需要如何去知道这个数自己需要读几个字节呢?知道了自己需要读取几个字节,那又需要如何去调整自身每次读取的大小呢?
以上操作计算机的确能够实现,但会将大部分时间花费到查找需要读取的字节数、调整每次需要读取的大小的,这将会浪费大量时间,降低计算机工作效率,这也是结构体对齐想要避免的,用空间来换取时间。
我们也来看看结构体对齐之后的内存:
假定计算机每次读取四个字节:
如此,计算机便能高效快速的完成三个数的读取操作,仅花费两次的读取时间且不需要花费额外的调整每次需要读取的大小的时间。
结构体对齐法则:
- 结构体变量的首地址一定 是这个结构体变量中 最大的基础(内置)类型的大小的整数倍
- 结构体变量中每一个成员 相对于结构体首地址的偏移 一定是该成员的基础数据类型大小的整数倍
- 结构体变量的总大小 一定是这个结构体变量中最大的基础类型的大小的整数倍
我们继续来举个栗子:
在这个例子中,我们自上而下来执行结构体对齐的规则。
首先 b相对于首地址的偏移量为1个字节,并非float大小的整数倍,所以我们需要进行调整.
调整完毕,整型c 的进入完全符合结构体对齐规则,直接进行相应插入。
随后d进行插入,但由于最大基础类型为float、int大小为4字节,结构体总大小15无法进行整除,所以需要调整
至此对齐完毕。
但,
如果允许修改默认对齐大小(也就是每次读取的字节数),则规则变更:
- 结构体变量的首地址一定 是这个结构体变量中 Min(最大的基础(内置)类型的大小, 默认的对齐规则大小)的整数倍
- 结构体变量中每一个成员 相对于结构体首地址的偏移 一定是该成员的min(基础数据类型大小, 默认的对齐规则大小)的整数倍
- 结构体变量的总大小 一定是这个结构体变量中 Min(最大的基础类型的大小, 默认的对齐规则大小)的整数倍
默认的内存对齐大小 :vs默认8字节 gcc默认4字节
我们能够在VS中使用#pargma pack()来对默认对齐大小来进行更改:
#pragma pack(4) //修改默认大小为4
#pragma pack()//恢复默认大小
这就相当于我们调整了每次读取的爪子(和夹娃娃很像)的大小,我们的对齐规则也需要依照这个爪子大小来做出相应改变。
我们也来看下这一遍的对齐流程:
一切都按照爪子的大小来,要求爪子能够刚好抓到比他小的变量,比他大的变量则多抓几次,一趟流程下来也就非常好理解了。
实验代码(供以调试)
#include<stdio.h>
#if 1
struct student //student为结构体类型名
{
//以下为结构体所需的内置类型
char name[10];
int age;
char _class[12];
char grade[10];
}stu, * stu;//结构体最后的分号不要忘记了
int main()
{
//struct student stu1;
//student stu2;
//student stu1 = {};
//student stu2 = { "liubei", 14, "一班", "初三" };
//student stu3 = { "sunquan", 17, "四班", "高二"};
//printf("学生1姓名: %s ,年龄: %d ,班级: %s ,年级:%s\n", stu1.name, stu1.age, stu1._class, stu1.grade);
//printf("学生2姓名: %s ,年龄: %d ,班级: %s ,年级:%s\n", stu2.name, stu2.age, stu2._class, stu2.grade);
//printf("学生3姓名: %s ,年龄: %d ,班级: %s ,年级:%s\n", stu3.name, stu3.age, stu3._class, stu3.grade);
//student *stu = &stu1; //仅适用于C++语法
//struct student* _stu = &stu2; //C C++均适用
//printf("学生1姓名: %s\n", (*stu).name);
//printf("学生2姓名: %s\n",_stu->name);
student StuArr[3] = { { "caocao", 13, "一班", "初二"} ,
{ "liubei", 14, "一班", "初三" } ,
{ "sunquan", 17, "四班", "高二"} };
student StuArr[3] = { "caocao", 13, "一班", "初二" ,
"liubei", 14, "一班", "初三" ,
"sunquan", 17, "四班", "高二" };
//student StuArr[3] = {"caocao"};
//struct A {
// //int a;
// //char b;
// //char c;
//};
//printf("%d", sizeof(A));
#pragma pack(2)
struct B {
char a;
float b;
int c;
char d[3];
};
printf("%d", sizeof(B));
return 0;
}