C语言结构体(1)
1.结构体类型的声明
①.变量类型
a.内置类型
char,short,int,long,long long,float,double等等C语言中自带的
b.自定义类型
结构体,枚举,联合体,数组
②结构体的概念
结构体可以包含各种类型的数据,用来描述一个复杂对象的属性。
结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
③结构体的声明
a.普通声明
struct Stu
{
char name[20];
int age;
float score;
};//正常声明
struct Stu
{
char name[20];
int age;
float score;
}s3 = { "wangwu", 24, 98.0f };//s3为全局变量
int main()
{
struct Stu s1 = { "zhangsan", 20, 98.5f };
struct Stu s2 = { .age = 20, .name = "lisi", .score = 100.0f };//不按照顺序的结构体初始化
printf("%s %d %.2f\n", s1.name, s1.age, s1.score);
printf("%s %d %.2f\n", s2.name, s2.age, s2.score);//打印结构体,s1和s2是局部变量
return 0;
}
b.特殊的声明
struct
{
int a;
char b;
float c;
}x;//匿名结构体,只能用一次
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
} * p;
int main()
{
p = &x;//不可以
}
编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。
2.结构体成员访问操作符和传参
结构体变量.成员变量名
结构体指针—>成员变量名
//结构体变量.结构体成员名
//结构体指针->成员变量名
struct Stu
{
char name[20];
int age;
};
void Print1(struct Stu s)
{
printf("%d\n", s.age);
}
void Print2(struct Stu* p)
{
printf("%d\n", p->age);
}
int main()
{
struct Stu s1 = { "zhangsan", 20 };
struct Stu* p = &s1;
Print1(s1);//(传结构体)
Print2(p);//(传地址)
return 0;
}
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
所以结构体传参的时候,要传结构体的地址。
3.结构体内存对齐
这个知识对应了求结构体大小这个题型。
①对齐规则
a.结构体起始成员对齐到相对结构体起始偏移量为0的地址处
b.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较小值。
- VS中默认的值为8
- Linux中没有默认对齐数,对齐数就是成员自身的大小
c. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的
整数倍。
d. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
区分清所占空间大小和对齐数
//练习1
struct S1
{
char c1;
int i;
char c2;
};
//练习2
struct S2
{
char c1;
char c2;
int i;
};
//练习3
struct S3
{
double d;
char c;
int i;
};
//练习4-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%zd\n", sizeof(struct S1));//易错得9,忽略了规则第三条,12
printf("%zd\n", sizeof(struct S2));//8
printf("%zd\n", sizeof(struct S3));//16
printf("%zd\n", sizeof(struct S4));//32
}
offsetof宏可以用来计算结构体成员相较于起始位置的偏移值
struct S4
{
char c1;
struct S3 s3;
double d;
};
#include <stddef.h>
int main()
{
printf("%zd\n", sizeof(struct S4));//32//内存对齐的规则,对齐数,内存大小
printf("%zd\n", offsetof(struct S4, c1));//0
printf("%zd\n", offsetof(struct S4, s3));//8
printf("%zd\n", offsetof(struct S4, d));//24
return 0;
}
②为什么存在内存对齐
- 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要 作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间
如何做到: 让占用空间小的成员尽量集中在⼀起 。
例如练习1和练习2
③修改默认对齐数
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对⻬数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S));//6,按顺序空间排序
return 0;
}