一、什么是结构体类型?
在C语言中有很多内置类型,比如int、char、float、double等。还有数组(一组相同类型元素的集合)。它们只能描述一种类型的数据,但是在实际应用中要描述一个复杂类型的对象时,只用一种类型的数据是不行的。例如我们要描述一个人,他有姓名(字符串)、性别(字符串)、年龄(整型)等多种类型的数据。那就需要一种自定义的类型,用来存放各种类型的数据,用这种自定义的类型作为一个整体来描述一个复杂类型的对象。结构体就是将各种类型的数据整合到一起的一种自定义类型。结构体就是一些值的集合,这些值称为成员变量,结构体的每个成员可以是不同类型的变量。
二、结构体类型的声明
结构体类型的声明形式如下:
● struct是结构体关键字
● tag是结构体的标签名,是自定义的
● struct tag就是结构体类型
● { }里放的是成员列表
● member1,member2是结构体成员
● variable-list是变量名
比如我们要描述一个学生:
struct Stu
{
char name[20]; //姓名
int age; //年龄
char sex[5]; //性别
char id[20]; //学号
}; //括号末尾还要有一个分号
三、结构体变量的创建和初始化
1.上面声明了一个描述学生的结构体类型,以后我们就可以通过这个类型来创建结构体变量。
#include <stdio.h>
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
int main()
{
//按照结构体成员的顺序进行初始化
struct Stu s1 = { "张三",18,"男","202400001" };
printf("%s\n", s1.name);
printf("%d\n", s1.age);
printf("%s\n", s1.sex);
printf("%s\n", s1.id);
//按照指定的顺序进行初始化
struct Stu s2 = { .id = "202400002",.age = 20,.name = "李四",.sex = "女" };
printf("%s\n", s2.name);
printf("%d\n", s2.age);
printf("%s\n", s2.sex);
printf("%s\n", s2.id);
return 0;
}
程序运行的结果:
上面的代码中就使用到了成员访问操作符(.)。这里结构体变量的创建是在结构体类型声明之后创建的,也可以在结构体类型声明的同时创建变量,如下:
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s1,s2;
需要注意的是如果是在大括号{ }外面创建的结构体变量,那就是全局变量。如果是在大括号{ }内部创建的结构体变量,那就是局部变量。
2.结构体还有一种特殊声明,就是省略标签名:
struct
{
int a;
char b;
float c;
}x;
上面就是一种匿名结构体,它省略了标签名,如果我们再创建一个和上面一样的匿名结构体并创建一个结构体指针变量:
struct
{
int a;
char b;
float c;
}*p;
再对结构体指针进行赋值:p=&x。这合理吗?看似好像没有问题,但这是非法的,编译器会把上面的两个声明当成完全不同的两个类型,会报警告:
所以对于这种匿名结构体类型,如果没有对其进行重命名的话,基本上只能使用一次。
四、结构体的自引用
1.在结构中包含一个类型为该结构本身的成员是否可以呢?
比如,定义一个链表的节点:
struct Node
{
int data;
struct Node next;
};
这种形式的定义正确吗?答案是不对的,如果我们要计算这个结构体的大小sizeof(struct Node),这怎么算呢?一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷的大,根本计算不了,是不合理的。正确的自引用方式:
struct Node
{
int data;
struct Node* next;
};
上面这种自引用方式就是正确的,因为指针的长度是确定的,所以这个结构体的大小就可以计算出来了。
2.在结构体自引用使用的过程中,如果使用typedef对匿名结构体类型进行重命名,也容易引入问题,看看下面的代码:
typedef struct
{
int data;
Node* next;
}Node;
上面这段代码正确吗?答案是不正确,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。解决这种错误的办法就是不要使用匿名结构体:
typedef struct Node
{
int data;
struct Node* next;
}Node;
注意:
● 在定义结构体类型时是不会分配空间的。只有定义了结构体变量,才会分配内存空间。
● 结构体中的各成员的定义和之前的变量定义是一样的,只是在定义时,不会分配内存空间。
● 结构体类型的声明需要在主函数之上进行声明,如果在主函数之后进行声明,则会报错。因为这是编译器由上往下顺序执行的原则。
● 结构体类型(struct tag)是一种自定义的数据类型,是创建变量的模版,不占用内存空间;只有结构体变量才需要内存空间来存储。(就像int,char,float这些类型名一样,它们本身是不占用内存的,只有用它们创建变量时,才会向内存申请空间)[引用]
结构体数组
结构体数组:是一个数组,数组中的每个元素是一个结构体。比如创建关于一个班级人员信息的结构体类型:
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
}class[10]; //表示一个班有10个人
上面的就是一个结构体数组,该数组有10个元素,每个元素都是结构体类型。结构体数组常被用来表示一个拥有相同数据结构的群体。
五、结构体指针
结构体指针也是结构体中一个重要的知识点,通过指针来访问结构体中的成员:
#include <stdio.h>
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
int main()
{
struct Stu s = { "张三",18,"男","202400001" };
struct Stu* p = &s;
printf("%s\n", p->name);
printf("%d\n", p->age);
printf("%s\n", p->sex);
printf("%s\n", p->id);
return 0;
}
程序运行结果:
这里就用到了一个操作符(->),通常称之为箭头。也是在C语言中唯一能用到 “->” 的地方。如果不用箭头,还是要通过指针来访问结构体中的成员,要怎么办呢?看如下代码:
#include <stdio.h>
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
int main()
{
struct Stu s = { "张三",18,"男","202400001" };
struct Stu* p = &s;
printf("%s\n", (*p).id);
printf("%d\n", (*p).age);
printf("%s\n", (*p).name);
printf("%s\n", (*p).sex);
return 0;
}
程序运行结果:
因为 . 运算符的优先级要高于 *,所以要对 *p 加上括号,这样对结构体指针p解引用就相当于找到了结构体变量s,对其用 . 操作符就可以访问结构体中的成员了。