初识C++(二)

news/2025/1/14 21:00:38/

六、引用

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

通俗地讲,可以理解为一个人能够拥有多个称呼,这些所有的称呼都是表示这一个人的。

用法:类型& 引用变量名(对象名) = 引用实体;

注意:引用类型与引用实体的类型必须一致!

引用使用的注意事项

①引用在定义时必须初始化

②引用初始化后,无法再改变指向

③一个引用实体可以拥有多个引用/别名

下面是关于注意事项的图例:

引用使用举例

交换函数Swap:

那么问题来了:既然能够使用引用来替换指针的一些用途,引用能否完全替换掉指针呢?

不能的!C++设计出引用,是对指针使用复杂的场景进行一些替换,让代码简单易懂,并不能完全替换指针,引用不能够完全替换指针的根本原因在于引用不能够改变其指向。

对于Java/Python而言,是不存在指针的,这两门语言的引用能够改变指向,因此替换掉了指针。

也正是因为C++的引用不能够改变指向,对于链表、二叉树等基于指针链接前后物理不连续的空间的数据结构,也是不能使用引用替换掉指针的!

引用的使用场景

1.做参数

①输出型参数;②对象比较大时,使用引用能够减少拷贝、提高效率。

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

2.做返回值

①修改返回对象;②减少拷贝,提高效率。

错误示例

首先,C语言中学到过,当我们在函数中返回临时变量/局部变量的值时,由于函数栈帧的回收,会导致返回的该值是一个随机值,指针中讲到的野指针也是这样类似的情况。

以下是错误的示例

上图就返回了函数中创建的一个临时变量的值,这样做是错误的。

那么同样的,我们也不能够在上图的返回类型使用引用类型,同样为错误示例,如下所示:

上图函数Func返回类型int&,引用类型的返回,同样不能够作用在临时变量/局部变量上!否则相当于对一块未经允许访问的空间进行了访问,本质是野引用!将这个未经访问的空间a中的值赋给ret,那么ret就是随机值。

同样,ret是别名的错误示例

返回变量出了函数作用域,生命周期到头,需要销毁(栈帧回收) ,因此不能够使用引用返回!

如局部变量与临时变量。

正确示例

引用不能作用于临时变量、局部变量,那么引用一般作用于什么呢?

引用能够作用于静态变量(如static修饰)、全局变量、堆区开辟空间的变量。

首先在C++中,变量一般被称之为对象,同时C++将结构体升级成了类:结构体中不再只能够定义变量/对象,还能够定义函数。这意味着对于一些数据结构的实现,不再需要采取结构定义与实现函数分离的形式,而是可以直接在类中定义结构和函数进行操作

类中定义的函数名也较C语言简洁,因为该函数存在于哪个类,就属于哪个类的函数,不需要用复杂的名称来与其他的结构中的函数进行区分!

拿顺序表简单地举个例子: 

顺序表的类:

//正确示范
//C++中的结构体中能够定义对象和函数,结构体一般称之为类
//同时C++不需要使用typedef去除struct,允许直接使用struct后名称
struct SeqList
{int* a;int size;int capacity;//相较于C语言的函数操作更为简便void Init()//初始化{int* tmp = (int*)malloc(sizeof(int) * 4);if (tmp == NULL){perror("malloc fail");return;}a = tmp;size = 0;capacity = 4;}void PushBack(int x)//尾插{//CheckMemorySize(省略)a[size++] = x;}int& Get(int pos)//获取pos下标的数据、使用int&返回别名,也能够修改pos处的数据{assert(pos >= 0);assert(pos < size);return a[pos];}void Destroy()//销毁{free(a);a = NULL;}
};

如上我们可以看见,在SeqList类中直接定义初始化、尾插、获取任意位置数据并修改等函数,在形参部分和整体代码量相较于C语言简洁便利许多。同时C++不需要使用typedef去除struct,直接使用类型名即可。

同时,对于获取pos下标处数据与修改,在C语言中本是两个函数分别完成,但是现在可以使用int&的引用类型作为返回值,一个函数就能够搞定,因为它返回的是该下标处数据的别名,对其的修改能够直接体现在数据的修改上。

传值返回,返回的是变量的临时拷贝;传引用返回,返回的是变量的别名。

正因如此,传值无法做到直接改变目标变量,但是传引用却能够轻松完成。

简单插入4个数据对其进行一些数据操作如下图:

如果我们将Get函数的返回类型改为int,那么就会返回目标下标数据的拷贝,即一份临时变量。

临时变量具有常性,即常量性,无法被修改!

如上图,使用引用作为返回值是能够起到很重要的作用的。

引用与指针的区别

引用在底层转换为指针的解释如下图: 

我们对比引用与指针在汇编层面上的指令,发现引用经过编译过程后也被转换为了指针,那么就说明,在底层上,引用是会开空间的,语法层面上引用是别名,不开空间。

引用底层使用指针实现的,引用的语法含义与底层实现是背离的!

通俗点,就像鱼香肉丝中没有鱼,老婆饼也不是老婆做的一样。

七、内联函数

如果调用函数次数过多、需要创建的函数栈帧过多,那么为了提高效率,C语言中通过宏函数的方式来替换函数;而C++通过内联函数的方式使函数在外部直接展开而不用创建栈帧。

对于宏而言,需要注意:1.宏并不是函数;2.宏属于预处理指令,末尾不需要分号,本质是一种替换;3.额外重视括号!括号控制优先级。

宏在预处理阶段就会被替换!

对于一个加法的Add函数,写成宏函数如下:

为什么x、y要单独括号括起来?

因为x、y不一定代表一个值,可能代表一个表达式!如果x、y是表达式,且表达式中的运算符优先级低于+号,那么替换进去就会出现问题!举例如下:

因此我们需要加括号确保绝对的正确顺序。

宏的优缺点?

优点:1.增强代码的复用性;2、提高效率/性能

缺点:1、宏在预处理阶段就被替换,不方便调试;2、可读性差,可维护性差,容易误用;3、没有类型安全的检查。

C++替代宏的技术?

1、常量定义---换用const enum;2、短小函数定义,换用内联函数。

内联函数的概念

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。

未使用inline修饰时,调用Add函数的反汇编代码如下图:

在汇编层面上,我们看到编译器会去找函数Add的指令首地址,从而调用函数。

如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用,不调用函数,也就不用创建函数栈帧,消除了函数栈帧创建的开销:

release下:直接查看反汇编下汇编代码是否存在call Add

debug下:我们需要将属性-常规-调试信息格式改为程序数据库(/Zi),属性-优化-内联函数扩展改为只适用于_inline(/Ob1),这样就能够调试-反汇编查看了

那么使用inline修饰Add函数,使其成为内联函数后,内联函数展开的反汇编如下:

通俗来讲,内联函数就是将函数里面的运算逻辑灵活地在外面实现,而不去创建调用函数所需要的栈帧空间。

内联特点

内联本质上是向编译器发出请求,当函数较大,编译器就会忽略内联的请求,较小函数能够正常内联。

因此较小函数的多次调用,我们可以使用内联inline,较大函数则使用静态static。

1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。

2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。

3. inline不建议声明与定义分离,分离的情况会导致链接错误,inline展开函数,就不会在符号表中生成该函数的地址,链接时就无法找到对应函数。

#pragma once解决头文件重复引入的问题,但是并不能解决两个.cpp文件中都包含相同头文件,该头文件又包含某个函数的定义,如下图所示,那么就会存在函数的重定义问题。

有三种方式能够解决这一问题: 

①声明与定义分离

如果不采用声明与定义分离,只通过定义,那么可以使用static或者inline进行操作。 

②采用static静态链接,只在当前文件可见 

③采用inline内联,同理只在当前文件展开

八、auto关键字---C++11

auto:自动推导类型。

对于auto而言,必须要初始化,不然怎么自动推导呢?

实际意义:对于较长类型,可以使用auto简便替代:

如下图的函数指针类型:

再如其他较长的类型:

auto的利弊

优点:简化较长的类型。

缺点:对不熟悉当前代码的人而言,一定程度上影响代码可读性。

注:①C++规定auto不能定义数组。②慎用auto,C++目前允许auto作返回值,但是不建议。

③同一行使用auto声明多个变量,这些变量必须同类型,否则编译器不会通过,编译器只对第一个类型进行推导,然后用该类型定义后面的变量。

上图中auto a = 9 , k = 'c' ;就无法编译通过,因为a与k类型不同但是他俩想用一个auto。

auto不能推导的场景

①auto不能作为函数的参数

②auto不能直接用来声明数组

九、范围for循环---C++11

范围for循环的使用

for(auto e : array)
{cout << e ;
}
cout << end;

auto自动推导数组中数据类型,也可以自己填入数组数据类型;e是数组数据的依次的临时拷贝。

范围for循环会自动迭代、自动判断结束。

范围for循环可以使我们的数组遍历过程变得非常简便:

范围for循环的使用条件

①for循环迭代的范围必须确定。

数组即第一个元素到最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

②迭代的对象要实现++和==的操作

十、指针空值nullptr---C++11

在C++中,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。

因此C++11中创建了nullptr关键字,用于表示指针空值,就不用NULL来表示指针空值了,以避免可能出现的差错。

注:

①在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的    ②在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。                                        ③为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。


http://www.ppmy.cn/news/1563125.html

相关文章

【网络】:网络编程套接字

目录 源IP地址和目的IP地址 源MAC地址和目的MAC地址 源端口号和目的端口号 端口号 VS 进程ID TCP协议和UDP协议 网络字节序 字符串IP和整数IP相互转换 查看当前网络的状态 socket编程接口 socket常见API 创建套接字&#xff08;socket&#xff09; 绑定端口号&…

Redis优化建议详解

Redis优化建议详解 1. 内存优化 1.1 内存配置 设置最大内存 maxmemory 4gb 内存淘汰策略 maxmemory-policy allkeys-lru 样本数量 maxmemory-samples 51.2 内存优化策略 数据结构优化 使用压缩列表&#xff08;ziplist&#xff09;合理设置hash-max-ziplist-entries使用整数…

LVS 支持 UDP 协议代理

在现代网络架构中,负载均衡技术是保证高可用性和高性能的关键组成部分。Linux Virtual Server(LVS)作为一个高效、稳定的负载均衡解决方案,广泛应用于处理 TCP 流量的场景。然而,随着实时通信、视频流和在线游戏等应用的不断发展,UDP 协议的支持成为了 LVS 负载均衡的重要…

【update 更新数据语法合集】.NET开源ORM框架 SqlSugar 系列

系列文章目录 &#x1f380;&#x1f380;&#x1f380; .NET开源 ORM 框架 SqlSugar 系列 &#x1f380;&#x1f380;&#x1f380; 文章目录 系列文章目录前言 &#x1f343;一、实体对象更新1.1 单条与批量1.2 不更新某列1.3 只更新某列1.4 NULL列不更新1.5 无主键/指定列…

什么是TCP重传率,有什么用?如何查看?

TCP重传率是一个衡量TCP网络性能的重要指标&#xff0c;它指的是在TCP通信过程中&#xff0c;由于数据包丢失、损坏或确认(ACK)未按预期到达而导致的数据包重传的比例或率。 TCP协议通过重传机制来保证数据传输的可靠性&#xff0c;但过高的重传率通常意味着网络质量问题&…

作用域、this上下文、闭包

作用域&#xff08;静态分析&#xff09; 作用域是定义变量和访问变量的范围。 作用域可以通过静态分析&#xff0c;不需要运行代码&#xff0c;就可以分析出当前作用域。 作用域分为&#xff1a;全局作用域、函数作用域、块级作用域。 全局作用域&#xff1a;在顶层声明的…

一个使用 Golang 编写的新一代网络爬虫框架,支持JS动态内容爬取

大家好&#xff0c;今天给大家分享一个由ProjectDiscovery组织开发的开源“下一代爬虫框架”Katana&#xff0c;旨在提供高效、灵活且功能丰富的网络爬取体验&#xff0c;适用于各种自动化管道和数据收集任务。 项目介绍 Katana 是 ProjectDiscovery 精心打造的命令行界面&…

Swagger学习⑰——@Link注解

介绍 Link 是 Swagger/OpenAPI 3.0 注解库中的一个注解&#xff0c;用于在 OpenAPI 文档中定义链接&#xff08;Link&#xff09;。链接是一种在 API 响应中提供相关操作或资源引用的机制&#xff0c;通常用于描述操作之间的关系或提供额外的操作提示。 Link 注解的作用 Link…