指针

指针 属于 C++

1 个练习

关于 指针

和许多其他语言一样,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。 在处理指向对象的指针时,这个运算符能增强代码的可读性,也减少了冗长。

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

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;

// Memory cleanup:
delete dianaPrince;

指针与引用

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

Advanced

动态内存分配与空指针

在 C++ 中,new用于在运行时为对象在堆上动态分配内存。 它返回一个指向所分配内存地址的指针,让对象的生命周期不必绑定到特定的作用域。 不过,务必要让new与delete配对使用,以便正确释放内存。 动态内存分配应谨慎使用,以防出现内存泄漏、访问已释放内存等常见问题。

指针还可以被设为一个特殊的值,叫做_空指针_,表示它们不指向有效的地址。 空指针通常用于在为指针赋予有效值之前对其进行初始化。

// There is a dark mark bearer, but who is it?
std::string* darkMarkBearer{nullptr};
// It is Bellatrix!
darkMarkBearer = new std::string{"Bellatrix Lestrange"};
// She has been defeated
delete darkMarkBearer;
// Reset pointer to null, symbolizing there is no more darkMarkBearer at the moment
darkMarkBearer = nullptr;

一个指针要么持有有效对象的地址,要么被设为nullptr。 这样你只需检查指针是否为空,就可以假定任何非空指针都是有效的。 当一个对象被销毁时,所有指向它的指针都会变成_悬空_状态(它们不会被自动设为nullptr)。 你有责任发现这些情况,并确保随后把这些指针都设为nullptr。

在较旧的代码中,你可能会遇到nullptr的两种替代写法。 首先,字面量0会被特别解释为指针的空值,不过这也是整数字面量唯一可以赋给指针的场景。 其次,从 C 继承来、定义在<cstddef>头文件中的preprocessor macro NULL,是空指针的另一种表示,不过在现代 C++ 代码中不太常见。

通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 指针

练习已锁定

再解锁 1 个练习即可练习 指针