一、引用的基本使用
- 作用:给变量起别名
- 语法:数据类型 &别名=原名
引用的本质就是带有const的指针
int & a = b <---> int *const a = &b
char & a = b <---> char *const a = &b
double & a = b <---> double *const a = &b
(结构体类型) &a = b <---> (结构体类型) *const a = &b
为什么要C++要出现“引用”?
通过上面的对比,可以得出几条结论:
- 引用比指针更加简化;
- 引用使用起来更加像普通变量一样赋值;
- 引用更加安全。因为新手一般不会定义 int *const a这样的指针,而是直接int * a,才会造成所谓的野指针(没有给指针初始化,不知道默认指向哪了),一个不小心修改野指针的内容,严重的可以导致电脑崩掉。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int &b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b = 100;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout<<"a address= "<<&a;
cout<<", b address= "<<&b<<endl;
return 0;
}
运行结果:
a = 10
b = 10
a = 100
b = 100
a address= 0x6dfee8 , b address= 0x6dfee8
Process returned 0 (0x0) execution time : 0.368 s
Press any key to continue.
由上述运行结果得出以下结论:
- a,b任一变量改变,a,b变量都改变
- a,b的值和地址都相同
二、C++ 引用 vs 指针
引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
下面我们通过引用传递、按值传递、按地址传递来体会引用与指针的不同
#include <iostream>
using namespace std;
void swapr(int &a,int &b);
void swapp(int *p,int *q);
void swapv(int a, int b);
int main()
{
int wallet1=300;
int wallet2=350;
cout <<"wallet1=$ "<<wallet1;
cout <<"wallet2=$ "<<wallet2<<endl;
cout<<"Using references to swap contents:\n";
swapr(wallet1,wallet2);
cout<<"wallet1 =$ "<<wallet1;
cout<<"wallet2 =$ "<<wallet2<<endl;
cout<<"Using pointers to swap contents again:\n";
swapp(&wallet1,&wallet2);
cout<<"wallet1 =$ "<<wallet1;
cout<<"wallet2 =$ "<<wallet2<<endl;
cout<<"Trying to use passing by value:\n";
swapv(wallet1,wallet2);
cout<<"wallet1 =$ "<<wallet1;
cout<<"wallet2 =$ "<<wallet2<<endl;
return 0;
}
//引用传递
void swapr(int &a,int &b)
{
int temp;
temp=a;
a=b;
b=temp;
}
//按地址传递(引用传递)
void swapp(int *p,int *q)
{
int temp ;
temp=*p;
*p=*q;
*q=temp;
}
//按值传递
void swapv(int a,int b)
{
int temp;
temp=a;
a=b;
b=temp;
}
运行结果
wallet1=$300wallet2=$350
Using references to swap contents:
wallet1 =$ 350wallet2 =$ 300
Using pointers to swap contents again:
wallet1 =$ 300wallet2 =$ 350
Trying to use passing by value:
wallet1 =$ 300wallet2 =$ 350
Process returned 0 (0x0) execution time : 0.556 s
Press any key to continue.
总结:
1、若采用指针传递的方式,我们在函数定义和函数声明时使用 *来修饰形参,表示这个变量是指针类型;在进行函数调用时,使用 & 来修饰实参,表示是将该变量的地址作为参数传入函数。
2、引用实际上是某一个变量的别名,和这个变量具有相同的内存空间。 实参把变量传递给形参引用,相当于形参是实参变量的别名,对形参的修改都是直接修改实参。在类的成员函数中经常用到类的引用对象作为形参,大大的提高代码的效率
- 按值传递、引用传递的相同与不同
相同点:调用过程相同
swapr(wallet1,wallet2);
swapv(wallet1,wallet2);
不同点:外在区别:声明函数的参数的方式不同
void swapr(int &a,int &b);
void swapv(int a, int b);
内在区别:在swapr()中,变量a和b是wallet1和wallet2的别名,所以交换a 和b的值相当于交换wallet1和wallet2的值;但在swapv()中,变量a和 b是复制wallet1和wallet2的值的新变量,因此交换a与b的值并不会影响wallet1与wallet2的值。
三、常引用
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
常引用声明方式:const 类型标识符 &引用名=目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。
int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确
四、引用作为返回值
要以引用返回函数值,则函数定义时要按以下格式:
类型标识符 &函数名(形参列表及类型说明)
{函数体}
- 不要作为返回局部变量的引用
#include <iostream>
using namespace std;
int &plus10(int &r) {
int m = r + 10;
return m; //返回局部数据的引用
}
int main() {
int num1 = 10;
int num2 = plus10(num1);
cout << num2 << endl;
int &num3 = plus10(num1);
int &num4 = plus10(num3);
cout << num3 << " " << num4 << endl;
return 0;
}
注意:
不同的编译器对于返回局部变量的引用有所区别对待:
对于gcc、codeblocks和g++, 编译报警告,运行的时候会出现错误
对于msvc:编译报警告,warning C4172: 返回局部变量或临时变量的地址: n,但是运行的时候不会出现错误,而是像正常的运行一样,比如上面的代码结果为5.
为什么不要返回局部变量的引用呢?
因为当该函数调用结束之后,该函数内部创建的局部变量出了作用域会被销毁,为这个函数开辟的栈帧也会被系统回收,在调用下一个函数之前会对这一部分栈空间里的垃圾数据进行清理,因此你也会失去对这个空间的管控能力。
- 返回全局变量的引用
#include <iostream>
using namespace std;
int &plus10(int &r) {
r += 10;
return r;
}
int main()
{
int num1 = 10;
int num2 = plus10(num1);//用num2接受返回值
cout << num1 << " " << num2 << endl;
return 0;
}