Skip to content

C++ 指针

指针是 C++ 的核心特性,提供了直接的内存访问,并实现了动态内存分配和高效数据结构等功能。然而,原始指针(raw pointers)需要小心处理,以避免常见的陷阱,如内存泄漏(memory leaks)和悬空指针(dangling pointers)。

存储在内存中的每个变量都有一个唯一的内存地址。您可以使用取地址运算符(&)获取变量的地址。

#include <iostream>
#include <string>
int main() {
int score = 100;
std::string playerName = "Alice";
// Print the memory addresses of the variables // 打印变量的内存地址
// The exact address values will vary each time you run the program. // 每次运行程序时,具体的地址值会不同。
std::cout << "Address of score: " << &score << std::endl;
std::cout << "Address of playerName: " << &playerName << std::endl;
// Note: For std::string, &playerName gives the address of the string object itself, // 注意:对于 std::string,&playerName 给出了 string 对象本身的地址,
// not necessarily the start of the character data it manages internally. // 不一定是它内部管理的字符数据的起始地址。
return 0;
}

示例输出(地址会有所不同):

Address of score: 0x7ffc4f3b1a14
Address of playerName: 0x7ffc4f3b1a20

指针是一种专门设计用于存储内存地址的变量。它“指向”另一块数据的位置。

您使用星号(*)符号在指针指向的数据类型和变量名之间声明指针:

type* pointerName;

示例:

int* intPtr; // Declares a pointer to an integer // 声明一个指向整数的指针
double* doublePtr; // Declares a pointer to a double // 声明一个指向双精度浮点数的指针
std::string* strPtr; // Declares a pointer to a std::string object // 声明一个指向 std::string 对象的指针
char* charPtr; // Declares a pointer to a character // 声明一个指向字符的指针
// It's good practice to initialize pointers: // 初始化指针是一个好习惯:
int* safeIntPtr = nullptr; // Initialize to nullptr (the null pointer value in modern C++) // 初始化为 nullptr(现代 C++ 中的空指针值)

指定的数据类型(int、double 等)告诉编译器指针将保存的地址处是什么类型的数据。这对于解引用(dereferencing)至关重要。

涉及指针的主要操作有:

  1. 赋值 (Assignment): 使用取地址运算符(&)将变量的地址存储到指针中。
  2. 解引用 (Dereferencing): 使用解引用运算符(*)访问存储在指针中的地址处的值。
#include <iostream>
#include <string>
int main() {
int score = 95;
int* scorePtr = nullptr; // Declare and initialize pointer // 声明并初始化指针
// 1. Assignment: Store the address of 'score' in 'scorePtr' // 1. 赋值:将 'score' 的地址存储到 'scorePtr' 中
scorePtr = &score;
std::cout << "Value of score: " << score << std::endl; // 'score' 的值
std::cout << "Address stored in scorePtr: " << scorePtr << std::endl; // Prints the memory address // 打印内存地址
std::cout << "Address of score variable: " << &score << std::endl; // Should be the same address // 应该与上一个地址相同
// 2. Dereferencing: Access the value pointed to by scorePtr // 2. 解引用:访问 scorePtr 指向的值
std::cout << "Value pointed to by scorePtr (*scorePtr): " << *scorePtr << std::endl;
// Modify the original variable's value through the pointer // 通过指针修改原始变量的值
*scorePtr = 100;
std::cout << "New value of score (after *scorePtr = 100): " << score << std::endl; // 'score' 的新值
return 0;
}

输出(地址会有所不同):

Value of score: 95
Address stored in scorePtr: 0x7ffeeabc1a0c
Address of score variable: 0x7ffeeabc1a0c
Value pointed to by scorePtr (*scorePtr): 95
New value of score (after *scorePtr = 100): 100

不指向任何有效内存位置的指针称为空指针(null pointer)。在现代 C++(C++11 及更高版本)中,应使用关键字 nullptr 表示空指针。它是类型安全的,优先于旧的 NULL 宏或字面值 0。

int* ptr1 = nullptr;
// double* ptr2 = NULL; // Old style, avoid // 旧风格,避免使用
// char* ptr3 = 0; // Old style, avoid // 旧风格,避免使用
if (ptr1 == nullptr) {
std::cout << "ptr1 is a null pointer." << std::endl; // ptr1 是一个空指针。
}
// Dereferencing a null pointer leads to undefined behavior (often a crash!) // 解引用空指针会导致未定义行为(通常是崩溃!)
// *ptr1 = 10; // DANGER! CRASH! // 危险!崩溃!

在解引用指针之前,如果它可能为空,务必检查它是否是 nullptr。

指针对于动态内存分配(dynamic memory allocation)至关重要,动态内存分配是指在运行时(从堆 heap 或自由存储区 free store)向操作系统请求内存,而不是在栈 stack 上自动分配。

new 运算符分配内存并返回指向它的指针。delete 运算符释放使用 new 分配的内存。对于使用 new[] 分配的数组,必须使用 delete[]。

// Allocate a single integer on the heap // 在堆上分配一个整数
int* heapIntPtr = new int;
*heapIntPtr = 42;
// Allocate an array of 10 doubles on the heap // 在堆上分配一个包含 10 个双精度浮点数的数组
double* heapDoubleArray = new double[10];
heapDoubleArray[0] = 3.14;
std::cout << "Heap integer: " << *heapIntPtr << std::endl; // 堆上整数:
std::cout << "First element of heap array: " << heapDoubleArray[0] << std::endl; // 堆上数组的第一个元素:
// CRUCIAL: Release the allocated memory // 至关重要:释放分配的内存
delete heapIntPtr; // Use delete for single objects // 对于单个对象使用 delete
delete[] heapDoubleArray; // Use delete[] for arrays // 对于数组使用 delete[]
heapIntPtr = nullptr; // Good practice to nullify pointers after delete // delete 后将指针置为 nullptr 是好习惯
heapDoubleArray = nullptr;

问题: 使用 new 和 delete 手动管理内存容易出错。忘记使用 delete 会导致内存泄漏。在使用 delete 后的内存(悬空指针)会导致未定义行为(undefined behavior)。错误地使用 delete 代替 delete[](反之亦然)也是未定义行为。

为了安全地自动管理动态内存,现代 C++ 强烈提倡使用智能指针(smart pointers),智能指针在 <memory> 头文件中定义。它们遵循 RAII(Resource Acquisition Is Initialization,资源获取即初始化)原则,在智能指针超出作用域时自动释放所管理的内存。

  • std::unique_ptr: 表示对动态分配对象的独占所有权(exclusive ownership)。同一时间只有一个 unique_ptr 可以拥有该对象。当 unique_ptr 被销毁(例如,超出作用域)时,它会自动删除所管理的对象。它轻量级,默认是首选。
  • std::shared_ptr: 表示共享所有权(shared ownership)。多个 shared_ptr 实例可以指向同一个对象。对象只有在指向它的最后一个 shared_ptr 被销毁时才会被删除。它内部使用引用计数(reference counting)。
  • std::weak_ptr: 一个非拥有的指针,它持有对 std::shared_ptr 管理的对象的“弱”引用。它不影响引用计数,可用于打破 shared_ptr 实例之间的循环依赖(circular dependencies)。
#include <iostream>
#include <memory> // Required for smart pointers // 智能指针所需头文件
#include <vector>
class MyResource {
public:
MyResource() { std::cout << "Resource Acquired\n"; } // 构造函数:资源获取
~MyResource() { std::cout << "Resource Released\n"; } // Destructor // 析构函数:资源释放
void use() { std::cout << "Using Resource\n"; } // 使用资源
};
int main() {
// Using unique_ptr (preferred for single ownership) // 使用 unique_ptr(单所有权首选)
{
auto uniqueRes = std::make_unique<MyResource>(); // Preferred way to create unique_ptr // 创建 unique_ptr 的首选方式
uniqueRes->use();
} // uniqueRes goes out of scope here, ~MyResource() is called automatically // uniqueRes 在此处超出作用域,~MyResource() 自动调用
std::cout << "----\n";
// Using shared_ptr (for shared ownership) // 使用 shared_ptr(共享所有权)
std::shared_ptr<MyResource> sharedRes1;
{
auto sharedRes = std::make_shared<MyResource>(); // Preferred way to create shared_ptr // 创建 shared_ptr 的首选方式
sharedRes1 = sharedRes; // Both pointers now share ownership // 两个指针现在共享所有权
std::cout << "Use count: " << sharedRes1.use_count() << std::endl; // 引用计数:
sharedRes->use();
} // sharedRes goes out of scope, but sharedRes1 still exists // sharedRes 超出作用域,但 sharedRes1 仍然存在
std::cout << "Use count after inner scope: " << sharedRes1.use_count() << std::endl; // 内部作用域后的引用计数:
} // sharedRes1 goes out of scope here, resource is released // sharedRes1 在此处超出作用域,资源被释放

输出:

Resource Acquired
Using Resource
Resource Released
----
Resource Acquired
Use count: 2
Using Resource
Use count after inner scope: 1
Resource Released

指南: 在现代 C++ 中,对于管理动态内存所有权,优先使用智能指针(std::unique_ptr、std::shared_ptr)而不是原始指针(new/delete)。

概念 (Concept)描述 (Description)
指针算术 (Pointer Arithmetic)可以对指针执行基本的算术运算(+、-、++、—),通常用于数组。ptr + n 将指针向前移动 n 个元素(考虑指向类型的尺寸)。使用时要小心;对于容器,优先使用迭代器(iterators)或范围 for 循环(range-based loops)。
指针与数组 (Pointers and Arrays)在表达式中,数组名通常会退化(decay)为指向其第一个元素的指针。int arr[10]; int* p = arr; 使 p 指向 arr[0]。*(p + i) 等效于 arr[i]。
指针数组 (Array of Pointers)可以声明一个数组,其中每个元素都是指针:std::string* strArray[5];
指向指针的指针 (Pointer to Pointer)一个指针可以指向另一个指针:int x = 10; int* ptr1 = &x; int** ptr2 = &ptr1;。**ptr2 访问 x 的值。
指针与函数 (Pointers and Functions)可以将指针作为参数传递给函数,以允许函数修改原始变量(按指针传递 pass-by-pointer)。从函数返回指针时需要小心,特别是对于动态分配的内存(所有权转移 Ownership transfer 至关重要——智能指针有帮助)。
函数指针 (Function Pointers)指针也可以存储函数的地址,允许将函数作为参数传递或存储起来。
const 与指针 (const and Pointers)const 可以应用于指向的数据(const int* p),或指针本身(int* const p),或两者(const int* const p)。理解指针与 const 的正确性(Const-correctness)至关重要。

尽管原始指针功能强大,但需要严格遵守规则。现代 C++ 实践强调在可能的情况下使用更安全的替代方案,如引用(references)、标准库容器和智能指针。