C++引用详解

引用

参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。

对于基本类型的数据,它们占用的内存往往只有几个字节,对它们进行内存拷贝非常快速。而数组、结构体、对象是一系列数据的集合,数据的数量没有限制,可能很少,也可能非常多,对它们进行频繁的内存拷贝可能会消耗很多时间,拖慢程序的执行效率。

C/C++ 中禁止在函数调用时直接传递数组的内容,而是强制传递数组指针;而对于结构体和对象没有这种限制,调用函数时既可以传递指针,也可以直接传递内容;为了提高效率,建议传递指针。

引用(Reference)是 C++ 相对于C语言的又一个扩充。引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据。引用必须在定义的同时初始化,不能像指针那样,先声明在赋值,并且一旦与某个变量关联起来,就将一直效忠于它。

#include <iostream>
using namespace std;
int main() {
    int a = 666;
    int &b = a;
    b = 6666;
    cout << a << ", " << b << endl;
    return 0;

将引用作为函数参数

#include <iostream>
using namespace std;
void swap1(int a, int b);
void swap2(int *p1, int *p2);
void swap3(int &r1, int &r2);
int main() {
    int num1, num2;
    cout << "Input two integers: ";
    cin >> num1 >> num2;
    swap1(num1, num2);
    cout << num1 << " " << num2 << endl;
    cout << "Input two integers: ";
    cin >> num1 >> num2;
    swap2(&num1, &num2);
    cout << num1 << " " << num2 << endl;
    cout << "Input two integers: ";
    cin >> num1 >> num2;
    swap3(num1, num2);
    cout << num1 << " " << num2 << endl;
    return 0;
}
//直接传递参数内容
void swap1(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}
//传递指针
void swap2(int *p1, int *p2) {
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}
//按引用传参
void swap3(int &r1, int &r2) {
    int temp = r1;
    r1 = r2;
    r2 = temp;
}
结果:
Input two integers: 12 34
12 34
Input two integers: 88 99
99 88
Input two integers: 100 200
200 100

由结果可以看出,引用和指针方法都成功进行了值的交换,而按值传递不能实现值的交换。在swap1中变量a和b是复制了num1和num2的值的新变量,交换a和b的值并不会影响num1和num2的值,而在swap3中,变量r1和r2是num1和num2的别名,交换r1和r2相当于交换num1和num2的值。

将引用作为函数返回值

1.引用作为函数的返回值时,必须在定义函数时在函数名前将&
2.用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本

#include<iostream>
using namespace std;
float temp;
float fn1(float r){
    temp=r*r*3.14;
    return temp;
} 
float &fn2(float r){ //&说明返回的是temp的引用,换句话说就是返回temp本身
    temp=r*r*3.14;
    return temp;
}
int main(){
    float a=fn1(5.0); //case 1:返回值
    //float &b=fn1(5.0); //case 2:用函数的返回值作为引用的初始化值 [Error] invalid initialization of non-const reference of type 'float&' from an rvalue of type 'float'
                           //(有些编译器可以成功编译该语句,但会给出一个warning) 
    float c=fn2(5.0);//case 3:返回引用
    float &d=fn2(5.0);//case 4:用函数返回的引用作为新引用的初始化值
    cout<<a<<endl;//78.5
    cout<<b<<endl;//78.5
    cout<<c<<endl;//78.5
    cout<<d<<endl;//78.5
    return 0;
}

引用在本质上是什么,与指针的区别

从概念上讲。指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。

而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。

在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的:

指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。

而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

引用传递和指针传递是不同的,虽然它们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。

程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

指针和引用的相同点和不同点:
★相同点:
●都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
★不同点:
●指针是一个实体,而引用仅是个别名;
●引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
●引用没有const,指针有const,const的指针不可变;
●引用不能为空,指针可以为空;
●“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
●指针和引用的自增(++)自减(–)运算意义不一样。对指针使用 ++ 表示指向下一份数据,对引用使用 ++ 表示它所指代的数据本身加 1;

引用不能绑定到临时数据

指针就是数据或代码在内存中的地址,指针变量指向的就是内存中的数据或代码。这里有一个关键词需要强调,就是内存,指针只能指向内存,不能指向寄存器或者硬盘,因为寄存器和硬盘没法寻址。

其实 C++ 代码中的大部分内容都是放在内存中的,例如定义的变量、创建的对象、字符串常量、函数形参、函数体本身、new或malloc()分配的内存等,这些内容都可以用&来获取地址,进而用指针指向它们。除此之外,还有一些我们平时不太留意的临时数据,例如表达式的结果、函数的返回值等,它们可能会放在内存中,也可能会放在寄存器中。一旦它们被放到了寄存器中,就没法用&获取它们的地址了,也就没法用指针指向它们了。

这些表达式的结果都会被放到寄存器中,尝试用&获取它们的地址都是错误的

int n = 100, m = 200;
int *p1 = &(m + n);    //m + n 的结果为 300
int *p2 = &(n + 100);  //n + 100 的结果为 200
bool *p4 = &(m < n);   //m < n 的结果为 false

寄存器离 CPU 近,并且速度比内存快,将临时数据放到寄存器是为了加快程序运行。但是寄存器的数量是非常有限的,容纳不下较大的数据,所以只能将较小的临时数据放在寄存器中。int、double、bool、char 等基本类型的数据往往不超过 8 个字节,用一两个寄存器就能存储,所以这些类型的临时数据通常会放到寄存器中;而对象、结构体变量是自定义类型的数据,大小不可预测,所以这些类型的临时数据通常会放到内存中。

编译器会为const引用创建临时变量

临时数据无法寻址,不能写入。而引用是绑定到一份数据时,就可以通过引用对数据进行读取和修改。即使为临时数据创建一个临时变量的时候,修改的也是临时变量的数据,而不是源数据。这样引用所绑定的数据和源数据不能同步更新,失去了操作数据的作用。

void swap(int &r1, int &r2){
    int temp = r1;
    r1 = r2;
    r2 = temp;
}
如果编译器为r1和r2创建了临时变量,那么r1和r2的值怎样都不会发生交换。

常引用只能通过const引用读取数据的值,而不能修改数据的值。所以不用考虑数据更新的问题,也不会产生两份数据。

//该函数用来判断数字是否为奇数
bool isOdd(const int &n){  //改为常引用
    if(n/2 == 0){
        return false;
    }else{
        return true;
    }
}
int main(){
    int a = 100;//
	isOdd(a);  //正确
	isOdd(a + 9);  //正确
	isOdd(27);  //正确
	isOdd(23 + 55);  //正确
}
对于第12行的代码,编译器不会创建临时变量,引用n会直接绑定a,而对于13~15行代码,编译器会创建临时变量来存储临时数据。即编译器只有在必要的时候才会创建临时变量。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值