开启C++之旅(下):引用、内联函数及现代特性(auto和范围for循环)

上次介绍了:开启C++之旅(上):探索命名空间与函数特性(缺省参数和函数重载)

今天就接着进行c++入门的知识讲解


文章目录

  • 1.引用
    • 1.1引用概念
    • 1.2引用特性
    • 1.3常引用
      • 其他情况
    • 1.4引用使用场景
      • 1.4.1做参数
      • 1.4.2做返回值
    • 1.5引用与指针的区别
  • 2.内联函数
    • 2.1内联函数概念
    • 2.2内联函数特性
  • 3. auto关键字
    • 3.1概念
    • 3.2auto的使用细则
    • 3.3auto不能使用的场景
  • 4.基于范围的for循环(C++11)
    • 4.1范围for的语法
    • 4.2范围for的使用条件
  • 5.指针空值nullptr(C++11)


1.引用

1.1引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。通过引用,你可以使用一个变量的多个名称来访问和修改它的值

定义形式:

类型& 引用变量名 = 引用实体(这里&就不是c中大家熟知的取地址了)

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

int main()
{
	int a = 10;
	int& b = a;
	cout << a << endl;
	b = 1;
	cout << a << endl;
	return 0;
}

请添加图片描述

二者也是共用一块内存空间

请添加图片描述

1.2引用特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用(可以起多个别名)
  3. 引用一旦引用一个实体,再不能引用其他实体(不能改变指向)
int main()
{
	int a = 0;
	int& b = a;
	int c = 10;
	b = c;//那这个到底是   c变成d的别名?还是d赋值给c?
	return 0;
}

因为引用不能改变指向,这个是赋值

1.3常引用

int main()
{
	const int a = 10;
	int& ra = a;//这样编译器会报错
	return 0;
}

实际上这样会扩大权限:本身用const修饰后,不能改变a的值了,但是如果引用后就能利用引用改变。这样扩大了权限

这时使用常引用

int main()
{
	const int a = 10;
	const int& ra = a;//这样才对,没有扩大权限
	return 0;
}

其他情况

  1. 对非常量定义常引用
int main()
{
	int a = 10;
	const int& ra = a;//这样也可以,不会报错
	return 0;
}

权限缩小没问题

  1. 隐式类型转换,截断,强制类型转换

在 C++ 中,隐式类型转换、截断和强制类型转换都可能导致临时变量的创建,并且这些临时变量通常具有常量性质,那就需要用常引用

请添加图片描述

加了const就好了

请添加图片描述

在 C++ 中进行类型转换时,通常会创建一个临时变量来存储转换后的结果。这个临时变量是一个匿名对象,它存储了转换后的值,但并不会影响原始变量的值。这也是为什么对a进行类型转换后赋值,但是a不发生变化

1.4引用使用场景

1.4.1做参数

在函数中使用引用作为参数,可以让你直接操作传递给函数的变量,而不是对其进行复制。这样可以避免复制大型对象,提高效率,同时允许函数修改传递的变量值(可以简单理解为:我们把别名传了过来,当然能通过别名来改变本身)

void Swap(int& a, int& b)//交换两个整形
{
	int temp = b;
	b = a;
	a = temp;
}

1.4.2做返回值

在 C++ 中,函数可以返回引用,以避免在返回函数结果时产生拷贝。直接返回传递的变量的引用,允许你对该变量进行操作。然而,使用引用作为返回值需要小心,确保引用所指向的变量在函数返回后仍然有效

使用引用作为返回值的语法是在函数声明或定义中将函数返回类型声明为引用类型。

但是注意

  1. 不能返回局部变量的引用
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;
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

请添加图片描述

  • 在这里返回值是不是随机值取决于是否清理栈帧!!!(没有清理就是原值,清理了就是随机的了)由于我是用VS编译器,VS出栈没有清理栈帧,所以导致这里打印出的是需要的到的值
  • ret始终是函数调用时使用的空间里变量c的别名

出了函数作用域,返回对象(局部变量)就销毁了,不能用引用返回,否则结果是不确定

最好返回指向全局变量、静态变量、或动态分配内存的引用,确保引用在函数返回后仍然有效

  1. 如果使用static来解决上述问题,一定把静态变量初始化和赋值分开
int& Add(int a, int b)
{
	static int c = a + b;
	return c;
}

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

请添加图片描述

仍然有问题,因为

static 关键字用在局部变量上表示该变量在程序运行期间只初始化一次,后续再次调用时,不走那条语句了,直接return c

int& Add(int a, int b)
{
	static int c ;
	c = a + b;//分开就行了
	return c;
}

请添加图片描述

1.5引用与指针的区别

相信大家一开始都会抱有疑问,引用现在能做的指针不也都可以吗? 还有那个别名的底层是什么,怎么理解?

虽然在底层里,创建的引用变量实际是有空间的,可以通过汇编来观察引用是按照指针方式来实现

请添加图片描述

但是

在语法上:我们只是给那个空间取了一个别名没有开辟空间

int main()
{
	char a = '1';
	char& b = a;//如果开了空间,大小是4,没有就是1
	cout << sizeof(b);
	return 0;
}

请添加图片描述

其他区别:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

2.内联函数

在学习c时,我们认识了宏

优点: 1.增强代码的复用性 2.提高性能。

缺点: 1.不方便调试宏(因为预编译阶段进行了替换) 2.导致代码可读性差,可维护性差,容易误用。

3.没有类型安全的检查

为了解决缺点,c++中采用:

  1. 常量定义 换用const enum

  2. 短小函数定义 换用内联函数

2.1内联函数概念

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

请添加图片描述

我们使用内联函数:

请添加图片描述

2.2内联函数特性

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。 缺陷:可能会使目标文件变大。 优势:少了调用开销,提高程序运行效率。
  2. inline对于编译器而言只是一个建议(会不会真的使用,看编译器自己决定)不同编译器关于inline实现机制可能不同,一般情况:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到

关于第三点:内联函数因为直接展开,也就不要地址查询(内联函数名不会进入符号表),我们之前经常在头文件里进行声明,一个源文件里面进行实现。现在在其他源文件里使用内联函数时不行的


3. auto关键字

随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:

  1. 类型过长难于拼写
  2. 含义不明确导致容易出错

auto就是来解决这个问题

3.1概念

C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得

int main()
{
	auto a = 1;
	auto b = 1.1;
	auto c = 'c';//但这些都不是主要的使用场景
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	return 0;
}

请添加图片描述

3.2auto的使用细则

  1. auto与指针和引用结合起来使用

auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须&

请添加图片描述

  1. 在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量

3.3auto不能使用的场景

  1. auto不能作为函数的参数
  2. auto不能直接用来声明数组
  3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
  4. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用

4.基于范围的for循环(C++11)

4.1范围for的语法

之前我们写c的时候,在C++98中如果要遍历一个数组,可以按照以下方式进行:

void Test1()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		arr[i] *= 2;
	}
	for (int* p = arr; p < arr + sizeof(arr) / sizeof(arr[0]); p++)
	{
		cout << *p << endl;
	}
}

现在我们可以这样:

void Test2()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto& e : array)//用for(int& e:array)也可以
	{
		e *= 2;
	}
	for (auto e : array)
	{
		cout << e << " ";
	}
}

C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围

基于范围的for循环会依次将容器中的元素赋值给迭代变量(通常命名为element)。在每次循环迭代中,迭代变量将会被赋值为容器中的下一个元素,直到遍历完整个容器

请添加图片描述

如果想要改变数组里,就使用引用

void Test2()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto& e : array)
	{
		e *= 2;
		cout << e << " ";
	}
	cout << endl;
	for (auto e : array)
	{
		cout << e << " ";
	}
}

请添加图片描述

4.2范围for的使用条件

  1. for循环迭代的范围必须是确定

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

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

5.指针空值nullptr(C++11)

我们经常使用的NULL实际上是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

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

可以看到,==NULL可能被定义为字面常量0,或者被定义为无类型指针(void)的常量==。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦*

所以我们使用nullptr来对指针进行初始化,来替代NULL,以免NULL定义为0时出现错误

注意:

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

好啦,c++入门的知识先到这里啦,下面就要开启面向对象的篇章了。感谢大家支持!!!

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

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

相关文章

.net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别

//全局过滤器 builder.Services.AddMvc(m > { m.Filters.Add<AllResultFilter>(); }); 1、实现过滤器 public class AllResultFilter : IResultFilter {/// <summary>/// 结果执行后方法/// 不可更改结果/// </summary>/// <param name"con…

vue下载文件流效果demo(整理)

在 Vue 项目中&#xff0c;你可以使用 FileSaver.js 库来方便地下载文件流。FileSaver.js 封装了不同浏览器的下载方式&#xff0c;使得下载文件更加简单和兼容。以下是一个完整的示例方法&#xff1a; 首先&#xff0c;安装 FileSaver.js 库&#xff1a; <template>&l…

使用Go语言的HTTP客户端和服务器

使用Go语言进行HTTP客户端和服务器开发是一种高效且强大的方式。Go语言的标准库提供了对HTTP协议的全面支持&#xff0c;使得创建HTTP客户端和服务器变得简单。 首先&#xff0c;让我们来看一下如何创建一个简单的HTTP服务器。在Go中&#xff0c;可以使用net/http包来创建HTTP…

墙地砖外形检测的技术方案-图像分割

基础原理 由于对碗口进行缺口检测&#xff0c;因此只需要碗口的边界信息。得到陶瓷碗区域填充后的图像&#xff0c;对图像进行边缘检测。这是属于图像分割中的内容&#xff0c;在图像的边缘中&#xff0c;可以利用导数算子对数字图像求差分&#xff0c;将边缘提取出来。 案例…

Odrive 学习系列三:在odrive工程中添加SEGGER RTT 日志输出功能

一、背景: 对于嵌入式来讲,有个日志输出真真真真的太重要啦! SEGGER JLink自带的RTT日志输出对于老嵌入式而言更是开发利器。 Odrive本身的工程是不带这个功能的,尽管使用stlink可以查阅寄存器等,但感觉还是差了点意思。因此在本系列第二节的基础上,希望能给Odrive工程添…

蓝桥杯AcWing学习笔记 9-2复杂DP的学习(下)

蓝桥杯 我的AcWing 题目及图片来自蓝桥杯C AB组辅导课 复杂DP&#xff08;下&#xff09; 非传统DP问题思考方式&#xff0c;全新的DP思考方式&#xff1a;从集合角度来分析DP问题——闫式DP分析法 例题 AcWing 1303. 斐波那契前 n 项和 矩阵乘法快速幂 此题并非dp问题…

《 乱弹篇(三)》

题记 前两篇《乱弹》&#xff0c;一是乱弹了“北宋名相寇准的《六悔铭》”&#xff0c;并议及“不择手段&#xff0c;只谋功利”&#xff1b;二是乱弹了晚清重臣、湘军统帅曾国藩的为官之道和处世哲学&#xff0c;并介绍了他在《冰鉴》一书里用来识人的对联和口诀。 这两篇《…

网络安全自学入门:(超详细)从入门到精通学习路线规划,学完即可就业

很多人上来就说想学习黑客&#xff0c;但是连方向都没搞清楚就开始学习&#xff0c;最终也只是会无疾而终&#xff01;黑客是一个大的概念&#xff0c;里面包含了许多方向&#xff0c;不同的方向需要学习的内容也不一样。 算上从学校开始学习&#xff0c;已经在网安这条路上走…

第08章_面向对象编程(高级)拓展练习(关键字:static,代码块,关键字:final,抽象类和抽象方法,接口,内部类,枚举类,注解,包装类)

文章目录 第08章_面向对象编程&#xff08;高级&#xff09;拓展练习01-关键字&#xff1a;static1、银行账户类2、图形类3、数组工具类4、二分查找5、二分查找6、素数7、阅读代码&#xff0c;分析运行结果8、阅读代码&#xff0c;分析运行结果 02-代码块9、阅读代码&#xff0…

【嘉立创EDA-PCB设计指南】1.PCB基本概念及原理图绘制

前言&#xff1a;本文详解PCB基本概念以及实现MCU最小系统原理图的绘制&#xff08;原理图包括MCU芯片GD32F103C8T6、外部晶振、输出端口、USB输入口、5v转3v3稳压输出、复位按键、唤醒按键、LED&#xff09;。为本专栏后面章节实现PCB绘制做准备。 最终绘制的原理图如下所示&…

鸿蒙开发-UI-布局-层叠布局

鸿蒙开发-UI-布局 鸿蒙开发-UI-布局-线性布局 文章目录 前言 一、基本概念 二、对齐方式 三、Z序控制 四、使用场景 总结 前言 上文详细学习了线性布局&#xff0c;学习了线性容器内子元素在主轴以及交叉轴上的排列方式&#xff0c;子元素自适应相关的知识点&#xff0c;本文继…

强化学习(二)多臂老虎机 “Multi-armed Bandits”——2

1、增量算法估计动作价值 由之前的内容可知&#xff0c;某一个动作被选择 n − 1 n-1 n−1 次后&#xff0c;该动作的价值估计值为 Q n ≐ R 1 R 2 ⋯ R n − 1 n − 1 Q_n\doteq\dfrac{R_1R_2\cdotsR_{n-1}}{n-1} Qn​≐n−1R1​R2​⋯Rn−1​​ 很明显&#xff0c;随着…

小规模团队更适合什么样的客户管理系统?

小规模团队更适合什么样的客户管理系统&#xff1f; 一般情况下&#xff0c;小规模对客户管理系统的需求通常有以下特点&#xff1a; 团队规模&#xff1a;小规模&#xff0c;不超过10人——尽可能降低使用成本使用人员&#xff1a;销售人员使用——无代码基础&#xff0c;最…

学习JavaEE的日子 day12 构造方法 类的制作

Day12 需求&#xff1a;创建人类的对象&#xff0c;并操作对象 分析&#xff1a; 人类 - Person 属性&#xff1a;name、sex、age 方法&#xff1a;eat、sleep 场景&#xff1a;创建多个对象&#xff0c;去操作对象 //测试类&#xff1a;该类中有main方法&#xff0c;测试我们写…

Mybatis配置动态数据源以及参数传递等

Mybatis必知必会 一、Mybatis动态加载数据源 在配置数据源连接时,在企业的真实开发中数据源一般都会写在配置文件中&#xff0c;而不会直接写在mybatis的核心配置文件中 所以,Mybatis为了方便开发人员去动态的获取数据源连接制定了一些特定的标签用于加载这些数据源。 具体做法…

【leetcode刷题】模拟专题

模拟 一、替换所有的问号1、题目链接2、解析3、代码 二、提莫攻击1、题目链接2、解析3、代码 三、Z字形变换1、题目链接2、解析3、代码 四、外观数列1、题目链接2、解析3、代码 五、数青蛙1、题目链接2、解析3、代码 一、替换所有的问号 1、题目链接 leetcode链接 2、解析 3、…

2024年人才缺口大,学网络安全找不到工作?原因竟然在这!

为什么网络安全人才缺口那么大&#xff0c;但很多人还是找不到工作&#xff1f;其实大家都忽略了1个重点&#xff0c;那就是不清楚企业在招什么样的人。 我花了2天的时间统计了主流招聘网站的岗位信息&#xff0c;发现了一个惊人的真相&#xff0c;那就是企业都喜欢招这3种人&…

深度学习笔记(四)——使用TF2构建基础网络的常用函数+简单ML分类实现

文中程序以Tensorflow-2.6.0为例 部分概念包含笔者个人理解&#xff0c;如有遗漏或错误&#xff0c;欢迎评论或私信指正。 截图和程序部分引用自北京大学机器学习公开课 TF2基础常用函数 1、张量处理类 强制数据类型转换&#xff1a; a1 tf.constant([1,2,3], dtypetf.floa…

Android APP开发集成微信登陆流程(手把手新手版)

本文比较适合新手玩家&#xff0c;老玩家就不要看了 昨天整了下微信登陆&#xff0c;乍一看官方文档还有点难懂&#xff01;遂自己整理了下流程&#xff0c;给大家参考参考。 官方文档链接&#xff1a;准备工作 | 微信开放文档微信开发者平台文档https://developers.weixin.q…

智能代码:生成式 AI 在软件开发中的革命性角色

想象一下&#xff0c;在智能手机革命性地改变了我们的生活之后&#xff0c;现在轮到了生成式 AI 在软件开发领域掀起风暴。你知道吗&#xff0c;如果代码能自己编写自己&#xff0c;这将是多么惊人的一步&#xff1f;这就好比我们现在能轻松地用手机应用管理日常生活一样&#…