⭐️ 往期相关文章
✨ 链接:C++基础知识(命名空间、输入输出、函数的缺省参数、函数重载)
⭐️ 引用
引用从语法的层面上讲:引用不是定义一个新的变量,而是给已存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
🌠 例1
#include <iostream>
using namespace std;
int main() {
int a = 10;
int& ra = a;
cout << ra << endl;
ra = 20;
cout << a << endl;
return 0;
}
ps:
int& ra = a
这里的 ra
是 a
的引用(或者可以理解成 a
的别名),当 cout << ra
的时候相当于 cout << a
,当修改 ra = 20
的时候,a
的值也被修改了。
🌟 引用的特性:
- 引用在定义时必须初始化
int& ra;
这种行为是error
的
- 一个变量可以存在多个引用
int num = 10;
int& q_num1 = num;
int& q_num2 = num;
- 引用一旦引用一个实体,就不能再引用其他的实体
int num = 10;
int n = 20;
int q_num = num;
q_num = n
这里并不代表更换了引用。而是赋值:这里q_num = n
是把n
的值赋值给q_num
而q_num
又是num
的引用,就相当于n
的值赋值给num
。
🌟 引用的使用场景:
🌠 例2
#include <iostream>
using namespace std;
// 做参数
void swap(int& qa, int& qb) {
int temp = qa;
qa = qb;
qb = temp;
}
int main() {
int a = 10;
int b = 20;
cout << a << " " << b << endl; // 10 20
swap(a , b);
cout << a << " " << b << endl; // 20 10
return 0;
}
ps:
当参数过大时,传值与传引用效率上会有一定的差距。
🌠 例3
#include <iostream>
using namespace std;
// 做返回值
int& func() {
static int count = 0;
cout << count << endl;
count++;
return count;
}
int main() {
int res = func();
res++;
func();
return 0;
}
ps:
首先这里 count
变量是被 static
修饰过的,所以并不是存在在栈区,而是静态区。return count
但是函数的返回是引用相当于返回了 count
的别名(这里可以理解为当函数栈帧销毁并且返回值的时候,会有一个临时变量来存储这个要返回的值,也就是可以理解成 int& temp = count
再把temp
引用返回),其实本质上就 count
,所以当 count
的引用赋值给 res
就相当于把 count
赋值给 res
所以 res
的改变并不会影响静态区中的 count
。 但是下面这种情况又不一样了。
🌠 例4
#include <iostream>
using namespace std;
int& func() {
static int count = 0;
cout << count << endl;
count++;
return count;
}
int main() {
int& res = func();
res++;
func();
return 0;
}
ps1:
这里返回值是被 int& res
接收的。而 res
相当于是静态区中 count
的别名,res++
就相当于静态区中的 count++
, 所以第二次函数调用的时候 cout << count << endl
的时候是 2
。
ps2:
如果函数中的变量会被销毁则不可以使用引用返回,则必须使用传值返回。
🌠 例5
#include <iostream>
#include <assert.h>
using namespace std;
int& At(int * nums) {
assert(nums);
return nums[0];
}
int main() {
int nums[10] = { 1 , 2 };
At(nums) = 10; // 直接可以修改返回值
cout << nums[0] << endl; // 10
return 0;
}
ps:
使用返回引用的方式是可以直接修改返回值的,但是如果使用返回值的方式是不可以直接修改的,因为返回值会创建一个临时变量来保存,这个临时变量是常值变量不可被修改。
🌟 常引用:
🌠 例6
int main () {
const int b = 20;
//int& qb = b; // error
const int& qb = b;
}
ps:
b
是被 const int
修饰的常量只有读的权限 而 qb
是 int
类型的引用,这里是 error
的因为对于引用来说权限是不可以放大的。
🌠 例7
int main () {
int c = 30;
const int& qc = c; // 这样做是可以的 权限缩小
const int& d = 20; // 这样也是可以的
}
ps:
权限是可以缩小的 c
是可读可写的,而它的引用也可以是只读的。
🌠 例8
int main () {
int e = 50;
//double& dd = e; // error
const double& dd = e;
}
ps:
当把整型给到浮点型时会发生自动类型转换,但是为什么整型 e
给不了 double
的引用呢?因为在自动类型转换的时候,首先并不会改变 e
的类型,而是在它们中间创建一个临时的变量,把 e
给到这个临时变量在通过临时变量给到 dd
。而这个临时变量是一个常值变量是不可以被修改的,所以这里本质上是发生权限放大的问题,如果原变量没有被 const
修饰,或者说不是一个常量,那么它的引用可以被 const
修饰也可以不被 const
修饰(引用的权限平移或者权限缩小),但是如果原变量是一个常量或者被 const
修饰,那么它的引用只能被 const
修饰,而不可以不被const修饰(权限放大)。
🌟 引用和指针的区别
在语法概念上引用就是一个别名,没有独立的空间,和引用的实体共用同一块空间。但是在底层实现上实际是有空间的,因为引用的是按照指针方式来实现的。
🌟 引用和指针的不同点:
- 引用概念上是定义一个变量的别名,而指针是存储一个变量的地址。
- 引用在定义时必须初始化,指针没有要求。
- 引用在初始化时引用一个实体后,就不能引用其他的实体,而指针可以随意的更换指向。
- 没有
NULL
引用,但是有NULL
指针。 - 在
sizeof
中的含义不同:引用结果为引用类型的大小,但是指针始终是地址空间所占的字节个数(32位平台下4字节)。 - 引用自增
1
即引用的实体增加1
,指针加1
代表的是指针的指向后偏移一个类型的大小。 - 有多级指针,没有多级引用。
- 访问实体的方式不同,指针需要解引用操作符,而引用编译器会自己处理。
- 引用相比指针更安全。