目录
1 为什么需要结构体
2 什么是结构体
3 声明结构体类型
3.1 语法格式
3.2 案例演示
3.2.1 学生信息结构体
3.2.2 通讯录条目结构体
3.2.3 猫咪结构体
4 声明结构体变量
4.1 什么是结构体变量
4.2 声明结构体变量的常见方式
5 结构体和结构体变量的区别与联系
6 访问结构体成员
6.1 结构体成员的访问方式
6.2 为结构体成员逐个赋值
6.2.1 案例演示 1:逐个赋值与修改
6.2.2 案例演示 2:初始化三种方式声明的结构体
6.3 使用大括号一次性初始化结构体成员
6.3.1 案例演示 1:初始化猫咪结构体
6.3.2 案例演示 2:初始化学生结构体
6.3.3 案例演示 3:初始化三种方式声明的结构体
7 结构体指针
7.1 按值传递结构体
7.2 声明结构体指针
7.3 通过结构体指针访问成员
7.4 结构体类型转换
8 编程练习
8.1 小狗案例
8.2 盒子案例
8.3 景区门票案例
1 为什么需要结构体
在 C 语言中,除了几种基本的数据类型之外,数组是唯一内置的复合数据类型,能够存储多个相同类型的值。然而,由于其只能包含同类型的数据,这在实际应用中可能会带来一定的限制。指针数组虽然能够存储不同类型的数据,但对其数据进行操作不是很方便。
场景一:
现有需求开发一个学生档案管理系统,其中需要详细记录每位学生的信息,包括学号、姓名、性别、年龄和家庭住址等内容,这些信息综合反映了学生的整体概况。
场景二:
隔壁的老王养了两只猫咪。其中一只名为“小黄”,是一只 2 岁的橘色猫咪;另一只名为“小黑”,是一只 3 岁的黑色猫咪。请编写一个程序,该程序能够根据用户输入的猫咪名字,输出对应的猫咪名字、年龄和颜色。若用户输入的名字与老王家的猫咪名字不符,则程序应提示“老王没有这只猫”。
传统的方法可能包括以下两种尝试:
单独定义多个变量:为每只猫咪的名字、年龄和颜色分别定义独立的变量来存储信息。这种方法虽然直接,但随着猫咪数量的增加,变量的数量也会迅速增长,导致代码难以管理和维护。
使用数组:数组是一种用于存储一系列相同类型数据的结构。然而,在本例中,我们需要存储的是每只猫咪的不同类型的信息(如名字为字符串,年龄为整数)。由于这些信息的数据类型各不相同,因此无法将它们整合到同一个数组中,这限制了数组在这种场景下的适用性。
为了更有效地管理这些不同类型的猫咪信息,可以考虑使用 C 语言中的结构体(struct),它可以将不同类型的数据组织在一起,形成一个易于管理的整体。这样不仅能够清晰地表示每只猫咪的信息,还方便对多只猫咪的数据进行操作和扩展。
2 什么是结构体
C 语言通过 struct 关键字提供了一种自定义复合数据类型的能力,允许将多种不同类型的数据组合成一个单元,这种数据类型被称为结构体(structure)。
尽管 C 语言不具备其他高级语言中的对象(object)和类(class)概念,但结构体在很大程度上模拟了这些功能,使得数据的组织和管理更加高效和直观。通过结构体,我们可以轻松地创建复杂的数据模型,如学生记录、员工信息等,极大地增强了程序处理现实世界问题的能力。
3 声明结构体类型
3.1 语法格式
在 C 语言中,构建一个结构体类型的一般语法格式如下:
struct 结构体名称 {
数据类型1 成员名1; // 分号分隔结构体成员
数据类型2 成员名2;
...
数据类型n 成员名n;
}; // 分号不要忘记
- struct 是 C 语言的关键字,用于定义结构体类型。
- 结构体名称是自定义的标识符,用于命名结构体类型。
- 成员名1 至成员名 n 是结构体内部的数据成员,每个成员都有自己的数据类型,可以是 C 语言中的任何合法数据类型,包括基本数据类型、指针类型,甚至是其他结构体类型。
3.2 案例演示
3.2.1 学生信息结构体
// 定义结构体:学生
struct Student {
int id; // 学号
char name[50]; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
int age; // 年龄
char address[100]; // 家庭住址
};
在此示例中,Student 结构体包含了学号、姓名、性别、年龄和家庭住址五个成员。注意,对于姓名和家庭住址,使用了固定长度的字符数组(char name[50] 和 char address[100])来存储,而不是指针(char *name 和 char *address),这是因为直接使用字符数组可以避免内存分配和释放的问题,更适合于简单且固定的字符串存储。但在很多情况下,使用指针来定义字符串类型的成员变量比使用固定长度的字符数组更加灵活和方便。
3.2.2 通讯录条目结构体
// 定义结构体:通讯录
struct Contacts {
char name[50]; // 姓名
int birth_year; // 出生年份
int birth_month; // 出生月份
int birth_day; // 出生日
char email[100]; // 电子邮件地址
char mobile_number[12]; // 手机号码
};
在此示例中,Contacts 结构体设计用于存储个人的联系信息,包括姓名、出生日期、电子邮件地址和手机号码。通过将出生日期分为年、月、日三个独立的成员,可以使日期的处理更加灵活和方便。
3.2.3 猫咪结构体
// 定义结构体:猫咪
struct Cat {
char *name; // 名字
int age; // 年龄
char *color; // 颜色
};
在此示例中,Cat 结构体设计用于存储猫咪的基本信息,包括名字、年龄和颜色。通过使用指针来存储名字和颜色,可以灵活地处理不同长度的字符串,使结构体更加通用和方便管理。年龄则使用整型变量直接存储,适用于表示具体的数值信息。
提示:
选择使用固定长度的字符数组还是指针,取决于具体的应用场景和需求。
- 如果确定字符串的长度不会超过某个上限,并且希望简化内存管理,那么使用固定长度的字符数组可能是更好的选择。
- 如果字符串长度不定或者需要频繁修改,那么使用指针会更加合适。
一般来说,对于字符串类型,常用指针来表示。
4 声明结构体变量
4.1 什么是结构体变量
在 C 语言中,结构体是一种自定义的数据类型,用于将不同类型的数据组合在一起。结构体变量则是根据结构体类型创建的具体实例,用于存储实际的数据。
4.2 声明结构体变量的常见方式
方式 1:先定义结构体,然后再创建结构体变量
// 定义结构体
struct Student {
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
};
// 定义结构体变量
struct Student stu1, stu2;
这种方式首先定义了一个名为 Student 的结构体类型,然后基于这个类型创建了两个结构体变量 stu1 和 stu2。这种方式适合需要多次使用同一结构体类型的情况。
方式 2:在定义结构体的同时定义结构体变量
// 定义结构体的同时定义 stu1 和 stu2 两个变量
struct Student {
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu1, stu2;
这种方式在定义结构体类型的同时,直接创建了两个结构体变量 stu1 和 stu2。这种方式简洁,减少了代码行数。这种方式适合只需要一次定义的情况。
方式 3:在定义时也可以不给出结构体名
// 不给出结构体的名字
struct {
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu1, stu2;
这种方式定义了一个匿名结构体,并直接创建了两个结构体变量 stu1 和 stu2。由于没有给结构体命名,这种方式适用于只需要创建少数几个特定结构体变量的场景。
5 结构体和结构体变量的区别与联系
结构体:结构体是一种自定义的数据类型,类似于一个模板,定义了数据的格式。它描述了数据的结构,但不占用内存空间。
结构体变量:结构体变量是根据结构体类型创建的具体实例,用于存储实际的数据。每个结构体变量都会占用内存空间,存储相应的数据。
特征 | 结构体(Struct) | 结构体变量(Struct Variable) |
---|---|---|
定义 | 一种用户自定义的数据类型,定义了数据的格式和结构。 | 根据结构体类型创建的具体实例,用于存储实际的数据。 |
内存占用 | 不占用内存空间,只是一个类型定义。 | 占用内存空间,用于存储实际的数据。 |
作用 | 提供了一种数据组织的方式,定义了数据的结构。 | 存储和操作实际的数据,每个变量可以有不同的值。 |
生命周期 | 存在于编译阶段,用于类型检查和编译。 | 存在于运行阶段,用于存储和操作数据。 |
依赖关系 | 无,可以独立定义。 | 必须基于已定义的结构体类型创建。 |
数据一致性 | 确保所有结构体变量都遵循相同的格式,提供类型安全。 | 遵循结构体定义的格式,确保数据的一致性和类型安全。 |
灵活性 | 提供了一种通用的数据结构,可以被多个变量使用。 | 可以创建多个实例,每个实例可以有不同的数据值。 |
初始化 | 无需初始化,只是一个类型定义。 | 可以在定义时进行初始化,也可以在后续代码中动态赋值。 |
访问和修改 | 不能直接访问或修改,因为它只是一个类型定义。 | 可以直接访问和修改其成员的数据。 |
6 访问结构体成员
6.1 结构体成员的访问方式
结构体的成员是结构体的一个组成部分,可以是基本数据类型、数组、指针、结构体等。结构体的成员也称为属性。结构体和数组类似,是一组数据的集合,使用点号 . 获取单个成员【结构体变量.结构体成员】,可以进行赋值和取值。
假设有一个结构体变量 struct_name,其成员为 member_name,访问和操作该成员的格式如下:
struct_name.member_name
6.2 为结构体成员逐个赋值
6.2.1 案例演示 1:逐个赋值与修改
下面这个程序演示了如何使用 C 语言中的结构体来定义和操作学生信息。结构体 Student 包含了学号、年龄、姓名、性别和家庭住址等成员。程序首先定义了一个结构体类型 Student,然后创建了一个结构体变量 stu,并逐个对结构体的成员进行赋值。最后,程序输出了这些成员的值,并在输出性别时进行了相应的转换。此外,程序还展示了如何修改结构体成员的值。
#include <stdio.h>
int main()
{
// 声明结构体
struct Student
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
};
// 声明结构体变量
struct Student stu;
// 逐个对成员进行赋值
stu.id = 1001;
stu.age = 19;
stu.name = "Jack Ma";
stu.gender = 'M';
stu.address = "Beijing, China";
// 获取成员的值并输出
printf("id: %d \n", stu.id);
printf("name: %s \n", stu.name);
printf("age: %d \n", stu.age);
// printf("gender: %c \n", stu.gender);
if (stu.gender == 'M')
{
printf("性别: 男 \n");
}
else if (stu.gender == 'F')
{
printf("性别: 女 \n");
}
else
{
printf("性别未知\n");
}
printf("address: %s \n", stu.address);
// 修改成员的值
stu.address = "ShangHai, China";
printf("new address: %s \n", stu.address);
return 0;
}
输出结果如下所示:
6.2.2 案例演示 2:初始化三种方式声明的结构体
下面这个程序演示了在 C 语言中使用结构体来定义和操作学生信息的三种常见方式。结构体 Student 包含了学号、年龄、姓名、性别和家庭住址等成员。程序通过三种方式创建和初始化结构体变量,并输出每个变量的成员值,程序最后输出每个结构体变量的成员值,展示不同方式创建和初始化结构体变量的效果。
#include <stdio.h>
// 方式 1:先定义结构体,然后再创建结构体变量
struct Student1
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
};
// 方式 2:在定义结构体的同时定义结构体变量
struct Student2
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu2_1, stu2_2;
// 方式 3:在定义时也可以不给出结构体名
struct
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu3_1, stu3_2;
int main()
{
// 方式 1:创建并初始化结构体变量
struct Student1 stu1_1, stu1_2;
stu1_1.id = 1;
stu1_1.age = 20;
stu1_1.name = "张三";
stu1_1.gender = 'M';
stu1_1.address = "北京";
stu1_2.id = 2;
stu1_2.age = 21;
stu1_2.name = "李四";
stu1_2.gender = 'F';
stu1_2.address = "上海";
// 方式 2:初始化结构体变量
stu2_1.id = 3;
stu2_1.age = 22;
stu2_1.name = "王五";
stu2_1.gender = 'M';
stu2_1.address = "广州";
stu2_2.id = 4;
stu2_2.age = 23;
stu2_2.name = "赵六";
stu2_2.gender = 'F';
stu2_2.address = "深圳";
// 方式 3:初始化结构体变量
stu3_1.id = 5;
stu3_1.age = 24;
stu3_1.name = "钱七";
stu3_1.gender = 'M';
stu3_1.address = "杭州";
stu3_2.id = 6;
stu3_2.age = 25;
stu3_2.name = "孙八";
stu3_2.gender = 'F';
stu3_2.address = "南京";
// 输出结构体变量的信息
printf("方式1: 学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu1_1.id, stu1_1.name, stu1_1.age, stu1_1.gender, stu1_1.address);
printf("方式1: 学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu1_2.id, stu1_2.name, stu1_2.age, stu1_2.gender, stu1_2.address);
printf("方式2: 学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu2_1.id, stu2_1.name, stu2_1.age, stu2_1.gender, stu2_1.address);
printf("方式2: 学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu2_2.id, stu2_2.name, stu2_2.age, stu2_2.gender, stu2_2.address);
printf("方式3: 学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu3_1.id, stu3_1.name, stu3_1.age, stu3_1.gender, stu3_1.address);
printf("方式3: 学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu3_2.id, stu3_2.name, stu3_2.age, stu3_2.gender, stu3_2.address);
return 0;
}
输出结果如下所示:
6.3 使用大括号一次性初始化结构体成员
在 C 语言中,可以使用大括号 {} 一次性对结构体的所有成员进行初始化。这种方式简洁明了,适用于在定义结构体变量时直接初始化所有成员的场景。大括号内的值必须按照结构体成员定义的顺序提供。
使用大括号 {} 进行初始化必须在定义变量时进行。一旦变量已经定义,就不能再使用大括号 {} 进行赋值。对于已经定义的变量,需要逐个成员进行赋值。
6.3.1 案例演示 1:初始化猫咪结构体
下面这个程序定义了一个 Cat 结构体,用于存储猫咪的名字、年龄和颜色。然后,程序创建了一个 Cat 结构体变量 my_cat,并使用大括号 {} 一次性初始化其所有成员。最后,程序通过访问结构体的成员并打印它们的值,输出猫咪的信息。
#include <stdio.h>
// 定义结构体
struct Cat
{
char *name; // 名字
int age; // 年龄
char *color; // 颜色
};
int main()
{
// 使用大括号一次性初始化结构体成员
// 大括号内的值必须按照结构体成员定义的顺序提供。
struct Cat my_cat = {"Whiskers", 3, "Gray"};
// 访问结构体成员并打印
printf("Name: %s\n", my_cat.name); // Name: Whiskers
printf("Age: %d\n", my_cat.age); // Age: 3
printf("Color: %s\n", my_cat.color); // Color: Gray
return 0;
}
6.3.2 案例演示 2:初始化学生结构体
下面这个程序演示了在 C 语言中使用结构体来定义和操作学生信息。程序定义了一个结构体类型 Student,并在定义结构体变量时使用大括号 {} 一次性初始化所有成员。程序最后输出每个结构体变量的成员值。
#include <stdio.h>
int main()
{
// 声明结构体以及结构体变量,并给结构体成员赋值
struct Student
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu1 = {1002, 19, "Jim Liu", 'f', "Songjiang Shanghai"},
stu2 = {1003, 16, "Tom Chen", 'm', "Baoan Shenzhen"};
// 获取成员的值并输出
printf("stu1:\n");
printf("id: %d \n", stu1.id);
printf("name: %s \n", stu1.name);
printf("age: %d \n", stu1.age);
printf("gender: %c \n", stu1.gender);
printf("address: %s \n\n", stu1.address);
printf("stu2:\n");
printf("id: %d \n", stu2.id);
printf("name: %s \n", stu2.name);
printf("age: %d \n", stu2.age);
printf("gender: %c \n", stu2.gender);
printf("address: %s \n", stu2.address);
return 0;
}
输出结果如下所示:
6.3.3 案例演示 3:初始化三种方式声明的结构体
下面这个程序演示了在 C 语言中使用结构体来定义和操作学生信息的三种常见方式。每种方式都展示了如何定义结构体、创建结构体变量并一次性初始化所有成员。程序最后输出每个结构体变量的成员值。
#include <stdio.h>
// 方式 1:先定义结构体,然后再创建结构体变量
struct Student1
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
};
// 方式 2:在定义结构体的同时定义结构体变量
struct Student2
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu2_1 = {3, 22, "王五", 'M', "广州"},
stu2_2 = {4, 23, "赵六", 'F', "深圳"};
// 方式 3:在定义时也可以不给出结构体名
struct
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu3_1 = {5, 24, "钱七", 'M', "杭州"},
stu3_2 = {6, 25, "孙八", 'F', "南京"};
int main()
{
// 方式 1:创建并初始化结构体变量
struct Student1 stu1_1 = {1, 20, "张三", 'M', "北京"},
stu1_2 = {2, 21, "李四", 'F', "上海"};
// 输出结构体变量的信息
printf("方式1:\n");
printf("学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu1_1.id, stu1_1.name, stu1_1.age, stu1_1.gender, stu1_1.address);
printf("学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu1_2.id, stu1_2.name, stu1_2.age, stu1_2.gender, stu1_2.address);
printf("方式2:\n");
printf("学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu2_1.id, stu2_1.name, stu2_1.age, stu2_1.gender, stu2_1.address);
printf("学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu2_2.id, stu2_2.name, stu2_2.age, stu2_2.gender, stu2_2.address);
printf("方式3:\n");
printf("学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu3_1.id, stu3_1.name, stu3_1.age, stu3_1.gender, stu3_1.address);
printf("学号: %d, 姓名: %s, 年龄: %d, 性别: %c, 地址: %s\n", stu3_2.id, stu3_2.name, stu3_2.age, stu3_2.gender, stu3_2.address);
return 0;
}
输出结果如下所示:
7 结构体指针
7.1 按值传递结构体
当结构体按值传递时,会创建一个结构体的副本。这意味着函数接收到的是结构体的一个完整拷贝。对于大型结构体,按值传递会涉及大量的内存复制操作,从而影响性能。
#include <stdio.h>
struct MyStruct
{
int a;
float b;
};
void printStruct(struct MyStruct s)
{
printf("a: %d, b: %f\n", s.a, s.b);
s.a = 66; // 按值传递不会修改结构体的本身数据
}
int main()
{
struct MyStruct s = {1, 2.3};
printStruct(s); // 按值传递
printf("a: %d, b: %f\n", s.a, s.b); // a: 1, b: 2.300000
return 0;
}
输出结果如下所示:
7.2 声明结构体指针
结构体指针是指向结构体的指针变量,允许间接访问和操作结构体的成员。使用结构体指针可以更方便地处理结构体数据,特别是在函数参数传递和动态内存管理中非常有用。声明结构体指针的语法格式如下所示:
struct 结构体名 *结构体指针变量名;
声明结构体指针,通常需要先定义一个结构体变量,再创建结构体指针,并将结构体变量的地址赋值给结构体指针。
// 声明结构体
struct Student {
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
};
// 声明结构体变量
struct Student stu;
// 声明结构体指针并初始化
struct Student *ptr = &stu;
7.3 通过结构体指针访问成员
结构体指针可以通过 -> 操作符或解引用操作符 * 访问结构体的成员。以下是一个示例程序,展示了如何通过结构体指针访问和操作结构体的成员。
#include <stdio.h>
int main()
{
// 定义结构体
struct Student
{
char *name; // 姓名
int age; // 年龄
char gender; // 性别,使用字符型,例如 'M' 表示男性,'F' 表示女性
};
// 创建并初始化结构体变量
struct Student s = {"张三", 20, 'M'};
// 通过结构体变量访问成员
printf("通过结构体变量访问成员:\n");
printf("name=%s, age=%d, gender=%c\n", s.name, s.age, s.gender);
// 声明结构体指针并初始化
struct Student *ps = &s;
// 通过结构体指针解引用访问成员
printf("通过结构体指针解引用访问成员:\n");
printf("name=%s, age=%d, gender=%c\n", (*ps).name, (*ps).age, (*ps).gender);
// 通过结构体指针使用 -> 操作符访问成员
printf("通过结构体指针使用 -> 操作符访问成员:\n");
printf("name=%s, age=%d, gender=%c\n", ps->name, ps->age, ps->gender);
// 修改方式也可以这样
s.name = "Thanks";
printf("s.name=%s, (*ps).name=%s, ps->name=%s\n", s.name, (*ps).name, ps->name);
s.age = 20;
printf("s.age=%d, (*ps).age=%d, ps->age=%d\n", s.age, (*ps).age, ps->age);
s.gender = 'F';
printf("s.gender=%c, (*ps).gender=%c, ps->gender=%c\n", s.gender, (*ps).gender, ps->gender);
return 0;
}
输出结果如下所示:
7.4 结构体类型转换
C 语言是一种强类型语言,不同类型的结构体即使成员相似或完全相同,在类型系统上也被视为不同的类型。直接进行相似类型的结构体类型转换通常是不被推荐的做法,因为这可能导致未定义行为。
#include <stdio.h>
// 方式1:先定义结构体,然后再创建结构体变量
struct Student1
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
};
// 方式2:在定义结构体的同时定义结构体变量
struct Student2
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu2_1 = {3, 22, "王五", 'M', "广州"},
stu2_2 = {4, 23, "赵六", 'F', "深圳"};
// 方式3:在定义时也可以不给出结构体名
struct
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu3_1 = {5, 24, "钱七", 'M', "杭州"},
stu3_2 = {6, 25, "孙八", 'F', "南京"};
// 定义一个函数来输出学生信息
void print_student_info(struct Student1 student);
int main()
{
// 方式1:创建并初始化结构体变量
struct Student1 stu1_1 = {1, 20, "张三", 'M', "北京"},
stu1_2 = {2, 21, "李四", 'F', "上海"};
// 输出结构体变量的信息
printf("方式1:\n");
print_student_info(stu1_1);
print_student_info(stu1_2);
// printf("方式2:\n");
// 强制类型转换
// 在 C 语言中,不同类型之间的结构体不能直接进行强制类型转换,因为它们的内存布局可能不同。
// print_student_info((struct Student1)stu2_1); // 错误操作
// print_student_info((struct Student1)stu2_1); // 错误操作
// printf("方式3:\n");
// 强制类型转换
// 在 C 语言中,不同类型之间的结构体不能直接进行强制类型转换,因为它们的内存布局可能不同。
// print_student_info((struct Student1)stu3_1); // 错误操作
// print_student_info((struct Student1)stu3_1); // 错误操作
printf("方式2:\n");
// 使用了复合字面量来创建一个新的 struct Student1类 型的临时变量
print_student_info((struct Student1){stu2_1.id, stu2_1.age, stu2_1.name, stu2_1.gender, stu2_1.address});
print_student_info((struct Student1){stu2_2.id, stu2_2.age, stu2_2.name, stu2_2.gender, stu2_2.address});
printf("方式3:\n");
// 使用了复合字面量来创建一个新的 struct Student1类 型的临时变量
print_student_info((struct Student1){stu3_1.id, stu3_1.age, stu3_1.name, stu3_1.gender, stu3_1.address});
print_student_info((struct Student1){stu3_2.id, stu3_2.age, stu3_2.name, stu3_2.gender, stu3_2.address});
return 0;
}
// 定义一个函数来输出学生信息
void print_student_info(struct Student1 stu)
{
char *genderStr = (stu.gender == 'M') ? "男" : (stu.gender == 'F') ? "女"
: "未知";
printf("学号: %d, 姓名: %s, 年龄: %d, 性别: %s, 地址: %s\n",
stu.id, stu.name, stu.age, genderStr, stu.address);
}
结构体类型在定义时指定了其成员和布局,因此不同类型的结构体即使成员相似,也被视为不同的类型。直接将一个结构体类型的变量转换为另一个不兼容的结构体类型是不安全的,也是未定义行为。
输出结果如下所示:
但是,指针类型是可以进行转换的,如下所示:
#include <stdio.h>
#include <string.h>
// 方式1:先定义结构体,然后再创建结构体变量
struct Student1
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
};
// 方式2:在定义结构体的同时定义结构体变量
struct Student2
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu2_1 = {3, 22, "王五", 'M', "广州"},
stu2_2 = {4, 23, "赵六", 'F', "深圳"};
// 方式3:在定义时也可以不给出结构体名
struct
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别,建议使用字符型,例如 'M' 表示男性,'F' 表示女性
char *address; // 家庭住址
} stu3_1 = {5, 24, "钱七", 'M', "杭州"},
stu3_2 = {6, 25, "孙八", 'F', "南京"};
// 定义一个函数来输出学生信息
// 接受一个指向 struct Student1 类型的指针
void print_student_info(struct Student1 *student);
int main()
{
// 方式1:创建并初始化结构体变量
struct Student1 stu1_1 = {1, 20, "张三", 'M', "北京"};
struct Student1 stu1_2 = {2, 21, "李四", 'F', "上海"};
// 输出结构体变量的信息
printf("方式1:\n");
print_student_info(&stu1_1);
print_student_info(&stu1_2);
printf("方式2:\n");
// 通过使用 (struct Student1 *) 进行强制类型转换
// 可以将 struct Student2 类型的指针或匿名结构体类型的指针转换为 struct Student1 类型的指针
// 从而满足 print_student_info 函数的参数要求
print_student_info((struct Student1 *)&stu2_1);
print_student_info((struct Student1 *)&stu2_2);
printf("方式3:\n");
print_student_info((struct Student1 *)&stu3_1);
print_student_info((struct Student1 *)&stu3_2);
return 0;
}
// 定义一个函数来输出学生信息
void print_student_info(struct Student1 *student)
{
char *gender_str = (student->gender == 'M') ? "男" : (student->gender == 'F') ? "女"
: "未知";
printf("学号: %d, 姓名: %s, 年龄: %d, 性别: %s, 地址: %s\n",
student->id, student->name, student->age, gender_str, student->address);
}
在 C 语言中,将一种结构体类型的指针强制转换为另一种不兼容的结构体类型的指针确实是可行的,但这样做通常是不安全的,并且可能导致未定义行为。
输出结果如下所示:
8 编程练习
8.1 小狗案例
- 编写一个 Dog 结构体,包含 name(char *)、age(int)、weight(double) 属性。
- 编写一个 say 函数,返回字符串,方法返回信息中包含所有成员值。
- 在 main 函数中,创建 Dog 结构体变量,调用 say 函数,将调用结果打印输出。
#include <stdio.h>
#include <string.h> // 用于 sprintf 函数
// 定义 Dog 结构体
struct Dog
{
char *name; // 名字
int age; // 年龄
double weight; // 体重
};
// say 函数,返回字符串,信息中包含所有成员值
char *say(struct Dog dog);
int main()
{
// 测试
// 定义结构体变量
struct Dog dog;
// 空指针,安全行为
char *dogInfo = NULL;
// 初始化结构体变量
dog.name = "小黄";
dog.age = 1;
dog.weight = 3.4;
// 调用 say 函数,传入结构体变量(值传递)
dogInfo = say(dog);
// 打印返回的信息
printf("小狗信息:%s \n", dogInfo);
}
// say 函数,返回字符串,信息中包含所有成员值
char *say(struct Dog dog)
{
// 将这个信息放入到一个字符串(字符数组)
// char *info; // 返回局部指针变量,错误!!!
static char info[100]; // 局部静态变量,确保每次调用时内容不会被覆盖
sprintf(info, "name=%s age=%d weight=%.2f", dog.name, dog.age, dog.weight);
// 修改传入的结构体变量的 name 成员(仅在函数内部有效)
dog.name = "小花";
// 返回包含信息的字符串
return info;
}
8.2 盒子案例
编程创建一个 Box 结构体,在其中定义三个成员表示一个长方体的长、宽和高,长宽高可以通过控制台输入。
定义一个函数获取长方体的体积(volume)。
创建一个结构体指针,打印给定尺寸的长方体的体积。
#include <stdio.h>
// 定义 Box 结构体
struct Box
{
double length; // 长度
double width; // 宽度
double height; // 高度
};
// 获取立方体体积的函数
double getVolume(struct Box *box)
{
// 通过指针访问结构体成员并计算体积
// 指针形式可以在里面修改原始数据
// 值传递无法改变结构体的原始数据
return box->length * box->width * box->height;
}
int main()
{
// 创建结构体变量
struct Box box;
// 创建结构体指针,并将其初始化为结构体变量的地址
struct Box *cube = &box;
// 提示用户输入长度
printf("Enter length: ");
// 使用指针访问结构体成员,并读取用户输入
scanf("%lf", &cube->length);
// 提示用户输入宽度
printf("Enter width: ");
// 使用指针访问结构体成员,并读取用户输入
scanf("%lf", &cube->width);
// 提示用户输入高度
printf("Enter height: ");
// 使用指针访问结构体成员,并读取用户输入
scanf("%lf", &cube->height);
// 调用函数获取体积并打印
printf("Volume is: %.2f\n", getVolume(cube));
return 0;
}
输出结果如下所示:
8.3 景区门票案例
一个景区根据游人的年龄收取不同价格的门票。
请编写游人结构体(Visitor),根据年龄段决定能够购买的门票价格并输出。
规则:年龄>18,门票为20元,其它情况免费。
可以循环从控制台输入名字和年龄,打印门票收费情况,如果名字输入n,则退出程序。
#include <stdio.h>
#include <string.h>
// 定义 Visitor 结构体
struct Visitor
{
char name[10]; // 游客名字
int age; // 游客年龄
double pay; // 应付票价
};
// 编写函数处理业务
void ticket(struct Visitor *visitor)
{
// 根据年龄判断票价
if (visitor->age > 18)
{
visitor->pay = 20.0; // 成年人票价 20 元
}
else
{
visitor->pay = 0.0; // 未成年人免费
}
}
int main()
{
// 测试
// 创建结构体变量(创建一个游客)
struct Visitor visitor;
// 循环的输入名字和年龄
while (1)
{
printf("\n 请输入游客名字:");
scanf("%s", visitor.name);
// 判断如果名字输入 "n" ,则退出程序
if (!strcmp("n", visitor.name))
{
break;
}
printf("\n 请输入游客年龄:");
scanf("%d", &visitor.age);
// 调用函数 ticket , 获取应付的票价
ticket(&visitor);
// 打印应付的票价
printf("\n 该游客应付票价:%.2f\n", visitor.pay);
}
printf("退出程序\n");
return 0;
}
输出结果如下所示: