为什么要把数据组合在一起
到目前为止,每个变量都是独立的:这里一个 int,那里一个 string。但真实的程序处理的是由多个部分组成的东西:一个点有 x 和 y,一个学生有姓名、年龄和 GPA。把它们当作零散的独立变量传来传去很容易出错:没有任何东西把它们联系在一起,需要这三项的函数就必须接收三个参数。
结构体解决了这个问题。它定义一个新类型,把相关的变量(也就是它的成员)打包成一个整体。定义好之后,你就可以把整个包当作一个值来存储、拷贝和传给函数。
用点运算符访问每个成员(s.name、s.age)。结构体定义的右花括号 } 后面的分号是必需的:漏掉它是 C++ 初学者最常见的编译错误之一。
初始化结构体
逐个给字段赋值可行,但很啰嗦,也容易漏掉某个成员。更简洁的做法是聚合初始化:在花括号里按成员声明的顺序列出各个值。
小心空的默认写法:Point p;(没有花括号)会让 x 和 y 保存垃圾值,因为内置类型的成员不会自动清零。Point p{}; 则会把它们值初始化为 0。优先使用花括号。你也可以直接在定义里写好默认值,这样即使是 Point p; 也能干净地开始:
把结构体作为函数参数
结构体是一个值,所以函数可以只接收一个参数,而不是三个。只是要记住,按值传递结构体会拷贝每一个成员。对于比几个 int 更大的东西,按 const 引用传递来省掉拷贝,这和你在引用一页看到的规则相同。
从函数返回整个结构体,是一次交回多个值的惯用方式,比来回摆弄多个输出引用清楚得多。
添加成员函数和构造函数
结构体不只能装数据。它还可以包含操作自身成员的成员函数,以及在对象创建时就完成初始化的构造函数。到这里,结构体开始像一个带有行为的小对象了。
在成员函数内部,直接用名字访问成员(width、height),它们指的是这个对象自己的那一份。写成 area() const,是告诉编译器这个函数只读取对象,这样你也能在 const Rectangle 值上调用它。构造函数本身就是一个独立的主题,包括 : width(w) 这种初始化列表语法,构造函数那一页会深入讲解。
struct 与 class:真正的区别
你可能听说过“结构体用来装数据,类用来做对象”。这只是一种惯例,并不是语言规则。在 C++ 中,struct 和 class 几乎是同一个东西,唯一内置的区别是默认访问级别。
struct S {
int x; // public by default
};
class C {
int x; // private by default
};
struct 的成员除非另行标注,否则是 public 的;class 的成员一开始是 private 的。仅此而已:两者都可以有构造函数、成员函数、继承以及其他一切。你甚至可以给结构体加上显式的访问说明符来隐藏成员:
实际的结论:对于每个字段都可以随意访问的透明数据集合(一个 Point、一个 Color、一条配置记录),用 struct;当你想把内部状态藏在公共接口后面加以保护时,用 class。编译器对它们一视同仁,关键字只是表明你的意图。
结构体数组和结构体 vector
因为结构体是普通的类型,你可以把许多个结构体放进数组或 vector 中,像对待其他值一样遍历它们。
注意嵌套的花括号:每个 {"Keyboard", 49.99} 聚合初始化一个 Product,外层花括号则构造整个 vector。在范围循环中使用 const Product&,可以避免每次迭代都拷贝一个结构体:去掉 &,你就会毫无必要地复制每个元素。
下一节:枚举
结构体让你用多个打包在一起的值构建一个类型。下一章则反其道而行:枚举定义的类型只能从一个小的、有名字的集合中取一个值,非常适合 Color::Red、Direction::North 或状态机的状态这类东西。你会看到 enum class 如何提供可读且类型安全的常量,并与你正在构建的结构体和类自然地配合使用。
常见问题
C++ 中的结构体是什么?
struct 是一种用户自定义类型,它把几个相关的变量(称为成员)归到一个名字下。你不必分别管理 string name、int age 和 double gpa 这几个变量,而是把它们打包成一个 Student 类型,然后传递这一个对象即可。结构体也可以包含成员函数和构造函数:在现代 C++ 中,它就是一个成员默认为 public 的完整的类。
C++ 中 struct 和 class 有什么区别?
严格来说,只有默认访问级别不同:除非另行指定,struct 的成员是 public 的,而 class 的成员默认是 private 的。继承也是如此。其他一切,包括构造函数、成员函数、方法和继承,用法完全相同。按照惯例,程序员用 struct 表示单纯的数据集合,用 class 表示带有不变量和隐藏内部实现的类型。
C++ 中如何初始化结构体?
最简单的方法是用花括号按成员顺序进行聚合初始化:Point p{3, 4};。你也可以用点运算符逐个给字段赋值(p.x = 3;),在定义中给成员设置默认值(int x = 0;),或者写一个构造函数让结构体自己完成初始化。推荐使用花括号初始化,因为它不会悄悄地让字段处于未初始化状态。