指针与引用是 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