C++ 指针
C++ 指针和内存管理
Section titled “C++ 指针和内存管理”指针是 C++ 的核心特性,提供了直接的内存访问,并实现了动态内存分配和高效数据结构等功能。然而,原始指针(raw pointers)需要小心处理,以避免常见的陷阱,如内存泄漏(memory leaks)和悬空指针(dangling pointers)。
理解内存地址
Section titled “理解内存地址”存储在内存中的每个变量都有一个唯一的内存地址。您可以使用取地址运算符(&)获取变量的地址。
#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: 0x7ffc4f3b1a14Address of playerName: 0x7ffc4f3b1a20什么是指针?
Section titled “什么是指针?”指针是一种专门设计用于存储内存地址的变量。它“指向”另一块数据的位置。
您使用星号(*)符号在指针指向的数据类型和变量名之间声明指针:
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)至关重要。
涉及指针的主要操作有:
- 赋值 (Assignment): 使用取地址运算符(
&)将变量的地址存储到指针中。 - 解引用 (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: 95Address stored in scorePtr: 0x7ffeeabc1a0cAddress of score variable: 0x7ffeeabc1a0cValue pointed to by scorePtr (*scorePtr): 95New value of score (after *scorePtr = 100): 100空指针 (nullptr)
Section titled “空指针 (nullptr)”不指向任何有效内存位置的指针称为空指针(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。
动态内存分配 (new 和 delete)
Section titled “动态内存分配 (new 和 delete)”指针对于动态内存分配(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 // 对于单个对象使用 deletedelete[] 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++ 最佳实践:智能指针
Section titled “现代 C++ 最佳实践:智能指针”为了安全地自动管理动态内存,现代 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 AcquiredUsing ResourceResource Released----Resource AcquiredUse count: 2Using ResourceUse count after inner scope: 1Resource Released指南: 在现代 C++ 中,对于管理动态内存所有权,优先使用智能指针(std::unique_ptr、std::shared_ptr)而不是原始指针(new/delete)。
其他指针概念
Section titled “其他指针概念”| 概念 (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)、标准库容器和智能指针。