13-x 第13章总结与测验
恭喜!你又成功闯过一关。关于结构体的知识将在我们进入C++最重要的话题——类时派上用场!
快速回顾
程序定义类型 program-defined type(也称为用户定义类型user-defined type)是一种自定义类型,可供我们在自己的程序中创建使用。枚举类型和类类型(包括结构体、类和联合体)均支持创建程序定义类型。程序定义类型必须在使用前进行定义。程序定义类型的定义称为类型定义type definition。类型定义不受单一定义规则的限制。
枚举enumeration(也称为枚举类型enumerated type或enum)是一种复合数据类型,其中每个可能的值都被定义为符号常量(称为枚举器enumerator)。枚举是独立的类型distinct types,这意味着编译器能够将其与其他类型区分开来(不同于类型别名)。
无作用域枚举Unscoped enumerations 之所以得名,是因为其枚举项名称与枚举定义本身处于相同作用域(不同于命名空间会创建新作用域区域)。无作用域枚举同样为其枚举项提供命名作用域区域。此类枚举会隐式转换为整数值。
作用域枚举Scoped enumerations的工作原理与无作用域枚举类似,但不会隐式转换为整数,且枚举项仅置于枚举本身的范围区域内(而非枚举定义所在的范围区域)。
结构体struct(结构structure的缩写)是一种程序定义的数据类型,它允许我们将多个变量捆绑成单一类型。构成结构体(或类)的变量称为数据成员data members(或成员变量member variables)。访问特定成员变量时,需在结构体变量名与成员名之间使用成员选择运算符member selection operator(operator.)(适用于普通结构体及结构体引用),或使用指针成员选择运算符(operator->)(适用于结构体指针)。
在编程中,聚合数据类型aggregate data type(也称为聚合体aggregate)是指能够包含多个数据成员的任何类型。在C++中,仅包含数据成员的数组和结构体即为聚合体aggregates。
聚合体采用一种称为聚合初始化aggregate initialization的形式,允许我们直接初始化其成员。为此,我们提供初始化列表initializer list 作为初始化器,该列表仅由逗号分隔的值组成。聚合初始化执行成员级初始化memberwise initialization,即按声明顺序依次初始化结构体中的每个成员。
在C++20中,指定初始化器Designated initializers 允许显式定义初始化值与成员的映射关系。成员必须按结构体中声明的顺序初始化,否则将引发错误。
定义结构体(或类)类型时,可在类型定义中为每个成员提供默认初始化值。此过程称为非静态成员初始化non-static member initialization,初始化值称为默认成员初始化器default member initializer。
出于性能考虑,编译器有时会在结构体中添加间隙(称为填充padding),因此结构体的大小可能大于其成员大小的总和。
类模板class template是用于实例化类类型(结构体、类或联合体)的模板定义。类模板参数推导(CTAD)Class template argument deduction是C++17引入的功能,允许编译器从初始化表达式中推导出模板类型参数。
测验时间
耶!
问题 #1
在设计游戏时,我们决定加入怪物元素,因为人人都喜欢与怪物战斗。请声明一个表示怪物的结构体。怪物应具有以下类型之一:食人魔、巨龙、兽人、巨型蜘蛛或史莱姆。
每个怪物还应拥有名称(使用std::string)及生命值(表示其承受伤害上限)。编写名为printMonster()的函数,用于打印结构体所有成员。实例化食人魔和史莱姆,使用初始化列表初始化它们,并将其传递给printMonster()。
程序应输出以下结果:
This Ogre is named Torg and has 145 health.
This Slime is named Blurp and has 23 health.
显示解决方案:
#include <iostream>
#include <string>
#include <string_view> // C++17
// Our monster struct represents a single monster
struct Monster
{
// Define our different monster types as an enum
enum Type
{
ogre,
dragon,
orc,
giant_spider,
slime,
};
Type type{};
std::string name{}; // the Monster should be an owner of its name
int health{};
};
// Return the name of the monster's type as a string
// Since this could be used elsewhere, it's better to make this its own function
constexpr std::string_view getMonsterTypeString(Monster::Type type)
{
switch (type)
{
case Monster::ogre: return "Ogre";
case Monster::dragon: return "Dragon";
case Monster::orc: return "Orc";
case Monster::giant_spider: return "Giant Spider";
case Monster::slime: return "Slime";
}
return "Unknown";
}
// Print our monster's stats
void printMonster(const Monster& monster)
{
std::cout << "This " << getMonsterTypeString(monster.type) <<
" is named " << monster.name <<
" and has " << monster.health << " health.\n";
}
int main()
{
Monster ogre{ Monster::ogre, "Torg", 145 };
Monster slime{ Monster::slime, "Blurp", 23 };
printMonster(ogre);
printMonster(slime);
return 0;
}
问题 #2
请说明给定类型中的对象应通过值传递、常量地址传递还是常量引用传递。可假设接收这些类型参数的函数不会修改它们。
a) char
显示答案
char 是基本类型,因此应按值传递。
b) std::string
显示答案
std::string 在每次复制字符串时都必须创建一个副本。请通过 const 引用传递它。
c) unsigned long
显示答案
unsigned long 是基本类型,因此应按值传递。
d) bool
显示答案
bool 是基本类型,因此应按值传递。
e) 枚举类型An enumerated type
显示答案
枚举类型存储整数值(通常为 int)。由于整数值采用值传递方式,因此枚举类型也应采用值传递方式。
f)
struct Position
{
double x{};
double y{};
double z{};
};
显示答案
尽管当前状态下Player仅包含一个整型变量,按值传递本可实现高效操作,但未来将添加更多成员。为避免后续更新时需修改所有Player引用,我们采用常量引用方式进行传递。
h) int(当 null 是有效参数时)
显示解决方案
通常我们会按值传递整型变量,但若需要同时支持传递空值,按地址传递便是理想选择——这样既可传递整型的地址,也能传递空指针。
i) std::string_view
显示解决方案
std::string_view 不会创建被查看字符串的副本,且其复制成本低廉。请采用值传递方式。
问题 #3
创建一个名为 Triad 的类模板,该模板包含 3 个相同模板类型的成员。同时创建一个名为 print 的函数模板,用于打印 Triad。以下程序应能编译通过:
int main()
{
Triad t1{ 1, 2, 3 }; // note: uses CTAD to deduce template arguments
print(t1);
Triad t2{ 1.2, 3.4, 5.6 }; // note: uses CTAD to deduce template arguments
print(t2);
return 0;
}
并产生以下结果:
[1, 2, 3][1.2, 3.4, 5.6]
若使用 C++17,需为 CTAD 提供推导指南才能正常工作(相关信息参见 13.14 节——类模板参数推导(CTAD)与推导指南)。
显示解决方案
#include <iostream>
template <typename T>
struct Triad
{
T first {};
T second {};
T third {};
};
// If using C++17, we need to provide a deduction guide (not required in C++20)
// A Triad with three arguments of the same type should deduce to a Triad<T>
template <typename T>
Triad(T, T, T) -> Triad<T>;
template <typename T>
void print(const Triad<T>& t)
{
std::cout << '[' << t.first << ", " << t.second << ", " << t.third << ']';
}
int main()
{
Triad t1{ 1, 2, 3 };
print(t1);
Triad t2{ 1.2, 3.4, 5.6 };
print(t2);
return 0;
}

浙公网安备 33010602011771号