轨道
/
C++
C++
/
练习
/
Speedywagon 基金会
Speedywagon 基金会

Speedywagon 基金会

学习练习

简介

和许多其他语言一样,C++ 也有_指针_。 你已经知道_引用_和_指针_很相似,但不妨把它们看作更贴近计算机内部运作的一个层次。 _指针_是保存对象地址的变量。 用它们可以直接与对象交互,从而在 C++ 中实现动态内存分配和高效的数据处理。

如果你是第一次接触_指针_,可能会觉得它有点神秘,但一旦用熟了,就会发现它其实很直白。

它是 C++ 中至关重要的一部分,所以花点时间真正理解它。 这个知识点里介绍的最基础的版本也叫_笨指针_或_原始指针_。 现代 C++ 中还有_智能指针_,而这里的基础类型一点也不智能,所有工作都得你手动处理。

在深入细节之前,值得先理解_指针_的用途。 _指针_可以把对象的地址共享给程序的其他部分,这在两个主要方面很有用:

  1. 和_引用_一样,指针可以避免拷贝,有助于减少程序的资源占用。
  2. 与_引用_不同,指针可以重新指向别的对象。
  3. 指针还可以指向空值,表示它当前没有指向任何对象。

通用语法

在 C++ 中声明指针,需要指定指针所指向的数据类型,后面跟一个星号(*)和指针的名字。 声明指针时,它不会自动初始化。 如果没有显式赋值,指针通常保存着一个不确定的值,常被称为“垃圾地址”。 虽然有些编译器可能会把指针初始化为nullptr,但并非所有编译器都保证这一行为,所以千万不要依赖它。 为了避免潜在问题,最佳做法是显式地初始化原始指针,并在使用前确认它不为空。

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

要把一个变量的地址赋给指针,可以使用取地址运算符(&)。 解引用指针则要使用_间接运算符_(*)。

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

注意:解引用必须显式写出,而_引用_用起来就像别名。

指针运算

_指针运算_让你可以对指针做算术操作,在配合数组使用时尤其有用。 给指针加上一个整数,它就会指向另一个元素。

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

在 C++ 中,指针运算如果处理不当,很容易导致__未定义行为__。 未定义行为可能表现为意料之外的运行结果、程序崩溃,甚至安全漏洞。 未定义行为的后果有一个臭名昭著的例子:1996 年的阿丽亚娜 5 号火箭爆炸。当时,把一个 64 位浮点数转换成 16 位有符号整数所引发的软件异常,最终导致了灾难性的失败。

访问成员变量

在 C++ 中,->运算符用于通过指向某个对象的指针来访问该对象的成员。 它是一种简写方式,简化了访问指针所指对象成员的操作。 例如,如果ptr是指向某个对象的指针,而该对象有一个成员变量x,那么你可以直接用ptr->x,而不必写(*ptr).x。 这个运算符提高了代码的可读性,也让处理对象指针时不再那么冗长。

下面是一个简短的例子,其中有一个名为Superhero的_结构体_,它带有一个成员变量superpower。 主函数创建了一个指向Superhero对象的指针dianaPrince(代表神奇女侠)。 这里用->运算符访问成员变量superpower,展示了神奇女侠标志性的“Lasso of Truth”。

struct Superhero {
    std::string superpower;
};
Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";
// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

指针与引用

指针和引用都能间接访问对象,但它们的能力和安全性考量不同。 指针可以灵活地更换所指向的对象,也可以被赋为空值。 不过,这种灵活性也带来了风险,比如解引用空指针,或者产生悬空指针。 而引用不能为空,并且在创建时就绑定到有效的对象上,因此避免了这些风险。 正因如此,除非确实需要指针提供的额外功能,否则应该优先使用引用。

说明

欢迎你,工程师! 你是 Speedywagon Foundation 最后几位资深成员之一,这是一个秘密组织,几十年来一直在对抗像 Pillar Men 这样的古老威胁。 在这项工作中,你花了数年时间维护 Speedywagon Foundation 的技术系统,这些系统由前沿技术和老旧的库混合搭建而成。

然而,最近用来追踪 Pillar Men 活动的传感器出了故障。Speedywagon Foundation 的系统很老旧,代码要与一个无法更新的遗留 C++ 库交互。你的任务是实现四个核心函数,用一套老式的、基于指针的库来监控 Pillar Men 传感器的活动。

作为一名现代 C++ 工程师,你更愿意使用智能指针,但可惜,遗留代码要求你尊重老办法。人类的命运可能就系于这些指针之上,所以务必谨慎,愿 Hamon 能量指引你。

Note

由于传感器读数可能非常大,我们提供了一个模拟用的 struct,实际库中就会用到它。相关代码已经在头文件中为你实现好了。

struct pillar_men_sensor {
    int activity{};
    std::string location{};
    std::vector<int> data{};
};

1. 检查传感器连接(connection_check)

你的第一个任务是确保 Pillar Men 传感器连接正常。不能让传感器断连引发误报。 你将编写connection_check函数,它通过检查nullptr来判断传感器指针是否有效。

  • 定义一个函数,接受一个指向pillar_men_sensor struct 的指针。
  • 如果传感器指针不为空,函数应返回true,否则返回false。
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false

2. 统计传感器的活动量(activity_counter)

Pillar Men 潜伏在暗处,我们需要知道传感器是否检测到了任何活动。 你将编写activity_counter函数,它接受一个传感器数组,以及一个表示数组中传感器数量的容量值。

  • 定义一个函数,接受指向数组第一个元素的指针以及数组的容量。
  • 使用指针运算遍历传感器数组,累加活动读数。
  • 返回累加后的活动量。
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123

3. 报警控制(alarm_control)

除非真的存在危险,否则并非每个传感器都应该触发报警。 alarm_control函数确保只有当传感器的活动量大于 0 时才触发报警。 这个函数还应检查空传感器,以防止系统崩溃。

  • 定义一个函数,接受指向pillar_men_sensor的指针。
  • 函数应首先检查传感器是否为nullptr。如果传感器是nullptr,返回false。
  • 如果传感器有效且其活动量大于 0,返回true;否则返回false。
pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true

4. 用uv_alarm函数检查数据中的异常

在这个任务中,你将实现uv_alarm函数,根据紫外线照射水平和传感器活动量来判断是否应该触发报警。 uv_alarm函数应使用提供的uv_light_heuristic函数,后者对一个数据向量进行处理,并根据特定的阈值返回一个值。 这是生产环境中将要运行的复杂代码的模拟版本,请不要修改接口。

在speedywagon命名空间中定义uv_alarm函数。它应该:

  • 接受一个指向pillar_men_sensor struct 的指针作为形参。
  • 如果传感器指针为空,返回false。
  • 调用uv_light_heuristic函数,并传入传感器data数组的地址。
  • 如果uv_light_heuristic返回的值大于sensor->activity的活动水平,返回true,否则返回false。
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
C++ Exercism

准备好开始 Speedywagon 基金会 了吗?

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