【C++入门】引用详解(引用的特性、引用的使用、引用与指针的区别)

news2025/1/22 12:24:58

文章目录

  • 1 引用概念
  • 2 引用特性
  • 3 使用场景
  • 4 常引用
  • 5 传值与传引用的效率比较
    • 5.1 值和引用作为函数参数的性能比较
    • 5.2 值和引用作为返回值类型的性能比较
  • 6 引用和指针的区别


1 引用概念

引用不是新定义一个变量,而是给已存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用一块内存空间。
格式:引用类型& 引用变量名(对象名) = 引用实体;
注意: 引用类型和引用实体是同种类型的。

引用示例:

引用示例图


2 引用特性

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

  2. 一个变量可以有多个引用,也可以对引用再进行引用(或者说可以为变量取多个别名,也可以为别名再取别名)
    示例:
    引用示例

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

示例


3 使用场景

  1. 引用做参数
void Swap(int& num1, int& num2)
{
	int temp = num1;
	num1 = num2;
	num2 = temp;
}

示例

  1. 引用做返回值

前言:传值返回

  • 当用值作为返回值时,不是直接将变量返回,而是返回变量的一份临时拷贝(临时变量)。考虑到对于函数中的局部变量,在调用完函数时即被销毁,此时需要返回的变量也会被销毁(空间不再属于该变量),因此选择在销毁前将变量值拷贝到一个临时变量中(这个临时变量一般由寄存器(4/8个字节大小)充当,如eax;如果返回值过大,则会选择在上层栈帧中创建临时变量),再将临时变量作为返回值返回。
    示例

  • 那如果作为返回值的变量出作用域后不会被销毁,那还会产生临时变量吗?事实上,编译器不管需要返回的变量出作用域后会不会被销毁,在返回时都会产生临时变量。
    示例

传引用返回

  • 那既然需要返回的变量都不会被销毁,将其再拷贝到临时变量中是不是麻烦了些?因此,在下面我们以引用作为返回值,但这不是说就不会创建临时变量了,临时变量依然会被创建,但这个临时变量相当于返回变量的一个别名,因为该变量出作用域后不会被销毁,因此我们依然可以进行访问,此时临时变量与返回变量共用一块空间,相比于传值返回,中间就减少了拷贝的过程,返回时同样将临时变量赋值给接收的变量即可,可以认为传引用返回就相当于将变量直接返回了。
    示例


    下面再看一段示例代码:
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

示例输出结果:
示例

说明:对于以出了作用域即被销毁的变量作为返回值的,如果使用传引用返回实际上是无意义的,因为局部变量的空间已被销毁,不再属于该变量,再通过传引用返回的别名去访问该空间时,其值是不确定的,无法确保得到的还是正确结果。

引用做返回值示例

注意:如果函数返回时,出了函数作用域,如果返回对象还在(空间还没还给系统,如:静态变量、全局变量、上一层栈帧中的变量、malloc开辟的等等),则可以使用引用返回,否则,必须使用传值返回。

  • 传引用返回还可修改返回值。

    示例代码:
#define N 10
typedef struct Array {
	int a[N];
	int size;
}AY;

//查找数组中某个下标位置的元素
int& PosAt(AY& ay, int i) {
	assert(i < N);//保证不越界
	return ay.a[i];
}

int main() {
	AY ay;
	for (int i = 0; i < N; i++) {
		PosAt(ay, i) = i * 10;//用返回的别名进行修改
	}

	for (int i = 0; i < N; i++) {
		cout << PosAt(ay, i) << " ";
	}
	cout << endl;
	return 0;
}

示例输出结果:
示例


4 常引用

指针和引用,在赋值和初始化时,权限可以保持或缩小,但不能放大。

示例代码:

//传值返回
int Count() {
	int n = 0;
	n++;
	return n;
}

void TestConstRef() {
	//示例1
	const int a = 10;//const修饰的变量,具有常属性,不可修改
	//错误语法,权限被放大。ra引用与a共用一块空间,而a具有常属性,
	//如果ra引用不具有常属性,则修改ra的值时则违背了a的常属性
	//int& ra = a;//编译时会报错
	const int& ra = a;//正确语法

	//示例2
	//int& b = 10;//错误语法,10为常量,引用权限放大
	const int& b = 10;//正确语法,使引用变量b也具有常属性

	//示例3
	//Count函数是传值返回的,返回的是临时变量,临时变量具有常属性
	//int& ret = Count();//编译报错
	const int& ret = Count();//正确语法,ret为返回的临时变量的常引用
	int ret2 = Count();//正确语法,将返回的临时变量赋值给ret2

	//示例4
	double d = 3.14;
	//int& rd = d;//编译报错,引用类型与引用实体不是同一种类型
	//解释:
	//在进行类型转换(无论是强制类型转换还是自动类型提升)时都会产生临时变量
	//如有int i = 0; (double)i:其并不是直接将int型变量i转换为double型,
	//而是通过一个临时变量将i转化为double,如果是double d = i;则是再将临时变量赋值给d
	
	//下句代码则表示rd为double型变量d强制转换为int型变量时产生的 临时变量 的常引用
	//但注意,rd本质上并不是d的引用,二者也不是共用一个空间
	//d仍是double型变量,rd则为int型
	const int& rd = d;//编译通过
	printf("d = %f\nrd = %d\nd的地址为:%p\nrd的地址为:%p\n", d, rd, &d, &rd);
}

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

示例


5 传值与传引用的效率比较

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

下面用两段测试代码来比较传值与传引用的效率:

5.1 值和引用作为函数参数的性能比较

测试代码:

#include <iostream>

using namespace std;

#include <time.h>
struct A { int a[100000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
	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(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

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

测试结果:

性能比较结果
分析:从测试结果来看,值作为参数的函数运行时间要比引用作为参数的函数运行时间多得多,以引用作为参数能很好提升程序运行效率。


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

测试代码:

#include <iostream>

using namespace std;

#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 < 1000000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 1000000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

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

测试结果:

测试结果
分析:从测试结果来看,值作为返回值类型的函数运行时间要比引用作为返回值类型的函数运行时间多得多,以引用作为返回值类型能很好提升程序运行效率。


6 引用和指针的区别

  • 语法概念上 引用就是一个别名,没有独立空间,和其引用实体共用一块空间。而指针是有开辟独立空间的,空间中存放的是指向的变量的空间地址。

    示例代码:
int main()
{
	int a = 10;
	int& ra = a;//引用a
	int* pa = &a;//指向a
	cout << "&a = " << &a << endl;
	cout << "&ra = " << &ra << endl;
	cout << "&pa = " << &pa << endl;
	return 0;
}

示例输出结果:

示例结果

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

    示例代码:
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 中的含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个所指向实体类型的大小。
  7. 有多级指针,但没有多级引用。
  8. 访问实体方式不同,指针需要显示解引用,引用是由编译器自己处理。
  9. 引用比指针使用起来相对安全。不会有类似空指针等的问题。

以上是我对C++中引用相关的一些学习记录总结,如有错误,希望大家帮忙指正,也欢迎大家给予建议和讨论,谢谢!

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

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

相关文章

浅谈网络流

网络 网络是一张单向图 , 每条边都有一个权值 c(u,v)c(u,v)c(u,v) 表示边 (u,v)(u,v)(u,v) 的容量. 特别的 , 图上有源点 (s)(s)(s) 和汇点 (t)(t)(t). 网络流 在一张网络上 , 从源点流出 , 最终流入汇点的流. f(u,v)f(u,v)f(u,v) 表示 (u,v)(u,v)(u,v) 的流量. 满足 : 容…

【测试总结系列-1】质量保障之测试左移和右移

在开发一个系统或者说软件&#xff0c;需求分析、软件设计、程序编码、软件测试、运行维护&#xff0c;这些阶段必不可少。整个周期中&#xff0c;作为测试人员&#xff0c;不是只在测试阶段才能发挥作用&#xff0c;也不是仅有测试对软件质量负责&#xff0c;一个项目团队&…

关于图片上传和在页面显示问题

最近在工作中遇到一个关于图片上传的问题。根据之前项目的经验&#xff0c;我知道目前这个公司上传图片有两种方式&#xff0c; 一种是把图片上传到公司服务器上&#xff0c;然后把图片放在服务器上的地址存在数据库中&#xff0c;要获得图片的时候直接从库中拿地址就行了另一…

分析网络抓包用 python 更高效

Abstract分析网络抓包用 python 更高效AuthorsWalter FanCategorylearning noteStatusv1.0Updated2023-01-10LicenseCC-BY-NC-ND 4.0 网络抓包分析用的最多的两大工具是 tcpdump 和 wireshark. 一般我们通过 tcpdump 或者 wireshark 来捕获网络包为 *.pcap 或者 *.pcapng 文件 …

Elasticsearch:在 Elasticsearch 中按距离有效地对地理点进行排序

计算搜索中两点之间的距离有很多用例。 如果你正在处理地理数据&#xff0c;那么无论你从事何种业务&#xff0c;这都必然会出现。 然后&#xff0c;在对这些点进行排序时&#xff0c;你可能需要考虑距离&#xff0c;因为……好吧&#xff0c;为什么不呢&#xff1f; 所以这里…

算法训练营DAY48|121. 买卖股票的最佳时机、122.买卖股票的最佳时机II

这一期到了买卖股票专题&#xff0c;买卖股票的有一些题型&#xff0c;可以使用贪心算法来求解&#xff0c;甚至有时候比动态规划更简单一些&#xff0c;但是本期是讲动态规划的运用&#xff0c;所以不做对于贪心的分析。今天只讲两道例题&#xff0c;其中第二题是第一题的变种…

[NeurIPS 2017] Poincaré Embeddings for Learning Hierarchical Representations

ContentsIntroductionPoincar EmbeddingsThe Limitations of Euclidean Space for Hierarchical DataEmbedding Hierarchies in Hyperbolic SpaceEvaluationReferencesIntroduction 如今&#xff0c;表征学习变得越来越重要 (e.g. word embedding, embeddings of graphs, embe…

如何在电脑上安装 Windows 版桌面编辑器 v7.3

线上编辑器的桌面端版本不需要持续的互联网连接&#xff0c;还可在计算机上处理脱机文件。因此&#xff0c;如果您需要此类功能&#xff0c;可从我们的网站中下载并安装桌面编辑器。 ONLYOFFICE桌面编辑器最新版 最近 ONLYOFFICE 发布了 v7.3&#xff0c;最新版本的桌面编辑器…

SpringBoot+Vue点餐系统

简介&#xff1a;本项目采用了基本的springbootvue设计的点餐。系统。详情请看截图。经测试&#xff0c;本项目正常运行。本项目适用于Java毕业设计、课程设计学习参考等用途。 项目描述 项目名称SpringBootVue点餐系统源码作者LHL项目类型Java EE项目 &#xff08;前后分离&a…

ESP-IDF:命令模式测试

ESP-IDF:命令模式 /命令模式/ /设计模式之开闭原则&#xff0c;对增加开放对修改关闭/ #include #include class ClientCommands{ //统一管理命令&#xff0c;这是比观察者模式多出来的地方 public: void AddMoney(){ cout<<“add money”<<endl; } void AddEqu…

图解LeetCode——1145. 二叉树着色游戏(难道:中等)

一、题目 有两位极客玩家参与了一场「二叉树着色」的游戏。游戏中&#xff0c;给出二叉树的根节点 root&#xff0c;树上总共有 n 个节点&#xff0c;且 n 为奇数&#xff0c;其中每个节点上的值从 1 到 n 各不相同。 最开始时&#xff1a; 「一号」玩家从 [1, n] 中取一个值…

Ubuntu22.04安装nvidia显卡驱动

Ubuntu22.04安装nvidia显卡驱动 目录 方法一&#xff1a;使用官方的NVIDIA驱动进行手动安装&#xff08;稳定、靠谱&#xff09; 方法二&#xff1a;使用系统自带的“软件和更新”程序-附加驱动更新&#xff08;需要联网&#xff0c;稳定性无法验证&#xff09; 浓缩极简方法…

MySQL入门篇-视图简介

备注:测试数据库版本为MySQL 8.0 这个blog我们来聊聊MySQL视图。 前言: 视图是从一个或几个基本表导出的表。视图本身不独立存储在数据库中&#xff0c;是一个虚表。 即数据库中只存放视图的定义而不存放视图对应的数据&#xff0c;这些数据仍存放在导出视图的基本表中。 视…

[Windows] 微信超级管家,自动好友回复、计数、自动同意、群发、好友导出、消息日志、无限多开

简介 微信超级管家是一款大神针对微信制作的工具&#xff0c;它的主要功能包括了自动回复、好友计数、自动同意、群发、好友导出、消息日志、无限多开等等&#xff0c;让你拥有无限潜力哈&#xff0c;经常使用微信电脑版的朋友一定会用的上。 下载 微信超级管家 软件功能 1…

安全测试之浅析静态应用

SAST&#xff0c;Static Application Security Testing&#xff0c;即静态应用安全测试&#xff0c;也叫静态分析&#xff0c;是一种测试方法&#xff0c;一直是应用程序安全性工作的核心部分。根据Forrester的 The State Of Application Security, 2022一文的预测&#xff0c;…

云计算|OpenStack|社区版OpenStack安装部署文档(七--- 仪表盘服务dashboard的安装部署---Rocky版)

前言&#xff1a; 仪表盘是一般项目的标配&#xff0c;有了仪表盘可以降低运维工作&#xff0c;并且很多的管理工作是可以可视化的。本节计划在控制节点安装openstack官网的仪表盘项目 openstack由于是一个开源的社区版本云计算项目&#xff0c;因此&#xff0c;它的web仪表盘…

【Core】.net core 3.1 api 返回实体类数据存在null,导致小程序调用接口也直接显示了null,原来要这样设置才可

对接过API接口的小伙伴都知道&#xff0c;接口返回的Json格式数据&#xff0c;有些字段可能会出现null的情况&#xff0c;并且还是个字符串&#xff0c;直接显示在用户界面上给人感觉出bug了 文章目录【开发环境】【场景描述】【返回null值重现】1&#xff09;创建新项目2&…

细讲TCP三次握手四次挥手(二)

TCP/IP 协议族 应用层 应用层( application-layer &#xff09;的任务是通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程&#xff08;进程&#xff1a;主机中正在运行的程序&#xff09;间的通信和交互的规则。 对于不同的网络应用需要不同的应用层协议…

基本放大器电路- (一)

运算放大器组成的电路五花八门&#xff0c;令人眼花瞭乱&#xff0c;是模拟电路中学习的重点。在分析它的工作原理时倘没有抓住核心&#xff0c;往往令人头大。为此本人特搜罗天下运放电路之应用&#xff0c;来个“庖丁解牛”&#xff0c;希望各位从事电路板维修的同行&#xf…

精讲rpc框架,这么讲不怕你不明白!

谈到rpc框架可能有点陌生感&#xff0c;但是如果换成框架语言Ocaml&#xff0c;大家一定不陌生。 众所周知&#xff0c;ocaml是一款专门做functional programming的一款软件&#xff0c;尤其是它的界面非常简洁&#xff0c;还是专门的server进行线上编写。 rpc框架和ocaml是什…