指针与引用是 C++ 中两个重要的概念,虽然它们都可以用来间接访问对象,但在语法、语义和使用方式上有着显著的区别。

基本概念对比

定义和初始化

#include <iostream>
 
int main() {
    int x = 42;
    int y = 100;
    
    // 指针的声明和初始化
    int* ptr;           // 可以声明但不初始化
    ptr = &x;           // 后续可以赋值
    int* ptr2 = &x;     // 声明时初始化
    int* ptr3 = nullptr; // 可以初始化为空
    
    // 引用的声明和初始化
    int& ref = x;       // 必须在声明时初始化
    // int& ref2;       // 错误!引用必须初始化
    // int& ref3 = nullptr; // 错误!引用不能为空
    
    std::cout << "指针和引用的基本使用:" << std::endl;
    std::cout << "x = " << x << std::endl;
    std::cout << "通过指针访问: *ptr = " << *ptr << std::endl;
    std::cout << "通过引用访问: ref = " << ref << std::endl;
    
    return 0;
}

内存占用和地址

#include <iostream>
 
int main() {
    int x = 42;
    int* ptr = &x;
    int& ref = x;
    
    std::cout << "内存和地址分析:" << std::endl;
    std::cout << "sizeof(int) = " << sizeof(int) << " 字节" << std::endl;
    std::cout << "sizeof(ptr) = " << sizeof(ptr) << " 字节" << std::endl;  // 指针大小
    std::cout << "sizeof(ref) = " << sizeof(ref) << " 字节" << std::endl;  // 与原变量相同
    
    std::cout << "\n地址信息:" << std::endl;
    std::cout << "&x = " << &x << std::endl;      // x 的地址
    std::cout << "&ptr = " << &ptr << std::endl;  // 指针变量的地址
    std::cout << "ptr = " << ptr << std::endl;    // 指针存储的地址(x的地址)
    std::cout << "&ref = " << &ref << std::endl;  // 引用的地址(实际是x的地址)
    
    // 验证引用和原变量地址相同
    std::cout << "\n地址比较:" << std::endl;
    std::cout << "(&x == &ref): " << (&x == &ref) << std::endl;  // true
    std::cout << "(&x == ptr): " << (&x == ptr) << std::endl;    // true
    std::cout << "(&x == &ptr): " << (&x == &ptr) << std::endl;  // false
    
    return 0;
}

语法差异

访问和修改

#include <iostream>
 
int main() {
    int x = 10;
    int y = 20;
    
    int* ptr = &x;
    int& ref = x;
    
    std::cout << "=== 访问方式对比 ===" << std::endl;
    
    // 直接访问
    std::cout << "直接访问 x: " << x << std::endl;
    std::cout << "通过指针访问: *ptr = " << *ptr << std::endl;  // 需要解引用
    std::cout << "通过引用访问: ref = " << ref << std::endl;    // 直接使用
    
    // 修改值
    *ptr = 30;  // 通过指针修改(需要解引用)
    std::cout << "通过指针修改后 x = " << x << std::endl;
    
    ref = 40;   // 通过引用修改(直接赋值)
    std::cout << "通过引用修改后 x = " << x << std::endl;
    
    // 重新指向/绑定
    ptr = &y;   // 指针可以重新指向其他变量
    std::cout << "指针重新指向后: *ptr = " << *ptr << std::endl;
    
    ref = y;    // 这不是重新绑定,而是将y的值赋给x
    std::cout << "引用'重新绑定'后 x = " << x << ", y = " << y << std::endl;
    
    return 0;
}

算术运算

#include <iostream>
 
int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    
    int* ptr = arr;        // 指向数组第一个元素
    int& ref = arr[0];     // 引用数组第一个元素
    
    std::cout << "=== 算术运算对比 ===" << std::endl;
    
    // 指针算术运算
    std::cout << "指针算术运算:" << std::endl;
    std::cout << "*ptr = " << *ptr << std::endl;           // 10
    std::cout << "*(ptr + 1) = " << *(ptr + 1) << std::endl; // 20
    std::cout << "*(ptr + 2) = " << *(ptr + 2) << std::endl; // 30
    
    ptr++;  // 指针可以递增
    std::cout << "ptr++ 后: *ptr = " << *ptr << std::endl;  // 20
    
    ptr += 2;  // 指针可以加法运算
    std::cout << "ptr += 2 后: *ptr = " << *ptr << std::endl; // 40
    
    // 引用不支持算术运算
    std::cout << "\n引用访问:" << std::endl;
    std::cout << "ref = " << ref << std::endl;  // 10
    // ref++;     // 错误!这是对值的递增,不是对引用的运算
    // ref += 2;  // 错误!这是对值的运算
    
    // 引用只能通过数组下标或重新绑定来访问其他元素
    int& ref2 = arr[2];
    std::cout << "ref2 = " << ref2 << std::endl;  // 30
    
    return 0;
}

功能差异

空值处理

#include <iostream>
 
// 使用指针的函数(需要检查空值)
void processWithPointer(int* ptr) {
    if (ptr != nullptr) {  // 必须检查空指针
        std::cout << "指针处理: " << *ptr << std::endl;
        *ptr *= 2;
    } else {
        std::cout << "收到空指针" << std::endl;
    }
}
 
// 使用引用的函数(不需要检查空值)
void processWithReference(int& ref) {
    // 引用总是有效的,不需要检查
    std::cout << "引用处理: " << ref << std::endl;
    ref *= 2;
}
 
// 返回指针的函数(可能返回空)
int* findValue(int* arr, int size, int target) {
    for (int i = 0; i < size; ++i) {
        if (arr[i] == target) {
            return &arr[i];  // 找到了,返回地址
        }
    }
    return nullptr;  // 没找到,返回空指针
}
 
// 返回引用的函数(必须返回有效引用)
int& getElement(int* arr, int index) {
    // 假设调用者保证 index 有效
    return arr[index];  // 必须返回有效引用
}
 
int main() {
    int x = 10;
    int arr[5] = {1, 2, 3, 4, 5};
    
    std::cout << "=== 空值处理对比 ===" << std::endl;
    
    // 指针可以为空
    processWithPointer(&x);      // 传递有效指针
    processWithPointer(nullptr); // 传递空指针
    
    // 引用不能为空
    processWithReference(x);     // 必须传递有效对象
    // processWithReference(nullptr); // 编译错误!
    
    std::cout << "\n=== 查找函数对比 ===" << std::endl;
    
    // 指针版本可以表示"未找到"
    int* found_ptr = findValue(arr, 5, 3);
    if (found_ptr) {
        std::cout << "找到值: " << *found_ptr << std::endl;
    }
    
    int* not_found_ptr = findValue(arr, 5, 10);
    if (!not_found_ptr) {
        std::cout << "未找到值" << std::endl;
    }
    
    // 引用版本必须返回有效值
    int& element_ref = getElement(arr, 2);
    std::cout << "获取元素: " << element_ref << std::endl;
    
    return 0;
}

重新赋值和绑定

#include <iostream>
 
int main() {
    int a = 10, b = 20, c = 30;
    
    std::cout << "=== 重新赋值和绑定对比 ===" << std::endl;
    
    // 指针可以重新指向不同的对象
    int* ptr = &a;
    std::cout << "初始指针: *ptr = " << *ptr << std::endl;  // 10
    
    ptr = &b;  // 重新指向 b
    std::cout << "重新指向后: *ptr = " << *ptr << std::endl;  // 20
    
    ptr = &c;  // 重新指向 c
    std::cout << "再次重新指向后: *ptr = " << *ptr << std::endl;  // 30
    
    // 引用一旦绑定就不能重新绑定
    int& ref = a;
    std::cout << "\n初始引用: ref = " << ref << std::endl;  // 10
    std::cout << "a = " << a << std::endl;
    
    ref = b;  // 这不是重新绑定,而是将 b 的值赋给 a
    std::cout << "ref = b 后: ref = " << ref << ", a = " << a << std::endl;  // a 变成 20
    
    ref = c;  // 同样是赋值操作
    std::cout << "ref = c 后: ref = " << ref << ", a = " << a << std::endl;  // a 变成 30
    
    // 验证 b 和 c 没有改变
    std::cout << "b = " << b << ", c = " << c << std::endl;  // b=20, c=30
    
    return 0;
}

使用场景对比

函数参数传递

#include <iostream>
#include <vector>
#include <string>
 
class LargeObject {
public:
    std::vector<int> data;
    std::string name;
    
    LargeObject(const std::string& n, size_t size) 
        : name(n), data(size, 42) {}
};
 
// 1. 使用指针:可选参数,可能为空
void processOptional(LargeObject* obj) {
    if (obj) {
        std::cout << "处理对象: " << obj->name 
                  << " (大小: " << obj->data.size() << ")" << std::endl;
    } else {
        std::cout << "没有对象需要处理" << std::endl;
    }
}
 
// 2. 使用引用:必需参数,保证有效
void processRequired(const LargeObject& obj) {
    std::cout << "处理对象: " << obj.name 
              << " (大小: " << obj.data.size() << ")" << std::endl;
}
 
// 3. 修改参数:指针版本
void modifyWithPointer(LargeObject* obj, const std::string& new_name) {
    if (obj) {
        obj->name = new_name;
    }
}
 
// 4. 修改参数:引用版本
void modifyWithReference(LargeObject& obj, const std::string& new_name) {
    obj.name = new_name;
}
 
// 5. 数组处理:指针更灵活
void processArray(int* arr, size_t size) {
    if (arr) {
        for (size_t i = 0; i < size; ++i) {
            std::cout << arr[i] << " ";
        }
        std::cout << std::endl;
    }
}
 
// 6. 容器处理:引用更安全
void processVector(const std::vector<int>& vec) {
    for (int value : vec) {
        std::cout << value << " ";
    }
    std::cout << std::endl;
}
 
int main() {
    LargeObject obj("TestObject", 1000);
    
    std::cout << "=== 可选 vs 必需参数 ===" << std::endl;
    processOptional(&obj);    // 传递对象
    processOptional(nullptr); // 传递空值
    
    processRequired(obj);     // 必须传递有效对象
    // processRequired(nullptr); // 编译错误
    
    std::cout << "\n=== 修改参数 ===" << std::endl;
    std::cout << "修改前: " << obj.name << std::endl;
    
    modifyWithPointer(&obj, "PointerModified");
    std::cout << "指针修改后: " << obj.name << std::endl;
    
    modifyWithReference(obj, "ReferenceModified");
    std::cout << "引用修改后: " << obj.name << std::endl;
    
    std::cout << "\n=== 数组 vs 容器 ===" << std::endl;
    int arr[] = {1, 2, 3, 4, 5};
    processArray(arr, 5);
    processArray(nullptr, 0);  // 可以处理空数组
    
    std::vector<int> vec = {6, 7, 8, 9, 10};
    processVector(vec);
    // processVector(nullptr);  // 编译错误
    
    return 0;
}

数据结构中的应用

#include <iostream>
#include <memory>
 
// 链表节点:使用指针表示可选的下一个节点
struct ListNode {
    int data;
    ListNode* next;  // 可能为空
    
    ListNode(int val) : data(val), next(nullptr) {}
};
 
// 二叉树节点:使用指针表示可选的子节点
struct TreeNode {
    int data;
    TreeNode* left