在编程中,有许多情况需要使用多个变量来表示某个感兴趣的事物。正如我们在上一章(复合数据类型简介)中所讨论的,一个分数由分子和分母组成,它们结合在一起形成一个单一的数学对象。
再举一个例子,假设我们要编写一个程序,用于存储公司员工的信息。我们可能需要跟踪员工的姓名、头衔、年龄、员工编号、经理编号、工资、生日、入职日期等属性。
如果我们使用独立的变量来跟踪所有这些信息,可能会像这样:
std::string name;
std::string title;
int age;
int id;
int managerId;
double wage;
int birthdayYear;
int birthdayMonth;
int birthdayDay;
int hireYear;
int hireMonth;
int hireDay;
然而,这种方法存在许多问题。首先,这些变量是否相关并不立即清楚(你可能需要阅读注释,或者查看它们在上下文中的使用方式)。其次,现在有12个变量需要管理。如果我们想将这个员工传递给一个函数,我们需要传递12个参数(并且顺序必须正确),这会使函数原型和函数调用变得混乱。而且,由于函数只能返回一个值,那么一个函数如何返回一个员工呢?
如果我们想要多个员工,我们需要为每个额外的员工定义12个更多的变量(每个变量都需要一个独特的名称)!这显然无法扩展。我们真正需要的是一种方法,将所有这些相关数据组织在一起,以便更容易管理。
幸运的是,C++ 提供了两种复合类型来解决这类问题:结构体(我们现在将介绍)和类(我们很快会探讨)。结构体是一种程序定义的数据类型(13.1 – 程序定义(用户定义)类型简介),它允许我们将多个变量捆绑在一起,形成一个单一的类型。正如你即将看到的,这使得相关变量集合的管理变得简单得多!
提醒
结构体是一种类类型(类和联合体也是如此)。因此,适用于类类型的所有规则也适用于结构体。
定义结构体
由于结构体是一种程序定义的类型,我们首先需要告诉编译器我们的结构体类型是什么样的,然后才能开始使用它。以下是一个简化版员工的结构体定义示例:
struct Employee
{
int id {};
int age {};
double wage {};
};
struct
关键字用于告诉编译器我们正在定义一个结构体,我们将其命名为 Employee
(因为程序定义的类型通常以大写字母开头)。
然后,在一对大括号内,我们定义每个 Employee
对象将包含的变量。在这个例子中,我们创建的每个 Employee
都将有3个变量:一个 int id
、一个 int age
和一个 double wage
。结构体内的变量称为数据成员(或成员变量)。
提示
在日常用语中,成员是一个属于某个群体的个体。例如,你可能是篮球队的成员,而你的妹妹可能是合唱团的成员。
在 C++ 中,成员是一个属于结构体(或类)的变量、函数或类型。所有成员都必须在结构体(或类)定义内声明。
在未来的课程中,我们会频繁使用“成员”这个词,所以请确保你记住它的含义。
正如我们使用空的大括号来值初始化(1.4 – 变量赋值和初始化)普通变量一样,每个成员变量后面的空大括号确保了在创建 Employee
时,Employee
内的成员变量被值初始化。我们将在几节课后(13.9 – 默认成员初始化)讨论默认成员初始化时再详细讨论这一点。
最后,我们以分号结束类型定义。
作为提醒,Employee
只是一个类型定义——此时并没有实际创建任何对象。
定义结构体对象
为了使用 Employee
类型,我们只需定义一个 Employee
类型的变量:
Employee joe {}; // `Employee` 是类型,`joe` 是变量名
这定义了一个名为 joe
的 Employee
类型的变量。当代码执行时,会实例化一个 Employee
对象,其中包含3个数据成员。空的大括号确保我们的对象被值初始化。
就像其他任何类型一样,可以定义多个相同结构体类型的变量:
Employee joe {}; // 为 Joe 创建一个 Employee 结构体
Employee frank {}; // 为 Frank 创建一个 Employee 结构体
访问成员
考虑以下示例:
struct Employee
{
int id {};
int age {};
double wage {};
};
int main()
{
Employee joe {};
return 0;
}
在上述示例中,joe
这个名字指的是整个结构体对象(它包含了成员变量)。要访问特定的成员变量,我们在结构体变量名和成员名之间使用成员选择运算符(.
)。例如,要访问 Joe 的年龄成员,我们使用 joe.age
。
结构体的成员变量就像普通变量一样工作,因此可以在它们上执行正常的操作,包括赋值、算术运算、比较等。
#include <iostream>
struct Employee
{
int id {};
int age {};
double wage {};
};
int main()
{
Employee joe {};
joe.age = 32; // 使用成员选择运算符(.)选择变量 `joe` 的 `age` 成员
std::cout << joe.age << '\n'; // 打印 Joe 的年龄
return 0;
}
该程序的输出为:
32
结构体的一个最大优点是,我们只需要为每个结构体变量创建一个新名称(成员名称作为结构体类型定义的一部分是固定的)。在以下示例中,我们实例化了两个 Employee
对象:joe
和 frank
。
#include <iostream>
struct Employee
{
int id {};
int age {};
double wage {};
};
int main()
{
Employee joe {};
joe.id = 14;
joe.age = 32;
joe.wage = 60000.0;
Employee frank {};
frank.id = 15;
frank.age = 28;
frank.wage = 45000.0;
int totalAge { joe.age + frank.age };
std::cout << "Joe 和 Frank 总共活了 " << totalAge << " 年\n";
if (joe.wage > frank.wage)
std::cout << "Joe 的工资高于 Frank\n";
else if (joe.wage < frank.wage)
std::cout << "Joe 的工资低于 Frank\n";
else
std::cout << "Joe 和 Frank 的工资相同\n";
// Frank 升职了
frank.wage += 5000.0;
// 今天是 Joe 的生日
++joe.age; // 使用前置递增运算符将 Joe 的年龄加 1
return 0;
}
在上述示例中,很容易区分哪些成员变量属于 Joe,哪些属于 Frank。这比使用单独的变量提供了更高层次的组织性。此外,由于 Joe 和 Frank 的成员具有相同的名称,这在你有多个相同结构体类型的变量时提供了连贯性。
我们将在下一课继续探索结构体,包括如何初始化它们。