4. 指针和引用

指针基础

1. 指针概念

1
2
3
4
5
6
7
8
9
// 指针声明和初始化
int num = 42;
int* ptr = #               // 指向num的指针
int* null_ptr = nullptr;       // 空指针(C++11)

// 指针操作
std::cout << ptr << "\n";      // 打印地址
std::cout << *ptr << "\n";     // 解引用,打印42
*ptr = 100;                    // 通过指针修改值

2. 指针算术

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int arr[] = {1, 2, 3, 4, 5};
int* p = arr;                  // 指向数组首元素

// 指针移动
p++;                          // 移动到下一个元素
p--;                          // 移动到上一个元素
p += 2;                       // 向前移动2个元素
p -= 2;                       // 向后移动2个元素

// 指针比较
bool isFirst = (p == arr);    // 是否指向首元素
bool isLast = (p == &arr[4]); // 是否指向最后元素

3. const和指针

1
2
3
4
5
6
7
8
// const指针的不同形式
const int* p1 = &num;         // 指向常量的指针(不能通过指针修改值)
int* const p2 = &num;         // 常量指针(不能修改指针指向)
const int* const p3 = &num;   // 指向常量的常量指针

// 错误用法示例
*p1 = 100;                    // 错误:不能修改const int*指向的值
p2 = &other_num;             // 错误:不能修改const指针的指向

引用基础

1. 引用概念

1
2
3
4
5
6
7
8
// 引用声明和初始化
int num = 42;
int& ref = num;               // ref是num的引用
ref = 100;                    // 修改num的值

// 引用必须初始化
int& invalid_ref;             // 错误:引用必须初始化
int& ref2 = ref;              // 正确:引用的引用

2. 引用vs指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void modifyByPtr(int* ptr) {
    if (ptr) {                // 需要检查空指针
        *ptr = 100;
    }
}

void modifyByRef(int& ref) {
    ref = 100;                // 不需要检查,引用总是有效
}

// 使用示例
int num = 42;
modifyByPtr(&num);            // 传递地址
modifyByRef(num);             // 直接传递变量

3. const引用

1
2
3
4
5
6
7
8
// const引用
const int& cref = num;        // 常量引用
// cref = 100;               // 错误:不能通过常量引用修改值

// 常用于函数参数
void printValue(const int& val) {
    std::cout << val << "\n";
}

动态内存管理

1. new和delete

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 单个对象
int* p1 = new int(42);        // 分配并初始化
delete p1;                    // 释放内存

// 数组
int* arr = new int[5]{1,2,3,4,5};  // 分配并初始化数组
delete[] arr;                 // 释放数组内存

// 二维数组
int rows = 3, cols = 4;
int** matrix = new int*[rows];
for (int i = 0; i < rows; ++i) {
    matrix[i] = new int[cols];
}

// 释放二维数组
for (int i = 0; i < rows; ++i) {
    delete[] matrix[i];
}
delete[] matrix;

2. 内存泄漏防范

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 常见的内存泄漏情况
void leakMemory() {
    int* p = new int(42);
    // 忘记delete
    return;                   // 内存泄漏!
}

// 使用智能指针防止泄漏
#include <memory>

void noLeak() {
    std::unique_ptr<int> p(new int(42));
    // 自动释放内存
}

智能指针

1. unique_ptr

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <memory>

// 创建和使用
std::unique_ptr<int> up(new int(42));
std::unique_ptr<int> up2 = std::make_unique<int>(42);  // C++14

// 访问数据
std::cout << *up << "\n";     // 解引用
up.reset(new int(100));       // 重置指针
int* raw = up.get();          // 获取原始指针

// 所有权转移
std::unique_ptr<int> up3 = std::move(up);  // up变为nullptr

2. shared_ptr

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <memory>

// 创建和使用
std::shared_ptr<int> sp1(new int(42));
std::shared_ptr<int> sp2 = std::make_shared<int>(42);

// 共享所有权
std::shared_ptr<int> sp3 = sp1;  // 引用计数加1
std::cout << sp1.use_count() << "\n";  // 打印2

// 重置和检查
sp1.reset();                  // 放弃所有权
if (sp1) {                    // 检查是否为空
    std::cout << *sp1 << "\n";
}

3. weak_ptr

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <memory>

// 创建和使用
std::shared_ptr<int> sp(new int(42));
std::weak_ptr<int> wp = sp;   // 不增加引用计数

// 使用weak_ptr
if (auto spt = wp.lock()) {   // 获取shared_ptr
    std::cout << *spt << "\n";
} else {
    std::cout << "对象已释放\n";
}

常见陷阱和最佳实践

1. 悬垂指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 错误示例
int* dangling() {
    int local = 42;
    return &local;            // 错误:返回局部变量地址
}

// 正确做法
std::unique_ptr<int> safe() {
    return std::make_unique<int>(42);
}

2. 循环引用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct Node {
    std::shared_ptr<Node> next;
    // std::weak_ptr<Node> prev;  // 使用weak_ptr避免循环引用
    std::shared_ptr<Node> prev;   // 可能导致循环引用
};

// 避免循环引用
void createNodes() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();
    
    node1->next = node2;
    node2->prev = node1;      // 形成循环引用,内存永远不会释放
}

3. 指针安全使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 指针检查
void safePointerUse(int* ptr) {
    if (!ptr) {
        throw std::invalid_argument("空指针");
    }
    
    // 使用指针
    *ptr = 42;
}

// RAII原则
class Resource {
    int* data;
public:
    Resource() : data(new int(42)) {}
    ~Resource() { delete data; }
    
    // 禁止拷贝
    Resource(const Resource&) = delete;
    Resource& operator=(const Resource&) = delete;
    
    // 允许移动
    Resource(Resource&& other) noexcept : data(other.data) {
        other.data = nullptr;
    }
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            delete data;
            data = other.data;
            other.data = nullptr;
        }
        return *this;
    }
};

57.12k 字
43篇文章