1、为什么需要结构体
没有结构体之前,在C语言中,数据的组织依靠:变量+数组。最初简单的时候,只需要使用基本数据类型(int char float double)来定义变量,需要几个变量就定义几个。后来情况变复杂了,有时需要很多相同类型的变量(譬如需要存储及运算一个班级的学生分数),这时数组出现了,数组解决了需要很多类型相同的变量问题。
数组最大的不足在于,一个数组只能存储很多个数据类型相同的变量,所以碰到需要封装几个类型不同的变量的时候,数组就无能为力。譬如对于题目:使用一个数据结构来保存一个学生的所有信息:姓名、学号、性别,这时候就需要结构体。
C语言中的结构体(struct)是一种用户定义的数据类型,它允许你将不同类型的数据组合成一个单独的数据类型。这种功能对于多种场景来说都是非常有用的,以下是一些主要原因:
- 数据封装和抽象:结构体提供了一种将多个相关的数据项封装在一起的方式,从而形成一个更高层次的数据抽象。这使得数据的管理和操作更加容易和直观。
- 提高代码的可读性和可维护性:由于结构体的数据被封装在一起,可以为数据项提供有意义的名称,提高代码的可读性;同时,在修改数据结构时,只需要修改结构体定义,而不需要在整个代码中搜索和替换相关的变量名。
- 方便数据传递:在函数之间传递数据时,如果数据由多个部分组成,使用结构体可以方便地一次性传递所有数据,而不需要分别传递每个数据项。这可以简化函数调用和参数处理。
- 支持面向对象编程的概念:虽然C语言本身并不直接支持面向对象编程(OOP),但结构体可以被视为OOP中“对象”的一种简单形式。通过结构体,你可以定义具有属性和方法的“对象”,尽管在C语言中,方法通常是通过函数指针或函数表来实现的。
- 处理复杂数据:对于某些复杂的数据类型,如树、图、链表等,结构体提供了一种自然的方式来表示这些数据结构的节点。通过使用结构体,你可以轻松地定义节点的属性和与其他节点的关系。
- 支持可扩展性和灵活性:通过向结构体添加新的成员变量或方法(通过函数指针),你可以在不修改现有代码的情况下扩展结构体的功能。这使得结构体成为一种非常灵活和可扩展的数据类型。
2、定义结构体
(1)为了定义结构,必须使用 struct 关键词。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
- tag是结构体标签。
- member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。
- variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
struct tag {
member-list
member-list
member-list
...
} variable-list ;
结构体定义举例:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book1,book2;
(2)在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。以下为示例:
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
3、结构体变量的初始化
(1)和其他类型变量一样,对结构体变量可以在定义时指定初始值。
(2)示例1
#include <stdio.h>
// 定义一个名为Person的结构体,定义变量person并初始化
/*
struct Persons
{
char name[50]; // 姓名
int age; // 年龄
float pga;
} person = {"zjd", 28, 3.5};
*/
struct Persons
{
char name[50]; // 姓名
int age; // 年龄
float pga;
} person =
{
// 可以不完全初始化
.name = "zjd",
.age = 28
};
int main()
{
printf("name:%s\nage:%d\npga:%f\n", person.name, person.age, person.pga);
return 0;
}
执行输出结果:
name:zjd
age:28
pga:0.000000
(2)示例2
#include <stdio.h>
// 定义一个名为Person的结构体,定义变量person并初始化
struct Persons
{
char name; // 姓名
int age; // 年龄
float pga;
};
int main()
{
struct Persons person =
{
// 可以不完全初始化
person.age = 30,
person.name = 'a'
};
printf("name:%c\nage:%d\npga:%f\n", person.name, person.age, person.pga);
return 0;
}
编译执行输出:
name:a
age:97
pga:0.000000
4、访问结构体成员
(1)为例方位结构体的成员,我们使用成员访问运算符(.)。
(2)成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个英文句号。您可以使用 struct 关键字来定义结构类型的变量。
(3)示例:
#include <stdio.h>
#include <string.h>
// 定义一个名为"Student"的结构体
struct Student {
char name[50];
int age;
float gpa;
};
int main() {
// 创建"Student"结构体的一个实例
struct Student alice;
// 访问并设置结构体的成员
strcpy(alice.name, "Alice"); // 使用strcpy函数复制字符串到alice的name成员
alice.age = 20;
alice.gpa = 3.5;
// 访问并打印结构体的成员
printf("Name: %s\n", alice.name);
printf("Age: %d\n", alice.age);
printf("GPA: %.2f\n", alice.gpa);
return 0;
}
代码编译,执行输出如下结果:
Name: Alice
Age: 20
GPA: 3.50
5、结构体作为函数参数
(1)可以把结构体作为函数参数,传参方式与其他类型的变量或指针类似。
(2)示例:
#include <stdio.h>
#include <string.h>
// 定义一个名为"Student"的结构体
struct Students
{
char name[50]; // 姓名
int age; // 年纪
float gpa; // 平均绩点
};
/* 函数声明 */
void printStudent( struct Students student );
int main()
{
// 创建"Student"结构体的一个实例
struct Students alice;
// 访问并设置结构体的成员
strcpy(alice.name, "Alice"); // 使用strcpy函数复制字符串到alice的name成员
alice.age = 20;
alice.gpa = 3.5;
printStudent( alice );
return 0;
}
void printStudent( struct Students student )
{
// 访问并打印结构体的成员
printf("Name: %s\n", student.name);
printf("Age: %d\n", student.age);
printf("GPA: %.2f\n", student.gpa);
}
6、指向结构的指针
(1)可以定义指向结构体的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Students *struct_pointer;
(2)现在,你可以在上述定义的指针变量中存储结构体变量的地址。为了查找结构体变量的地址,需要把 & 运算符放在结构体变量名称的前面,如下所示:
struct_pointer = &alice;
(3)为了使用指向该结构体的指针访问结构体的成员,必须使用 -> 运算符,如下所示:
struct_pointer→name;
(4)使用指向结构体指针来重写上面的示例,如下所示:
#include <stdio.h>
#include <string.h>
// 定义一个名为"Student"的结构体
struct Students
{
char name[50]; // 姓名
int age; // 年纪
float gpa; // 平均绩点
};
/* 函数声明 */
void printStudent( struct Students *student );
int main()
{
// 创建"Student"结构体的一个实例
struct Students alice;
// 定义一个指向Students类型结构体的指针变量
struct Students *struct_pointer = &alice;
strcpy(struct_pointer->name, "Alice");
struct_pointer->age = 20;
struct_pointer->gpa = 3.5;
//printStudent( &alice );
printStudent( struct_pointer );
return 0;
}
void printStudent( struct Students *student )
{
// 访问并打印结构体的成员
printf("Name: %s\n", student->name);
printf("Age: %d\n", student->age);
printf("GPA: %.2f\n", student->gpa);
}
代码编译,执行输出如下结果:
Name: Alice
Age: 20
GPA: 3.50
7、位域
7.1、位域的定义和变量的说明
(1)有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如存放一个开关量时,只有0和1两种状态,用1位二进制位即可。为了节省存储空间,并使处理简单,C 语言又提供了一种数据结构,称为"位域"或"位段"。
所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
(2)典型的应用:
- 用1位二进制位存放一个开关量时,只有 0 和 1 两种状态。
- 读取外部文件格式——可以读取非标准的文件格式。例如:9位的整数。
(3)位域的定义
位域定义与结构体定义相仿,其形式为:
struct 位域结构名
{
位域列表
};
其中位域列表的形式为:
类型说明符 位域名: 位域长度
例如:
struct bs
{
int a: 8;
int b: 2;
int c: 6;
}data;
说明 data 为 bs 变量,共占两个字节。其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位。
(4)位域变量注意事项说明
- 一个位域存储在同一字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
// 在这个位域定义中,a 占第一字节的 4 位,后 4 位填 0 表示不使用 // b 从第二字节开始,占用 4 位,c 占用 4 位。 struct bs{ unsigned a:4; unsigned :4; /* 空域 */ unsigned b:4; /* 从下一单元开始存放 */ unsigned c:4 }
- 位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k{ int a:1; int :2; /* 该 2 位不能使用 */ int b:3; int c:2; };
7.2、位域的使用
(1)位域的使用和结构成员的使用相同,其一般形式为:
位域变量名.位域名
位域变量名->位域名
(2)位域允许用各种格式输出,示例如下:
int main()
{
struct bs
{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */
pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */
pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */
pbit->b=3;
pbit->c=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */
return 0;
}
编译执行输出结果:
1,7,15
0,3,1