C到C++的敲门砖-2

文章目录

  • 引用
  • 内联函数
  • auto关键字
  • 基于范围的for循环
  • 指针空值nullptr
  • 后记

引用

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

所谓引用就是给变量起别名,在语法上来说是不会开辟变量的。
例如:

int main()
{
	int a=0;
	int&ra=a;
	a=5;
	cout<<a<<endl;
	cout<<ra<<endl<<endl;
	ra=10;
	cout<<a<<endl;
	cout<<ra<<endl;	
}

在这里插入图片描述
如上,即为代码的运行结果。可以看到我们改变了a的值,ra也跟着改变。改变了ra的值,a也跟着改变。这个ra就类似于c语言的指针,但此处不同在于不需要解引用。
此外,引用还有以下特性:

  • 引用在定义时必须初始化
    这是很好理解的,如果不初始化就很容易有歧义:
int a=0;
int b=0;
int&ra;
ra=a;
ra=b;

如上,我们便没有对ra进行初始化,那么下面ra=a和ra=b,这到底是说ra是a的别名还是b的别名呢?所以,为了避免这样的歧义存在,引用在定义时必须初始化。

  • 引用一旦引用一个实体,再不能引用其他实体。原因还是为了避免歧义。这也是和指针不同的一点,这也导致有些场景只能用指针而不能用引用来解决
  • 一个变量可以有多个引用。
    一个人可以有多个别名,那么变量当然也可以有多个别名。

除了上面的特性外,使用引用还有以下需要注意的点:

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;
 }

上面我们了解了引用的定义,那么c++多出这么个概念又有什么作用呢?
别急,下面我们就来谈谈引用的使用场景:

  1. 做参数
void Swap(int& left, int& right)
 {
 int temp = left;
 left = right;
 right = temp;
 }
 int main()
 {
 	int a=10,b=5;
 	Swap(a,b);
 }

如上代码,我们以往使用c语言时传参就必须是穿址调用才能交换a,b的值。现在可以引用调用,只传a,b的值就能实现Swap。
这样做的一大好处就是增加了代码的可读性,不用时不时传一个指针、二级指针,不仅不好理解,传参的时候也麻烦。此外,由于引用实际上是给变量取别名,所以left和right实际上就是a和b,换句话说这两个变量不需要压栈,这就减少了空间开销。
不过话又说回来,引用的底层实际上还是用指针来实现的

  1. 做返回值
int& Count()
 {
 static int n = 0;
 n++;
 // ...
 return n;
 }

这里的返回值是n的一个引用,我们就可以通过改变返回值来改变n的值。但是正如指针会有野指针,引用也有野引用。引用做返回值的时候,需要注意返回的对象没被销毁。因此只有堆、静态区、全局变量才能返回引用。

学习了引用,细心的读者也可以发现这简直和指针太像了,一股子指针味。这下谁还分得清什么是指针,什么是引用啊!无妨,接下来我们谈谈引用和指针的区别:

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

阅读上面的指针和引用的区别,我们也了解到了引用的一些不足之处,但是总体来说,相较于指针,引用还是更方便的。

内联函数

学习内联函数前,我们要知道编译器调用函数时要进行申请栈帧、压栈、弹栈等操作的
如果一段较为简短的代码在程序里反复调用,那么上述过程产生的开销就很大。因此在c++中专门用一个关键字inline修饰内敛函数,来达到类似宏定义函数的效果,从而解决上述问题。
如下:

inline int Add(int a=0,int b=0)
{
	return a+b;
}
int main()
{
	Add();
	Add();
	Add();
	Add();
	Add();
	Add();
	Add();
	Add();
	Add();
	return 0;
}

如上函数,如果不加上inline修饰,那么Add调用消耗栈帧就比较大。现在用inline修饰后,Add这个函数会进行代码块展开,这用就不用进行开辟栈帧、压栈、弹栈等操作。
inline的特性:

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

auto关键字

随着程序逐渐复杂,变量类型也逐渐复杂。就算是一个简单的函数int Add(int ,int ),其函数指针的类型为int (*)(int ,int ),这也是比较复杂的。虽然可以通过typedef来进行一定程度的改善,但是就算是使用typedef也是比较繁琐的。
为了解决上述的问题,c++使用auto关键字来自动识别变量类型。
如下:

int TestAuto()
 {
 return 10;
 }
 int main()
 {
	 int a = 10;
	 auto b = a;
	 auto c = 'a';
	 auto d = TestAuto();
	 cout << typeid(b).name() << endl;
	 cout << typeid(c).name() << endl;
	 cout << typeid(d).name() << endl;
	 return  0;
 }

上面代码的b,c,d都用了auto来自动识别类型,其中b为int,c为char,d为int。是不是感觉方便了不少呢?
需要注意的是,使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
auto的使用场景:

  • auto与指针和引用结合起来使用,用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。(当然auto*声明的变量必须是指针类型,而auto声明的变量类型既可以是指针类型,也可以不是指针类型)
  • 在同一行定义多个变量
    当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译
    器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
void TestAuto()
 {
	auto a = 1, b = 2; 
	auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

auto不能使用的场景:

  • auto不能作为函数的参数
  • auto不能直接用来声明数组
void TestAuto()
 {
	 int a[] = {1,2,3};
	 auto b[] = {456};//不可行
 }

基于范围的for循环

范围for循环:
一个已知范围的数组可以用如下的遍历方式:

int main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,10};
	for(int i=0;i<sizeof(arr)/sizeof(int);i++)
		arr[i]++;
	return 0;
}

对于一个已知范围的数组,需要遍历其元素时用上述方式显得繁琐,而且还是错误的风险。
在c++中,我们就可以使用下面的方式来遍历数组元素:

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

在这里插入图片描述
就这样,我们使用了一种较为简洁的方式来遍历有范围的数组。
范围for的使用条件:

  • for循环迭代的范围必须是确定的。对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
void TestFor(int array[])
 {
	 for(auto& e : array)
	 cout<< e <<endl;
 }

上述代码就是一个典型的使用错误,因为函数压栈时是绝对不会传数组的,这样的空间开销太大。所以这里的array是一个指针,因此array的范围并不明确。

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

指针空值nullptr

在C语言中我们常用NULL来赋值空指针,但实际上NULL是一个宏定义的值,本质是int 0.
前面我们学习了函数重载,由此就引出了一个问题。

void Test(int x)
{
	cout << "int" << endl;
}
void Test(int* x)
{
	cout << "int*" << endl;
}
int main()
{
	Test(0);
	Test(NULL);
	return 0;
}

上述代码的理想的输出结果为:
int
int*
但实际的输出结果为:
在这里插入图片描述
也就是我们的NULL被识别成了int类型,这和我们的本意显然是冲突的。
由此,c++引入了指针空值nullptr。
注意:

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

后记

以上就是大致的C和C++的一些用法上的不同,显然C++的各种语法改良了C语言很多不足的地方,这也为我们叩响了C++的敲门砖。就借着此处的优势继续向着C++的更深处进发吧。

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

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

相关文章

uwsgi+nginx+django 部署学习

收集静态文件及部署配置 DEBUG False STATICFILES_DIRS [os.path.join(BASE_DIR, "static"), ] STATIC_ROOT /data/static python3 manage.py collectstatic 收集静态文件&#xff0c;成功后可在STATIC_ROOT目录查看 安装依赖 pip3 install uwsgi django项目结…

浅谈如何自我实现一个消息队列服务器(1)——需求分析

文章目录 一、什么是消息队列&#xff1f;二、当下主流的消息队列(MQ)三、自我实现一个消息队列服务器的前期准备——需求分析3.1 核心概念3.2 broker server 核心概念3.2.1、虚拟主机&#xff08;Virtual Host&#xff09;3.2.2、交换机&#xff08;Exchange&#xff09;3.2.2…

Apache-Doris基础概念

OLAP数据库Doris 一、Doris架构二、基本概念1. Row & Column2. Partition & Tablet3. 建表示例&#xff08;1&#xff09;列的定义&#xff08;2&#xff09;分区分桶&#xff08;3&#xff09;多列分区&#xff08;4&#xff09;PROPERTIES&#xff08;5&#xff09;E…

stable diffusion webui 搭建和初步使用

官方repo: GitHub - AUTOMATIC1111/stable-diffusion-webui: Stable Diffusion web UI 关于stable-diffusion的介绍&#xff1a;Stable Diffusion&#xff5c;图解稳定扩散原理 - 知乎 一、环境搭建和启动 准备在容器里面搞一下 以 ubuntu22.04 为基础镜像&#xff0c;新建…

健身·健康行业Web3新尝试:MATCHI

随着区块链技术进入主流&#xff0c;web3 运动已经开始彻底改变互联网&#xff0c;改写从游戏到金融再到艺术的行业规则。现在&#xff0c;MATCHI的使命是颠覆健身行业。 MATCHI是全球首个基于Web3的在线舞蹈健身游戏和全球首个Web3舞蹈游戏的发起者&#xff0c;注册于新加坡&a…

保研|资讯|夏令营|3.31截止!香港城市大学市场营销学系首届学术暑期夏令营

香港城市大学市场营销学系首届学术暑期夏令营 1 项目简介 我们的博士项目致力为未来营销科学和工商管理学界培养一流学者和行业领袖。博士项目一般为期四到六年&#xff0c;允许本科生直接申请。课程包括实证分析模型&#xff0c;消费者行为研究&#xff0c;博弈微观模型&…

龙芯新世界系统(安同AOCS OS)安装Cinnamon桌面最新版6.0.4

龙芯的新世界系统安同AOCS OS是十分优秀的操作系统&#xff0c;处于纯社区方式运行&#xff0c;她的各组件更新得很及时&#xff0c;很多组件都处于最新的状态&#xff0c;给我们安装使用最新的开源软件提供了很好的基础。由于本人一直使用Cinnamon桌面环境&#xff0c;各方面都…

Docker 哲学 - 容器操作 (二)

命令行启动 参数键值之间可以使 " " 或者 空格 卷的挂载是在容器创建时指定的&#xff0c;不能在容器运行时再添加 当加上 --network-alias 设置同一网络下别名参数后 &#xff0c;inspect 该容器发现 会同步到 容器信息中 2、给容器打日志 docker logs 【-…

【数据结构】栈与队列

一、栈 1.基本概念 栈是只能在一段进行插入或者删除的线性表 出栈进栈 只能在栈顶进行 三个术语&#xff1a;栈底&#xff0c;空栈&#xff0c;栈顶 字面理解即可 栈的基本操作&#xff1a; 创 销 增 删 查 2.顺序栈的实现 &#xff08;1&#xff09;初始化 #define…

玩转C语言——数组初探

一、前言 通过前面的学习&#xff0c;我们已了解C语言的结构变量、分支结构和循环结构。今天&#xff0c;我们一起来认识C语言的另一知识点——数组。先赞后看&#xff0c;养成习惯。 二、数组概念 学习数组&#xff0c;我们要明白数组是什么。在我看来&#xff1a;数组是⼀组…

Linux提权!!

本来是想更新PTH的&#xff0c;但我鸽了&#xff0c;那就来讲讲Linux的提权吧&#xff01;&#xff01;主要是下周我就要学免杀了啦&#xff01;&#xff01;&#xff01; 对于内网&#xff0c;什么难学&#xff1f;&#xff1f;&#xff1f; 是的&#xff0c;确实呢&#xff0…

ISIS默认层级实验简述

ISIS被划分为三个层级&#xff1a;Level 1、Level 2和Level 1-2。 默认情况下&#xff0c;ISIS路由器属于level 1-2,是指同时支持Level 1和Level 2的路由器。路由器既可以在同一个自治系统内部进行路由选择&#xff0c;也可以将路由信息传递到其他自治系统。 实验拓扑图&#…

Redis实战——优惠券秒杀:超卖问题一人一单问题

目录 优惠券秒杀Redis实现全局唯一ID业务场景代码实现&#xff1a; 添加优惠券业务场景新增普通券新增秒杀券测试添加秒杀券 实现秒杀下单业务分析流程代码编写 超卖问题超买场景分析解决方案使用乐观锁 一人一单业务场景解决步骤 集群环境下的并发问题 优惠券秒杀 Redis实现全…

AntV L7深圳智慧城市

本案例使用L7库和Mapbox GL JS构建深圳智慧城市。 文章目录 1. 引入 CDN 链接2. 引入组件3. 创建地图4. 创建场景5. 获取数据6. 创建面图层7. 演示效果8. 代码实现 1. 引入 CDN 链接 <!-- 1.引入CDN链接 --> <script src"https://unpkg.com/antv/l7"><…

Transformer的前世今生 day02(神经网络语言模型

神经网络语言模型 使用神经网络的方法&#xff0c;去完成语言模型的两个问题&#xff0c;下图为两层感知机的神经网络语言模型&#xff1a; 以下为预备概念 感知机 线性模型可以用下图来表示&#xff1a;输入经过线性层得到输出 线性层 / 全连接层 / 稠密层&#xff1a;假…

odoo17开发教程(8):设置界面UI的字段属性

目录 添加字段 给字段设置只读和不可拷贝 给字段添加默认值 保留字段 本节目标&#xff1a;在本文末尾&#xff0c;售价(selling price)应为只读值&#xff0c;卧室数量(bedrooms)和可用日期(availability date)应为默认值。此外&#xff0c;在复制记录时&#xff0c;售价和…

Langchain-chatchat+ChatGlm3-6b部署

我的环境 台式机 内存&#xff1a;16GB 显卡&#xff1a;GTX1060-6G 1. 基础环境准备 1.1. 安装anaconda&#xff0c;创建环境python版本3.11 conda create -n chatglm3 python3.11 conda activate chatglm3 1.2. 搭建cuda环境 # 查看cuda版本&#xff0c;版本是CUDA V…

cmd常用指令

cmd全称Command Prompt&#xff0c;中文译为命令提示符。 命令提示符是在操作系统中&#xff0c;提示进行命令输入的一种工作提示符。 在不同的操作系统环境下&#xff0c;命令提示符各不相同。 在windows环境下&#xff0c;命令行程序为cmd.exe&#xff0c;是一个32位的命令…

sqlite 常见命令 表结构

在 SQLite 中&#xff0c;将表结构保存为 SQL 具有一定的便捷性和重要性&#xff0c;原因如下 便捷性&#xff1a; 备份和恢复&#xff1a;将表结构保存为 SQL 可以方便地进行备份。如果需要还原或迁移数据库&#xff0c;只需执行保存的 SQL 脚本&#xff0c;就可以重新创建表…

Mybatis-xml映射文件与动态SQL

xml映射文件 动态SQL <where><if test"name!null">name like concat(%,#{name},%)</if><if test"username!null">and username#{username}</if></where> <!-- collection&#xff1a;遍历的集合--> <!-- …