今天来讲下C语言结构体
一.结构体
结构体是什么?
结构体是一些值的集合,这些值是结构体成员变量。(结构体成员可以是不同类型的变量,例如指针,数组,结构体,int等)
对于我们要储存一个人的信息,或者图书库存等,如果我们不用结构体,实现起来就会麻烦,结构体可以存储不同类型的数据,所以我们利用结构体就会更简洁一些。
2.结构体的声明(struct)
结构体的关键字是struct 。
对于一个结构体的定义
1.需要有struct关键字,以及结构体标识符(图中Peo)(这个在单个结构体可以不写,但最好写上,两个以上的结构体就必须要有结构体标识符)
2.花括号里放的是结构体成员变量
3.花括号后必须有分号,在花括号后也可以定义结构体变量,或单独声明结构体变量。
例如:
3.结构体变量的定义和初始化
对于结构体的定义,我们可以在声明时顺便定义,或者单独拿出来定义。
//对结构体的定义
//1
struct Peo
{
char name[10];
char tep[11];
char sex[3];
int high;
}p1,p2; // 声明类型的同时顺便定义结构体变量
//2定义结构体变量
struct Peo p3;
对于结构体的初始化,我们可以在上述定义完之后进行初始化
1.完全初始化
struct Peo
{
char name[10];
char tep[11];
char sex[5];
int high;
}p1 = { "hua","112233","man",180 }, p2 = {"liu","113322","woman",160};
struct Peo p3 = { "huo","123121","man",188 };
2.部分初始化
我们可以利用. 点操作符进行部分初始化
struct Peo
{
char name[10];
char tep[11];
char sex[5];
int high;
}p1 = {.tep="112233" }, p2 = {.name="liu"};
struct Peo p3 = {.high=180};
知道结构体怎么定义和初始化后,那么就需要知道如何访问结构体变量
4.结构体访问操作符
1.对于结构体成员的直接访问,是用(.)点操作符。点操作符接受两个操作数,即(结构体变量.成员名)。
例如:想要打印结构体成员时,我们创建了一个打印函数,在这个函数中,形参只是对实参的临时拷贝,所以我们想要访问结构体成员时,就可以用(.)点操作符。
2. 在我们想要打印结构体时,与上述相同,但传的形参是结构体地址,那么我们想要访问,就不可以用(.)操作符。需要用(结构体变量->结构体成员)。
二.结构体对齐
对齐规则:
1,结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的一个对齐数与该成员变量大小的较小值
(在vs中默认对齐数为8)
在gcc中没有默认对齐数,对齐数就是成员自身大
3.结构体总大小为最大对齐数的整数倍。
最大对齐数:在所以成员变量中对齐数最大的
4.如果嵌套了结构体,嵌套的结构体成员对齐到自己成员中的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
例子:
struct per1
{
char a;
char b;
int c;
};
struct per2
{
char a;
int b;
char c;
};
int main()
{
struct per1 t1;
struct per2 t2;
printf("%zd\n", sizeof(t1));
printf("%zd\n", sizeof(t2));
return 0;
}
8 12
为什么是这个数?
对于per1
a的占1个字节,又作为第一个成员变量,所以它的偏移量为0.
b的占一个字节,要对齐1的倍数,它的偏移量为1。
c的占4个字节,对齐数为4,所以它要对齐到4的整数倍,所以它要对齐到地址偏移量为4处。
在内存中存放的位置为4,5,6,7。
此时结构体所占字节大小为最大对齐数(最大对齐数为4)的整数倍 ,此时结构体在内存中正好占8,是最大对齐数的整数倍。
如图:
对于per2,同理
在图中,结构体内存占9个字节,但最大对齐数为4,明显不是最大对齐数的整数倍,所以得再向后数3个字节,当数到11时,正好12个字节是最大对齐数的整数倍,所以结构体在内存中总共占12个字节。
修改默认对齐数
我们如果想要改变默认的对齐数,可以使用#pragma pack(对齐数的大小)。
为什么会存在结构体对齐?
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
三.结构体位段
如果想要既节省空间又节省时间,那就可以使用位段。
1.位段的声明
位段的声明与结构类似但有两个不同:
1.位段成员必须是int,unsign int或者signed int。(在C99中可以是其他类型)
2.位段成员名后需跟冒号加数字(:数字)
冒号后的数字:该成员所占的比特位
例子:
2.位段内存的分配
1. 位段的成员可以是 int ,unsigned int ,signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
3.位段的跨平台问题
1. int位段被当成有符号数还是⽆符号数是不确定的。
2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利⽤,这是不确定的