c++之引用(五千字长文详解!)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++之引用(五千字长文详解!)相关的知识,希望对你有一定的参考价值。

c++之引用详解

#include <iostream>
using namespace std;

int main()

	int a = 0;
	int& b = a;//引用
	a++;
	printf("&a = %p,a = %d\\n", &a, a);
	b++;
	printf("&b = %p,b = %d\\n", &b, b);
	return 0;

引用在实际当中的价值

做参数!

//可用于减少拷贝次数!
void Swap(int& left, int& right)

   int temp = left;
   left = right;
   right = temp;
//这种就叫做输出型参数!就是说我的参数在里面进行修改,然后还要传回来!

BTNode* BinarYTreeCreat(BTDataType* a,int* pi);//此处的pi就可以使用 引用来代替!

//可以用于简化二级指针!
typedef struct ListNode

	int val;
	struct ListNode* next;
LTNode,*PTNode;

void SlistPushBack(LTNode** phead, int val);//这是最原始的链表插入,因为要改变头所以要引入二级指针!
void SlistPushBack(LTNode*& phead, int val);//这是对原本的基础上进行改进,使用引用代替二级指针!

void SlistPushBack(PTNode& phead, int val);//这是最后的优化版本!

做返回值

int& Count()

    static int n = 0;
    n++;
    return n ;
//着叫做引用返回

int Count()

    static int n = 0;
    return n;
//这叫做传值引用

传值返回

  • 讲n的数据拷贝到临时变量

  • 最后临时变量拷贝到变量a

    为了直观理解我将count的栈帧画在了main的上方,从物理上面看,main的栈帧是在上面的,因为栈帧是向下生长的!

  • 引用返回

  • 直接从n将数据拷贝到变量a

  • 对比传值返回和引用返回的效率

    以值作为参数或者返回值类型,在传参和返回期间,函数不会==直接传递实参或者将变量本身直接返回==,而是传递实参或者返回变量的==一份临时的拷贝==,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是==当参数或者返回值类型非常大时,效率就更低!==

    #include <time.h>
    struct A int a[10000]; ;
    void TestFunc1(A a)
    void TestFunc2(A& a)
    void TestRefAndValue()
    
     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();
    // 分别计算两个函数运行结束后的时间
     cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
     cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
    
    int main()
    
    	TestRefAndValue();
    
    

    关于static在引用中的作用!

    int& Count()
    
        static int n = 0;
        n++;
        return n ;
    
    

    在这种状态下进行访问,我们可能会得到我们想要的数据,也可能能得不到我们想要的数据,都有可能!

    关于引用返回的结论

    关于临时向量

    引用返回的意义

    关于引用和重载

    #include <iostream>
    using namespace std;
    void swap(int& a,int& b)
    
        int temp = a;
        a = b;
        b = temp;
    
    void swap(int a,int b)
    
        int temp = a;
        a = b;
        b = temp;
    
    int maim()
    
        int a =1;
        int b = 1;
        swap(a,b); //程序会报错!因为虽然可以构成重载!但是编译器不知道应该调用那个函数!   
        return 0;
    
    

    常引用

    int a = 10;
    

    这个变量a的类型为int ,它的权限能读能写,我们既可以访问,也可以修改

    const int a = 10;
    

    这个变量的类型为 const int ,它的权限只能读不能写,我们只能对它进行访问,但是不能对该变量进行修改!

    权限的赋予

    当我们将一个引用进行初始化的时候就是将权限进行了一次赋予

    int a = 10;
    int& test = a;
    

    ==这时候 变量test和变量a对于同一块内存空间都有着相同的权限!==因为两者同为int类型!

    int a = 10;
    const int& test = a;
    

    将int类型的变量,对常引用进行初始化,这就是典型的权限缩小

    因为常引用初始化后就可以当成一般变量进行使用,但是const 前缀的变量不可以对内存数据进行修改,而原本的变量仍然可以,这就是权限缩小!

    const int a = 10;
    int& test = a;
    

    **这就是权限放大!原本的变量为const int类型,初始化后只能对该变量进行访问但是不能修改!我们使用该变量对int&类型的引用进行初始化!就会发生权限放大!因为int类型的变量是被允许对内存进行修改和读取的!**这是不被允许的!会导致编译报错!

    int a = 10;
    const int& b = a;
    b++;//报错!
    a++;//可执行!
    

    b++会报错是因为变量b的权限仅仅只有读,不能写!但是a的变量权限可读可写,所以a++这行代码是可执行的!虽然它们同属一块内存空间,但是权限的不同!而且变量b的权限虽然缩小,但是变量a的权限仍然是不变的!

    常引用的实际价值

    常引用与临时变量

    double a = 1.00;
    int& b = (int*)a;
    

    当我们第一次看到这个代码的时候,我们可能会觉得这没有问题啊?double强制转成int然后作为目标变量名将b初始化。

    但是这个是个错误的代码!因为我们上文说过强制类型转换必产生==临时变量==,而临时变量具有==常性!==也就是说类型是const开头的!这就犯了权限放大的错误!

    正确的代码应该是

    double a = 1.00;
    const int& b = (int*)a;
    //下面这样也行
    //const int&b = a;
    //因为发生了隐形的强制类型转换!上面的是显性的强制类型转换!
    //但是无论是隐形还是显性都会产生临时变量!
    

    为了避免忘记,也请记住上文提到过的传值返回的是临时变量!具有常性!

    int Count()
    
        int n = 0;
        n++;
        return n;
    
    int main()
    
        int& a = Count();//显然这也是错误的!返回值的实际真正类型为const int!
        return 0;
    
    

    正确的代码为

    int Count()
    
        int n = 0;
        n++;
        return n;
    
    int main()
    
        const int& a = Count();
        return 0;
    
    

    常引用的常量初始化

    我们已知引用必须初始化!但是是否能使用常量对于引用进行初始化呢?

    int& a = 1;
    

    上面的代码是错误的!为什么?

    因为在c++中常量的类型为const int!还是老样子犯了权限放大的错误!所以解决办法也是一样的!

    const int& a = 1;//这样就可以对使用常量对a进行初始化了!
    

    常引用与缺省参数

    void func(int& N = 10)
    
        //...
    
    

    由上面的结论我相信,读者们也可以看出了吧,我们已知缺省参数一定要是全局变量或者常数,当我们缺省值使用常数的时候,类型为 const int,该代码又发生了权限放大!所以还是一样的修改!

    void func(const int& N = 10)
    
        //...
    
    

    结论

    引用和指针的不同点

    1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
    2. . 引用在定义时必须初始化,指针没有要求
    3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体
    4. 没有NULL引用,但有NULL指针(就是说引用不可以使用NULL进行初始化!)
    5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)
    6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
    7. 有多级指针,但是没有多级引用
    8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
    9. 引用比指针使用起来相对更安全

    常引用的使用注意

    1. 注意权限的放大和缩小
    2. 注意强制类型转换,传值返回产生了临时变量!
    3. 注意常数的类型!

    一点补充

    在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

    int main()
    
        int a = 10;
        int& ra = a;
        
        cout << "&a = " << &a <<endl;
        cout << "&ra = " << &ra <<endl;
    
    
    

    但是在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

    int main()
    
    	int a = 10;
    	int& ra = a;
    	ra = 20;
    	return 0;
    
    
    int main()
    
    	int a = 10;
    	int* pa = a;
    	*pa = 20;
    	return 0;
    
    

    以上是关于c++之引用(五千字长文详解!)的主要内容,如果未能解决你的问题,请参考以下文章

    c++之类和对象——类的定义,存储方式,this指针!(五千字长文详解!)

    C++之string的底层简单实现!(七千字长文详解)

    五千字长文详解Istio实践之熔断和限流工作原理

    初步认识c++之命名空间详解(千字长文带你刨析你命名空间的细节)

    拷贝构造,赋值运算符重载(六千字长文详解!)

    const成员,流插入,流提取重载,初始化列表! 流插入,流提取的重载(6千字长文详解!)