简介
与许多语言一样,C++ 具有指针。它与已学过的引用类似,但更接近计算机内部的工作方式。指针是保存对象地址的变量,可用来间接访问对象,支持动态内存管理与高效的数据操作。
初学时指针可能显得神秘,熟悉后会发现其基本机制十分直接。
指针是 C++ 的重要部分,请花时间理解。本节介绍的基础形式也称原始指针。现代 C++ 还提供智能指针,而原始指针本身不会自动管理资源,需要程序员妥善处理。
在深入语法前,先理解用途:指针可以把对象地址交给程序其他部分,主要具有以下特点:
- 与引用类似,可以避免复制,减少资源占用。
- 与引用不同,可以重新指向另一个对象。
- 可以保存空值,表示当前不指向任何对象。
基本语法
声明指针时,指定目标数据类型,再写星号 * 和指针名称。局部原始指针不会因为声明而自动获得可用地址;未初始化时可能具有不确定值。不能依赖某个编译器似乎会将其置空的表现。应显式初始化,并在需要时检查非空,再使用它。
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。