传感器与指针

概念学习C++17

简介

与许多语言一样,C++ 具有指针。它与已学过的引用类似,但更接近计算机内部的工作方式。指针是保存对象地址的变量,可用来间接访问对象,支持动态内存管理与高效的数据操作。

初学时指针可能显得神秘,熟悉后会发现其基本机制十分直接。

指针是 C++ 的重要部分,请花时间理解。本节介绍的基础形式也称原始指针。现代 C++ 还提供智能指针,而原始指针本身不会自动管理资源,需要程序员妥善处理。

在深入语法前,先理解用途:指针可以把对象地址交给程序其他部分,主要具有以下特点:

  1. 与引用类似,可以避免复制,减少资源占用。
  2. 与引用不同,可以重新指向另一个对象。
  3. 可以保存空值,表示当前不指向任何对象。

基本语法

声明指针时,指定目标数据类型,再写星号 * 和指针名称。局部原始指针不会因为声明而自动获得可用地址;未初始化时可能具有不确定值。不能依赖某个编译器似乎会将其置空的表现。应显式初始化,并在需要时检查非空,再使用它。

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);

注意:未定义行为 不谨慎的指针运算可能引发未定义行为,表现为错误结果、崩溃,甚至安全漏洞。数值与边界错误也可能造成严重后果,例如 1996 年阿丽亚娜 5 号首次发射事故涉及将 64 位浮点数转换为 16 位有符号整数时的异常。该事故并非 C++ 指针案例,但说明了边界与转换检查的重要性。

通过指针访问成员

-> 用于通过指向对象的指针访问其成员,是一种简写。例如,若 ptr 指向具有成员 x 的对象,可写 ptr->x,代替 (*ptr).x。这样可以减少冗余,提高可读性。

下面的简短示例定义 Superhero 结构体及 superpower 成员。主函数创建指向神奇女侠对象的 dianaPrince 指针,并使用 -> 访问她标志性的真言套索能力。

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 基金会仅存的资深成员之一。这个秘密组织几十年来一直对抗柱之男等古老威胁。你长期维护着混合使用前沿技术与老旧库的技术系统。

最近,追踪柱之男活动的传感器出现故障。系统依赖无法升级的旧 C++ 库。你的任务是使用基于原始指针的旧接口,实现四个监控传感器活动的核心函数。

作为现代 C++ 工程师,你可能更希望使用智能指针,但旧代码要求兼容原有方式。谨慎处理这些指针,人类的命运或许就掌握在你手中。

注意 传感器读数可能很大,因此练习提供了实际库所用结构体的简化版本。相关代码已经写在头文件中。

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

1. 检查传感器连接:connection_check

首先确认传感器连接正常,避免断开的传感器产生误报。编写 connection_check,检查传感器指针是否为 nullptr。

  • 接收指向 pillar_men_sensor 结构体的指针。
  • 非空时返回 true,否则返回 false。
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false

2. 累计传感器活动:activity_counter

柱之男潜伏在暗处,需要汇总传感器是否检测到活动。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,若为空则返回 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_light_heuristic,该函数接收数据容器的地址,并按阈值返回结果。它是正式系统中复杂逻辑的简化版本,请不要修改接口。

在 speedywagon 命名空间中定义 uv_alarm:

  • 参数是指向 pillar_men_sensor 的指针。
  • 指针为空时返回 false。
  • 调用 uv_light_heuristic,传入传感器 data 数组的地址。
  • 若返回值大于 sensor->activity,返回 true,否则返回 false。
需要一点帮助?查看分步提示

提示

通用提示

  • 留意 * 解引用和 -> 成员访问语法。
  • 必要时先检查 nullptr,避免访问无效内存。
  • 可以先补齐代码骨架,再运行测试查看结果。
  • 测试使用的所有函数都必须在头文件中声明。
  • 程序崩溃或表现异常时,检查是否解引用了空指针,以及是否正确处理 nullptr。
  • 指针运算必须保持在传感器数组的合法范围内,越界可能导致内存问题。

1. 检查连接

  • 核心是判断指针是否为空。
  • 可以使用 != 比较。
  • nullptr 表示没有指向有效对象,思考检查结果是否符合这一含义。

2. 累计活动

  • 需要遍历传感器数组。数组在此场景中可转换为指向首元素的指针。
  • 使用 sensor_array + i 访问下标为 i 的传感器。
  • 使用 -> 访问结构体成员。

示例

int sum = (sensor_array + i)->activity; // Access activity using pointer arithmetic

3. 控制警报

  • 访问传感器之前先检查指针是否为空。
  • 用 -> 访问 pillar_men_sensor 的 activity。
  • 活动值为 0 时是否应报警?

4. 检查紫外线异常

  • 使用 & 将数据数组的地址传给 uv_light_heuristic。
  • 访问传感器数据前检查空指针。
  • 比较函数返回值和传感器的 activity,决定是否报警。