轨道
/
C++
C++
/
练习
/
特洛伊之力
特洛伊之力

特洛伊之力

学习练习

简介

C++ 中的auto关键字

在 C++ 中,auto关键字是 C++11 引入的一项强大特性,用于声明数据类型可被推断的变量。 编译器会根据变量的初始化值推断出变量的类型,这能让代码更易读、更易维护。

用法示例

看看下面这个例子,其中用auto来声明变量:

auto dragon_population{3};          // dragon_population is deduced as an integer
auto westeros{7.7777};              // westeros is deduced as a double
auto wedding_location{"The Twins"}; // wedding_location is deduced as a const char*, not std::string

每一种情况下,变量的类型都由初始化它的那个值推断出来。

类型推断

auto关键字减少了显式类型的冗长写法,帮助我们写出更简洁、更易读的代码。

const std::vector<std::string> pigeon_pie{"flour", "butter", "pigeon", "salt"};
auto purple_wedding_pie{pigeon_pie};
purple_wedding_pie.emplace_back("the strangler");

在这个循环中,auto把purple_wedding_pie的类型推断为std::vector<std::string>,省去了再次显式写出类型。

兼容性

auto关键字与各种 C++ 结构都兼容,因此它是现代 C++ 编程中一件多才多艺的工具。

auto& element{array[0]};           // reference to an element
const auto object{otherObject};    // const type version of otherObject's type
auto* ptr{&x};                     // pointer to x with the same type as x, but as a pointer.

在后面的概念中,我们会经常看到auto关键字与 lambda 表达式、基于范围的for循环以及迭代器一起出现。

智能指针

智能指针是现代 C++ 的一项特性,用于提供自动内存管理,帮助避免内存泄漏和悬空指针,这些通常是裸指针容易出现的问题。 它们相当于对裸指针的封装,额外增加了自动释放内存等功能,指针不再需要时就会自动释放内存。

一般语法

智能指针在 C++ 标准库中通常以类模板的形式实现。 最常用的两个智能指针是std::unique_ptr和std::shared_ptr。

独占指针

std::unique_ptr是一种独占对象所有权的智能指针。 它确保在任何时刻,只有一个std::unique_ptr对象拥有该资源。 当拥有资源的std::unique_ptr被销毁或被重置时,它会自动析构对象并释放其内存。

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

std::make_unique()的优势

创建std::unique_ptr时,最好使用std::make_unique(),而不是直接用new分配内存。 std::make_unique()带来了几个好处:

  1. 异常安全:std::make_unique()保证异常安全。 如果在对象构造期间抛出异常,内存会被自动释放,从而避免内存泄漏。
  2. 清晰:使用std::make_unique()能让代码更清晰、更简洁。 由于模板实参会自动推断,它省去了显式指定所分配类型的麻烦。
  3. 优化机会:相比用new手动分配内存,编译器有机会更有效地优化std::make_unique(),从而有可能提升性能。
  4. 避免误用:手动构造std::unique_ptr时,是有可能删除底层资源的。 而当std::unique_ptr在其作用域结束时试图删除它,就会导致未定义行为。

共享指针

std::shared_ptr是一种允许多个std::shared_ptr对象共享同一资源所有权的智能指针。 它会记录有多少个共享指针正在引用该资源,只有当最后一个拥有该资源的共享指针离开作用域或被重置时,才会释放内存。

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;
Caution

在 C++17 及更低版本中,通过std::make_shared<T[]>让std::shared_ptr管理数组并不被直接支持。 虽然可以用std::make_shared<T[]>分配数组,但直接由此创建共享指针可能会导致未定义行为,因为单个对象与数组在内存管理上存在差异。 不如考虑使用std::vector或自定义删除函数,来有效地用共享指针管理数组。 在 C++17 中处理数组分配和共享指针时,务必确保与你的编译器和标准库实现兼容。

std::make_shared()的优势

与std::make_unique()类似,std::make_shared()也带来了更高的内存效率、异常安全和更好的可读性等好处。 它把控制块和被管理对象的内存分配合并为一次操作,从而提升效率并降低内存泄漏的风险。 此外,模板实参的自动推断简化了代码,也提升了可读性。 在 C++ 中使用std::shared_ptr对象时,使用std::make_shared()有助于写出更整洁、更安全、更高效的代码。

使用建议

默认使用智能指针:独占所有权用std::unique_ptr,共享所有权用std::shared_ptr。 裸指针则留给不拥有所有权的引用,或者用在与遗留代码交互的地方。 大多数情况下,std::unique_ptr就足以应付独占所有权,因为它提供轻量的内存管理,没有引用计数的额外开销。 除非确实需要共享所有权,否则应少用std::shared_ptr,因为它会带来额外开销和复杂性。

说明

这个练习会带你走进特洛伊的世界。 那里的人们生活在奇妙与魔法之中。 特洛伊的许多人拥有_能力_,并在日常生活中频繁使用它们。 能力可以用来重塑世界,或者影响特洛伊的动物和其他人。 魔法还会凝结成_独特的神器_,它们深受冒险者、工匠和贤者的追捧。

在这个练习中,你将编写代码,为特洛伊的人类、他们拥有的神器和能力交互建立模型。

一共有六个任务。 第一个任务与创造人类有关,其余五个则关于处理能力和神器。

1. 把人类带到特洛伊世界

在你的特洛伊模型中,人类是最重要的部分。 你建模的人类应当能够拥有一件_独特的神器_。 他们还应当能够显现一种_能力_。 这些能力可能会影响其他人,所以你还想对人类是否受到其他能力的影响进行建模。

这里已经提供了 artifact 和 power 结构体的基本实现。 请实现一个 human 结构体(或类),它有一个指向名为 possession 的 artifact 成员变量的_智能指针_。 任何时候,每件神器都只能被一个人拥有。

human 还必须有两个额外的成员变量。 一个保存他们的 own_power,另一个是他们 influenced_by 的能力。 own_power 和 influenced_by 都是指向 powers 的_智能指针_。 每个 power 可能只被一个人拥有,但同时也能影响其他人。

默认情况下,人类出生时没有任何神器,既不拥有任何能力,也不受任何能力影响。

human mindy_mccready{};
mindy_mccready.possession;
// => nullptr
mindy_mccready.own_power;
// => nullptr
mindy_mccready.influenced_by;
// => nullptr

2. 把神器带入特洛伊

如果各个部分之间没有交互,你的模型会十分无趣。 你想创造独特的神器,并把它们交给特定的人。

请定义函数 give_new_artifact,它不返回任何值,但接收一个 human 的引用和一个 string。 它应当用这个 string 新建一个 artifact 对象,并相应地设置该 human 的 possession 指针。 这个函数不应返回任何值。

human erik_magnus_lehnsherr{};
give_new_artifact(erik_magnus_lehnsherr, "Mind shielding helmet");

erik_magnus_lehnsherr.possession->name;
// "Mind shielding helmet"

3. 让物品可以交易

特洛伊的世界离不开交互。 你希望人们通过交换各自的物品来达成交易。

请编写函数 exchange_artifacts,它不返回任何值,接收两个神器的智能指针,用来交换物品。 记住,unique_ptr 是不能复制的。 函数参数中的用法也是如此。 请改用 unique_ptr 的引用。

human uchiha{};
give_new_artifact(uchiha, "konoha headband");
human uzumaki{};
give_new_artifact(uzumaki, "forehead protector");

exchange_artifacts(uchiha.possession, uzumaki.possession);

uchiha.possession->name;
// "forehead protector"
uzumaki.possession->name;
// "konoha headband"

4. 把能力赋予人们

特洛伊最令人兴奋的地方,是人们可能掌握的特殊能力。 有些人能凭意念熔炼钢铁,还有些人能在夜晚瞬间治愈任何伤口。

请定义函数 manifest_power,它不返回任何值,但接收一个 human 的引用和一个 string。 它应当用这个 string 新建一个 power 对象,并相应地设置该 human 的 own_power 指针。 这个函数不应返回任何值。

human eleven {};
manifest_power(eleven, "psychokinesis");

eleven.own_power->effect;
// "psychokinesis"

5. 使用能力

再强大的能力,不能用又有什么用呢。 你的模型重点关注人类,所以你想追踪能力的影响。

请编写一个 void 函数 use_power,它接收两个人类的引用。 第一个人是施法者,第二个代表目标。 目标的 influenced_by 指针应当指向施法者的能力。

为简单起见,人类只能受到一种能力的影响。 即使施法者不复存在,这种影响也会保留下来。

human pamela_isley{};
manifest_power(pamela_isley, "control pheromones");

human count_vertigo{};
use_power(pamela_isley, count_vertigo);
count_vertigo.influenced_by->effect;
// "control pheromones"

6. 留意能力的强度

在你的模拟中,某些能力作用于多个人时,会失去效力,或触发特定效果。 你想追踪与每种能力相连的人数。

请定义函数 power_intensity,它接收一个人,并以 int 的形式返回其能力的强度。 如果这个人没有能力,返回值应为 0。 否则,强度应当反映施法者以及当前所有受到影响的人。

human jean_grey{};
manifest_power(jean_grey, "uplifting personality");

human scott{};
human logan{};
human ororo{};

use_power(jean_grey, ororo);
use_power(jean_grey, logan);
use_power(jean_grey, scott);

power_intensity(jean_grey);
// 4
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
C++ Exercism

准备好开始 特洛伊之力 了吗?

注册 Exercism,借助 19 个概念100 个练习 和真人导师指导,学习并掌握 C++,全部免费。