指针和引用的区别
📃问题
指针和引用的区别
📝我的答案
首先,本质上指针是一个变量,存储的是内存地址;而引用只是一个别名,必须绑定到已有对象。
其次,指针可以不初始化,可以为空,也可以随时改变指向;而引用必须在定义时初始化,一旦绑定就不能改变,也没有空引用的概念。
在使用上,指针需要用星号解引用才能访问对象,比如 *p;引用则直接用变量名就能访问,更直观。
sizeof 的结果也不同,对指针执行 sizeof 得到的是指针自身的大小(通常 4 或 8 字节),而对引用执行 sizeof 得到的是所引用对象的大小。
在函数参数传递时,指针传递的是地址的副本,需要检查空指针;引用传递直接借用对象,更安全高效。
const 修饰也有区别,指针有 const 指针(T* const)和指向 const 的指针(const T)两种形式;引用只有指向 const 的引用(const T&)。
关于临时对象,const 引用可以绑定到临时对象并延长其生命周期,这在函数参数传递中特别有用。比如 void func(const string& s) 可以直接接受字符串字面量而不需要额外构造 string 对象。
在多态和继承方面,两者都能实现多态,但引用更安全,因为它保证指向有效对象,不需要像指针那样做空检查。
另外,在现代 C++ 中,右值引用(T&&)和移动语义的引入让引用的应用更加丰富。我们可以用它实现完美转发和移动语义,大幅提升性能。
从底层实现看,虽然引用通常被编译器实现为指针,但编译器会对引用添加更多优化假设,比如非空和可解引用的保证,这可能带来更好的性能。
最后,在实际项目中,我一般在需要表示'可能不存在'或'需要动态改变指向'的场景用指针,而在只需要避免拷贝开销、不涉及所有权转移的场景用引用。如果参数必须存在且只是借用,我用引用;如果参数可选或表达所有权转移,我用(智能)指针。现在有了智能指针,很多原来用裸指针的地方我都改用 unique_ptr 或 shared_ptr 了,这样可以通过类型系统表达设计意图,提高代码可读性和安全性。"
再来结合代码例子解释指针和引用的区别:
1. 本质区别:变量 vs 别名
int num = 10;
int* ptr = # // 指针是变量,存储num的地址
int& ref = num; // 引用是别名,ref就是num的另一个名字
*ptr = 20; // 通过指针修改numref = 30; // 通过引用修改numcout << num << endl; // 输出30
2. 初始化和重绑定
int* ptr1; // 指针可以不初始化(不推荐)
ptr1 = nullptr; // 可以为空
int a = 5, b = 10;
int* ptr2 = &a;
ptr2 = &b; // 可以改变指向
// int& ref; // 错误:引用必须初始化
int& ref = a;
// ref = &b; // 错误:这不是重绑定,而是给a赋值b的值
3. 访问语法
int val = 100;
int* ptr = &val;
int& ref = val;
*ptr = 200; // 指针需要解引用
ref = 300; // 引用直接使用
cout << val << endl; // 输出300
4. sizeof结果
int num = 42;
int* ptr = #
int& ref = num;
cout << sizeof(ptr) << endl; // 输出指针大小,如8(64位系统)
cout << sizeof(ref) << endl; // 输出int大小,如4
5. 函数参数传递
// 使用指针
void incrementPtr(int* p)
{if (p) { // 需要检查空指针(*p)++;}
}
// 使用引用
void incrementRef(int& r)
{r++; // 不需要检查,更简洁
}int x = 10;
incrementPtr(&x); // 传递地址
incrementPtr(nullptr); // 合法但不会做任何事
incrementRef(x); // 直接传递对象
// incrementRef(10); // 错误:非const引用不能绑定到临时值
6. const修饰
int value = 5;
const int* p1 = &value; // 指向const的指针(不能通过p1修改value)
// *p1 = 10; // 错误
p1 = nullptr; // 可以改变p1指向
int* const p2 = &value; // const指针(不能改变p2指向)
*p2 = 10; // 可以通过p2修改value
// p2 = nullptr; // 错误
const int& r = value; // 指向const的引用
// r = 10; // 错误:不能通过r修改value
7. 临时对象绑定
// 临时对象绑定到const引用,延长生命周期
const std::string& s = std::string("hello") + " world";
cout << s << endl; // 安全,临时对象在s的作用域内有效
// 函数参数中的应用
void printLength(const std::string& str)
{cout << str.size() << endl;
}
printLength("direct string"); // 不需要显式构造string
8. 多态和继承
class Base
{
public:virtual void show() { cout << "Base class" << endl; }virtual ~Base() {}
};class Derived : public Base {
public:void show() override { cout << "Derived class" << endl; }
};// 使用引用实现多态
void polymorphicRef(Base& base)
{base.show(); // 安全,一定有对象
}
// 使用指针实现多态void polymorphicPtr(Base* base)
{if (base) { // 需要检查base->show();}
}
Derived d;
polymorphicRef(d); // 安全调用
polymorphicPtr(&d); // 需要传地址
polymorphicPtr(nullptr); // 合法但不会调用show
9. 右值引用和移动语义
// 移动语义
void processVector(std::vector<int>&& vec)
{// 可以"窃取"vec的资源,因为它是右值std::vector<int> internal = std::move(vec);// 处理internal...
}// 完美转发
template<typename T>
void relay(T&& arg)
{process(std::forward<T>(arg)); // 保持值类别
}
std::vector<int> v{1, 2, 3};
processVector(std::move(v)); // v被移动,之后v为空
10. 实际项目中的选择
// 引用 - 当参数必须存在
void updateConfig(Config& config)
{config.setValue("timeout", 30);
}
// 指针 - 当参数可能不存在
bool findUser(const string& username, User* result) {if (database.hasUser(username)) {*result = database.getUser(username);return true;}return false;
}
// 智能指针 - 表达所有权
class ResourceManager {
private:std::unique_ptr<Resource> resource; // 独占所有权std::shared_ptr<Logger> logger; // 共享所有权
};