【浅尝C++】引用

在这里插入图片描述

🎈归属专栏:浅尝C++
🚗个人主页:Jammingpro
🐟记录一句:大半夜写博客的感觉就是不一样!!


文章前言:本篇文章简要介绍C++中的引用,每个介绍的技术点,在可能的情况下,都附上代码了。


文章目录

  • 引用的概念
  • 引用的特征
  • 常引用
  • 使用场景
    • 做参数
    • 做返回值
  • 值传递与引用传递效率比较
  • 引用和指针的区别


引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
就跟现实生活中一样,一个人原名叫做光头强,再给他取了个别名叫帅靓仔。一样地,我们为a变量申请一个空间地址为0x00001010,再为a变量取一个别名叫做superA。此时,a和superA指向的内存地址均是0x00001010。
在这里插入图片描述
引用的语法为:变量类型& 引用变量名(对象名) = 引用实体;
下面给出引用语法的示例代码,并演示引用实体及引用变量指向同一内存空间。

#include <iostream>
using namespace std;

int main()
{
	int a = 100;
	int& superA = a;
	cout << "a's address is " << &a << endl;
	cout << "superA's address is " << &superA << endl;
	return 0;
}

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

引用的特征

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体

对于上面3个属性,给出如下代码进行验证👇

#include <iostream>
using namespace std;

int main()
{
	int age = 18;
	// int& refer_age1;	//该代码编译不通过,引用在定义时必须初始化
	int& refer_age2 = age;
	int& refer_age3 = age;
	//这里说明了,一个变量可以有多个引用
	//age变量有refer_age2和refer_age3两个引用。当然,还可以设置更多age的引用
	int newAge = 20;
	refer_age2 = newAge;//这行代码可以正常运行,但这里不是将refer_age2变为newAge的引用,而是将age的值赋值为newAge的值
	
	cout << "age's value is " << age << " and its address is " << &age << endl;
	cout << "refer_age2's value is " << refer_age2 << " and its address is " << &refer_age2 << endl;
	cout << "newAge's value is " << newAge << " and its address is " << &newAge << endl;
	return 0;
}

上面代码运行后,agerefer_age2的地址相同,agerefer_age2newAge的值均为20。refer_age2 = newAge并不是将refer_age2设置为newAge的引用,而是将refer_age2指向的引用实体的值设置为newAge的值。这行语句的效果等同于age = newAge。因此,引用一旦引用一个实体,再不能引用其他实体。

常引用

我们知道,常量的值是不可以修改的。因此,常量的引用也不可以被赋值,即不能被修改数值。

void TestConstRef()
{
    const int a = 10;
    //int& ra = a;  //该条语句编译时会出错,a为常量
    const int& ra = a;
    //int& b = 10;  //该语句编译时会出错,b为常量
    const int&b = 10;
    double d = 12.34;
    //int& rd = d;	//该语句编译时会出错,类型不同
    const int&rd = d;
}

上面代码中,a是常量,因而,其引用必须为const类型的引用。也就是说,引用的操作权限不能高于被引用实体的操作权限(即a不能被修改,则其引用也一定不能被修改)。同理,10是字面常量,因此,引用它的b也必须是常量。将int类型的引用指向double类型的引用实体,此时会发生类型转换,即double类型会被转换为int类型。转换时,编译器会尝试创建一个const int的临时对象,并将double类型转换为const int类型。因此,若给引用赋予引用实体时需要类型转换,该引用也必须为const类型。
在这里插入图片描述

使用场景

做参数

在没有引用之前,我们需要使用地址传递才能实现swap函数。如下面的代码👇

#include <iostream>
using namespace std;

void swapByAddress(int* left, int* right)
{
	int temp = *left;
	*left = *right;
	*right = temp;
}

int main()
{
	int num1 = 10, num2 = 20;
	cout << "num1 = " << num1 << ", num2 = " << num2 << endl;
	swapByAddress(&num1, &num2);
	cout << "num1 = " << num1 << ", num2 = " << num2 << endl;
	return 0;
}

有了引用之后,我们就不再需要使用取地址符来传递变量地址,并在给变量赋值时使用解引用运算符了。实现代码如下👇

#include <iostream>
using namespace std;

void swapByReference(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

int main()
{
	int num1 = 10, num2 = 20;
	cout << "num1 = " << num1 << ", num2 = " << num2 << endl;
	swapByReference(num1, num2);
	cout << "num1 = " << num1 << ", num2 = " << num2 << endl;
	return 0;
}

做返回值

与引用做参数一样,如果返回值为指针类型,后续需要对指针进行操作,整体操作比较繁琐。我们可以选择使用引用做返回值。代码如下👇

#include <iostream>

int& addGongDe()
{
	static int gongde = 0;
	++gongde;
	return gongde;
}

int main()
{
	int myGongDe = addGongde();
	cout << myGongde << endl;
	return 0;
}

与指针类型做返回值一样,以引用做返回值时,如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

下面给出一段运行时出错的代码👇

#include <iostream>
using namespace std;

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	cout << "Add(1, 2) is : " << ret << endl;
	return 0;
}

main函数调用Add函数时,栈中增加了abc三个变量,在Add函数返回后,这三个变量的内存空间被释放。在输出时,ret访问了已经释放的内存空间,故出现错误。

值传递与引用传递效率比较

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

当传递的参数数据量较大时,可以发现:引用传递效率明显高于值传递。下面为该结论的验证代码👇

#include <iostream>
#include <time.h>
using namespace std;

struct Ming{ int a[10000]; };

void Func1(Ming a){}

void Func2(Ming& a){}

void TestRefAndValue()
{
	Ming a;
 	//以值作为函数参数
 	size_t begin1 = clock();
	 for(size_t i = 0; i < 10000; ++i)
	     Func1(a);
	 size_t end1 = clock();

	 //以引用作为函数参数
	 size_t begin2 = clock();
	 for(size_t i = 0; i < 10000; ++i)
	     Func2(a);
	 size_t end2 = clock();

	 //分别计算两个函数运行结束后的时间
	 cout << "Func1(a)->time:" << end1 - begin1 << endl;
	 cout <<  "Func2(s)->time:" << end2 - begin2 << endl;
}

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

不仅在传递函数参数时可以使用引用传递,引用还可以作为返回值。一样地,在数据量较大时,引用做返回值的效率明显高于值传递做返回值。下面为验证该结论的代码👇

#include <iostream>
#include <time.h>
using namespace std;

struct Ming{ int a[10000]; };

Ming a;
//值返回
Ming Func1(){ return a; }
//引用返回
Ming& Func2(){ return a; }

void TestReturnByRefOrValue()
{
	//以值作为函数的返回值类型
	size_t begin1 = clock();
	for(size_t i = 0; i < 100000; ++i)
	   Func1();
	size_t end1 = clock();
	//以引用作为函数的返回值类型
	size_t begin2 = clock();
	for(size_t i = 0; i < 100000; ++i)
	   Func2();
	size_t end2 = clock();

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

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

从上面的文字和代码中,我们可以总结出:在程序能正常运行的前提下,传递函数参数及函数返回值时,尽量使用引用传递。

引用和指针的区别

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用一块空间。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

int main()
{
	int a = 10;

	int& refer_a = a;
	refer_a = 20;
	
	int* point_a = &a;
	*point_a = 20;
	
	return 0;
}

引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体
  4. 没有NULL引用,但有NULL指针 【引用必须初始化】
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)
void test()
{
	long long num = 16;
	long long& refer_num = num;
	long* point_num = &num;
	
	cout << sizeof(refer_num) << endl;	//等于sizeof(long long)
	cout << sizeof(point_num) << endl;	//输出结果为指针所占内存空间大小
}
  1. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
void test()
{
	int arr[10] = {0};
	for(size_t i = 0; i < 10; i++)
	{
		arr[i] = i * 2;
	}
	int& refer_arr1 = &arr[1];
	int* point_arr1 = &arr[1];
	++refer_arr1;
	++point_arr1;
	cout << refer_arr1 << endl;		//输出结果为2
	cout << (*point_arr1) << endl;	//输出结果为4
}
  1. 有多级指针,但是没有多级引用
  2. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
void test()
{
	int num = 16;
	int& refer_num = num;
	int* point_num = &num;
	
	cout << refer_num << endl;		//不需要解引用
	cout << (*point_num) << endl;	//解引用操作
}
  1. 引用比指针使用起来相对更安全

文章结语:这篇文章对C++中的引用进行了简要的介绍。
🎈欢迎进入浅尝C++专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

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

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

相关文章

井盖异动传感器,守护脚下安全

随着城市化进程的加速&#xff0c;城市基础设施的安全问题日益受到关注。其中&#xff0c;井盖作为城市地下管道的重要入口&#xff0c;其安全问题不容忽视。然而&#xff0c;传统的井盖监控方式往往存在盲区&#xff0c;无法及时发现井盖的异常移动。为此&#xff0c;我们推出…

数据库与低代码:加速开发,提升效率的完美结合

随着技术的不断进步&#xff0c;数据库和低代码开发成为了现代应用程序开发中的两大关键要素。本文将探讨如何通过结合数据库和低代码开发&#xff0c;加速应用程序的开发过程&#xff0c;并提高开发效率和质量。 在过去的几十年中&#xff0c;数据库一直被视为应用程序开发中不…

【Linux进程】查看进程fork创建进程

目录 前言 1. 查看进程 2. 通过系统调用创建进程-fork初识 总结 前言 你有没有想过在使用Linux操作系统时&#xff0c;后台运行的程序是如何管理的&#xff1f;在Linux中&#xff0c;进程是一个非常重要的概念。本文将介绍如何查看当前运行的进程&#xff0c;并且讨论如何使用…

Sip - Ubuntu 配置 miniSIPServer 服务器(测试用)

客户提供的账号过期了&#xff0c;简单搭建 SIP 服务器&#xff0c;以便测试使用。个人认为这个配置起来最为简单&#xff0c;且测试功能足够。 官网miniSIPServer - 基于 Windows 以及 Linux 平台的 VoIP (SIP) 服务器软件. miniSIPServer 可能是最容易使用的 VoIP(SIP) 服务器…

获取进行逗号分隔的id值 Split的使用

获取进行逗号分隔的id值,Split的使用 后台实现对有逗号进行分割的字符串 使用这行代码就不会有一个空数组值,直接过滤调数组中的空值 var ids = key.Split(,).Where(s => !string.IsNullOrEmpty(s

进行交流负载测试的步骤和规范

交流负载测试是一种评估系统在正常或峰值负载下的性能和稳定性的测试方法。以下是进行交流负载测试的步骤和规范&#xff1a; 1. 确定测试目标&#xff1a;首先&#xff0c;需要明确测试的目标&#xff0c;例如&#xff0c;测试系统的响应时间、吞吐量、错误率等。 2. 设计测试…

Linux系统操作命令

Linux管理 在线查询Linux命令&#xff1a; https://www.runoob.com/linux/linux-install.htmlhttps://www.linuxcool.com/https://man.linuxde.net/ 1.Linux系统目录结构 Linux系统的目录结构是一个树状结构&#xff0c;每一个文件或目录都从根目录开始&#xff0c;并且根目…

双亲委派机制[人话版]

本篇文章仅作为记录学习之用,不具有参考价值. 如果您想系统学习,请移步最下方参考资料. 介绍 今天逛了一下牛客网, 看到有面试问到了双亲委派机制是什么, tomcat有没有打破双亲委派 , 瞬间懵逼, 听都没听过的名字, 听着就稀奇古怪. 然后翻了一下网上的答案,大概了解怎么回事.…

Python自动化测试数据驱动解决数据错误

数据驱动将测试数据和测试行为完全分离&#xff0c;实施数据驱动测试步骤如下&#xff1a; A、编写测试脚本&#xff0c;脚本需要支持从程序对象、文件或者数据库读入测试数据&#xff1b; B、将测试脚本使用的测试数据存入程序对象、文件或者数据库等外部介质中&#xff1b;…

知识库软件有很多,这几个最好用

时代进步的同时&#xff0c;逐渐优化的企业知识库已经成为企业优化工作效率、提升企业竞争力的重要工具。随着云计算和大数据技术的快速发展&#xff0c;知识库软件如雨后春笋般出现在人们的视野中。下面&#xff0c;我从寻宝者的角度&#xff0c;向大家稳稳地推荐三款最优秀的…

mp-html 微信原生小程序渲染富文本

引入组件 "usingComponents": {"mp-html": "/components/mp-html/index"}使用 <mp-html content"{{info.course_info.info}}" />获取组件 介绍 mp-html&#xff0c;小程序富文本解析利器 全面支持html标签 小程序大多数都是…

C++重新认知:拷贝构造函数

一、什么是拷贝构造函数 对于简单变量来说&#xff0c;可以轻松完成拷贝。 int a 10; int b a;但是对于复杂的类对象来说&#xff0c;不仅存在变量成员&#xff0c;也存在各种函数等。因此相同类型的类对象是通过拷贝构造函数来完成复制过程的。 #include<iostream>…

使用Notepad++将多行数据合并成一行

步骤 1、按CtrlF&#xff0c;弹出“替换”的窗口&#xff1b; 2、选择“替换”菜单&#xff1b; 3、“查找目标”内容输入为&#xff1a;\r\n&#xff1b; 4、“替换为”内容为空&#xff1b; 5、“查找模式”选择为正则表达式&#xff1b; 6、设置好之后&#xff0c;点击“全…

Spring Data JPA 踩过的坑实录

前言 游戏中台一直在使用spring 全家桶&#xff0c; 本文会左右使用Spring Data JPA的坑点记录总结 主要给大家总结介绍了关于使用Spring JPA注意事项及踩过的坑。 案例1&#xff1a; 为什么只调用了 org.springframework.data.repository.CrudRepository#findById(ID id) 却…

STM32入门教程-2023版【3-4】总结GPIO使用方法

三、总结GPIO使用方法 总体上来说是比较简单的 首先初始化时钟&#xff0c;然后定义结构体&#xff0c;赋值结构体 GPIO_Mode可以选择那8种输入输出模式&#xff0c;GPIO_Pin选择引脚&#xff0c;可以用按位或的方式同时选中多个引脚,GPIO_Speed选择输出速度&#xff0c;最后使…

基于springboot+vue的个人健康管理系统(有文档、Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

《异侠传S1赛季侠义九州》公测版本三端互通PC客户端与IOS下载地址!!!

尊敬的各位异侠玩家们&#xff1a; 我们怀着无比激动的心情&#xff0c;充满感激地向大家宣布&#xff1a;今天上午10&#xff1a;00我们即将迎来《异侠传S1赛季&#xff1a;侠义九州》的首发公测&#xff01;在这个特殊的时刻&#xff0c;我们想将我们最诚挚的感谢献给每一位…

MES管理系统解决方案在汽配企业中的重要性

在汽车制造业中&#xff0c;MES管理系统解决方案已成为引领精益生产的关键要素。该系统专注于监控和管理汽车生产流程&#xff0c;利用实时数据分析和采集技术&#xff0c;为企业提供了提高效率、降低成本和确保高质量产品的有效手段。本文将详细介绍汽配企业MES管理系统的特点…

MindOpt:阿里巴巴达摩院打造的优化求解器及其组件全面介绍

MindOpt 简介和获取 MindOpt 是阿里巴巴达摩院决策智能实验室研发的决策优化软件。团队组建于2019年&#xff0c;聚焦于研发尖端运筹优化和机器学习技术&#xff0c;构建智能决策系统&#xff0c;更快更好地向各行各业提供数学建模与求解能力&#xff0c;帮助业务更快更好地做…

网络多线程开发小项目--QQ登陆聊天功能(服务端推送新闻、离线留言和文件)

9.1.5、QQ登陆聊天功能&#xff08;服务端推送新闻、离线留言和文件&#xff09; 9.1.5.1、服务端推送新闻 1、需求分析 2、思路分析 3、代码实现 QQServer&#xff1a; 1&#xff09;cn.com.agree.qqserver.service.SendNewsToAllClient package cn.com.agree.qqserver.s…