什么是引用
类型& 引用变量名(对象名) = 引用实体;
#include <iostream>
using namespace srd;
int main()
{
int x = 10;
int& x1 = x;
cout << "x = " << x << endl;
cout << "int& x1 = x: " << x1 << endl;
cout << "&x " << &x << endl;
cout << "&x1 " << &x1 << endl << endl;
return 0;
}
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
创建引用
类型& 引用变量名(对象名) = 引用实体;
#include <iostream>
using namespace std;
int main ()
{
// 声明简单的变量
int i;
double d;
// 声明引用变量
int& r = i;
double& s = d;
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}
上面代码产生的结果:
Value of i : 5
Value of i reference : 5
Value of d : 11.7
Value of d reference : 11.7
引用类型
基本引用类型
#include <iostream>
using namespace std;
int main()
{
int a = 3;
int &b = a; //b就是a的引用,即b是a的一个别名。
//引用必须初始化,否则编译会报错
b = 10; //修改b就是修改a,因为b是a的别名
cout<< a << endl; //此时a 的值,已由原来的3变成了10.因为我们无论对别名做什么操作,其实都是对变量的本身做操作。
return 0;
}
结构体类型的引用
#include <iostream>
using namespace std;
//定义一个名叫Coor的结构体,这是一个坐标。
typedef struct
{
int x;
int y;
}Coor;
int main()
{
Coor c1; //定义一个结构体变量,叫c1.
Coor& c = c1; //给c1起了一个别名叫c.
c.x = 10; //通过引用给结构体变量的数据成员赋值
c.y = 20;
cout<< c1.x << c2.y; //输出的结果:10 20
return 0;
}
指针类型的引用
#include <iostream>
using namespace std;
int main()
{
int a = 10; //定义一个整型的a变量,a的值为10。
int *p = &a; //定义一个指向a变量的指针
int*& q = p; //定义一个指针的引用,即q为p的别名。 定义方法:类型 *&指针引用名 = 指针;
*q = 20; //把20赋给*q,相当于是把20赋值给*p,也就相当于把20赋值给a.
cout<< a << endl; //输出a 的值为20.
return 0;
}
引用特性
- 引用在定义的时候必须初始化
//正确定义引用
int temp = 10;
int& temp1 = temp;
- 一个变量可以有多个引用
同样,这些引用都是temp的别名,修改temp或者修改这些引用就可以修改temp本身
- 引用一旦引用一个实体,再不能引用其他实体
#include <iostream>
using namespace std;
int main()
{
int temp = 10;
int& temp1 = temp;
cout << "temp1 " << temp1 << endl;
int temp2 = 11;
temp1 = temp2;
cout << "temp1 = temp2之后的结果" << temp1 << endl;
return 0;
}
说明上面的temp1 = temp2
仅仅是拷贝的意思,将temp2的值拷贝到引用里面,也就是相当于改变了temp的值,并不是将引用实体进行更换
引用的作用
作为函数的参数
#include <iostream>
using namespace std;
void swap(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值 */
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
上述代码执行结果
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100
作为函数的参数(示例二)
单链表进行尾插
typedef struct ListNode
{
int val;
struct ListNode* next;
}ListNode;
void PushBack(struct LinkNode** head)
{
if (*head == NULL)
{
//malloc创建结点
}
else
{
//链接结点
}
}
这个时候必须使用二级指针,因为如果主函数里面的链表指针是NULL,那我们就要malloc头结点,使用引用的代码如下
typedef struct ListNode
{
int val;
struct ListNode* next;
}ListNode;
void PushBack(struct LinkNode*& head)
{
if (head == NULL)
{
//malloc创建结点
}
else
{
//链接结点
}
}
这里head指针直接就是函数实参的别名,直接就可以在函数调用里面修改指针
引用作函数的返回值(修改返回对象)
#include <iostream>
using namespace std;
//全局变量:数组
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues(int i) {
double& ref = vals[i];
return ref; // 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i]
}
// 要调用上面定义函数的主函数
int main ()
{
cout << "改变前的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
//引用返回的是vals[1]和vals[3],通过 别名 直接可以修改数组元素
setValues(1) = 20.23; // 改变第 2 个元素
setValues(3) = 70.8; // 改变第 4 个元素
cout << "改变后的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
上述代码的执行结果
改变前的值
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
改变后的值
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50
注意点(反例)
#inlcude <iostream>
using namespace std;
int count()
{
int n = 0;
n++;
return n;
}
int main()
{
//正常打印 1
int ret = count();
//报错:error C2440: “初始化”: 无法从“int”转换为“int &”
int& ret = count();
}
调整上述代码(仍然错误)
#inlcude <iostream>
using namespace std;
int& count()
{
int n = 0;
n++;
return n;
}
int main()
{
//将n的引用返回给ret
int& ret = count();
cout << ret << endl;
}
但是这里打印出来却是随机值:因为cout是函数,我们先从栈帧里面拿到ret的值,调用函数的时候先进行传参,然后建立cout栈帧,这里可能会覆盖之前n的内存地址,虽然count函数执行完栈帧就销毁了,里面的局部变量也就销毁了,但是局部的n的地址仍旧在,所以count函数返回的引用仍然是n那块的地址,里面的值我们不得而知
所以通过上面两个“引用作为返回值的”的例子我们可以知道:引用作为返回值得目的是想要修改返回对象。引用作为返回值的要求:出了函数作用域之后对象还在,还可以正常使用。就像上面count函数一样,它返回的是一个局部变量的引用,但是局部变量出了函数的作用域就会消失,这个就像是“野引用”一样,引用的变量是被回收的。一般全局变量、静态变量、或者malloc创建的节点空间使用引用作为返回值
常引用
#include <iostream>
using namespace std;
int main()
{
//字面值是常量
const int& p1 = 3.114;
//错误 常量的引用必须是常量
int& p2 = 3.114;
//常量的引用必须是const
const int p3 = 66;
const int& p4 = p3;
//错误 发生整形提升、截断、类型转换的时候编译器会产生一个临时对象,这个临时对象是const类型的
int p5 = 13;
double& p6 = p5;
//上述代码也就是:
const double& temp =(double) 13;
const double& p6 = temp;
//所以修改后正确的代码:
int p5 = 13;
const double& p6 = p5;
//注意上面的p6的内容是不会根据p5的改变而改变,因为p6是编译器自己创建的临时变量的引用,我们不知道在哪里、叫什么名字!
}
以C++的语义来说,如果一个程序员只想传递参数给函数,而不希望函数修改传入的参数时,那么,或者使用值传递,或者采用常量型引用(const &)。考虑到大对象复制时产生的开销,一般使用常量型引用const &。如果函数的参数是某个类型的一个非常量的引用,那就相当于告诉编译器,程序员希望得到函数对参数的修改结果。
临时变量是由编译器生成的,C++语言规范没规定编译器生成临时变量的规则,程序员无法得知由编译器生成的临时变量的名字,程序员无法访问那个临时变量。这意味着,以引用的方式传递一个临时变量做为函数参数,如果函数内部对此临时变量做了修改,那么函数返回后,程序员无法获得函数对临时变量的修改。函数对临时变量所做出的所有更改,都将丢失
在《C++函数的返回值》“当函数的返回值是非引用变量时,会用一个临时变量来保存该返回值;
当函数的返回值是引用变量时,不使用临时变量,直接返回该引用”。