C++初学者指南第一步---12.引用

news2025/2/7 19:36:28

C++初学者指南第一步—12.引用

文章目录

  • C++初学者指南第一步---12.引用
    • 1. 功能(和限制)
      • 1.1 非常量引用
      • 1.2 常量引用
      • 1.3 auto引用
    • 2.用法
      • 2.1 范围for循环中的引用
      • 2.2 常量引用的函数形参
      • 2.3 非常量引用的函数形参
      • 2.4 函数参数的选择:copy / const& / & ?
      • 2.5 避免输出型参数
    • 3.绑定规则
      • 3.1 右值和左值
      • 3.2 引用绑定规则
    • 4. 陷阱
      • 4.1 永远不要返回函数内部的对象引用!
      • 4.2 小心引用向量元素!
      • 4.3 避免生命周期延长!

1. 功能(和限制)

1.1 非常量引用

int   i = 2;
int& ri = i;  // 引用 i

ri 和 i 都指向相同对象/内存位置。

cout << i  <<'\n';   // 2
cout << ri <<'\n';   // 2
i = 5;
cout << i  <<'\n';   // 5
cout << ri <<'\n';   // 5
ri = 88;
cout << i  <<'\n';   // 88
cout << ri <<'\n';   // 88

运行上面代码

  • 引用不能为“null”,即它们必须始终指向一个对象。
  • 引用必须始终指向相同的内存位置。
  • 引用类型必须与被引用对象的类型一致。
int  i  = 2;
int  k  = 3;
int& ri = i;     // 引用 i
ri = k;          // 将 k 的值赋给 i(ri 的目标)
int& r2;         //  编译错误: 引用必须初始化
double& r3 = i;  //  编译错误:类型必须相同

1.2 常量引用

= 对象的只读访问

int i = 2;
int const& cri = i;  //  i 的常量引用
  • cri 和 i 都指向相同对象/内存位置。
  • 但 const 表示 i 的值无法通过 cri 更改。
cout << i   <<'\n';   // 2
cout << cri <<'\n';   // 2
i = 5;
cout << i   <<'\n';   // 5
cout << cri <<'\n';   // 5
cri = 88;  //  编译错误: 常量!

运行上面代码

1.3 auto引用

引用类型是从赋值符的右侧推导出来的。

int i = 2;           
double d = 2.023;       
double x = i + d;       
auto & ri = i;        // ri:  int &
auto const& crx = x;  // crx: double const&

运行上面代码

2.用法

2.1 范围for循环中的引用

std::vector<std::string> v;
v.resize(10);
// 修改vector中的元素:
for (std::string & s : v) { cin >> s; }
// 只读访问 vector 中的元素:
for (std::string const& s : v) { cout << s; }
// 修改:
for (auto & s : v) { cin >> s; }
// 只读访问:
for (auto const& s : v) { cout << s; }

2.2 常量引用的函数形参

只读访问 ⇒ const&

  • 避免高开销的副本
  • 向函数的用户清楚地传达只读意图

示例:计算中位数的函数
需要从向量中读取值!

错误:通过复制⇒值传递正确:通过 const& ⇒ 没有副本
int median (vector);
auto v = get_samples(“huge.dat”);
auto m = median(v);
// 运行时间和内存开销很大!
int median (vector const&);
auto v = get_samples(“huge.dat”);
auto m = median(v);
// 不复制 ⇒ 没有开销!

示例:混合传递(按引用 + 按值)

incl_first_last ({1,2,4},{6,7,8,9}) → {1,2,4,6,9}
incl_first_last ({1,2,4},{6,7,8,9}) → {1,2,4,6,9}

该实现是在第一个向量的本地副本 ‘x’ 上进行操作,并且通过常量引用 ‘y’ 从第二个向量中读取:

auto incl_first_last (std::vector<int> x, std::vector<int> const& y) {
  if (y.empty() return x;
  // append to local copy 'x'
  x.push_back(y.front());
  x.push_back(y.back());
  return x;
}

2.3 非常量引用的函数形参

示例:交换两个变量值的函数

void swap (int& i, int& j) {
  int temp = i;  // copy i's value to temp
  i = j;         // copy j's value to i
  j = temp;      // copy temp's (i's original value) to j
}
int main () {
  int a = 5;
  int b = 3;
  swap(a,b);
  cout << a << '\n'   // 3
       << b << '\n';  // 5
}

运行上面代码
注意:可以使用 std::swap 来交换对象的值(#include )。它可以像上面的功能一样使用,但是可以避免对于支持移动语义的对象(如std::vector)的大开销的临时复制(它的实现将在移动语义章节中解释)。
虽然在某些情况下非 const 引用可能很有用,但总体上你还是应该避免使用这种输出参数(查看下面的内容获取更多细节)。

2.4 函数参数的选择:copy / const& / & ?

void read_from (int);  // 基本类型按值传递即可
void read_from (std::vector<int> const&);
void copy_sink (std::vector<int>);
void write_to  (std::vector<int> &);

从可以廉价复制的对象读取(所有基本类型)⇒ 值传递
如:

double sqrt (double x) { … }

从内存占用量较大(> 64位)的对象中读取内存时 ⇒ 用const &传递
如:

void print (std::vector<std::string> const& v) {
  for (auto const& s : v) { cout << s << ' '; }
}

在函数内部需要复制的内容 ⇒ 值传递
按值传递而不是在函数内显式复制。 其原因将在更高级的文章中解释。
如:

auto without_umlauts (std::string s) {
  s.replace('ö', "oe");  // modify local copy
  …
  return s;  // return by value!
}

写入到函数外部对象 ⇒ 由非常量&传递
(尽管它们在某些情况下可能很有用,但总的来说,你应该避免使用这种输出参数,请参阅下面内容。)
如:

void swap (int& x, int& y) { … }

2.5 避免输出型参数

像这样有非const引用参数的函数:

void foo (int, std::vector<int>&, double);

可能会在调用位置造成混乱/歧义:

foo(i, v, j);
  • 哪个参数 (i, v, j) 改变了,哪个保持不变?
  • 引用的对象是如何以及何时更改的,它是否被更改了?
  • 引用参数只充当输出(函数只向它写入数据)还是同时充当输入(函数也从它读取数据)?
    ⇒ 一般来说很难调试和推理!
    示例:一个只会造成混乱的接口
void bad_minimum (int x, int& y) {
  if (x < y) y = x;
}
int a = 2;
int b = 3;
bad_minimum(a,b);  
// 哪个变量再次保存了较小的值?

3.绑定规则

3.1 右值和左值

左值 = 我们可以获取内存地址的表达式

  • 指向持久存在内存中的对象
  • 一切有名称的东西(变量、函数参数……)

右值 = 我们无法获取内存地址的表达式

  • 字面值(123,“string literal”,…)
  • 临时的运行结果
  • 从函数返回的临时对象
int a = 1;      // a 和b 都是左值
int b = 2;      // 1 和 2 都是右值
a = b;
b = a;
a = a * b;      // (a * b)表达式的结果是右值
int c = a * b;  // OK,右值可以赋值给左值
a * b = 3;      //  编译错误:不能赋值给右值
std::vector<int> read_samples(int n) { … }
auto v = read_samples(1000);

3.2 引用绑定规则

&只能绑定到左值
const&可以绑定到const左值和右值

在这里插入图片描述

4. 陷阱

4.1 永远不要返回函数内部的对象引用!

在这里插入图片描述
只有在被引用对象的生命周期长于函数的情况下才有效!
在这里插入图片描述

4.2 小心引用向量元素!

警告:std::vector 中的元素引用可能在改变向量元素数量的任何操作之后失效!

vector<int> v {0,1,2,3};
int& i = v[2];
v.resize(20);  
i = 5; //  未定义行为:原始内存可能已经释放。

悬空引用 = 指的是指向一个不再有效的内存位置的引用。
std::vector 存储元素的内部内存缓冲区在某些向量操作期间可以被替换为新的缓冲区,因此对旧缓冲区的任何引用可能会变得悬空。

4.3 避免生命周期延长!

引用可以延长临时对象(右值)的生命周期。

auto const& r = vector<int>{1,2,3,4};
⇒ 向量对象的生命周期被引用 r 延长了

从函数返回的对象呢?

std::vector<std::string> foo () { … }
以值传递(推荐)
vector<string> v1 = foo();  
auto v2 = foo();
不推荐:忽略它→立即销毁
foo()
不推荐:获取对它的常量引用 ⇒ 临时对象的生命周期被延长
...只要这个引用还存在
vector<string> const& v3 = foo();  
auto const& v4 = foo();
禁止:不要引用它的成员
返回对象的成员(这里指向量的内容)不能延长生命周期!
string const& s = foo()[0];  // 悬空引用!
cout << s;                   //  未定义行为

不要通过引用来延长生命周期!

  • 容易造成混淆
  • 容易写出错误
  • 没有真正的好处

只需按值返回对象。 这对于现代C ++ 中的大多数函数和类型来说并不涉及大开销的复制,尤其是在C++17及更高版本中。

附上原文链接

如果文章对您有用,请随手点个赞,谢谢!^_^

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

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

相关文章

emqx5.6.1 数据、配置备份与迁移

EMQX 支持导入和导出的数据包括&#xff1a; EMQX 配置重写的内容&#xff1a; 认证与授权配置规则、连接器与 Sink/Source监听器、网关配置其他 EMQX 配置内置数据库 (Mnesia) 的数据 Dashboard 用户和 REST API 密钥客户端认证凭证&#xff08;内置数据库密码认证、增强认证…

cas客户端流程详解(源码解析)--单点登录

博主之前一直使用了cas客户端进行用户的单点登录操作&#xff0c;决定进行源码分析来看cas的整个流程&#xff0c;以便以后出现了问题还不知道是什么原因导致的 cas主要的形式就是通过过滤器的形式来实现的&#xff0c;来&#xff0c;贴上示例配置&#xff1a; 1 <list…

海南聚广众达电子商务咨询有限公司抖音电商新引擎

在数字化浪潮席卷而来的今天&#xff0c;抖音电商作为新兴的商业模式&#xff0c;正以其独特的魅力和无限的潜力&#xff0c;引领着电子商务行业的革新与发展。海南聚广众达电子商务咨询有限公司&#xff0c;作为专注于抖音电商服务的领军企业&#xff0c;凭借其专业的团队、丰…

双例集合(三)——双例集合的实现类之TreeMap容器类

Map接口有两个实现类&#xff0c;一个是HashMap容器类&#xff0c;另一个是TreeMap容器类。TreeMap容器类的使用在API上于HashMap容器类没有太大的区别。它们的区别主要体现在两个方面&#xff0c;一个是底层实现方式上&#xff0c;HashMap是基于Hash算法来实现的吗&#xff0c…

【C语言】函数指针数组和指向函数指针数组的指针

1 函数指针数组 数组是一个存放相同类型数据的存储空间&#xff0c;那我们已经学习了指针数组。 比如&#xff1a; int *arr[10];//数组的每个元素是int* 那要把函数的地址存到一个数组中&#xff0c;那这个数组就叫函数指针数组&#xff0c;那函数指针的数组如何定义呢&am…

OS复习笔记ch11-2

上一节我们学习的内容是I/O系统的特点和设备分类和差异&#xff0c;这一节我们将主要关注I/O控制方式、OS设计问题、I/O逻辑结构等。 I/O功能的演变 在专栏的ch1-2中&#xff0c;我们详细讲解了CPU与外设的三种交互方式&#xff0c;这里简单地带过。 &#xff08;1&#xff0…

C++之STL(一)

1、泛型程序设计 目的&#xff1a;提供相同的算法&#xff0c;相同的逻辑&#xff0c;来对不同类型的数据结构进行操作。 所以需要将类型当作参数&#xff0c;也就是参数类型化。 2、什么是STL STL是基于模板实现的。编译的时候进行实例化 3、STL组件 4、容器算法迭代器关系 …

第二次IAG

IAG in NanJing City 我与南京奥体的初次相遇&#xff0c;也可能是最后一次&#xff01; 对我来说,IAG 演唱会圆满结束啦! 做了两场充满爱[em]e400624[/em]的美梦 3.30号合肥站&#xff0c;6.21号南京站[em]e400947[/em] 其实&#xff0c;没想到昨天回去看呀!(lack of money […

如何修改外接移动硬盘的区号

- 问题介绍 当电脑自身内存不够使用的时候&#xff0c;使用外接硬盘扩展内存是一个不错的选择。但是当使用的外接硬盘数量过多的时候&#xff0c;会出现分配硬盘的区号变动的情况&#xff0c;这种情况下会极大的影响使用的体验情况。可以通过以下步骤手动调整恢复 - 配置 版本…

SpringBoot 快速入门(保姆级详细教程)

目录 一、Springboot简介 二、SpringBoot 优点&#xff1a; 三、快速入门 1、新建工程 方式2&#xff1a;使用Spring Initializr创建项目 写在前面&#xff1a; SpringBoot 是 Spring家族中的一个全新框架&#xff0c;用来简化spring程序的创建和开发过程。SpringBoot化繁…

【C语言】操作符(上)

目录 1. 操作符的分类 2. 原码、反码、补码 3. 移位操作符 3.1 左移操作符 3.2 右移操作符 4. 位操作符&#xff1a;&、|、^、~ 5. 单目操作符 6. 逗号表达式 最近准备期末考试&#xff0c;好久不见啦&#xff0c;现在回归—— 正文开始—— 1. …

WPF文本绑定显示格式StringFormat设置-数值类型处理

绑定显示格式设置 在Textblock等文本控件中&#xff0c;我们经常要绑定一些数据类型&#xff0c;但是我们希望显示的时候能够按照我们想要的格式去显示&#xff0c;比如增加文本前缀&#xff0c;后面加单位&#xff0c;显示百分号等等&#xff0c;这种就需要对绑定格式进行处理…

【Java】已解决java.io.ObjectStreamException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.io.ObjectStreamException异常 在Java中&#xff0c;java.io.ObjectStreamException是一个在序列化或反序列化对象时可能抛出的异常基类。这个异常通常表示在对象流处理…

iMazing3软件下载-详细安装教程视频

​值得肯定的是智能备份&#xff1a;iMazing为使用者提供了免费的备份服务&#xff0c;并且支持两种连接方式&#xff1a;USB数据线连接备份和Wi-Fi无线连接&#xff0c;所备份的文件不会被覆盖。我们必须承认iMazing软件特色&#xff1a;使用你的 iOS 设备像外部驱动器。基本上…

MSPM0G3507——特殊的串口0

在烧录器中有串口0&#xff0c;默认也是串口0通过烧录线给电脑发数据。 如果要改变&#xff0c;需要变一下LP上的跳线帽。 需要更改如下位置的跳线帽

SpringBoot 搭建sftp服务 实现远程上传和下载文件

maven依赖&#xff1a; <dependency><groupId>com.jcraft</groupId><artifactId>jsch</artifactId><version>0.1.55</version> </dependency>application.yml sftp:protocol: sftphost: port: 22username: rootpassword: sp…

论文阅读03(基于人类偏好微调语言模型)

1.主题 基于人类偏好微调语言模型&#xff08;Fine-Tuning Language Models from Human Preferences&#xff09; 出处&#xff1a; Fine-Tuning Language Models from Human Preferences、 2.摘要 奖励学习使得强化学习&#xff08;RL&#xff09;可以应用于那些通过人类判断…

云安全下的等级保护2.0解决方案

云安全解决方案 知识星球&#x1f517;除了包含技术干货&#xff1a;Java代码审计、web安全、应急响应等&#xff0c;还包含了安全中常见的售前护网案例、售前方案、ppt等&#xff0c;同时也有面向学生的网络安全面试、护网面试等。 ​

Git简单使用和理解

workspace: 本地的工作目录。 index/stage&#xff1a;暂存区域&#xff0c;临时保存本地改动。 local repository: 本地仓库&#xff0c;只想最后一次提交HEAD。 remote repository&#xff1a;远程仓库。 对于Git,首先应该明白第一git是一种分布式版本控制系统&#xff0c;最…

Echarts饼图-实现今日进度-动态图

效果预览 本次实现的是一个饼图&#xff0c;蓝色科技背景色&#xff0c;星球转动效果 进度显示。 构建一个动态饼图&#xff0c;采用ECharts&#xff0c;背景为蓝色科技风&#xff0c;有星球转动效果。通过echarts.init初始化&#xff0c;设置图表尺寸和背景色&#xff0c;配…