C++领进门(第二讲)

目录

 

6.引用

6.1引用概念

6.2 引用特征

​编辑

6.3 应用场景

6.3.1.引用做参数

6.3.2 引用做返回值

​编辑

6.3.3 传值、传引用效率比较

 

6.3.4值和引用的作为返回值类型的性能比较

6.4 常引用

6.5 指针和引用的底层原理

6.6 引用和指针的不同点:🚀


本次内容大纲:

230de52ff835418a8b1e358c8eef39a9.png

6.引用

6.1引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。

如: 孙悟空也叫齐天大圣,引用(别名)的意思就是跟你起了一个外号

类型& 引用变量名(对象名) = 引用实体;

如: int& b = a;

这里b就是a引用(别名)

例:

#include <iostream>

int main()
{
    int a = 0;
    int& b = a;
    int& c = b;
    int& d = c;

    std::cout << &a << std::endl;
    std::cout << &b << std::endl;
    std::cout << &c << std::endl;
    std::cout << &d << std::endl;

	return 0;
}

测试运行:

0db63c2f9a794e0d8074d378eb089a5a.png

由上图可知a和b确实是在一个内存空间

注:引用类型必须和引用实体是同种类型的
 

6.2 引用特征

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

void TestRef()
{
   int& ra;   // 该条语句编译时会出错 
}

测试运行:

ac284dd3403b428ebbb7c071a5db5d66.png
 

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

#include <stdio.h>

int main()
{
    int a = 0;
    int& b = a;
    int& c = b;
    int& d = c;

    c++;
    d++;

    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
    std::cout << d << std::endl;
    return 0;
}

 测试运行:

ebd64c3a5ed54c7d9fe32702b2834148.png

值也是相同的f63a2e37b34740e3aa89080f931864de.png

这是对a进行多次引用,给d和c自加就等于a自加

 

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

#include <iostream>

int main()
{
    int a = 0;
    int& b = a;

    int x = 10;
    b = x;

    std::cout << &b << std::endl;
    std::cout << &a << std::endl;
    std::cout << &x << std::endl;
    return 0;
}

 测试运行:

dc6d6cb8371242f6ad840a3ff91213f7.png

 

6.3 应用场景

6.3.1.引用做参数

应用场景1:

void Swap1(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

void Swap2(int*& a, int*& b)
{
	int* tmp = a;
	a = b;
	b = tmp;
}

int main()
{
    int a = 1, b = 2;
    Swap1(a,b);
    std::cout << a << ' ' << b << std::endl;
    int* p1 = &a, * p2 = &b;
    std::cout << p1 << ' ' << p2 << std::endl;
    Swap2(p1, p2);
    std::cout << p1 << ' ' << p2 << std::endl;
    return 0;
}

注:int*&是对一级指针的引用,Swap2的作用是互换p1,p2的地址

 测试运行:

33d019caec8b476a90b654e1595a28ae.png

应用情景2:

#include <iostream>

typedef struct ListNode
{
    int val;
    ListNode* next;
}LTNode, *PLTNode;  //重定义了两个新名称


//传参的三种接收方式
//第一种方法
void LTPushBack(struct ListNode*& phead, int x);

//第二种方法
void LTPushBack(LTNode*& phead, int x);

//第三种方法
void LTPushBack(PLTNode& phead, int x);

int main()
{
    //第二种方式传参
    LTNode* plist = NULL;  
    LTPushBack(plist, 10);

    //第三种方式传参
    PLTNode plist = NULL;
    LTPushBack(plist, 10);
    return 0;
}

选一种使用即可

6.3.2 引用做返回值

beff4cc9019748958afcb2db7adc28d3.png

Count函数中最后返回时不是直接将n返回,是先把n的值放到寄存器(临时变量)中,会到main函数后,再将临时变量的值给ret,又由于n变量在静态区中,所以不会被销毁

e51f67404b9d422e9a9de46771daf034.png

由于n将值传到main函数中后,Count的栈帧就销毁了,销毁只是说将这部分空间的使用权归还给系统

0903970359f24d97be4556206a1a9602.png

78271fb7062144cbbd75bc0031800107.png

将Count函数在调用一次,这次传20,结果改变了ret的取值

1cd461973dbd448b853ab51a04389c29.png

b55d9768f06e40db90183fd8862d3ba8.png

在main函数中开辟了一个局部变量rand();之后,系统正好刷新了n所在的空间,ret就变成了随机值

总结:

1.基本任何场景都可以使用引用传参

2.谨慎用引用做返回值,函数出了作用域,对象不在了,就不能使用引用传参,还在就可以使用引用返回

938d674760ec4ecf9af2b87b7a97db40.png

注:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

 

这是我们之前写的 静态顺序表 ,当时没学引用写起来很麻烦,这里改用引用之后会很轻松

0932697181c7417cad9b968e2ce6a79c.png

一个SLAT函数就代替了SLGet和SLModify两个函数

6.3.3 传值、传引用效率比较

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

#include <time.h>

struct A
{ 
   a[10000]; 
};

A a;
// 值返回
void TestFunc1(A& a) 
{ 
  ;
}
// 引用返回
void TestFunc2(A a)
{ 
    return a;
}
void TestReturnByRefOrValue()
{
    A a;
    // 以值作为函数的返回值类型
    size_t begin1 = clock();
    for (size_t i = 0; i < 100000; ++i)
          TestFunc1(a);
    size_t end1 = clock();

    // 以引用作为函数的返回值类型
    size_t begin2 = clock();
    for (size_t i = 0; i < 100000; ++i)
          TestFunc2(a);
    size_t end2 = clock();

     // 计算两个函数运算完成之后的时间
    cout << "TestFunc1 time:" << end1 - begin1 << endl;
    cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

测试运行:

1fdd931ce4244a68955fff0c43544c4c.png

可以看到引用传值比值传参快多了

1.引用做参数(做输出型参数)

2.引用做参数(减少拷贝,提高效率)不需要开辟额外的临时空间

 

6.3.4值和引用的作为返回值类型的性能比较

#include <time.h>

struct A
{ 
    int a[10000]; 
};

A a;
// 值返回
A TestFunc1() 
{ 
    return a;
}
// 引用返回
A& TestFunc2()
{ 
    return a;
}
void TestReturnByRefOrValue()
{
    // 以值作为函数的返回值类型
    size_t begin1 = clock();
    for (size_t i = 0; i < 100000; ++i)
          TestFunc1();
    size_t end1 = clock();

    // 以引用作为函数的返回值类型
    size_t begin2 = clock();
    for (size_t i = 0; i < 100000; ++i)
          TestFunc2();
    size_t end2 = clock();

     // 计算两个函数运算完成之后的时间
    cout << "TestFunc1 time:" << end1 - begin1 << endl;
    cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

测试运行:

050f7c6ee20a4aa9b9806c571a2da102.png

由上图知引用提高的效率还是很可观的

1.引用做返回值(可以修改返回值,或者获得返回值)

2.引用做返回值(减少拷贝,提高效率)即:不需要临时变量来拷贝返回的值

 

6.4 常引用

这样可以

int main()
{
    int a = 10;
    int& b = a;
    const int& c = a;
    
    ++a;

    cout << c << endl;
    return 0;
}

这样不行

int main()
{
    int a = 10;
    int& b = a;
    const int& c = a;
    
    --c;
    cout << c << endl;
    return 0;
}

06ede2c572804dc784d442fed000ba2e.png

加上const意思就是c就不能改变,有所限制,没有const就无所拘束,随意赋值,在引用过程中权限可以平移和缩小,const int& c = a;就是平移

在看一个例子:

int main()
{
    double dd = 1.11;
    int ii = dd;
    
    const int& rii = dd;
    return 0;
}

我们将dd传给ii的时候,存在一个隐式转换,那dd是直接将值给ii吗?我们来看一下汇编代码来看一下编译器是怎么做的

6f32f936ef2a4062948822c858a775e0.png

临时变量(寄存器)具有常性

4c2fb13b806e4a93b4dc3ee31ec87cbe.png

这样写就会报错,我们刚刚知道临时变量具有常性,那如果我们加一个const

bc589b0f214d4a66ab2279ef51f7e2e8.png

果然没问题了

画个图理解一下

1c3170a4100e447f9644db31d5b8cbae.png

在举例理解一下:

3ded5cb3b01d4620892d5b76c16738a1.png

例:引用过程中权限可以平移和缩小,但是不能放大权限

986dbff6888141f8a838345cc50e259a.png

6.5 指针和引用的底层原理

#include <iostream>

int main()
{
    int a = 10;

    //引用
    int& b = a;
    b = 20;

    //指针
    int* p = &a;
    *p = 20;

    return 0;
}

汇编代码:

1ece9db137ea410691f90706a3b36457.png

可以看到在底层,引用和指针的工作原理是一样的

 

6.6 引用和指针的不同点:🚀

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

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

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

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

5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)

6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

7. 有多级指针,但是没有多级引用

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

9. 引用比指针使用起来相对更安全

最好都理解一下

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值