从这篇文章开始,我们来学习C语言中的自定义类型(构造类型),今天来看第一种自定义类型——结构体,一起来学习吧!!!
1.认识结构体
前面我们已经学习过了很多的数据类型,整型、浮点型、指针类型等等。
1.1为什么要学习结构体类型
已经有这么多数据类型了,那我们为什么还要学习结构体类型呢?
因为在开发的过程中,我们有时候难免要去描述一些复杂的对象,而想要描述这些对象,我们再使用之前学过的int,double等这些类型可能就不适用了。
比如我们想要描述一本书,对于书这个类型来说,它具有的特征不止一个,我们要想去描述一本书的话,可能要给出书的书名,书的作者,书的定价等等这些信息。
这时如果我们只用一个int,double,char类型的数据好像无法描述。
这时候,我们就需要使用结构体来描述了。
因此,结构体作为一种自定义类型,使得我们有能力去描述复杂类型。
1.2什么是结构体
那既然结构体这么牛,结构体到底是什么呢?
结构体(结构)是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
2.结构体的声明
我们已经知道结构体是什么了,那如果我们想用结构体来描述一个学生该怎么做呢?
首先我们要进行结构体的声明。
如果我们想要描述一个学生,那我们就先来声明一个学生类型,怎么声明呢?
结构体声明的语法是:
struct tag { member - list; } variable - list;
每一部分都是什么意思呢?解释一下:
光看这个图,可能还不是特别明白,给大家举个例子就明白了。
我们就来声明一个学生类型,指定该学生类型拥有的成员变量有姓名,年龄,性别,学号。
struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }; //分号不能丢
我们看到上面没有变量列表variable - list
,这个是可选的。
3.结构成员的类型
结构体的成员变量可以是什么类型呢?
结构的成员可以是标量、数组、指针,甚至是其他结构体。
struct SS { int a; char b; float c; }; struct Stu { char ch; int* p; double arr[10]; struct SS s1; };
4.结构体变量的定义
有了结构体类型,那如何定义变量,其实很简单。
定义结构体的方式有两种:
4.1 在声明结构体类型的同时定义结构体变量
即在声明类型的
}
后面直接创建结构体变量,但要注意这里创建的结构体变量是全局变量。
举个例子:
struct Point { int x; int y; }p1; //声明类型的同时定义变量p1
4.2用声明过的结构体类型定义结构体变量
struct Point { int x; int y; }; struct Point p2; int main() { struct Point p3; return 0; }
5.结构体变量的初始化
初始化,即在定义变量的同时赋初值。
和数组一样,初始化结构体变量也用的是
{}
。
举个例子:
struct Point { int x; int y; }p1 = { 2,3 };
struct Stu { char name[15];//名字 int age; //年龄 }; int main() { struct Stu s = { "zhangsan", 20 };//初始化 return 0; }
5.1结构体嵌套的初始化
#include <stdio.h> struct Point { int x; int y; }p1 = { 2,3 }; struct Node { int data; struct Point p; }n1 = { 10, {4,5},}; //结构体嵌套初始化 int main() { struct Node n2 = { 20, {5, 6}};//结构体嵌套初始化 printf("%d %d %d", n1.data, n1.p.x, n1.p.y); return 0; }
再嵌套一个大括号来初始化被嵌套的那个结构体。
打印一下看看:
5.2指定成员变量初始化
在初始化结构体的时候,我们可以不按成员变量的顺序去初始化,可以指定某个成员初始化,按我们想要的顺序初始化。
举个例子:
struct Stu { char name[15];//名字 int age; //年龄 }; int main() { struct Stu s = {.age=33,.name="zhangsan"};//指定成员初始化 printf("%d %s", s.age, s.name); return 0; }
打印出来看看:
这样也是可以的。
6.特殊的声明(匿名结构体类型)
除了上面介绍的结构体声明方式之外,还有一种特殊的结构体声明。
即在声明结构体的时候,可以不完全的声明。
那这个不完全声明又是什么意思呢?
就是在声明一个结构体的时候的时候省略掉结构体标签(tag),或者说该结构体没有类型名。
也称为匿名结构体类型。
举个例子:
struct { int a; char b; float c; }x;
那么匿名结构体类型有什么特点呢?
因为匿名结构体类型没有类型名,所以匿名结构体类型只能在定义的时候创建结构体变量,后面再想利用这个结构体类型创建变量就做不到了。
struct { int a; char b; float c; }x;//创建匿名结构体变量x
这样是可以的。
struct { int a; char b; float c; }; int main() { struct s; return 0; }
这样是不行的。
然后,我们再来分析一段代码:
struct { int a; char b; float c; }x; struct { int a; char b; float c; }*p; int main() { p = &x; return 0; }
大家思考一下,这样写,可以吗?
这样写是不行的,编译器会报警告的。
虽然上面两个匿名结构体类型的成员变量完全一样,但是编译器会把上面的两个声明当成完全不同的两个类型。
所以是非法的。
7.结构体成员的访问
对于结构体成员的访问,不同的情况下可以有不同的访问方式,一般可以分为两种:
7.1结构体变量访问成员
通过结构体变量访问成员是通过点操作符(.)访问的。点操作符接受两个操作数。
语法:结构体变量.成员变量
举个例子:
#include <stdio.h> struct Stu { char name[15]; int age; }; int main() { struct Stu s = { "zhangsan", 20 }; printf("%s %d", s.name, s.age); return 0; }
看看结果:
7.2结构体指针访问指向变量的成员
有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针。
那该如何访问成员?
两种方式:
我们可以对该结构体指针解引用,这样就找到了对应的结构体变量,然后我们就可以使用(.)操作符来访问成员变量了。
那我们可不可以直接通过结构体指针访问对应结构体的成员变量呢?
当然可以。
这时候我们可以使用->操作符来实现。
语法:结构体指针->成员变量
一起来看一个例子:
struct Stu { char name[15]; 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; }
看看结果:
两种方式都可以成功访问。
8.结构体的自引用
首先,我们来思考一个问题:
在结构体中包含一个类型为该结构体本身的成员是否可以呢?
像这样:
struct Node { int data; struct Node next; };
这样是否可行,如果可行,那sizeof(struct Node)是多少?
如果这样写,我们去计算struct Node的大小时,需要计算成员里面一个同类型的结构体struct Node next的大小,而在计算它的大小时,发现里面还包含一个自己,这样的话就会无限套娃下去,是不是没法计算啊。
那应该怎么写,我们可以考虑这样做:
struct Node { int data; struct Node* next; };
换成一个同类型的结构体指针,这样它就指向了一个类型为该结构体本身的结构体变量作为成员。
通过这样一个问题,我们引出一个新的概念:
结构体的自引用:在结构体内部,包含指向自身类型结构体的指针。
数据结构中链表的结点其实就是这样搞的。