【C++】引用变量相关

一:什么是引用变量
在自定义函数时,常常需要使用到形参。由于只能在函数体内部使用并且只能由外部实参传递数据给形参,形参也被称为形式参数或者虚拟参数。但是需要注意的一点是,形参是在调用函数时被创建并分配内存,在结束调用时释放内存,并不是真正“虚拟”的。当形参和实参的类型不是指针的时候,形参和实参是不同的变量,在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。比如一个简单的交换两个变量值的程序:

void Swap(int a,int b)  //a,b作为形参,在函数调用时被创建,在调用结束时释放内存
{   //交换两个参数数值 
 int temp;
 temp = a;
 a = b;
 b = temp;
}
int main(){
 int x = 1;
 int y = 2;
 Swap(x,y);  //调用函数Swap() 
 cout<<"x="<<x<<" y="<<y;
 return 0;
}

那么这样的代码能够改变两个变量的数值吗?
在这里插入图片描述
可见,对形参进行操作,没有改变原参数的值。同时,return关键字一次只能返回一个数据,通过return简单修改达到目的并不容易。或许可以考虑将变量设为全局变量,或者将原来的数据变为数组从而使用指针对其操作,但是这未免增大了工作量。
这时候可能不免想到,如果能够在调用函数的时候使用原始数据而非其拷贝,那么这个问题就能够得到解决。使用指针是一种方法,因为传递的参数是原始数据存储的地址,对形参的操作也是对实参的操作。但同时,C++也提供了另一种选择——引用变量。引用变量相当于已定义变量的一个别称。比如说一个人叫做张三,他的小名叫做小三,那么张三和小三都指的是同一个人。同样,引用变量和原始变量都指的是同一个内存单元中的数据,对引用变量的操作也是对原始数据的操作。引用不仅给函数处理大型结构提供了一种更便捷的途径,同时,在设计类的时候,引用也是必不可少的。那么接下来,简单介绍引用变量的创建和使用。

二:引用变量的创建和使用
<1>引用变量的创建
在之前的博客指针及动态管理内存相关中,介绍过操作符&,C和C++用其指示变量的地址,而C++还赋予了其另一个含义:用来声明引用。比如用bro作为变量brother的引用:

int brother = 10;
int & bro = brother;

需要注意的是,这里的&不是取地址操作符,而是在声明引用,正如int*是指向int类型变量的指针一样,int&是指向int的引用。在上面的范例中,代表brother和bro指向同样的内存单元和值。可以加上几行代码验证这个观点:

int brother;
 int & bro = brother;
 cout<<"address of brother is "<<&brother<<", brother = "<<brother<<endl;  //这里的&是取地址操作符
 cout<<"address of bro is "<<&bro<<", bro = "<<bro<<endl;

上述代码的运行结果为:
在这里插入图片描述
可见,引用变量和原始变量指向的内存单元和值是完全相同的。如果初次接触到引用变量,可能在区分引用和取地址操作符方面有困惑,但实际上二者的区别还是容易看出的:

 int brother;
 int & bro = brother; //声明引用变量 
 int * br = &brother; //声明指针

在声明指针时,可以先声明指针,再对指针进行初始化。但是,和const指针一样,引用变量的初始化必须在声明引用变量时进行!

 int a;
 //这样声明和初始化指针是正确的
 int * b;
 b = &a;  
 //这样声明和初始化引用是错误的!!
 int & c;
 c = b; 

同时,和const指针一样,引用变量一旦和某个变量相关联,那么就不能改变引用变量的效忠对象。如果在声明int &a = b;以后又写a = c;那么a不会变为c的引用,这种代码只会将c的值赋给a和b。

<2>将引用作为函数参数
在本博客的开篇,引入了交换两个变量数值的范例程序,那么使用引用参数代替形式参数,是不是就能够满足要求了呢?为了感受引用和指针的区别和联系,将同时使用三种方式交换变量:

void Swapv(int a,int b) //按值传参,交换两个参数数值   
{   
 int temp;
 temp = a;
 a = b;
 b = temp;
}
void Swapr(int &a,int &b) //使用引用变量,交换两个参数数值   
{   
 int temp;
 temp = a;
 a = b;
 b = temp;
}
void Swapp(int *a,int *b) //使用指针传参,交换两个参数数值   
{   
 int temp;
 temp = *a;
 *a = *b;
 *b = temp;
}
 int main(){
 int x = 1;
 int y = 2;
 Swapv(x,y);  //调用函数Swapv() 
 cout<<"x="<<x<<" y="<<y<<endl;
 Swapr(x,y);  //调用函数Swapr() 
 cout<<"x="<<x<<" y="<<y<<endl;
 Swapp(&x,&y);  //调用函数Swapp(),使用取地址操作符&取得变量的指针 
 cout<<"x="<<x<<" y="<<y;
 return 0;
}

那么这个测试用例的输出结果为:
在这里插入图片描述
可见,变量x和y交换了两次,按值传参没有改变两个变量的值,而引用变量和指针传参都成功交换了两个变量的值。

<3>引用的注意点
需要注意的是,传统的按值传参虽然无法直接对原始数据进行操作,但是这样的属性同时也有保护原始数据完整性的作用。比如下面这个函数:

int add(int &a){
 a += 1;
 return a;
}
int main(){
 int x = 1;
 cout<<x<<" + 1 = "<<add(x);
 return 0;
}

这样的代码的输出结果为:
在这里插入图片描述
之所以产生这样的结果,就是因为没有注意到引用参数无法保证初始数据的私有性产生的错误。

三:将引用运用到结构中
引用非常适合结构和类,尤其是结构在没有类公有函数时,使用引用参数能够很好的对结构进行操作。事实上,引入引用主要是为了类和结构而不是为了作为一种基本内置类型的。那么接下来用一段范例代码演示如何将结构运用到结构中:

using namespace std;
struct Student{  //声明一个结构Student 
 char name[10];
 int age;
};
//定义一个引用变量传参,返回一个对结构的引用的函数 
const Student & add_age(Student &stu){
 stu.age++;
 return stu;
}
int main(){
 Student stu1 = {"Alex",10};  //创建一个结构体实例 
 cout<<stu1.name<<"'s age is "<<stu1.age<<endl;
 Student stu2 = add_age(stu1);  //新创建一个结构体实例,将add_age()返回的引用赋给他 
 cout<<"Now "<<stu2.name<<"'s age is "<<stu2.age<<endl;
 cout<<"After add age,"<<stu2.name<<"'s age is "<<add_age(stu2).age<<endl;//输出再一次调用add_age()后的原结构体信息 
 return 0;
}

以上代码的输出结果为:
在这里插入图片描述
可知达到了预期的效果。需要注意的是,返回引用的函数实际上是被引用变量的别名,即是说:

const Student & add_age(Student &stu)

这样的函数的返回实际上就是被引用的变量。即是说即使没有上面代码中的新建一个结构,不将add_age()函数返回值赋给其他变量,作为函数参数的引用的被引用对象也被改变了。即是说:

 add_age(stu1);  
 cout<<"Now "<<stu1.name<<"'s age is "<<stu1.age<<endl;

也能得到和之前一样的结果。同时,需要注意的是,不要让函数返回临时变量的引用,因为临时变量在函数结束调用时会被释放内存,即是说,这样的代码是应当避免写下的:

const Student & add_age(Student &stu){
 Student stu1;
 stu1 = stu;
 return stu1;
} 

四:将引用运用到类中
在类中使用引用参数和在结构中使用引用十分相似,所以我将使用和上面类似的一个测试程序来演示如何将引用运用到类中:

#include <iostream>
#include <cstring>
using namespace std;
class Student{  //创建类Student 
private:
 char name[10];
 int age;
public:
 Student(char * n,int a);  //构造函数 
    const Student & add_age(Student &); //接受类的引用,返回改变后类的引用 
 void show();
};
Student::Student(char * n,int a){ //初始化姓名和年龄 
 strncpy(name,n,10);
 age = a;
}
const Student & Student::add_age(Student &stu){
 stu.age += 1;
 return stu;
}
void Student::show(){
 cout<<name<<"'s age is "<<age<<endl;
}
int main(){
 Student stu1("Alex",20); //创建类实例
 stu1.show();
 stu1.add_age(stu1);    //调用stu1的成员函数,将stu1自身的引用作为参数传入
 cout<<"after add ";
 stu1.show();
 return 0;
}

上述代码的执行结果为:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值