目录
在C语言中已经提供了内置类型,如:char、short、int、long、float、double等。但是在实际应用中只有这些内置类型还是不够的,我们无法用单一的内置类型来描述一个复杂的对象。就像描述一个学生,我们他的需要 名字、年龄、学号、⾝⾼、体重等;所以C语言就给出了除内置类型之外的一种类型:自定义类型,有了这种类型我们就能够自己来构建类型了。而比较常用的自定义类型就有结构体。
1.什么是结构体
结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚⾄是其他结构体。(注意:注意:结构体与数组一样,也是一种值的集合,只不过数组的每个元素都要是相同类型的,而结构体的每个成员可以是不同类型的。)
2.结构体的声明
首先,要使用结构体就必须声明(创建) 一个结构体类型,就必须用到结构体关键字:struct。如何声明一个结构体类型,如下所示。其中 stu为结构体标签,用来区分不同的结构体类型。struct tag表示结构体类型,声明完结构体后就可以用该类型来创建变量了。{ 之中的 } 为成员列表,它是结构体所包含的基本的结构类型。s1表示变量列表,这个列表可写可不写,写了就代表你用上面所创建的结构体类型定义了一个该类型的变量,没写则表示你仅仅只创建了一个结构体类型。
struct stu//申明结构体
{
char name[20];//定义一个名字
int age;//定义他的年龄
float height;//定义身高
}s1;//分号不能少,结构体也相当于一条语句
如上图所示,声明完结构体类型,就可以用它来定义和初始化结构体变量了。
其实还可以通过typedef来重定义结构体的类型名,如下所示:
typedef struct
{
char name[20];
int age;
float height;
}S;
int main()
{
S s1;
return 0;
}
这里的 S 就是对 sturct 的重命名。之后我们就可以用 S来创建变量,比如 S s1; 。
3.匿名结构体类型
我们在声明结构的时候,可以不完全的声明,也就是不写结构体标签,所以称之为匿名。如下所示:
struct
{
int a;
char b;
double c;
}x;
这种语法结构C语言是支持的,但不被我们所喜,因为这种声明出来的结构体类型我们只能用其定义一次变量,之后想用都用不了了。
还值得注意的一点是,就算两个匿名结构体类型的成员变量完全一样,但在编译器看来它们两个仍然是两个完全不同的类型。如下举例所示:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
} *p;
int main()
{
p = &x;
return 0;
}
他最后运行的结果是会报错的如下:
4.结构体的自引用
结构体是可以自引用的我们只需要在结构体中包含一个类型为该结构体本身的成员,这样就可以像套娃一样,在一个数据中找到另一个数据,在另一个数据中又找到另另一个数据。但是C语言是不支持结构体包含一个类型为本身结构体的这种语法。
但是我们可以在结构体中传入本身类型的指针如下:
struct Node
{
int data;
struct Node* next;
}n1 = {10, NULL};//结构体嵌套初始化
struct Node n2 = {20, NULL};//结构体嵌套初始化
5.结构体的内存对齐
结构体在内存中是如何存放的呢!
上面两种代码明明很相似可是在内存中的大小却又不一样。
这是因为结构体有他的内存对齐规则
- 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处。
- 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。 对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
- 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
s1如下图所示;
s2如下图所示;
为什么要存在内存对齐呢!
- 平台原因(移植原因):
不是所有的硬件平台都能够访问任意地址处的任意数据的,某些硬件平台只能在某些地址处取特定类型的数据,否则就会抛出硬件异常。 - 性能原因:
数据结构(尤其是栈)因该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的数据,处理器需要做两次内存访问,而对齐的内存只需要一次访问就够了。
结构体的内存对齐就是一种用空间换取时间的做法,浪费空间来换取性能上的提升。
6.结构体传参
结构体与普通内置类型一样也有两种传参方式:1. 传值,2. 传址。如果是传值,函数在传参的时候参数是需要压栈的,若此时结构体过大,参数压栈时系统的开销也就较大,会导致性能的下降。可传址就不同了,不管你结构体有多大我传递的地址永远是4/8个字节,开销远比传值小的多。而且传值和传址都能够实现对结构体变量的调用,故首选传址调用。
struct S
{
int data[1000];
int num;
};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
struct S s = {{1,2,3,4}, 1000};
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
感谢大家的观看!