一、什么是结构体?
我们知道C语言有内置类型,比如char、short、int、long、float、double等,但在生活中我们如果去描述一个人,一本书该怎么描述呢?人有许多属性比如身高,体重,年龄等单一的内置类型是不能完全表示出来的,C语言为了解决这种问题就增加了结构体这种自定义的数据类型,让程序员可以创造合适的类型去表述它们。
struct tag
{
member-list;
}variable-list;
这就是结构体的表示形式,结构体的关键字是struct,结构体是一些值的集合,这些值称为成员变量,结构体的每个成员的类型可以是任意的,如指针、数组、内置类型,甚至是结构体类型。
如描述一本书,代码可以写成如下形式:
struct Book
{
char name[20];//书名
float price;//价格
char author[20];//作者
};
二、结构体变量的定义和初始化
通过上面的代码,我们已经知道了一个结构体的形式,接下来我们具体说一说结构体变量怎么定义。
struct Book
{
char name[20];//书名
float price;//价格
char author[20];//作者
}s1;
struct Book s2;
上述代码在结构体后有一个是s1,s1是声明结构体的同时定义结构体变量,s2是类型名定义的结构体变量,两种方式都可以。
那么单单定义是不行的,我们要对结构体变量进行初始化。
struct Book s2 = {"C程序与设计",19.9,"xxx"}
这就是简单的初始化,按结构体成员变量顺序初始化,当然,我们也可以指定顺序,代码如下:
struct Book s2 = {.price = 19.9,.name = "C程序与设计",.author = "xxx"}
如果对s1初始化则可以直接在其后面初始化,代码如下:
struct Book
{
char name[20];//书名
float price;//价格
char author[20];//作者
}s1={"C陷阱与缺陷",29.9,"xxxxx"};
如果结构体中包含结构体,像下面情况:
struct Student
{
char name[20];
struct Book b;
};
struct Student s={"zhangsan",{"C设计",39.9,"xx"}};
结构体变量就可以像这样初始化。
三、结构体成员如何访问?
通过上述,我们已经知道结构体变量的定义和初始化,那么我们如何去访问它们呢?结构体成员的直接访问是通过点操作符(.)访问的,有的地方叫句点操作符。点操作符接受两个操作数。
struct Point
{
int x;
int y;
};
int main()
{
struct Point p={1,2};
printf("%d %d\n",p.x,p.y);//打印结果为:1 2
return 0;
}
代码中,p.x就是通过点操作符进行访问的,p和x是两个操作数。
那还有没有其他访问方式呢?答案是有的,下面请看代码:
struct Point
{
int x;
int y;
};
int main()
{
struct Point p={1,2};
struct Point* po=&p;
printf("%d %d\n",po->x,po->y);//打印结果为:1 2
return 0;
}
我们知道指针是非常"强大"的,当然也有结构体指针,C语言给出了箭头操作符(->),po是结构体指针通过箭头操作符可以访问到结构体的成员变量。
四、结构体内存对齐
在vs2019环境下,short类型变量占2个字节,int类型变量占4个字节,那么一个结构体变量在内存中会占多少个字节呢?这就涉及到结构体内存对齐,下面先介绍结构体内存对齐规则:1.结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处。2.其他成员变量要对齐到某个数(对齐数)的整数倍的地址处。
对齐数=编译器默认的⼀个对齐数与该成员变量大小的较小值。
在vs2019环境下,编译器的默认对齐数是8
在Linux和gcc中没有默认对齐数,对齐数就是成员自身大小
3.结构体总大小为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对齐数中最⼤的)的整数倍。4.如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对齐数的整数倍处,结构体的整体大小就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
我们现在只需要记住怎么求对齐数即可,其他不用担心,接下来我会一一介绍。
请看下面代码:
struct S1
{
char c1;//对齐数为1
int a;//对齐数为4
char c2;//对齐数为1
};
int main()
{
printf("%zd",sizeof(struct S1));
return 0;
}
我们根据代码来具体分析,c1的大小为1字节,在vs2019下默认对齐数为8,1小于8,所以c1的对齐数为1,依次类推a的对齐数为4,c2的对齐数为1,我们第一步工作已经完成了。
在vs2019环境下,根据规则1、2我们知道c1处的偏移量为0,存放完c1后偏移量加1,此时偏移量为1,接着到变量a了,变量a的对齐数为4,偏移量为1不是4的倍数,偏移量为4可以满足,因此变量a要在偏移量为4的位置开始存放4字节,存放完后,偏移量为8,最后存放变量c2,c2的对齐数为1,8是1的倍数所以紧接着存放c2,然后偏移量加1,此时偏移量为9,至此结构体中3个成员变量已经存放完毕,我们再看规则3,变量c1,a,c2中最大对齐数为4,9不是4的倍数,所以偏移量要到12,最后结构体大小就是从偏移量为0的位置于偏移量为12的位置之间的字节数,即12字节。
我们也可以利用#pragma这个预处理指令修改编译器的默认对齐数,例如:
#pragma pack(1)
//代码
#pragma pack()
第一个#pragma就可以将编译器的默认对齐数改为1,第二个#pragma就可以使对齐数回到原来的对齐数。
为什么会存在内存对齐呢?
⼤部分的参考资料都是这样说的:
1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
以上就是全部内容了,祝大家天天开心!