1.引用的基本语法
(1)语法形式:
数据类型 &别名 = 原名
(2)原理
给一个已有的变量起别名
int main()
{
int a = 10;
int& b = a;
cout << "a=" << a << " b=" << b << endl;
return 0;
}
运行结果:
int main()
{
int a = 10;
int& b = a;
cout << "a=" << a << " b=" << b << endl;
b = 100;
cout << "a=" << a << " b=" << b << endl;
return 0;
}
a和b访问的时同一块内存。引用的本质就是给一个变量起别名。
2.引用的注意事项
(1)引用必须初始化
int &b;//错误
(2)引用在初始化后,不可以再改变
int main()
{
//引用的定义
int a = 10;
int& b = a;
int c = 20;
b = c;//不是更改引用而是赋值操作
//上面这行代码的意义并不是把b作为c的别名,而是将c的值赋值给b
cout << "a=" << a << " b=" << b << " c=" << c << endl;
return 0;
}
运行结果:
分析:
c本身的值就是20,没有发生改变;b=c;
相当于把b指向的那块内存赋值为20,所以b的值为20;由于b是a的别名,b和a指向的是同一块内存,所以a的值为20。
3.引用做函数参数
调用函数的传参的方式一共有三种:值传递、地址传递和引用传递
(1)值传递
不可以通过形参修改实参
(2)地址传递
可以通过形参修改实参
(3)引用传参
可以通过形参修改实参,简化了指针修改实参。通过引用参数产生的效果和地址传递是一样的,引用语法更简单。
三种传递就是不一样的赋值结果。值传递,形参复制实参值;值地址传递,形参存入实参的地址;引用传递,将实参重命名为形参。
【示例】
(1)值传递
void myswap(int x, int y)
{
int temp = x;
x = y;
y = temp;
cout << "x=" << x << " y=" << y << endl;
}
int main()
{
int a = 10;
int b = 20;
myswap(a, b);
cout << "a=" << a << " b=" << b << endl;
return 0;
}
运行结果:
分析:
根据输出结果可以看出,主函数中的实参a和b并没有发生改变,但是被调函数中的形参的值发生了改变。所以值传递时,形参的改变并不会引起实参的改变,即不可以通过形参修改实参。
(2)地址传递
void myswap1(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temp;
cout << "x=" << *x << " y=" << *y << endl;
}
int main()
{
int a = 10;
int b = 20;
//myswap(a, b);
myswap1(&a, &b);
cout << "a=" << a << " b=" << b << endl;
return 0;
}
运行结果:
分析:
在主函数中,myswap1(&a, &b);
这条语句的意思是把a和b的内存的地址分别传给了myswap函数的形参x和y,所以在函数myswap中修改的数据就是外侧主函数中a和b的数据。所以,地址传递,形参的改变会影响实参,即可以通过形参去修改实参。
(3)引用传参
void myswap2(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
cout << "x=" << x << " y=" << y << endl;
}
int main()
{
int a = 10;
int b = 20;
//myswap(a, b);
//myswap1(&a, &b);
myswap2(a, b);
cout << "a=" << a << " b=" << b << endl;
return 0;
}
运行结果:
分析:
主函数中,在调用函数myswap2(a, b);
时,myswap2函数的形参x就是主函数中实参a的一个别名,形参y就是主函数中实参b的一个别名,所以在myswap2函数中对形参x和y的任何操作和修改实际上就是对实参a和b的操作和修改。根据结果可以看出,引用传递的形参的改变也会影响实参,即可以通过形参去改变实参。
4.引用做函数返回值
(1)不要返回局部变量的引用
int& test1()
{
int a = 10;//存放在栈区
return a;
}
int main()
{
int& ref = test1();
cout <<"ref=" << ref << endl;//第一次结果正确
cout << "ref=" << ref << endl;//第二次错误
return 0;
}
代码分析:
test1函数以引用的方式作为返回值,并在函数体中返回局部变量a的值,相当于a有一个别名,把她的别名返回,这个别名在主函数中定义的一个引用ref来接收它。
运行结果:
结果分析:
局部变量a在test1函数运行结束之后就应该消失掉了,之所以第一次可以输出test1函数的局部变量a的值,是因为编译器做了保留。因为a的内存以及释放掉了,其实这块内存我们以及没有权利去操作了,所以第二次的结果是一个随机值,是错误的。
所以不要返回局部变量的引用,如果返回的是局部变量的引用,那么在函数外侧再用别名去操作这段内存已经是非法操作了。
(2)函数的调用可以作为左值
int& test2()
{
static int a = 10;//该变量存放在全局区,全局区的数据在程序结束后由系统释放
return a;
}
int main()
{
//int& ref = test1();
//cout <<"ref=" << ref << endl;//第一次结果正确
//cout << "ref=" << ref << endl;//第二次错误
int &ref2 = test2();
cout << "ref2=" << ref2 << endl;
cout << "ref2=" << ref2 << endl;
test2() = 1000;//如果函数的返回值是一个引用,这个函数的调用可以作为左值。
cout << "ref2=" << ref2 << endl;
cout << "ref2=" << ref2 << endl;
return 0;
}
运行结果:
分析:
函数test2调用完之后返回的是静态变量a的一个引用,把a变量返回,主函数中的 test2() = 1000;
就相当于做了一个a=1000;
的操作,用原名a赋值1000,test2本身就是初始化a这个返回值的别名。因为test2本身返回的就是一个a的引用,在 test2() = 1000;
这条语句执行完之后,因为主函数中的ref2就是a的一个别名,所以无论是用test2调用完之后给它赋值1000,还是让静态变量a等于1000,或者使用别名ref2去访问这块内存,都可以。
所以如果函数的返回值是一个引用,这个函数的调用可以作为左值。
4.引用的本质
引用的本质在C++内部实现是一个指针常量
void fun(int& b)
{
b = 100;
}
int main()
{
int a = 10;
int& b = a;
b = 20;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
fun(a);
return 0;
}
运行结果:
结果分析:
在主函数中创建了一个整型数据a并初始化为10,在程序执行到int& b = a;
这行代码时,系统编译器会自动将其转换为int *const b=&a;
,这时候就相当于定义了一个指针常量,指向a的地址,这里const修饰的是指针的指向,说明指针的指向是固定的不可以修改,这也就说明为什么引用不可以更改。但是指针常量指向的内容可以修改。再继续执行到b = 20;
这一行代码的时候,内部发现b是一个引用,会自动帮我们将其转换为*b=20;
。所以输出a和b的值其实都是a的内存单元中存放的值。主函数中最后的 fun(a);
这行代码,调用函数fun,将a作为实参传给fun函数的形参,fun函数中对应的形参为引用的方式int& a
,这里发现它是引用,就会自动转换为int *const b=&a;
,同理fun函数体中的b=100;
这行代码也会自动转换为*b=100;
。
引用的本质就是一个指针常量。 所以引用一旦初始化之后就不可以发生改变。C++推荐使用引用技术,因为语法方便,引用的本质是指针常量,但是所有的指针操作编译器都帮我们做了。
5.常量引用
(1)使用场景
修饰形参
(2)作用
常量引用主要用来修饰形参,防止误操作。
在函数的形参列表中,可以加const修饰,防止形参改变实参。
(3)示例
【例1】
int main()
{
int a = 10;
//int& b = 10;//错误 10是一个常量,引用必须引用的是内存上的合法内存空间
const int& b = 10;//正确
//这个引用,引用的是一块临时的空间,我们想要操作它的时候找不到它的原名,我们只能用这个别名去操作它
//加上const之后,编译器将代码修改为 int temp=10; int&b=temp;
//b = 20;//错误,加了const之后变为只读,不可以修改
}
分析:
①引用不可以指向一个常量,引用必须引用的是内存上的合法内存空间
②如果一个引用指向常量并用const来修饰,那么就是正确的,加了const之后就相当于编译器自动创建了一个临时变量来存放这个常量。加了const之后引用就使只读,不可修改。
【例2】
//打印数据的函数
void Show(int &s)
{
s = 1000;//修改形参s的值
cout << "形参s=" << s << endl;
}
//主函数
int main()
{
int a = 100;//创建一个变量
Show(a);//调用Show函数把a以引用的方式传入
cout << "实参a=" << a << endl;
return 0;
}
运行结果:
结果分析:
在打印数据函数Show中,不小心或者不经意修改了形参s的值,而主函数中的实参a也被同步修改了,因为s是a的一个别名,所以修改了别名s的值就相当于修改了a的值,但是在主函数中的a并不想被修改。代码比较少的时候我们会发现不小心修改了形参的值,但是如果代码非常多,我们不容易发现,并且忘了Show函数一开始只是打印数据的功能,那么就会输出错误的结果,不容易找到出错原因。为了防止这个误操作,我们就在形参的前面加一个const来修饰它:void Show(const int &s)
,这时候去修改Show函数中的形参s的值就会编译不通过:
这时,我们就不能在被调函数的函数体内修改形参的值,也就不会使主调函数中的实参被修改而发生误操作。