C++篇:入门(2)

news2024/11/16 5:34:46

引用

        引用的概念以及定义:

        在C++中,引用(Reference)是一个非常重要的概念又可以称之为取别名,它允许我们创建一个已存在对象的别名。引用提供了一种机制,通过它可以直接访问另一个变量、对象或函数的值,而无需对其进行复制,通俗的来说就是给变量取另一个外号。比如西游记中,孙悟空有一个别名叫孙大圣,那么孙大圣就是孙悟空的外号,本质上都是指向同一个人。

        那么引用的定义则是:类型& 引⽤别名 = 引⽤对象;

       

        在上图,先是定义了一个int类型的变量a,接着下一行又写了int& b = a,这里的&的用法并不是表示取地址的意思,而是表示对a这个变量取了一个别名,称为b,而c又是b的别名。但通过输出发现它们都输出了同样的值,并且分别将a,b,c的地址都打印出来,这里的&符号才是取地址的意思。可以看出a,b,c都所处在一个相同的地址空间中。

        引用的特性

                1.引⽤在定义时必须初始化

                        

        对于引用的初始化必须要有一个确定的值这样编译器才知道是对谁引用,就像一个人肯定要现有本名才会有小名。

                2.⼀个变量可以有多个引⽤

        正如上图所示,a的变量可以有小名b,甚至还可以有小名c,但回归本质,b与c其实都是a。

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

        

        通过上图所示,一开始创建了a,c变量。接着将b引用了a,那么先输出b的值,可以看出b目前存储的是a变量里的值。下一步将b=c,那么这一步是将b重新引用了c还是修改b变量里的值?而答案显而易见,在之后输出a,b,c的地址时可以观察到,b的地址还是与a的地址相同,并没发生改变。而a的值被修改为20,可以看出在b=c这步操作里,是将c的值赋到了别名b中,而b的本体其实是a,修改b的值同时也是修改a的值。

        在C++中引用一旦与实体进行绑定那么就不能被修改,类似夫妻两个人结了婚,正常来说基本上两个人就是绑定终生。

        

        引用的使用

                1.做输出型参数使用以及提高效率(大对象)

                        

        如上图,创建了一个Swap函形参为int &x,int &y。这里int &x与int &y的意思传进来的并不是参数的地址,而是参数的别名。

        在主函数里定义了a与b,使用 Swap(a, b),这里要注意传进来的是a与b的别名也就是引用,那么别名里参数的改变,本体的值肯定也跟着发生变化。如果在c语言中想要通过函数来交换两个变量则需要传地址相对会更加麻烦。

        

        上图定义了一个结构体A,A里存放着数组a其长度为一万。接着定义了fun1函数与fun2函数,fun1的参数是A a,fun2的参数则是A &a。通过调用一万次fun1与fun2可以看出,执行一万次fun1函数需要8毫秒时间,而fun2则是0毫秒。

                  

                因为fun1函数里的参数是一个形参,而形参是实参的一份临时拷贝。也就是说每调用一次fun1就会生成一份实参的临时拷贝,调用一万次就生成一万次,效率就会很差。

                而fun2函数里的参数是一个引用,而引用不会产生临时拷贝,使用引用参数可以避免复制大型对象,可以大大提高效率。

                2.做返回值使用
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST&st,int n=4)
{
	st.a = (STDataType*)malloc(sizeof(int) * n);
	st.top = 0;
	st.capacity = n;
}

void STPush(ST&st,STDataType x)
{
	assert(&st);
	if ((st.capacity==st.top))
	{
		printf("扩容\n");
		int newcapacity = st.capacity == 0 ? 4 : st.capacity * 2;
		STDataType* tmp = (STDataType*)realloc(st.a, newcapacity *sizeof(STDataType));
		assert(tmp);
		st.a = tmp;
		st.capacity *= newcapacity;
	}
	st.a[st.top] = x;
	st.top++;
}

int& STTop(ST&st)
{
	assert(st.top > 0);
	return st.a[st.top-1];
}


int main()
{
	ST st;
	STInit(st);
	STPush(st, 1);
	STPush(st, 2);
	STPush(st, 3);
	cout << STTop(st) << endl;
	STTop(st)++;
	cout << STTop(st) << endl;
}

        上图写了一段栈的代码,而和C语言时候写的又有些许不同,在函数STInit(ST&st,int n=4)与 STPush(ST&st,STDataType x)中,它们的第一个参数变成了ST&st,不再是指针的形式。从上图代码中可以看出对于栈的初始化以及插入,使用引用比使用指针条理会清晰许多。指针还需要解引用操作这些,而引用不需要,引用就是本体,对引用的操作就是对本体进行操作,简洁明了。

        int& STTop(ST&st) :该函数用于获取栈st 的栈顶元素的引用。先确保栈中至少有一个元素,然后返回栈顶元素的引用。这里代码返回引用后直接进行++表示返回的是栈顶元素里3的引用,而对3的引用进行++,不就是对3进行++吗。

        可以看出返回类型是引用既取到了栈顶元素的值,又可以对栈顶元素值之间进行++或--之类的操作。而如果在c语言中,至少要写两个函数一个用于返回找到元素,一个用于修改,会麻烦许多。C++的引用在一定程度上优化了指针,但并没有完全抛弃指针,因为引用只能绑定一个对象,而指针可以改变指向的对象。

                3.引用的注意事项

                介绍了引用的优点但并不代表时候使用引用就没有风险,使用引用同样是有风险的。

        可以看出当第一次输出ret1,2,3的值时还是正常的,而第二次的时候ret3变成了一串随机数,那么这是为什么呢?在这我们就要分情况进行讨论:

        

        因为返回的是参数,而函数在返回的时候并不是直接返回值,而是会通过一个临时变量将k的值存储起来可能是寄存器的存储,当函数栈帧销毁时,再把存储的值赋给ret1,此时ret1的值是安全的。

       函数N2中声明了一个整型变量k,并将其初始化为参数n的值。然后,通过返回k的引用来返回一个整型的引用,而因为k是函数内的局部变量,而当函数进行执行完则进行销毁,此时k的引用指向一个已经不存在的内存位置,使用引用进行返回相当于跨过了将值传递给临时变量的步骤。

        那为什么还是会ret2还是能获取到10呢?这其实跟编译器有关,此时编译器在销毁完函数栈帧并没有清理,所以k的引用还是原本的值。如果编译器在函数销毁完就进行清理那么此时ret2获取到的值很可能就是随机值,所以ret2其实是危险的。

        函数N2返回的是别名,变量ret3被声明为对N2函数返回值的引用。由于返回的是对局部变量k的引用,ret3实际上指向一个已经销毁的对象。这里其实相当于了一个野指针,ret3指向了一段野变量,当栈帧被清理或覆盖时候,ret3就是一个随机值,结果就更加危险了。

        小结:

        引用做返回值需要考虑两点

        1.如果引用的返回值是一个函数内变量,出了函数则会销毁,那么这个引用则是危险的

        2.如果不是,则是安全的,如:全局变量或malloc堆上申请的。

        const引用

        可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访问权限在引⽤过程中可以缩⼩,申请可以平移,但不能放大。
        

                

          从上图见到,当我给变量a进行const限定,再对a进行引用,而这里引用会导致a的权限被放大,程序就进行了报错。就比如a是一个管理员,管理员规定这个文件只能读不能写,而b身为用户,却想要拥有读写的权限那么肯定是不行的。

        而上图对const修饰的a变量再取一个用const修饰的别名b,那么此时权限并没有被放大,权限只是被平移了,a与b都不能修改其值。

        权限还可以进行缩小,如上图定义了变量c,c没有被const修饰,可以改变值。而对c取一个用const修饰的引用b,这就是权限的缩小,c依旧可以改变自身的值,而b没有权限改变c的值。

        需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场景下a*3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产⽣临时对象存储中间值,也就是时,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥就触发了权限放⼤,必须要⽤常引⽤才可以。
                           
        所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。            

                引用与指针的关系

        C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有⾃⼰的特点,互相不可替代。
        1.语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
        2.引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
        3.引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
        4.引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
        
        5.sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
        6.指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。

inline

        inline的概念

⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联 函数就需要建⽴栈帧了,就可以提⾼效率。
        inline对于编译器⽽⾔只是⼀个建议,也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁 调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。

        inline的使用:

        inline的使用是不易观察的,因为inline对于编译器只是一个选择性建议,真正会不会执行inline还要看编译器最终的执行。

        通过汇编码可以看出,当汇编执行到Add (0F013DEh)的时候就要为函数Add开辟一块栈帧空间,并没有因为inline就直接在主函数里展开还是创建了函数栈帧,那么想要观察inline就必须对编辑器进行设置。

        

        从上图可以看到,调试完后再次运行程序,此时就不再执行call指令,而是直接展开来在main函数栈帧里直接进行操作,这样做就可以提⾼效率。

        inline与宏的区别

        在C语言中宏是一种预处理指令,通过简单的文本替换来展开宏定义。预处理阶段会将代码中所有出现的宏名称替换为对应的宏定义内容,宏在展开时没有类型检查,它只是简单地进行文本替换这就会导致很多问题。

        可以看到一个宏的写法非常多,而在上图三个宏里只有第三个才算是正确的,所以宏的使用需要更加的小心谨慎。

        inline则没有这种歧义,inline提示编译器将函数的调用处直接展开成函数体,而不是像普通函数那样进行函数调用,inline函数具有类型安全性,编译器会进行参数类型检查和返回类型检查,避免了宏带来的潜在问题。

nullptr

        NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

        上段代码的意识是,如果是在cpp的文件中那么NULL表示为常量值0,如果是在c语言中则表示为((void)*0)。

        C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的  f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖 f((void*)NULL);
调⽤则会报错。

        所以C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换 成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。

                所以在今后的使用中使用nullptr更正确。

                                                                                                                      那么文章到这里就结果了,感谢各位大佬的观看,咱们下篇再见!

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

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

相关文章

Nginx进阶-常见配置(一)

一、nginx Proxy 反向代理 1、代理原理 反向代理产生的背景&#xff1a; 在计算机世界里&#xff0c;由于单个服务器的处理客户端&#xff08;用户&#xff09;请求能力有一个极限&#xff0c;当用户的接入请求蜂拥而入时&#xff0c;会造成服务器忙不过来的局面&#xff0c…

【实现100个unity特效之12】Unity中的冲击波 ——如何使用ShaderGraph制作一个冲击波着色器

最终效果 文章目录 最终效果新增LitShaderGraph圆环扭曲效果优化冲击波效果屏幕全屏冲击波圆形冲击波最终连线图代码控制补充源码完结 新增LitShaderGraph 圆环扭曲效果 让我们从一个UV节点开始 创建一个Vector2变量RingSpawnPosition表示冲击波生成位置,在X和Y上将其默认值…

springboot实现前后端调用axios异步请求(后端单体服务器static部分)

目的&#xff1a;让页面调用controller&#xff0c;将数据加载到页面中&#xff08;只不过这个前端页面我们直接就是放到了static里面了&#xff09;。 第一步&#xff1a;导入文件 所需要的文件见本文最后“文件获取”&#xff1a; &#xff08;1&#xff09;文件如下&…

汇昌联信拼多多运营怎么样?

汇昌联信拼多多运营怎么样?在探讨汇昌联信在拼多多平台的运营情况时&#xff0c;首先需要明确的回答是&#xff1a;汇昌联信在拼多多的运营表现是积极的&#xff0c;并取得了一定的成效。接下来&#xff0c;我们将从五个不同的角度深入分析其运营策略及效果。 一、产品多样性与…

Centos7挂载数据盘

查看当前服务器有哪些磁盘 fdisk -l 2.格式化 mke2fs -t ext4 /dev/vdc 3.挂载数据盘 mkdir /sdxinfang mount /dev/vdc /sdxinfang/ 为了避免每次开机都要重新挂载&#xff0c;直接设置系统挂载信息&#xff0c;这样开机会自动挂载 vim /etc/fstab 在文件末尾增加以下内容&…

Axure八大优质Web端系统框架模版

在当今数字化转型的浪潮中&#xff0c;Axure作为一款强大的原型设计工具&#xff0c;以其快速、直观和易用的特点&#xff0c;成为了众多设计师和产品经理的首选。本文将详细介绍六套基于Axure制作的智慧系统原型框架模版&#xff0c;包括智慧园区、智慧社区、智慧乡村、智慧驾…

4个好用的 CSS 伪类 :not()、:has()、 :is()、:where()

文章目录 &#xff08;1&#xff09;:not()&#xff08;2&#xff09;:has()&#xff08;3&#xff09;:is()&#xff08;4&#xff09;:where()&#xff08;5&#xff09;:where()与:is() 的区别 &#xff08;1&#xff09;:not() :not 伪类&#xff1a;用于选择不满足给定条…

微信小程序开发费用一览表,不同开发方式的费用对比

微信小程序作为当前移动互联网领域的重要入口之一&#xff0c;其开发费用因开发方式、功能需求、设计复杂度及开发团队的不同而有所差异。本文将详细梳理微信小程序开发的几种主要方式&#xff0c;并对比各方式的费用情况&#xff0c;以便企业和个人在选择时能够有更清晰的了解…

PHP 打印 V 和倒 V 图案的程序(Program to print V and inverted-V pattern)

倒 V 型模式&#xff1a;给定 n 的值&#xff0c;打印倒 V 型模式。示例&#xff1a; 输入&#xff1a;n 5 输出 &#xff1a; E D D C C B B A A 输入&#xff1a;n 7 输出 &#xff1a; G F F E E D D C C B B A…

pycharm中安装、使用扩展工具,以QT Designer为例

pycharm中安装、使用扩展工具&#xff0c;以QT Designer为例 第一步&#xff0c;下载QT Designer安装包。找到QT Designer.exe所在位置&#xff0c;复制路径 第二步&#xff0c;打开Pycharm&#xff0c;选择Setting&#xff0c;找到扩展工具&#xff08;External Tools&#xf…

git回退未commit、回退已commit、回退已push、合并某一次commit到另一个分支

文章目录 1、git回退未commit2、git回退已commit3、git回退已push的代码3.1 直接丢弃某一次的push3.2 撤销push后&#xff0c;不丢弃改动&#xff0c;重新修改后要再次push 4、合并某一次commit到另一个分支 整理几个工作上遇到的git问题。 1、git回退未commit git回退未comm…

【C++】STL-哈希表封装unorder_set和unordered_map

目录 1、实现哈希表的泛型 2、unordered_set和unordered_map的插入 3、迭代器 3.1 operator 3.2 const迭代器 4、find 5、unordered_map的operator[] 6、对于无法取模的类型 7、介绍unordered_set的几个函数 7.1 bucket_count 7.2 bucket_size 7.3 bucket 7.4 rese…

Gcc/G++编译C/C++文件(主要以C++语言为主,C语言就做阐述 用法一样 就是将G++换成GCC)

首先&#xff0c;我们在Linux中创建一个helloc.cc文件(C文件) vim helloc.cc 直接用g裸编译 g helloc.cc 生成的a.out就是二进制可执行文件 如果要产生 自定义可执行文件 就需要下面的编译步骤 繁琐操作 g -c helloc.cc 会生成目标文件 g -o hello helloc.o 此时hell…

仿SOUL社交友附近人婚恋约仿陌陌APP系统源码

专门为单身男女打造的恋爱交友社区&#xff0c;就是一个由千千万万单身男女组建的大家庭。他们来自全国各地&#xff0c;或许有着不同的人生经历&#xff0c;却有着共同的对恋爱交友的渴望。他们可以通过文字、语音、视频聊天的方式&#xff0c;和镜头前的彼此诉说自己工作中发…

95页PPT丨IBM-IT应用规划

一、IBM针对IT应用规划项目核心内容IBM在IT应用规划项目中的核心内容&#xff0c;旨在帮助企业实现数字化转型&#xff0c;优化IT资源配置&#xff0c;并确保IT战略与业务目标的一致性。以下是IBM IT应用规划项目的详细核心内容&#xff1a; 资料下载方式&#xff0c;请看每张…

LabVIEW与CANopen实现自动化生产线的设备控制与数据采集

在某工厂的自动化生产线上&#xff0c;多个设备通过CANopen网络进行通信和控制。这些设备包括传感器、执行器和PLC&#xff0c;它们共同负责监测和控制生产过程中的关键参数&#xff0c;如温度、压力、速度等。为了实现对整个生产线的集中监控和管理&#xff0c;工厂决定使用La…

深入理解同城代驾系统源码:技术架构与实现细节

今天&#xff0c;小编将深入讲解同城代驾系统的技术架构与实现细节。 一、同城代驾系统的基本功能模块 一个完整的同城代驾系统通常包括以下核心功能模块&#xff1a; 1.用户端应用 2.司机端应用 3.后台管理系统 4.消息推送与通知 二、技术架构设计 同城代驾系统的技术架…

程序设计基础(c语言)_补充_1

1、编程应用双层循环输出九九乘法表 #include <stdio.h> #include <stdlib.h> int main() {int i,j;for(i1;i<9;i){for(j1;j<i;j)if(ji)printf("%d*%d%d",j,i,j*i);elseprintf("%d*%d%-2d ",j,i,j*i);printf("\n");}return 0…

DNS处理模块 dnspython

DNS处理模块 dnspython 标题介绍安装dnspython 模块常用方法介绍实践&#xff1a;DNS域名轮询业务监控 标题介绍 Dnspython 是 Python 的 DNS 工具包。它可用于查询、区域传输、动态更新、名称服务器测试和许多其他事情。 dnspython 模块提供了大量的 DNS 处理方法&#xff0c…

Flink 实时数仓(五)【DWD 层搭建(三)交易域事实表】

前言 今天开始交易域事实表的创建&#xff0c;上一节流量域中的表&#xff08;其实就是一个 kafka 主题&#xff09;数据来自于日志&#xff0c;而交易域的数据来自于业务系统&#xff0c;业务表之间是有关联性的。 我们之前在离线数仓中&#xff08;声明粒度&#xff08;最细粒…