文章目录
- 前言
- 一、结构体类型的简单介绍和声明
-
- [1. 为什么引入结构体类型](#1. 为什么引入结构体类型)
- 2.结构体类型的声明
-
- [2.1 结构体类型的声明](#2.1 结构体类型的声明)
- [2.2 特殊结构体类型的声明(匿名结构体类型)](#2.2 特殊结构体类型的声明(匿名结构体类型))
- 二、结构体变量的创建和初始化
- 三、结构成员访问操作符
- 四、结构体的大小计算(结构体内存对齐)
- 五、结构体传参
前言
一 ~ 三、介绍了结构体的声明(包含对匿名结构体的介绍)、结构体变量的创建和初始化以及如何使用结构成员访问操作符去访问结构体的成员
四、结构体的大小计算(通过结构体内存对齐规则来计算)
五、结构体传参的时候,最好传结构体的地址
一、结构体类型的简单介绍和声明
1. 为什么引入结构体类型
C语言已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,假设我想描述学生,描述一本书,这时单一的内置类型是不行的。
比如,描述一个学生需要名字、年龄、学号、身高、体重等信息;描述一本书需要作者、出版社、定价等信息。所以C语言为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以自己创造适合的类型。
2.结构体类型的声明
2.1 结构体类型的声明
结构体类型的定义(声明)如下:
struct tag//结构体名
{
member-list;//成员变量列表
}variable-list;//结构体变量列表
结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚至是其他结构体。
2.2 特殊结构体类型的声明(匿名结构体类型)
结构体类型还有一种特殊的声明方法。那就是在定义结构体的时候,进行不完全的声明。如下:
//匿名结构体类型(顾名思义:在声明结构体类型时省去结构体名)
struct
{
member-list;
}variable-list;
在使用匿名结构体类型时,有一些注意事宜,通过以下示例展示:
c
struct
{
int a;
char b;
float c;
}x;//声明匿名结构体类型的同时,创建结构体变量x
struct
{
int a;
char b;
float c;
}* p;//声明匿名结构体类型的同时,创建结构体指针p
int main()
{
p = &x;//在上⾯代码的基础上,这条代码合法吗?
return 0;
}
编译器会对这条代码报警告:
编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。
二、结构体变量的创建和初始化
结构体变量的创建和初始化的示例如下:
c
#include <stdio.h>
struct Stu//创建结构体类型struct Stu
{
char name[20];//名字
int age;//年龄
char sex[10];//性别
int id;//学号
}A = { "zhangsan",18,"male",20201252 };//在创建结构体类型struct Stu的同时创建结构体变量A(全局变量)并初始化
int main()
{
struct Stu B = { "lisi",16,"female",20201214 };//创建结构体变量B并初始化
return 0;
}
三、结构成员访问操作符
结构体成员的直接访问是通过点操作符(.)访问的, 点操作符接受两个操作数。使用方式:结构体变量.成员名,如下所示:
c
#include <stdio.h>
struct Stu
{
char name[20];
int age;
int id;
}A = { "zhangsan",18,20201252 };
int main()
{
struct Stu B = { "lisi",16,20201214 };
printf("%s %d %d\n", A.name, A.age, A.id);
printf("%s %d %d\n", B.name, B.age, B.id);
return 0;
}
结构体成员的间接访问是通过操作符(->)访问的,操作符 -> 接受两个操作数。使用方式:指向结构体变量的指针变量->成员名,如下所示:
c
#include <stdio.h>
struct Stu
{
char name[20];
int age;
int id;
}A = { "zhangsan",18,20201252 };
int main()
{
struct Stu B = { "lisi",16,20201214 };
struct Stu* p1 = &A;
struct Stu* p2 = &B;
printf("%s %d %d\n", p1->name, p1->age, p1->id);
printf("%s %d %d\n", p2->name, p2->age, p2->id);
return 0;
}
四、结构体的大小计算(结构体内存对齐)
1.对齐规则
要想计算结构体的大小,首先得掌握结构体的对齐规则:
(1). 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
(2). 其他成员变量要对齐到某个数字(对齐数)的整数倍的偏移量地址处。
注:对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值。
-VS 中默认的值为 8
-Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
(3). 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
(4). 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
例一(计算结构体类型struct s0的大小):
注:以下示例结果均是在 VS 编译器上运行的
c
#include <stdio.h>
struct s0
{
char c1;
int i;
char c2;
};
int main()
{
printf("struct s0的大小 : %zd\n", sizeof(struct s0));
return 0;
}
例二(计算结构体类型struct s1的大小):
c
#include <stdio.h>
struct s1
{
char c1;
int i;
char c2;
double j;
};
int main()
{
printf("struct s1的大小 : %zd\n", sizeof(struct s1));
return 0;
}
例三(计算结构体类型struct s2的大小):
c
#include <stdio.h>
struct s0
{
char c1;
int i;
char c2;
};
struct s2
{
char c;
struct s0 s;
double d;
};
int main()
{
printf("struct s2的大小 : %zd\n", sizeof(struct s2));
return 0;
}
2.修改默认对齐数
#pragma 这个预处理指令,可以改变编译器的默认对齐数。
c
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct s0
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
struct s1
{
char c1;
int i;
char c2;
double j;
};
int main()
{
printf("struct s0的大小 : %zd\n", sizeof(struct s0));
printf("struct s1的大小 : %zd\n", sizeof(struct s1));
return 0;
}
五、结构体传参
c
#include <stdio.h>
struct S
{
int data[1000];
int num;
};
void print1(struct S s2)//结构体传参
{
printf("%d\n", s.num);
}
void print2(struct S* ps)//结构体地址传参
{
printf("%d\n", ps->num);
}
int main()
{
struct S s1 = {{5,2,0}, 1314 };
print1(s1); //传结构体
print2(&s1); //传地址
return 0;
}
上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
直接传结构体给print1,print1函数的形参部分也会创建一个同类型的结构体来接收实参,如果实参传递的结构体过大,参数压栈的的系统开销会比较大,需要使用更多的时间和空间,所以会导致性能的下降。
结论:
结构体传参的时候,最好传结构体的地址。