一、结构体的声明
1. 结构体的基本概念
结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。结构体使得C语言有能力描述复杂类型。
如学生,有姓名、学号、性别等;如书,有作者,出版日期,价格等
2. 结构体的声明
(1)声明格式为:
struct tag
{
member-list;
}variable-list;
其中member-list为结构体成员变量列表;variable-list为需要创建的结构体变量的列表
例如描述一个学生的结构体声明就可以写成:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//分号不能丢
(2)特殊的声明:
在声明结构体的时候可以不完全声明。与正常的声明相比省略了tag(标签)的内容,这样的结构体类型也被称为匿名结构体类型。如:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
需要注意的是:匿名结构体类型的变量只能使用一次。
如在上面代码的基础上写了这样的语句:p = &x;
,就是非法的。因为编译器会把上面两个声明当成完全不同的两个类型。 就好像int
和float
是完全不同的两个类型。
所以,对于这种特殊的声明了解即可,别真正去用
3. 结构体的成员类型
结构体的成员可以是标量、数组、指针,甚至是其他结构体。
4. 结构体变量的定义和初始化
(1)定义
- 沿用上面描述学生的声明,声明一个结构体变量可以有两种方式:
声明类型的同时定义变量,即将变量名直接写在变量列表中;或者另起一行像定义其他类型变量一样定义结构体变量
如下面这段代码:
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
}Stu1,Stu2; //声明类型的同时定义变量Stu1和Stu2
struct Stu Stu3; //另起一行定义结构体变量Stu3
注意这里另起一行定义结构体变量的写法与下面即将提到的另一种方式区别
- 还有一种定义方式,不过此时结构体声明部分与上面的略有不同,因为用到了
typedef
类型重命名关键字,代码如下:
typedef struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
}Stu;
注意这里最后的Stu不是上面说所的变量列表中的内容,即不是声明类型的同时定义的变量,而是这个结构体类型的一个别名,此时定义一个结构体变量的正确写法就应为:Stu Stu4;
当然写成struct Stu Stu4;
也没错。但需要注意是,上面没有用typedef关键字进行类型重命名的声明只能写成struct Stu 变量名;
而不能写成Stu 变量名;
(2)初始化
即定义变量的同时赋予初值
这里继续沿用上面学生的声明:
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
}Stu1 = {"小明",16,"男","124410"} //声明类型的同时定义变量Stu1
struct Stu Stu2 = {"小红",16,"女","124450"};
//另起一行定义结构体变量Stu2
- 结构体的嵌套初始化:
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //{4,5}就属于结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//{5,6}就属于结构体嵌套初始化
5. 结构体的自引用
有了3, 4两点的内容之后我们就可以来理解结构体的自引用。结构体的自引用就是在结构体中包含一个类型为该结构体本身的成员。
在第3点中说过,结构体中的成员可以是其他结构体,在第4点的结构体的嵌套初始化中也可看到:
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL};
struct Node n2 = {20, {5, 6}, NULL};
其中的成员struct Point p
就是其他的结构体。而成员struct Node* next
就是结构体的正确自引用。
下面看一个错误的自引用:
struct Node
{
int data;
struct Node next;
};
如果上面是可行的,那么sizeof(struct Node)
的值将会是无穷大,因为会 “无限套娃”。
所以正确的自引用应该是通过指针找到同类型的节点,而不能直接包含同类型的节点
补充:typedef
重命名下的正确自引用:
typedef struct Node
{
int data;
struct Node* next;
}Node;
对比一下错误的自引用:
typedef struct
{
int data;
Node* next;
}Node;
总结:用typedef
对结构体类型进行重命名时,在结构体内部进行自引用(包含一个类型为该结构体本身的成员)时注意需要加上struct
。
6. 补充说明
结构体的声明一般是放在主函数之外的,故声明类型的同时定义的变量一般为全局变量。虽然语法也允许结构体的声明放在主函数中,不过一般不这么做。
二、结构体成员的访问
1. 结构体变量访问成员
结构变量的成员是通过点操作符 ‘ . ’ 访问的。点操作符接受两个操作数,形式为结构体变量名.成员变量名
。如下面这段代码:
#include <stdio.h>
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}Stu1 = { "小明",16,"男","124410" };//分号不能丢
int main()
{
printf("%s\n", Stu1.name);
printf("%d\n", Stu1.age);
printf("%s\n", Stu1.sex);
printf("%s\n", Stu1.id);
return 0;
}
运行结果:
2. 结构体指针访问成员
有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针。
那么此时我们通过另一个操作符 ‘->’ 进行访问。如下代码:
#include <stdio.h>
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);//结构体地址传参
return 0;
}
运行结果:
代码中的print函数的参数是一个结构体指针,故在进行参数传递时传的是结构体的地址(下面一部分还会说明),那么在print函数内部进行的成员访问实际上是通过结构体指针进行的。所以用到操作符 ‘->’ 。
由运行结果可以看出,使用操作符 ‘->’ 就等价于先解引用结构体指针得到结构体变量,再通过操作符 ‘ . ’ 进行成员访问。
三、结构体的传参
结构体的传参和变量的传参一样,分为值传递和地址传递。结构体在进行传参时一般用的都是地址传递,即传递结构体的地址。
原因:函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
所以,结构体在进行传参时,要传结构体的地址。
本章完。
看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或有误地方的地方还恳请过路的朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹