首先明确定义:
指针 是一个变量,用来存放另一个变量的地址。
引用 是某一个变量的别名。
引用分为左值引用和右值引用。
补充:
左值(loactor value),可以看作是存储在内存中的,有明确存储地址(可寻址)的数据;
右值(read value),指的是可以提供数据值的数据(不一定可以寻址,比如常量是存储于寄存器中的数据)。
左值引用与指针有重叠部分,右值引用则属于另一部分,所以区别方面主要讨论左值引用和指针的区别(以下引用未说明则都是左值引用)。
从定义可以看出,指针和引用都变量有关,那么我们可以从变量使用的角度分析差别。大致分为以下三个个角度:
- 变量本身
- 作为函数参数
- 作为返回值
变量本身
- 初始化:
指针可以先定义再初始化。
引用作为变量的别名,必须在定义的时候初始化,即引用到一个有效的对象。避免了空指针和野指针,使用时不用验证其合法性。
// 初始化
int a = 10;
int* p1; // 正确 未初始化 野指针
int* p2 = nullptr; // 正确 初始化为 空指针
int* p3 = &a; // 正确 p2 初始化为 a 的地址
// int& r1; // 错误 引用必须初始化 error: 'r1' declared as reference but not initialized
int& r2 = a; // 正确 r2 初始化为 a 的引用
- 使用:
操作引用与直接操作变量完全一样,指针需解引用。
// 使用
int b = 20;
b = a; // 变量
b = *p3; // 指针
b = r2; // 引用
引用初始化后不可改变指向、指针可以改变。
// 修改
p1 = p3; // 正确 修改 p1 指向,现在 p1 指向 a
int& r3 = b;
r3 = r2; // 正确 修改 r3 的值为 r2, 即 b = a
std::cout << "a=" << a << " b=" << b << std::endl; // a=10 b=10
可以有多级指针,没有多级引用。
// 多级
int** pp3 = &p3; // 正确 pp3 为 a 的二级指针
int*** ppp3 = &pp3; // 正确 ppp3 为 a 的三级指针
// int&& rr1 = r2; // 错误 error:cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
int&& rr2 = 123; // 正确 && 右值引用
// int&&& rrr2 = rr2; // 错误 error:cannot declare reference to 'int&&', which is not a typedef or a template type argument
函数参数
在函数参数方面,引用可以避免拷贝。在函数内部,若想修改实参,直接修改引用即可。
指针本身是一个变量,传参时指针会发生拷贝,在函数内修改形参指针的指向,不会改变实参指针的指向。
返回值
在返回值方面,主要是可安全性和链式编程两方面。
安全性:
由于指针初始化后可以改变指向,如果程序设计不当,调用者可能修改预期以外的数据。引用初始化后不可修改指向,安全性会好一些。
std::vector<int> vec = {0,1,2,3,4,5,6,7,8,9};
// 返回对应下标元素的地址,给外面修改
int* rt_p(int pos) {
// TODO
return &vec[pos];
}
int& rt_r(int pos) {
// TODO
return vec[pos];
}
// 输出 vec
void printVec(){
for (auto& i : vec) {
std::cout << i << " ";
}
std::cout << std::endl;
}
void test3() {
int* p = rt_p(0); // 取得 0 号位置的元素
p += 8; // 本意是 *p += 3 结果改变了 p 的指向, p = &vec[8];
// TODO // 业务逻辑
// ...
*p += 5; // 本意是对 0 号元素赋值,结果改变了 vec[8]
printVec(); // 输出: 0 1 2 3 4 5 6 7 13 9
int& r = rt_r(0); // 取得 0 号位置的元素
r += 3;
printVec(); // 输出: 3 1 2 3 4 5 6 7 13 9
}
链式编程:
返回当前对象的引用,实现成员函数、仿函数、操作符等的链式调用。
class A{
public:
A(): m_a(0) {}
// 成员函数
A& add(const int v) {
m_a += v;
return *this;
}
// 仿函数
A& operator()(const int v) {
m_a += v;
return *this;
}
// 操作符
A& operator<<(const int v) {
m_a += v;
return *this;
}
int m_a;
};
void test4(){
A a;
a.add(1).add(2).add(3); // 成员函数链式调用
std::cout << a.m_a << std::endl; // 6
a.m_a = 0;
a(1)(2)(3); // 仿函数链式调用
std::cout << a.m_a << std::endl; // 6
a.m_a = 0;
a << 1 << 2 << 3; // 操作符链式调用
std::cout << a.m_a << std::endl; // 6
}
上面从变量本身、参数、返回值三个方面分析了引用和指针的区别,可以看出引用相比于指针有很多优点,但是其也有其缺点,简单叙述如下:
引用作为一个变量的别名,相当于一个变量的附属品,没有独立性,任何操作都是与引用关联的对象相关的。指针作为一个独立的变量,其灵活性更高。
指针可以作为放入数组的元素。
指针自身也有地址,所以有多级指针,可以任意嵌套。
指针可以改变指向,方便迭代计算。
涉及右值引用时可以参考这篇文章C++知识篇--右值引用_c++ 右值引用_煮雪品茶的博客-CSDN博客