前言
C语言的数据类型分为内置数据类型和自定义的数据类型。所谓的内置的数据类型可以认为是C语言自带的数据类型(char、int、float、double、bool),而自定义的数据类型(数组、结构体、联合体和枚举)中的结构体类型就是本文所要分享给大家的。
1. 结构体类型的含义
我们在正式学习结构体之前,得先了解一下结构体到底是个什么东西?
有句古话说的好:知己知彼,方能百战不殆。
我们想要学好结构体,就必须得对它有一定的了解。为了让大家能够更好的学习结构体,这里就先引入我们之前学过的数组的一些知识。
我们都是知道,数组里面的元素都是相同类型的,并且它们在内存空间中是连续存放的。那这时我们就会有这么一个疑惑?C语言有没有一种数据类型能够存放一次性包括多种不同的数据类型。答案是有的,那就是我们本讲的主角——结构体。
总而言之,当我们在编程时遇到了一个个体,这个个体包含着许多不同的属性,一个一个定义就显得过于繁琐且不方便,那么结构体就刚好适应这种场景的需求。
2.结构体的声明
在讲完结构体的含义之后,那我们该如何去定义一个结构体呢?
2.1 结构体声明的语法
struct tag
{
member-list;
}varible-list;
符号 | 含义 |
---|---|
tag | 标签,可以理解为结构体名字 |
member-list | 成员列表,之后我们就是在这个花括号内来写我们想要存储的变量 |
varible-list | 变量列表,这个就是我们利用结构体创建之后直接给结构体变量的名称 |
注意:结构体名称和结构体变量名称是两个不一样的东西,不要给混淆了。
例如:我们描述一个学生
struct Student
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //这里有个分号,一定不要忘记写了
2.2 结构体变量的创建和初始化
struct S
{
char c;
int i;
double d;
};
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
int main()
{
//按照结构体的成员顺序进行赋值
struct Stu s1 = { "张三",22,"男","123456789" };
printf("name: %s\n",s1.name);
printf("age: %d\n", s1.age);
printf("sex: %s\n", s1.sex);
printf("id: %s\n", s1.id);
printf("\n");
//按照知道指定的顺序赋值
struct Stu s2 = { .age = 18,.name = "梨花",.id = "563217485",.sex = "女"};
printf("name: %s\n", s2.name);
printf("age: %d\n", s2.age);
printf("sex: %s\n", s2.sex);
printf("id: %s\n", s2.id);
return 0;
}
除了上述在main函数内创建结构体变量的方式之外,我们还可以在面函数外面创建结构体变量,如果是这样做的话,这个变量就相当于全局变量了。
//方法一
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s1,s2; //变量列表
//方法二
struct Stu s3;
int main()
{
...
}
3.结构体的特殊声明
在声明结构体时,我们也可以做到不完全声明。具体一点来讲,就是不给结构体名字。
我们将这种结构体称为“匿名结构体”
比如:
//匿名结构体类型
struct
{
int i;
char ch;
double d;
}s;
struct
{
int i;
char ch;
double d;
}* ps;
可以观察到,匿名结构体在声明时,的确出现了省略标签(tag)的现象。
不过需要注意的是,匿名结构体只能用一次。这里的“用一次”也就是说该匿名结构体在本声明之初就应该一次性在变量列表中创建好所有变量的名称,等到脱离了匿名结构体的声明后,就再也没有办法创建一个新的结构体变量了。
那么我现在有个问题:
ps = &s; //这样写可以吗?
乍一看,好像确实没有什么问题啊!ps这个结构体指针所指向的结构体与结构体变量s里面的成员变量是一摸一样的。可事实真的如此吗?
警告信息为:
“=”
: 从“*”
到“*”
的类型不兼容
也就是说ps所指向的结构体与变量s所代表的结构体不属于同一个结构体类型,尽管它们里面成员一模一样
为此,我们总结一下匿名结构体使用的注意事项:
- 匿名结构体只能使用一次
- 即使两个甚至是多个匿名结构体里面的成员列表的内容是一摸一样的,编译器也会把这些结构体视作不同类型的结构体变量
4. 结构体的自引用
在结构体包含一个类型为改结构体本身的成员是否可以呢?
答案是肯定的,不过有一定的语法规则。为什么会这样说呢?请看下面的代码:
//根据上面的意思,我们可以写出以下代码:
struct Node
{
int data;
struct Node next;
};
上面的代码合理吗?如果合理的话,请告诉我sizeof(struct Node)
的值为多少?
显然,问到这里,你就会发现,这个结构体这样声明确实有点问题。这个结构体的声明给我们一种陷入了死循环的感觉,而这个结构体的大小就为无穷大。
正确子引用的方式:
struct Node
{
int data;
struct Node* next;
};
我们用一个指向该结构体的指针作为该成员变量,这样做不仅可以知道该结构体的大小,还可以利用该指针访问其内部的数据,一举两得。
例外,我们还需要注意一种极其容易出现错误的写法:
我们再利用typedef给结构体重命名时:
typedef struct Node
{
int data;
Node* next;
}Node;
上述写法可行吗?
原因是:编译器是从上往下来执行代码的,当执行到 Node* next 这条语句时,编译器由于还未完全读到typedef的所有语句,因此就将Node视作未定义的标识符。
正确的写法是:
typedef struct Node
{
int data;
struct Node* next;
}Node;
5.小结
在本文中,我想告诉大家的是:结构体的用途、结构体的声明、结构体的变量的创建及初始化还有结构体的自引用。内容不是很多,希望读者们能好好消化理解。
最后,如果觉得本文写的还不错的话,希望可以给偶带上个赞👍,阿里嘎多❤️❤️❤️。
让我们在详解结构体(下)这个篇章再见!!!