一、结构体的声明
1、结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
2、结构的声明
下面是结构体声明的形式
struct tag { member-list; }variable-list;
例如现在要使用结构体来描述一个学生:里面就存放了一个学生所具有的基本信息,如姓名、年龄、性别、身高
struct Stu { char name[20]; //姓名 int age; //年龄 char sex[2]; //性别 float height; //身高 };
或者用结构体来描述一本书,里面存放了:书名、作者、定价、书号
struct Book { char name[20]; char author[20]; double price; int id; };
==不仅如此,结构的成员还可以是标量、数组、指针,甚至是其他结构体==
3、特殊的声明
接下去我来介绍一种特殊的结构体声明形式,也就是【匿名结构体】,什么是匿名结构体呢?也就是没有结构体名称
//匿名结构体类型 struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], * p;
- 可以看到,我故意将两个结构体的内容设置成一样,然后直接在声明的时候就定义出了它们各自的结构体成员,第二个结构体定义了一个结构体数组和结构体指针
- 此处我使用这个结构体指针去接收第一个结构体定义出来的变量x,此时你应该会觉得合情合理,但是编译之后却看到【它们的类型不兼容】,这是为什么呢?
p = &x;
原因其实就在于这个【匿名结构体】
- 对于匿名结构体来说呢只能在声明的时候用一次,后面就不能再用了。所以这个东西其实也没什么用,了解一下就行
- 不过这个语法形式和C++中的【匿名对象】很类似,声明周期只在当前行,进入下一行就会调用析构函数
4、结构的自引用
有关结构体自引用这块,就要说到数据结构里面的相关知识了,可能有的读者没听过,这也没关系,准备发车🚗
- 说起自引用这一块最典型的其实就是数据结构中的《链表》,可能有的C语言教科书上也会提及。但如果要说链表的话那就得先说说顺序表
- 那要如何去声明一个链表的结构体,下面这样可行吗?
struct Node { int data; struct Node next; };
- 可以看到,程序报出了错误,说是【struct Node未定义】,这是为什么呢?
- 其实对于链表的单个结点来说,是由两个域组成的,一个叫做【数据域】,是存放结点数据的,一个叫做【指针域】,是存放下一个结点在堆内存中地址,图示如下:
- 这里讲的不是很清楚,了解一下即可,有兴趣的同学可以去看看上面那篇链表的文章
- 那指针域要存放一个结点,也就是结构体的地址,就必须要一个结构体指针,此时我们就可以将代码写成下面这样,每个结构体指针都指向下一个结点的地址
struct Node { int data; struct Node* next; };
- 此时就可以通过这个结构体定义出有关链表的结点,然后去初始化链接每个结点即可
struct Node list;
但你是否有觉得上面这种形式太麻烦了,每次在定义一个结构体变量的时候都要在前面加上一个
struct
,如果可以不加该多好
- 这个其实很好办,只需要在结构体声明的时候在前面加上要一个
typedef
关键字即可,然后再定义变量的位置为其重命名一下,那在定义结构体变量的时候就不需要再加上struct
关键字了
typedef struct Node { int data; struct Node* next; }Node;
Node list;
【友情提示】:可不能把结构体定义成下面这样,Node* next;
这种写法是错误的,因为到这行为止,结构体还不认识Node
,所以是不可以使用它的
typedef struct Node { int data; Node* next; }Node;
二、结构体变量的定义和初始化
有了结构体类型,那如何定义变量,其实很简单。
- 第一种方法就是直接在声明出结构体的时候就可以定义结构体变量,例如这里的
s1、s2、s3
指的的都是一个学生,而且它们属于全局变量
struct Stu { char name[20]; //姓名 int age; //年龄 char sex[2]; //性别 float height; //身高 }s1, s2, s3; //全局变量
- 第二种方法就是脱离结构体进行定义,不过这和我们在定义普通变量的格式是一样的,也要在前面加上数据类型,比如
int a
前面的int
- 像
struct Stu
就是这个结构体的类型,不要忘记加上前面的struct这个修饰符了。下面的【ss】定义在外面,那就是全局变量;【su】定义在函数内部,那就是局部变量
struct Stu ss; //全局变量 int main(void) { struct Stu su; //局部变量 return 0; }
- 如果你觉得每次写
struct Stu
太麻烦了,也是有办法了,那就是为其进行一个重命名,一般我们直接在结构体最前面加上一个typedef
关键字,然后在定义处为其做一个重命名
typedef struct Stu { char name[20]; //姓名 int age; //年龄 char sex[2]; //性别 float height; //身高 }S;
因此可以说 S == struct Stu,定义方式就简洁了许多
struct Stu su; S su2;
接下去来讲讲结构体如何初始化
- 我们将初始化的内容使用花括号
{ }
括起来,里面就可以对结构体的成员进行一个初始化,分别意义对照进行初始化即可
struct Stu su = { "zhangsan", 20, "男", 180 };
- 这里我又定义了一个【点】的结构体,成员变量即为坐标x和坐标y
struct Point { int x; int y; };
- 对其初始化也很简单,如下
struct Point p = { 10, 20 };
但是现在我又有了一个结构体,这个结构体内部呢又有一个结构体,就是上面这个【点】,这该如何初始化呢?
struct MyStruct { char c; struct Point p; double d; char str[20]; };
- 很简单,其他普通的成员变量就正常初始化,结构体成员变量的话也按照结构体的方式使用花括号括起来即可
struct MyStruct ms = { 'c', {40, 80}, 3.14f, "haha" };
来看看初始化后的结果
- 那有同学问了,若是我不想按照顺序来进行初始化呢?可以吗?答案是可以的
- 只需要用
[.]
操作符然后选择对应的成员变量进行初始化即可
struct MyStruct ms2 = {.d = 6.28, .str = "abcdef", .c = 'cc'}; //乱序初始化
来看看这样初始化后的结果为多少。可以观察到没有被初始化到的变量就取为默认值,也就是这个【点】