<C++> 引用

news2024/9/24 13:21:50

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.coloradmin.cn/o/823040.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小程序如何从分类中移除商品

​有时候商家可能需要在商品分类中删除某些商品&#xff0c;无论是因为商品已下架、库存不足还是其他原因。在这篇文章中&#xff0c;我们将介绍如何从分类中移除商品。 方式一&#xff1a;分类管理中删除商品。 进入小程序管理后台&#xff0c;找到分类管理&#xff0c;在分…

记录一次通过iostat命令定位系统数据库CPU飙升的案例

一、背景 我们有个移动考勤的系统&#xff0c;运维监控系统显示&#xff0c;每到上下班时间&#xff0c;考勤数据库的CPU就飙升到100%&#xff0c;磁盘读写请求等待时间变长&#xff0c;最初无法确定是磁盘性能下降导致的CPU飙升&#xff0c;还是CPU飙升导致的磁盘性能下降&…

牛客网Verilog刷题——VL55

牛客网Verilog刷题——VL55 题目答案 题目 请用Verilog实现4位约翰逊计数器&#xff08;扭环形计数器&#xff09;&#xff0c;计数器的循环状态如下&#xff1a;   电路的接口如下图所示&#xff1a; 输入输出描述&#xff1a; 信号类型输入/输出位宽描述clkwireInput1系统…

C5.0决策树建立个人信用风险评估模型

通过构建自动化的信用评分模型&#xff0c;以在线方式进行即时的信贷审批能够为银行节约很多人工成本。本案例&#xff0c;我们将使用C5.0决策树算法建立一个简单的个人信用风险评估模型。 导入类库 读取数据 #创建编码所用的数据字典 col_dicts{} #要编码的属性集 cols [che…

【Spring Cloud一】微服务基本知识

系列文章目录 微服务基本知识 系列文章目录前言一、系统架构的演变1.1单体架构1.2分层架构1.3分布式架构1.4微服务架构1.5分布式、SOA、微服务的异同点 二、CAP原则三、RESTfulRESTful的核心概念&#xff1a; 四、共识算法 前言 在实际项目开发过程中&#xff0c;目前负责开发…

AVX 贴片钽电容的频率特性分析

在介绍 AVX 钽电容的温度特性曲线前&#xff0c;我们必需对以下两个基本概念有所认识&#xff1a; 额定容量(CR) 这是额定 电容。对于钽OxICap?电容器的电容测量是在25 C 时等效串联电路使用测量电桥提供一个0.5V RMS120Hz 的正弦信号&#xff0c;谐波与2.2Vd.c. 电容公差 这是…

测试|性能测试相关理论

测试|性能测试相关理论&#xff08;了解&#xff09; 文章目录 测试|性能测试相关理论&#xff08;了解&#xff09;1.什么是性能测试生活中遇到的软件性能问题&#xff1a;性能测试定义&#xff1a;性能测试和功能测试有什么区别&#xff1a;性能好坏的评价指标影响一个软件性…

Stable Diffusion AI绘画学习指南【插件安装设置】

插件安装的方式 可用列表方式安装&#xff0c;点开Extensions 选项卡&#xff0c;找到如下图&#xff0c;找到Available选项卡&#xff0c;点load from加载可用插件&#xff0c;在可用插件列表中找到要装的插件按install 按扭按装&#xff0c;安装完后(Apply and restart UI)应…

第5章 最佳实践

过去的错误 不要怪罪JavaScript 游览器遇到不合法的html会想尽办法将他展现出来游览器遇到不合法的js将拒绝执行它们并报错写js要保障自己代码的健壮性 质疑一切 写js功能前一定要考虑这个功能的合理性&#xff0c;避免造成不可预见的后果写js功能前一定要考虑用户的游览器…

2023牛客暑期多校训练营5-B Circle of Mistery

2023牛客暑期多校训练营5-B Circle of Mistery https://ac.nowcoder.com/acm/contest/57359/B 文章目录 2023牛客暑期多校训练营5-B Circle of Mistery题意解题思路代码 题意 解题思路 感性地想一下&#xff0c;若已有一个环&#xff0c;则再出现其他环就显得多余&#xff0…

spring中怎么通过静态工厂和动态工厂获取对象以及怎么通过 FactoryBean 获取对象

&#x1f600;前言 本章是spring基于XML 配置bean系类中第4篇讲解spring中怎么通过静态工厂和动态工厂获取对象以及怎么通过 FactoryBean 获取对象 &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希望…

【IMX6ULL驱动开发学习】21.Linux驱动之PWM子系统(以SG90舵机为例)

1.设备树部分 首先在 imx6ull.dtsi 文件中已经帮我们定义好了一些pwm的设备树节点&#xff0c;这里以pwm2为例 pwm2: pwm02084000 {compatible "fsl,imx6ul-pwm", "fsl,imx27-pwm";reg <0x02084000 0x4000>;interrupts <GIC_SPI 84 IRQ_TYP…

2023年第四届“华数杯”数学建模思路 - 案例:FPTree-频繁模式树算法

## 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模式树算法&#xff0c;他与Apriori算法一样也是用来挖掘频繁项集的&#xff0c…

Databend 开源周报第 104 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 从 Kafka 载入数…

内存“银行”

项目介绍 本项目实现的是一个内存银行&#xff0c;它的原型是Google的一个开源项目tcmalloc&#xff0c;tcmalloc全称Thread-Caching Malloc&#xff0c;即线程缓存的malloc&#xff0c;实现了高效的多线程内存管理&#xff0c;用于替换系统的内存分配相关函数malloc和free。 有…

Linux第四章之权限理解

一、Linux用户的概念 Linux下有两种用户&#xff1a;超级用户&#xff08;root&#xff09;、普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff0c;不受限制普通用户&#xff1a;在linux下做有限的事情。 超级用户的命令提示符是“#”&#xff0c;普通用户…

2023年第四届“华数杯”数学建模思路 - 案例:最短时间生产计划安排

文章目录 0 赛题思路1 模型描述2 实例2.1 问题描述2.2 数学模型2.2.1 模型流程2.2.2 符号约定2.2.3 求解模型 2.3 相关代码2.4 模型求解结果 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; 最短时间生产计划模型 该模型出现在好几个竞赛赛题上&#x…

【React】搭建React项目

最近自己在尝试搭建react项目&#xff0c;其实react项目搭建没有想象中的那么复杂&#xff0c;我们只需要使用一个命令把React架子搭建好&#xff0c;其他的依赖可以根据具体的需求去安装&#xff0c;比如AntDesignMobile的UI框架&#xff0c;执行npm install antd-mobile --sa…

什么是注意力机制?注意力机制的计算规则

我们观察事物时&#xff0c;之所以能够快速判断一种事物(当然允许判断是错误的)&#xff0c;是因为我们大脑能够很快把注意力放在事物最具有辨识度的部分从而作出判断&#xff0c;而并非是从头到尾的观察一遍事物后&#xff0c;才能有判断结果&#xff0c;正是基于这样的理论&a…

Stable Diffusion VAE:改善图像质量的原理、选型与使用指南

VAE Stable Diffusion&#xff08;稳定扩散&#xff09;是一种用于生成模型的算法&#xff0c;结合了变分自编码器&#xff08;Variational Autoencoder&#xff0c;VAE&#xff09;和扩散生成网络&#xff08;Diffusion Generative Network&#xff09;的思想。它通过对变分自…