<C++> 引用

1.引用的概念

引用(Reference)是一种别名,用于给变量或对象起另一个名称。引用可以理解为已经存在的变量或对象的别名,通过引用可以访问到原始变量或对象的内容。引用在声明时使用 & 符号来定义。

示例:

#include<iostream>
using namespace std;
int main(){
	int a = 0;
	int& b = a;  //b是a的别名
	int& c = b;  //c是b的别名

	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
    
    cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	return 0;
}

在这里插入图片描述
可以发现输出结果地址相同,可知b和c其实都是a,只是a的别名而已。

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

我们将a变量设置为double类型,可以看到编译器直接报错了。
在这里插入图片描述

2.引用的特性

  1. 别名:引用本身是已经存在变量或对象的别名,没有自己的存储空间。它和原始变量共享同一块内存地址。
  2. 必须初始化:一旦引用被声明,必须在创建时进行初始化,即需要指定它的初始对象。之后引用将一直指向该对象,不可以再改变引用的目标。
  3. 不能为 null:引用不能被赋值为 nullptrNULL,必须始终指向一个有效的对象。
  4. 一个变量可以有多个引用

示例

#include<iostream>
using namespace std;
int main(){
	int a = 10;
	int& b;    
	int& c = nullptr;
	return 0;
}

在这里插入图片描述

3.常引用

常引用(const reference)是指通过引用访问的对象在引用生命周期内保持为只读(不可修改)的引用类型。常引用用于确保通过引用不会对引用的对象进行修改,从而提供了更高的安全性。

常引用的声明方式为在引用类型前加上 const 关键字。

常引用的特点:

  1. 值只读性:通过常引用访问的对象在引用生命周期内不可修改。试图通过常引用修改对象的值将导致编译错误。
  2. 只能引用 const 对象或字面常量:常引用通常用于引用 const 对象或字面常量。

例如:

const int x = 42;
const int& ref = x;

​ 3.常引用可以绑定到右值:和引用不同,常引用可以绑定到右值(例如字面常量、临时对象等)。这使得常引用在函数传参时非常有用,可以接受右值参数而不产生额外开销。

示例1:

#include <iostream>

void printValue(const int& value) {
    std::cout << "Value: " << value << std::endl;
}

int getValue() {
    return 42;
}

int main() {
    // 常引用可以绑定到右值,例如字面常量或临时对象
    const int& x = 10; // x 引用了右值常量 10
    printValue(x);

    int y = 100;
    printValue(y); // x也可以引用左值y

    // 常引用在函数返回值中也很有用
    const int& z = getValue(); // 可以将函数返回的右值绑定到常引用
    printValue(z);

    return 0;
}

在这个例子中常引用允许我们在函数传参时使用右值,避免了额外的复制开销,并确保在函数内部不会修改原始值。

示例2:

#include<iostream>
using namespace std;

int main() {
	double d = 12.34;
	int& rd1 = d; // 该语句编译时会出错,类型不同
	const int&2 rd = d;  
	return 0;
}

在这里插入图片描述

然而,const int& rd = d; 这一行是合法的,而且具有一个重要的特性:当一个非常量对象被绑定到一个常量引用上时,编译器会创建一个临时的 const 变量,并将常量引用绑定到这个临时 const 变量上。但是编译器还是会警告,从“double”转换到“const int”,可能丢失数据,但不会报错。

4.引用的使用场景

1.做参数

引用通常用于在函数调用中传递参数,这样可以避免复制大型对象,提高性能。通过传递引用,函数可以直接修改传递的参数,而不需要返回新值。

示例:

#include<iostream>
using namespace std;

void swap(int* p1, int* p2){
    int tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

void swap(int& r1, int& r2){
    int tmp = r1;
    r1 = r2;
    r2 = tmp;
}

int main(){
    int a = 10, b = 20;
    swap(&a, &b);
    cout << a << " " << b << endl;   //stdout:10 20
    swap(a, b);
    cout << a << " " << b << endl;   //stdout:20 10

    return 0;
}

2.返回值优化

函数可以返回引用类型,这样可以避免产生临时对象,并优化性能。

示例1:

int& findMax(int& a, int& b) {
    return (a > b) ? a : b;
}

int main() {
    int x = 5, y = 10;
    int& maxRef = findMax(x, y); // maxRef引用x或y中的较大值
    maxRef = 15; // 修改x或y的值,此处修改x的值为15
    return 0;
}

3.迭代器与范围循环

在C++标准库中,许多容器类都使用引用来返回迭代器或在范围循环中使用引用来遍历容器元素。

std::vector<int> nums = {1, 2, 3, 4, 5};

// 使用引用遍历容器元素
for (int& num : nums) {
    num *= 2; // 修改容器中的元素值
}

4.与类成员的关联

引用常用于在类中使用其他类对象作为成员。这样,类成员可以直接引用其他对象,而不需要创建新的副本。

class Car {
public:
    Car(Engine& engine) : engineRef(engine) {}

    void start() {
        engineRef.turnOn();
    }

private:
    Engine& engineRef;
};

5.引用做返回值的问题

下面代码输出什么结果?为什么?

int& Add(int a, int b){
    int c = a + b;
    return c;
}

int main(){
    int ret = Add(1, 2);
    int& ret1 = Add(1, 2);
    cout << "Add(1, 2) is :" << ret << endl;
    cout << "Add(1, 2) is :" << ret1 << endl;

    return 0;
}

在这里插入图片描述

为什么ret1的值非常的奇怪呢?观察下编译器给的警告

在这里插入图片描述

ret是变量接收,Add函数返回了c的引用,进行类型转换,产生临时变量,赋值给ret,结果3,但是这是vs中的结果,vs可能对这种处理结果进行了优化,这并不符合C++的标准,在g++编译器中,会直接报错,返回局部变量或临时变量的地址: c

ret1是引用接收,Add函数返回了c的引用,Add函数结束后,函数栈帧被销毁,c也被销毁,ret1的别名就是随机值
在这里插入图片描述
注意:

如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。或者是值接收返回值,而不使用引用接收返回值

6.传值、传引用效率比较

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

#include <iostream>
#include <time.h>
using namespace std;
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;
}

int main() {
    TestReturnByRefOrValue();
    return 0;
}

这是DEBUG版本下的测试结果:
在这里插入图片描述

这是Release版本下的测试结果:
在这里插入图片描述

可以发现编译器没有优化之前,发现传值和指针在作为传参以及返回值类型上效率相差很大。但是经过编译器优化后,几乎没有差别了。

7.引用和指针的区别

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

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

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

我们来看下引用和指针的汇编代码对比:
在这里插入图片描述

发现汇编指令是一模一样的,说明引用的底层是指针实现的

总结引用和指针的不同点:

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

8.引用的权限问题

指针和引用,赋值/初始化,权限可以缩小,但是不能放大

示例1:权限放大

int main(){
    // 权限放大
    const int c = 2;
    int& d = c;

    const int* p1 = NULL;
    int* p2 = p1;
    
	return 0;
}

在这里插入图片描述

示例2:权限保持

int main(){
	const int c = 2;
    const int& d = c;

    const int* p1 = NULL;
    const int* p2 = p1;
	return 0;
}
//可以通过编译

示例3:权限缩小

int Count(){
    int n = 0;
    n++;

    return n;
}

int main(){
	// 权限缩小
    int x = 1;
    const int& y = x;

    int* p3 = NULL;
    const int* p4 = p3;
    
    const int& ret = Count();
    int i = 10;

    double dd = i;
    const double& rd = i;
	return 0;
}
//可以通过编译

注意不要被混淆:

 const int m = 1;
 int n = m;

这是可以的。const变量可以被赋值给普通变量,不要被混淆

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/55169.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ad+硬件每日学习十个知识点(16)23.7.27 (总线保持、lin报文、逻辑器件手册解读)

文章目录 1.总线保持是怎么实现的&#xff1f;有什么需要注意的&#xff08;驱动电流和电阻&#xff09;&#xff1f;2.LIN报文3.芯片datasheet的features、applications、description看完&#xff0c;应该能大致判断逻辑器件能否满足我们的要求。4.什么是逻辑器件的传输延时&a…

InnoDB存储引擎——事务原理

1.什么是事务 2.redo log 脏页是指缓冲区的数据与磁盘中的数据不一致时的状态。脏页的数据并不是实时刷新的&#xff0c;而是一段时间之后通过后台线程把脏页的数据刷线到磁盘&#xff0c;假如说脏页的数据在往磁盘中刷新的时候出错了&#xff0c;内存中的数据没有刷新到磁盘当…

软件测试技能大赛任务二单元测试试题

任务二 单元测试 执行代码测试 本部分按照要求&#xff0c;执行单元测试&#xff0c;编写java应用程序&#xff0c;按照要求的覆盖方法设计测试数据&#xff0c;使用JUnit框架编写测试类对程序代码进行测试&#xff0c;对测试执行结果进行截图&#xff0c;将相关代码和相关截…

如何运行疑难解答程序来查找和修复Windows 10中的常见问题

如果Windows 10中出现问题&#xff0c;运行疑难解答可能会有所帮助。疑难解答人员可以为你找到并解决许多常见问题。 一、在控制面板中运行疑难解答 1、打开控制面板&#xff08;图标视图&#xff09;&#xff0c;然后单击“疑难解答”图标。 2、单击“疑难解答”中左上角的…

RocketMQ 在业务消息场景的优势详解

作者&#xff1a;隆基 01 消息场景 RocketMQ 5.0 是消息事件流一体的实时数据处理平台&#xff0c;是业务消息领域的事实标准&#xff0c;很多互联网公司在业务消息场景会使用 RocketMQ。 我们反复提到的“消息、业务消息”&#xff0c;指的是分布式应用解耦&#xff0c;是 R…

nmake编译Qt第三方库出现无法打开包含文件type_traits

最近需要为个人项目ShaderLab添加内嵌的代码编辑窗口功能&#xff0c;支持语法高亮和Intellisense&#xff0c;最初使用了QCodeEditor,发现这个第三方的库对词法分析的实现效果不太行. 代码换行后直接缩进到首行&#xff0c;无法定位到前一句的首行 考虑换QScintilla&#xff…

Java 基础进阶总结(一)反射机制学习总结

文章目录 一、初识反射机制1.1 反射机制概述1.2 反射机制概念1.3 Java反射机制提供的功能1.4 反射机制的优点和缺点 二、反射机制相关的 API2.1 一、初识反射机制 1.1 反射机制概述 JAVA 语言是一门静态语言&#xff0c;对象的各种信息在程序运行时便已经确认下来了&#xff0…

微信小程序防盗链referer问题处理

公司使用百度云存储一些资源&#xff0c;然后现在要做防盗链&#xff0c;在CDN加入Referer白名单后发现PC是正常的&#xff0c;微信小程序无法正常访问资源了。然后是各种查啊&#xff0c;然后发现是微信小程序不支持Referer的修改&#xff0c;且在小程序开发工具是Referer是固…

优先级队列 (堆)

目录 一&#xff0c;堆的概念 二&#xff0c; 堆的存储结构 三&#xff0c; 堆的实现 3.1 shiftDown() 3.2 shiftUp() 3.3 shiftDown 与 shiftUp 的时间复杂度 四&#xff0c;堆排序 一&#xff0c;堆的概念 堆常用于实现优先队列&#xff08;Priority Queue&#xff0…

偶数科技与白鲸开源完成兼容性认证

近日&#xff0c;偶数科技自主研发的云原生分布式数据库 OushuDB v5.0 完成了与白鲸开源集成调度工具 WhaleStudio v2.0 的兼容性相互认证测试。 测试结果显示&#xff0c;双方产品相互良好兼容&#xff0c;稳定运行、安全&#xff0c;同时可以满足性能需求&#xff0c;为企业级…

git从主仓库同步到fork仓库

git从主仓库同步到fork仓库 1. fork远程仓库到本地仓库2. 将远程仓库添加到本地3. 更新本地项目主库地址4. 将远程仓库更新到本地仓库5. 将本地仓库合到远程分支 1. fork远程仓库到本地仓库 方式一&#xff1a;通过git命令 git clone fork库地址方式二&#xff1a;通过git页面…

【100天精通python】Day22:字符串常用操作大全

目录 专栏导读 一、 字符串常用操作 1 拼接字符串 2 计算字符串长度 3 截取字符串 4 分割合并字符串 5 检索字符串 6 字母的大小写转换 7 去除字符串的空格和特殊字符 8 格式化字符串 二 、字符串编码转换 2.1 使用encode()方法编码 2.2 使用decoder()方法编码 专栏…

mysql的json处理

写在前面 需要注意&#xff0c;5.7以上版本才支持&#xff0c;但如果是生产环境需要使用的话&#xff0c;尽量使用8.0版本&#xff0c;因为8.0版本对json处理做了比较大的性能优化。你你可以使用select version();来查看版本信息。 本文看下MySQL的json处理。在正式开始让我们先…

HarmonyOS 开发基础(四) 子父组件双向绑定

一、知识点在代码注释里 index.ets // 导出方式直接从文件夹 import MyInput from "../common/commons/myInput" Entry Component /* 组件可以基于struct实现&#xff0c;组件不能有继承关系&#xff0c;struct可以比class更加快速的创建和销毁。*/ struct Index {…

【hive】Install hive using mysql as hive metadata service

文章目录 一. Requirements二. Installing Hive from a Stable Release三. Running Hive四. Running Hive CLI五.Running HiveServer2 and Beeline1. 下载安装mysql2. 下载mysql驱动3. 配置hive-site.xml4. 初始化元数据库5. 通过beeline进行连接 一. Requirements Users are s…

BES 平台 SDK之LED的配置

本文章是基于BES2700 芯片&#xff0c;其他BESxxx 芯片可做参考&#xff0c;如有不当之处&#xff0c;欢迎评论区留言指出。仅供参考学习用&#xff01; BES 平台 SDK之代码架构讲解二_谢文浩的博客-CSDN博客 关于SDK 系统框架简介可参考上一篇文章。链接如上所示&#xff01…

防火墙监控工具

防火墙监控是跟踪在高效防火墙性能中起着关键作用的重要防火墙指标&#xff0c;防火墙监控通常应包括&#xff1a; 防火墙日志监控防火墙规则监控防火墙配置监控防火墙警报监控 防火墙监控服务的一个重要方面是它应该是主动的。主动识别内部和外部安全威胁有助于在早期阶段识…

Devops系统中jira平台迁移

需求:把aws中的devops系统迁移到华为云中,其中主要是jira系统中的数据迁移,主要方法为在华为云中建立一套 与aws相同的devops平台,再把数据库和文件系统中的数据迁移,最后进行测试。 主要涉及到的服务集群CCE、数据库mysql、弹性文件服务SFS、数据复制DRS、弹性负载均衡ELB。 迁…

你知道HTTP与HTTPS有什么区别吗?

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 一、什么是HTTP&#xff1f; 二、什么是HTTPS&#xff1f; 三、HTTPS 的工作原理 1、客户端发起 HTTPS 请求 2、服务端的配置 3、…

2023年第四届“华数杯”数学建模思路 - 案例:异常检测

文章目录 赛题思路一、简介 -- 关于异常检测异常检测监督学习 二、异常检测算法2. 箱线图分析3. 基于距离/密度4. 基于划分思想 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 一、简介 – 关于异常检测 异常…