一、结构体类型的引入
C语言中的内置类型不能表示所有的场景,有时候我们需要其中的几种一起来修饰某个变量,例如一个学生的信息就需要学号(字符串),姓名(字符串),年龄(整形)等等,这些数据类型都不同但是他们又是表示一个整体,要存在联系,那么我们就需要一个新的数据类型。
结构体类型:结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。简单来说,结构体是具有相同或不同元素类型的集合。
二、结构体的声明
struct tag//命名
{
member-list;//成员列表
}variable-list;//结构体变量
例如描述一个学生:
struct Stu
{
char name[20]; //名字
int age;//年龄
char sex[5]; //性别
char id[20]; //学号
};
typedef struct stu
{
char name[20]; //名字
int age;//年龄
char sex[5]; //性别
char id[20]; //学号
}Stu;
结构体的成员可以是标量、数组、指针,甚至是其他结构体。
三、结构体变量的定义和初始化
struct Point{
int x;
int y;
}p1;//声明类型的同时定义变量p1
struct Point p2;//定义结构体变量p2
struct Point p3 = { 10, 20 };//初始化,定义变量的同时赋初值
struct Stu//类型声明
{
char name[15];//名字
int age;//年龄
};
struct Stu s = { "Zhangsan", 20 };//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4,5},NULL};//结构体嵌套初始化
struct Node n2 = { 20, { 5, 6 }, NULL };//结构体嵌套初始化
四、结构体成员的访问
- 结构体变量访问成员:结构体变量的成员是通过点(.)操作符访问的。
struct Stu
{
char name[20];
int age;
};
struct Stu s;
strcpy(s.name, "Zhangsan");//使用.访问name成员
s.age = 20;//使用.访问age成员
- 结构体指针访问指向变量的成员:有时候我们得到的不是一个结构体变量,而是指向一个结构体题的指针。
struct Stu
{
char name[20];
int age;
};
void print(struct Stu* ps){
printf("name = %s age = %d\n", (*ps).name, (*ps).age);
//使用结构体指针访问指向变量的成员
printf("name = %s age = %d\n", ps->name, ps->age);
}
int main()
{
struct Stu s = {"Zhangsan",20};
print(&s);
system("pause");
return 0;
}
五、结构体内存对齐
1.内存对齐是什么?
内存对齐本质是以牺牲空间的方式来换取时间,提升效率的策略。
2.为什么存在内存对齐?
硬件寻址的限制可能会导致CPU寻址效率降低
-
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定的数据,否则抛出硬件异常
-
性能原因:数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存仅需要一次访问。
3.如何计算内存对齐?
对齐:起始偏移量能整除对齐数即为对齐;
结构体的对齐规则:
1> 第一个成员在与结构体变量偏移量为0的地址处;
2> 其他成员变量要对齐到对齐数的整数倍的地址处;
对齐数=编译器默认的一个对齐数与该成员大小的较小值(VS中的默认值为8)—— 默认自身对齐;
3> 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍;
4> 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍;
5> 数组的对齐数就是它的基本类型大小,和数组的元素个数无关。
练习:
struct S
{
double d;//8
char c;//1
int i;//3+4
};
struct S1
{
char c1;//1
struct S s;//7+16
double d;//8
};
struct S2
{
char x;//1
char *cp;//3+4
char a[3];//3
double *b[5];//1+20
struct S1 *s;//4 ---->36
struct S1 d[3];//4+32*3 ---->136
char e;//1 ---->137
};//137->8:144
int main()
{
printf("%d\n", sizeof(struct S));//16
printf("%d\n", sizeof(struct S1));//32
printf("%d\n", sizeof(struct S2));//144
system("pause");
return 0;
}
- 修改默认对齐数
#pragma pack(8)//设置默认对齐数为8
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
#pragma pack()//取消设置的默认对齐数,还原为默认
六、结构体传参
结构体传参的时候,要传结构体的地址
原因:函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大参数压栈的系统开销比较大,所以会导致性能的下降。
struct S
{
int data[1000];
int num;
};
struct S s = { { 1, 2, 3, 4 }, 1000 };
//结构体传参
void print1(struct S s){
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps){
printf("%d\n", ps->num);
}
int main()
{
print1(s);//传结构体
print2(&s);//传地址
system("pause");
return 0;
}