目录
引入
一.引用的基本使用
(1)引用的概念:
(2)引用的表示方法
(3)引用注意事项
(4)引用权限
二.引用的本质
三.引用与函数
(1)引用做函数参数
(2)引用做函数返回值
四.常量引用
五.引用与指针
引入
绰号,又称外号,是人的本名以外,别人根据他的特征给他另起的名字,大都含有亲昵、憎恶或开玩笑的意味。那么在C++中,为了可以给一个已存在的变量取别名,产生了引用。引用就是给已经存在的变量取别名。
一.引用的基本使用
(1)引用的概念:
引用并不是新定义一个变量,而是给已经存在的变量取别名,编译器不会为引用变量开辟新的内存空间,它和它引用的变量共用一块内存空间。
(2)引用的表示方法
类型 & 别名 = 原名;
小白: &不是按位与吗?
小明: 这是其实是运算符重载。通过重载,同一个运算符将会有不同的含义。编译器会通过上下文来确定运算符的含义。
小白: 啊,原来如此,我说&怎么还是取地址符呢,懂了。
代码实例
#include <iostream>
using namespace std;
int main() {
int money = 99999;
int& money_1 = money;
cout << "money=" << money<<endl;
cout << "money_1=" << money_1<<endl;
return 0;
}
小白: 果然他们的值是相等的,那怎么确定他们共用一块内存空间呢?
小明: 我们一起来调试看一下。
小明: 看,引用和它引用的对象是同一块地址。
小白: 学会了,小明厉害啊。
(3)引用注意事项
1. 引用在 定义时必须初始化
2. 一个变量可以有多个引用,但一个引用只能引用一个变量。
3. 引用一旦初始化,就不可更改。
1.引用在定义的时候必须初始化
由于引用是对已经存在的变量进行取别名,因此使用引用时必须指定变量(初始化)。
int& a;//错误,未初始化
2.一个变量可以有多个引用,但一个引用只能引用一个变量
在C++语法中,一个变量有多个引用,就类似于一个人可以有多个外号。但是一个引用变量只能指向一个引用对象。
代码实例:
#include <iostream>
using namespace std;
int main() {
int x = 100;
int& y = x;
int& z = x;
return 0;
}
3. 引用一旦初始化,就不可更改。
引用一旦指向引用对象后,不可以更改引用对象。
小白: 啊啊啊,好多内容,脑袋记不住啊!
小明: 不急慢慢来,你哪里不明白?
小白: 为什么一个引用只能引用一个对象?
小明: 你想,如果一个引用类型同时是两个对象的别名,那计算机编译时怎么知道应该用哪一个呢?
小白:也就是说,同时指向多个对象时,具有二义性?
小明:对的。
小白:那为什么初始化后,不能更改引用对象啊?
小明:这个涉及引用的本质,文章后面会讲到的。
小白 :好的,我会继续努力的。
(4)引用权限
引用原则:对原变量的引用,权限不能放大。
代码实例:
#include <iostream>
using namespace std;
int main() {
int x = 100;
int& y = x;
//权限不变可行
const int& z = x;
//权利缩小可行
return 0;
}
权限没有扩大可行
代码实例:
#include <iostream>
using namespace std;
int main() {
const int x = 100;
int& y = x;
//权限扩大可行
const int& z = x;
//权利不变可行
return 0;
}
存在权限扩大不可行。
小白:这个我知道,就像班长指挥军长干活,军长上去就是一脚。
小明:哈哈哈哈,对,权限不能扩大,只能缩小
二.引用的本质
引用的本质是C++内部实现的一个指针常量
代码实例:
#include <iostream>
using namespace std;
int main() {
int a = 10;
int& b = a;
//本质:int* const b=&a
b = 100;
//本质:*b=100;
return 0;
}
小白:原来如此,这就解释了为什么初始化后,不能更改引用对象了。
小明:对的,因为const修饰b,b中存储的值不能改变,因此b指向的内存地址不能改变,所以引用初始化后不能更改引用对象。
小白:这个引用本质,还是有点糊涂......
小明:你可以这样理解,当代码编译到int& b=a时,编译器其实执行int* const b=&a,当执行b=100时,编辑器实际执行*b=100。
小白:我可以理解为这两种写法是一样的,只是引用简化了吗?
小明:可以的。
三.引用与函数
(1)引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参,也更安全
一般认为函数传参有两种形式,值传递和地址传递。值传递的形参是对实参的一份临时拷贝。地址传递是传递指针,通过指针访问实参所在的内存区间。所以地址传递不用开辟新空间,因此速度更快,还能减少内存。
值传递代码实例:
#include <iostream>
using namespace std;
void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
cout << "形参:" << "x=" << x << ' ' << "y=" << y << endl;
}
int main() {
int x = 10, y = 20;
swap(x,y);
cout << "实参:" << "x=" << x <<' ' << "y=" << y << endl;
return 0;
}
值传递不改变实参的值
地址传递代码实例:
#include <iostream>
using namespace std;
void swap(int* x, int* y) {
int temp = *x;
*x = *y;
*y = temp;
cout << "形参:" << "x=" << x << ' ' << "y=" << y << endl;
}
int main() {
int x = 10, y = 20;
swap(&x,&y);
cout << "实参:" << "x=" << x <<' ' << "y=" << y << endl;
return 0;
}
地址传递可以改变实参的值
小白:理解地差不多了,不过值传递的形参是对实参的一份临时拷贝是什么意思?
小明:我画个图你就知道了
小白:明白了,值传递就是从新开辟空间,只是内容一样而已
小明:对的。
小白:那地址传递呢?
小明:地址传递就是利用指针,形参是存储实参的指针,解引用就找到实参了。
小白:明白了,那引用做形参又是怎么回事?
小明:你忘了刚学的引用本质了?
小白:哦我明白了,这个代码让我来,嘿嘿。
#include <iostream>
using namespace std;
void swap(int& x, int& y) {
int temp = x;
x = y;
y = temp;
cout << "形参:" << "x=" << x << ' ' << "y=" << y << endl;
}
int main() {
int x = 10, y = 20;
swap(x,y);
cout << "实参:" << "x=" << x <<' ' << "y=" << y << endl;
return 0;
}
小明:没错,引用当形参就是这样写的。
小明:你能根据引用本质写出指针版本吗?
小白:小问题啦,看我大展身手。
小白:怎么样,我写的对吗?
小明:对的,其实引用也是地址传递,只是因为const的缘故,不能改变指向的对象而已。
小白:我明白了。
小明:相比指针,引用更加简便,而且因为const,比指针更加安全。
(2)引用做函数返回值
引用作为函数返回值特性:
1.不能返回局部变量的引用。
2.函数的调用可以作为左值。
小白:为什么引用不能返回局部变量的引用啊?
小明:引用本质是指针,是指针常量。因为指针不能返回局部变量的地址。
小白:指针不能返回局部变量的指针吗?
小明:是的,因为局部变量会在函数执行完之后销毁,该变量的内存会返回给系统,此时函数返回该变量地址,并且调用的话,属于非法访问,是失效指针。
小白:哦哦,明白了,也就是函数执行完之后,该地址不属于该程序了,再次访问是非法的。
小明:对的。
代码实例:
#include <iostream>
using namespace std;
int& test() {
int n = 0;
n = 100;
return n;
}
int main() {
int& res = test();
cout << res << endl;
cout << "系统清除" << endl;;
cout << res<<endl;
return 0;
}
注意 :
如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已 经还给系统了,则必须使用传值返回。
小白:那只能创建全局变量,返回全局变量了吗?
小明:其实可是使用static关键字,static关键字创建在全局区(静态区),生命周期是整个程序。
小白:static创建的变量,程序不停止,变量不销毁对吗?
小明:对的。
小白:还是有些懵。
小明:不急,我们来看一下代码就懂了。
代码实例:
#include <iostream>
using namespace std;
int& test() {
static int n = 0;
n = 100;
return n;
}
int main() {
int& res = test();
cout << res << endl;
cout << "系统清除" << endl;
cout << res<<endl;
return 0;
}
小白:懂了,只要返回值在函数调用不销毁就可以是吗?
小明:对的。
小白:那函数的调用可以作为左值,又是什么意思?
小明:就是说函数可以放到等号左边,给函数赋值。
小白:给函数赋值,不可能吧?
小明:实际上,函数返回值是引用时,会产生一个引用变量,只是没有名字,给函数赋值,实际就是给该引用赋值。
小白:还是不明白。
小明:我们来看代码。
代码实例:
#include <iostream>
using namespace std;
int& test() {
static int n = 0;
n = 100;
return n;
}
int main() {
int& res = test();
test() = 99;
cout << res<<endl;
return 0;
}
小白:哦哦,也就是说此时函数相当于一个匿名的引用,函数当左值相当于通过匿名引用修改n的值。
小明:对的,那你看看下面代码什么意思。
代码实例:
#include <iostream>
using namespace std;
int& test() {
static int n = 0;
n = 100;
return n;
}
int main() {
int res = test();
test() = 99;
cout << res<<endl;
return 0;
}
小白:int类型能接收int&类型的值吗?
小明:可以的,其实相当于吧匿名引用解引用后,把值传给res。
小白:还是不明白。
小明:我们接着看代码。
代码实例:
#include <iostream>
using namespace std;
int& test() {
static int n = 0;
n = 100;
return n;
}
int main() {
/*int res = test();*/
int& m = test();
int res = m;
test() = 99;
cout << res<<endl;
return 0;
}
小明:这个明白吧。
小白:这个明白。
小明:那么换成匿名引用不懂了?
小白:懂了,也就是直接通过,匿名引用传值给变量。
小明:对的。
四.常量引用
const 类型 & 别名 = 原名;
1.可以直接指向一块常量。
2.函数形参列表中,可以防止形参改变从而改变实参。
小白:这个我懂相当于指向常量的指针常量。
小明:没错,例如const int* const p;p的值不能改变,*p的值也不能改变。
小白:可以直接指向一块常量,是什么意思?
小明:其实常量保存在常量区,声明周期和程序共存亡,并且常量区只有只读权限,没有写权限。如果用普通的引用,由于引用可以更给指向内存的值,属于权限扩大,会报错,使用常量引用则引用也只有读权限,则可以指向常量区。
小白:有些糊涂。
小明:没事,我们来看看代码。
代码实例:
#include <iostream>
using namespace std;
int main() {
/*int& res = 10;*/
const int& res = 10;
cout << 10;
return 0;
}
小白:明白了,防止实参改变,从而改变实参,是为了防止手误没小心改变形参吧。
小明:厉害,直到抢答了,某些场景我们只需要值,但不许要改变引用的值,可以使用常量引用,防止粗心改变形参的值。
代码实例:
#include <iostream>
using namespace std;
void text(const int& x) {
//x = 100;
cout << x;
}
int main() {
/*int& res = 10;*/
text(9);
return 0;
}
五.引用与指针
引用其实就是指针,引用的本质是C++内部实现的一个指针常量,建设建立引用int& b=a时,编译器其实执行int* const b=&a,当执行b=100时,编辑器实际执行*b=100。之后遇到的b可以都看成指针常量的*p,但也要看上下文。其实引用也是占用空间的,只是被C++隐藏了。
宏观区别:
1.指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;。
2. 引用在初始化时引用一个实体后,就不能再引用其他实体(指针常量),而指针可以在任何时候。指向任何一个同类型实体。
3. 在sizeof中含义不同:引用结果为引用类型的大小(sizeof(*p)),但指针始终是地址空间所占字节个数(4/8)。
4. 引用自增即引用的实体增加1((*p)++),指针自增即指针向后偏移该类型的大小的字节。
5. 有多级指针,但是没有多级引用。
6. 访问实体方式不同,指针需要显式解引用,引用编译器编译器自行处理。
理解引用的本质,这些理解起来其实都会简单很多啦。
(其余浅谈C++系列持续更新中,请三连关注,本篇完)