C++之引用

C++之引用,第1张

工具为了解决问题而创造出来,不要为了制造工具而制造工具。

目录

概念

引用的特性

引用的常见用法

引用作为函数返回值

引用作为参数

常引用

引用的效率

引用与指针

指针与引用的区别


概念

引用(reference)就是C++对C语言的重要扩充,C++在C++1.0版本中添加了引用等一系列概念。

引用并不是重新定义一个变量,引用是目标变量的一个别名,对引用的 *** 作与对变量的直接 *** 作完全一样。

类型标识符 & 引用名 = 目标变量名

引用的定义方法如上。

引用类型 必须和引用 实体 同种类型
引用的特性

1.引用必须在定义时初始化。

2.一个变量可以有多个引用。

3.引用一旦引用一个实体,不能再引用其它实体。

4.引用本身不占内存空间,与被引用对象共用内存。

5.不能建立引用的数组。因为数组是一个由若干个元素所组成的集合,所以无法建立一个由引用组成的集合。但是可以建立数组的引用。

引用的常见用法 引用作为函数返回值

引用类型是可以作为函数返回值的,但是它其中涉及到的部分要素容易被忽略,导致程序出错。

int& add(int a, int b) {
    int ret = a + b;
    return ret;
}

int main() {
    int& r = add(2, 3);
    return 0;
}

如以上代码示例中, 如果运行,在编译器中可能不会报错,但是会有一个警告:

 这意味着我们函数返回的空间可能是非法的。

当我们将 ret 的引用作为返回值返回给主函数时,ret 所占用的空间在编译器中已经成为了无主的地盘了。如果我们在 add 函数执行之后再加上其它的内容,就可能将 r 引用空间再次占用,导致报错。

所以,引用作为函数返回值时,一定要注意,不能返回函数栈上的空间。

由此我们也可以得到引用类型作为函数返回值的要求:

返回实体的生命周期一定要比函数长,即不能随着函数的结束而销毁。 

引用作为参数

在C++中,参数传递的方式有两种:传值与传引用。

传值是我们都熟悉的,函数的形参是实参的一个拷贝,函数在执行过程中,形参的改变不会影响到实参。

传引用的传参方式下,形参是对应实参的引用,而引用的一大特性在于:对引用的 *** 作与对实体的 *** 作完全一致。即,通过传引用的方式传入参数,我们对形参的修改就等同于对实参的修改。

#include 

using std::cout;
using std::endl;

void swap1(int a1, int b1) {
    int tmp = a1;
    a1 = b1;
    b1 = tmp;
}

void swap2(int& a2, int& b2) {
    int tmp = a2;
    a2 = b2;
    b2 = tmp;
}

int main() {
    int a = 1;
    int b = 230;
    cout << "初始:" << "a = " << a << " b = " << b << endl;
    swap1(a, b);
    cout << "swap1:" << "a = " << a << " b = " << b << endl;
    swap2(a, b);
    cout << "swap2:" << "a = " << a << " b = " << b << endl;
    return 0;
}

 结果是显而易见的,单纯的传值是无法完成我们预期的交换功能的,而传引用却可以。

这是因为传值传入的形参本质是实参的拷贝,两者的地址并不相同,对形参的 *** 作自然无法影响到实参。

而传引用传入的引用是实参的别名,形参实参二者使用的是同一块地址空间,对形参的 *** 作自然也就可以对实参造成影响。

我们之前在C语言中绝大多数使用一级指针的地方搜可以使用引用代替,同理,大多数使用二级指针的地方可以使用一级指针的引用代替。

常引用

常引用,顾名思义,即 const 类型的引用。定义引用时,在前面加 const 关键字,则该引用为常引用。

常引用与普通引用的差别在于——不能通过常引用来修改实体的内容。

注意,只是不能通过常引用来修改实体内容,如果实体本身不是 const 类型,我们可以通过修改其它引用或实体本身来达到修改的目的。 

#include 

using std::cout;
using std::endl;

int main() {
    int a = 10;
    const int& ca = a;
    cout << ca << endl;
    a = 20;
    cout << ca << endl;
    int& nca = a;
    nca = 30;
    cout << ca << endl;
    return 0;
}

 

 对于 const 常量的引用只能是常引用,同理,对于常引用的引用只能是常引用。 

 常引用在特殊情况下可以产生临时变量。 (该情况并不具备太高的实用性)

常引用引用不同类型的变量。 

#include 

using std::cout;
using std::endl;

int main() {
    double d = 12.3;
    const int& cd = d;
    cout << cd << endl;

    d = 32.1;
    cout << cd << endl;
    return 0;
}

 

可见,以上代码并没有发生报错,并且还成功运行了起来,但是这次对于实体的修改并没有影响到实体的常引用。

通过对代码运行时的进一步分析可以得到:

 变量实体与其常引用的地址并不相同。所以,对变量实体的修改与常引用并没有关系。

在对 double 类型变量 d 进行常引用时,因为变量类型并不相同,编译器便会创建一个 int 类型的临时变量用来存储 double 类型变量的整型部分的数据,然后将临时变量给常引用作为实体。

同时,由于临时空间是编译器创建的,用户无法知道空间的名字以及地址,所以用户无法修改临时空间中的内容,所以该空间具有常性,所以可以使用 const 类型的变量来引用。

引用的效率

我们可以通过令程序多次重复调用满足某些条件的函数,计算第一次开始调用到最后一次调用完毕之间的时间间隔,通过比较得出传值,传引用;普通返回值,引用作为返回值的效率关系。

#include 
#include 

using namespace std;

struct A { int a[10000]; };

A TestFunc1(A a) { return a; }
A TestFunc2(A& a) { return a; }
A& TestFunc3(A& a) { return a; }

int main()
{
    A a;
    // 以值作为函数参数
    size_t begin1 = clock();
    for (size_t i = 0; i < 10000; ++i) {
        TestFunc1(a);
    }
    size_t end1 = clock();
    // 以引用作为函数参数
    size_t begin2 = clock();
    for (size_t i = 0; i < 10000; ++i) {
        TestFunc2(a);
    }
    size_t end2 = clock();
    //以引用作为返回值
    size_t begin3 = clock();
    for (size_t i = 0; i < 10000; ++i) {
        TestFunc3(a);
    }
    size_t end3 = clock();
    // 分别计算三个函数运行结束后的时间
    cout << "A_TestFunc1(A)-time:" << end1 - begin1 << endl;
    cout << "A_TestFunc2(A&)-time:" << end2 - begin2 << endl;
    cout << "A&_TestFunc3(A&)-time:" << end3 - begin3 << endl;
    return 0;
}

运行结果:  

 可见传引用的效率明显高于传值,引用作为返回值的效率明显高于普通返回值

引用与指针
int main() {
    int a = 3;
    int& ra = a;
    int* pa = &a;
    a = a + 1;
    ra = ra + 1;
    *pa = (*pa) + 1;
    return 0;
}

 

 由以上代码部分反汇编可以看出,从汇编指令的角度来看,对指针与引用在底层的实现与相关 *** 作是完全一致的。即,从底层实现方式上:指针与引用的实现方法是一样的,即引用在底层是按照指针的方式来实现的。

而在概念层面,为了方便使用者的理解,C++将引用解释为:引用是一个别名,与其被引用的实体公用一分内存空间,编译器不会给引用变量分配新的内存空间。

所以,引用实际上是有空间的,空间中的内容是引用实体的地址。

T& 可以看作是 T * const 类型的指针。

指针与引用的区别

1.概念上,引用是定义一个变量的别名,指针是存储一个地址。

2.引用在定义时必须初始化,指针没有要求。

3.引用在初始化时引用一个实体后,就不能再引用其它实体,指针可以再任何时候指向任何一个同类型实体。

4.没有空引用,但有NULL指针

5.再 sizeof 中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节数。

6.引用自加为实体加一,指针自加为向后偏移一个类型大小。

7.有多级指针,没有多级引用。

8.访问实体方式不同,指针需要显式解引用,引用由编译器自己处理。

9.引用比指针更安全。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/675948.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-19
下一篇 2022-04-19

发表评论

登录后才能评论

评论列表(0条)

保存