【C++深入浅出】初识C++下篇(auto关键字、范围for、nullptr指针)


目录

一. 前言

二. auto关键字

2.1 auto的引入

2.2 auto简介

2.3 auto的使用细则

2.4 auto不能推导的场景

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

3.1 范围for的语法

3.2 范围for的原理

3.3 范围for的使用条件

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


一. 前言

        上期我们介绍了c++新增的两个重要语法:引用和内联函数,今天我们带来的内容是auto关键字范围for以及nullptr指针,本期也是初识C++的最后一期。上期回顾:

【C++深入浅出】初识C++中篇(引用、内联函数)http://t.csdn.cn/LCvY0        话不多说,直接上菜!!!

二. auto关键字

2.1 auto的引入

        在我们写代码的过程中,可曾发现,随着程序越来越复杂,程序中用到的类型也越来越复杂,包括但不限于以下两点:1. 类型难于拼写、2. 含义不明确导致容易出错。例如:

#include <string>
#include <map>
int main()
{
	std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange","橙子" },{ "pear","梨" } };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

你没有看错,上面的std::map<std::string, std::string>::iterator 其实是一个类型,是不是非常吓人。像这种长类型特别容易写错,有的人可能已经想到了解决方案:可以通过typedef给类型取别名。是的,这无疑也是种好方法,但使用typedef前我们必须先知道类型,有时候这并不容易做到。那怎么办呢?不急,C++11中的auto关键字就是为了解决这个问题。

2.2 auto简介

        在早期C/C++中使用auto修饰的变量,是具有自动存储器的局部变量,但由于用处不大,一直没有人去使用它。

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

        为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法。

#include<string>
int TestAuto()
{
	return 10;
}
int main()
{
	int a = 10;
	string s;
	auto b = a;
	auto c = 'a';
	auto d = TestAuto();
	auto e = s.begin();
	cout << typeid(b).name() << endl; //typeid类似于sizeof一样,是一个操作符,其可以用来获取变量的类型。
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(e).name() << endl;
	//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
	return 0;
}

输出结果如下 

可以看出,编译器自动帮我们将类型推导出来了,是不是非常方便

2.3 auto的使用细则

        学会了auto的基本使用,接下来就是避坑时间惹 

        1. auto定义变量时必须对其进行初始化

         auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”。在编译阶段编译器需要根据初始化表达式来推导auto的实际类型,然后将auto替换为变量实际的类型。

int main()
{
	int val = 10;
	auto a; //错误写法,编译时会报错
	auto b = val; //正确写法,定义时进行初始化,编译器才能进行推导然后将auto替换
	return 0;
}

        2. auto和指针和引用

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

int main()
{
	int x = 10;
	auto a = &x;
	auto* b = &x;
	auto& c = x;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	*a = 20;
	*b = 30;
	c = 40;
	return 0;
}

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

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

int main()
{
	auto a = 1, b = 2; // 正确写法
	auto c = 3, d = 4.0; // 编译失败,因为c和d的初始化表达式类型不同

	return 0;
}

2.4 auto不能推导的场景

        1、auto不能作为函数的参数

// 此处代码编译失败,auto不能作为形参类型,
// 原因:函数调用传参发生在运行阶段,故在编译阶段编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}

        2.、auto不能直接用来声明数组

int main()
{
	int a[] = { 1,2,3 };
	auto b[] = { 4,5,6 };
	return 0;
}


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

3.1 范围for的语法

        在C++98中如果要遍历一个数组,可以按照以下方式进行:

int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		array[i] *= 2; //利用下标访问
	}
	cout << endl;
	for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
	{
		cout << *p << ' '; //利用指针访问
	}
	cout << endl;

}

        但是对于array这个有范围的集合而言,由程序员来说明循环的范围显然显得有点多余,有时候还会容易犯错误产生越界。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号分为两部分:第一部分是范围内迭代取得的变量,第二部分则表示进行迭代的集合对象。上述代码使用范围for改写如下

int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto& e : array) //依次取数组的数据给引用变量e,自动判断结束,自动迭代 + 1
	{
		e *= 2;
	}
	for (auto e : array) //依次取数组的数据赋值给变量e,自动判断结束,自动迭代 + 1
	{
		cout << e << " ";
	}
	return 0;
}

可以看到,我们使用到了之前学的auto关键字,利用auto的自动类型推导,我们无需显式地写出e的类型,使得范围for的使用更加简洁方便,这也是auto的常见优势用法之一。


在第一个范围for中,由于e是引用变量,因此e表示的是数组每个元素的别名,对e进行修改就是对数组元素进行修改。

而在第二个范围for中,e是普通变量,表示的是数组每个元素的拷贝,对e进行修改对数组元素没有影响。


注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。


3.2 范围for的原理

        可能会有的小伙伴会好奇:范围for这个东西这么智能,既能自动迭代,还能自动判断结束,那么它的原理究竟是什么呢 

         实际上,并没有想象中的那么复杂。范围for的底层原理实际上就是迭代器遍历编译器在编译时会自动将范围for的代码替换为迭代器遍历相关代码。迭代器的知识我们后续会介绍,这里大家将其理解为指针即可。

        下面是一段vector容器的遍历代码:

int main()
{
	vector<int> v;
	for (int i = 1; i <= 5; i++) //插入1-5的数据
	{
		v.push_back(i);
	}

	for (auto e : v) //范围for遍历
	{
		cout << e << ' ';
	}
	cout << endl;
	return 0;
}

       编译器编译时范围for会替换成类似于如下的代码:

int main()
{
	vector<int> v;
	for (int i = 1; i <= 5; i++) //插入1-5的数据
	{
		v.push_back(i);
	}

	vector<int>::iterator it = v.begin();
	//auto it = v.begin(); //迭代器的类型较长,也可以使用auto自动推导

	while (it != v.end()) //迭代器遍历,这里将it当做指针理解即可
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
	return 0;
}

结论:范围for其实就是编译器进行了替换,本质上还是迭代器的遍历

3.3 范围for的使用条件

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

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

void putArray(int array[])
{
	//数组传参发生降维,array是个指针,指向数组首元素
	for (auto e : array) //由于array只是个int*指针,我们无法确定迭代的范围,故这里的范围for会报错
	{
		cout << e << ' ';
	}
}
int main()
{

	vector<int> v;
	for (int i = 1; i <= 5; i++) //插入1-5的数据
	{
		v.push_back(i);
	}

	for (auto e : v) //范围for遍历vector容器类。范围:v.begin()~v.end()
	{
		cout << e << ' ';
	}
	cout << endl;

	int array[5] = { 1,2,3,4,5 };
	for (auto e : v) //范围for遍历array数组。范围:从第一个元素的下标0到最后一个元素的下标4
	{
		cout << e << ' ';
	}

	putArray(array);
	return 0;
}

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

        上面我们看到范围for替换为迭代器遍历的代码中,使用迭代器it进行遍历时需要用到++和==的操作,顾迭代器需要支持++和==的操作。(目前不清楚的了解一下即可,等到我们讲解迭代器时再深入讨论)


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

        在C语言中,我们对指针进行初始化时,经常会用NULL空指针进行初始化,如下:

int main()
{
	int* p1 = NULL;
	return 0;
}

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

        可以看到,NULL在C++中被定义为字面常量0,在C语言中被定义为无类型指针(void*)常量。而字面常量0在编译器看来默认是整形常量,这就会导致出现一些不可预料的错误,例如:

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

        上述代码在C++中的结果如下所示

本意我们是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL在C++中被定义成字面常量0,因此调用了更符合的f(int)函数,与程序的初衷相悖。如果我们执意要调用f(int*)函数,只能将NULL强制类型转换为int*,再进行调用即可,但这样会显得非常奇怪 


        出于以上原因,C++11新增了一个关键字nullptr用来表示指针空值,如下:

void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);
	f(nullptr); //nullptr表示空指针
	return 0;
}

        此时代码的结果就符合我们初衷了:

关于nullptr的几点说明与建议 

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

以上,就是本期的全部内容啦🌸

制作不易,能否点个赞再走呢🙏

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

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

相关文章

Javascript 中的 debugger 拦截

debugger 指令&#xff0c;一般用于调试&#xff0c;在如浏览器调试执行环境中&#xff0c;可以在 JavaScript 代码中产生中断。 如果想要拦截 debugger&#xff0c;是不容易的&#xff0c;常用的函数替代、proxy 方法均对它无效&#xff0c;如&#xff1a; window.debugger …

[ZenTao]源码阅读:加载自定义任务类型

www/index.php config/config.php framework/base/router.class.php tmp/model/common.php module/common/model.php framework/router.class.php

lib61850 学习笔记一 (概念)

IEC61850 定义60多种服务满足变电站通信需求。支持在线获取数据模型&#xff0c;也支持IED水平通信&#xff08;GOOSE报文&#xff09; 术语定义 间隔 bay: 变电站由据应公共功能紧密连接的子部分组成。 例如 介于进线或者 出线 和母线之间的断路器&#xff1b;二条母线之间…

IntelliJ 中如何配置 Tomcat 调试

Tomcat 在 IntelliJ 中的配置要求首先你要下载 Tomcat。 设置服务器 在 IntelliJ 下面先选择 Run&#xff0c;然后选择配置运行配置。 在弹出的界面中&#xff0c;有一个编辑配置的选项。 然后在弹出的页面中选择添加。 选择 Tomcat 在弹出的添加页面中选择添加 Tomcat&…

QGIS-计算几何内部点(一定在几何内)

在提取几何图像的中心点相关的X Y时&#xff0c;我们往往希望提取的点在几何内部&#xff0c;因为对于不规则图形而言&#xff0c;特别是凹几何&#xff0c;提取的点可能在图形外&#xff0c;QGIS中提供了相关的函数用于提取点中心点&#xff1a; 打开图形的属性列表&#xff…

《Flink学习笔记》——第五章 DataStream API

一个Flink程序&#xff0c;其实就是对DataStream的各种转换&#xff0c;代码基本可以由以下几部分构成&#xff1a; 获取执行环境读取数据源定义对DataStream的转换操作输出触发程序执行 获取执行环境和触发程序执行都属于对执行环境的操作&#xff0c;那么其构成可以用下图表示…

文件上传漏洞-upload靶场1-2关 通过笔记(如何区分前段验证和后端验证)

文件上传漏洞-upload靶场1-2关 通过笔记&#xff08;区分前段验证和后端验证&#xff09; 前言 upload是一个文件上传的专用靶场&#xff0c;搭设也非常简单&#xff0c;只需要把相关源码文件放到apache的网站目录下即可使用&#xff0c;或者去github下载一键绿化包进行安装链…

路由转发(详细理解+实例精讲)

系列文章目录 华为数通学习&#xff08;5&#xff09; 目录 华为数通学习&#xff08;5&#xff09; 前言 一&#xff0c;最长匹配原则 实例1&#xff1a; 实例2&#xff1a; 二&#xff0c;路由转发流程&#xff1a; 三&#xff0c;IP路由表小结&#xff1a; 总结 前…

【同步异步可并发日志系统】设计及实现

1. 项⽬介绍2. 开发环境3. 项目核⼼技术4. 环境搭建5. ⽇志系统介绍5.1 为什么需要⽇志系统5.2⽇志系统技术实现5.2.1 同步写⽇志5.2.2 异步写⽇志 6. ⽇志系统框架设计6.1 各模块测试代码 7. 代码设计7.1 实⽤类设计7.2 ⽇志等级类设计7.3 ⽇志消息类设计7.4 ⽇志格式化输出设…

Easy Rules规则引擎(2-细节篇)

目录 一、序言二、规则引擎参数配置实例1、skipOnFirstAppliedRules示例(1) FizzRule(2) BuzzRule(3) FizzBuzzRule(4) NonFizzBuzzRule(5) FizzBuzzRulesLauncher 2、skipOnFirstNonTriggeredRule示例3、skipOnFirstFailedRule示例 三、组合规则1、UnitRuleGroup组合规则2、Ac…

java八股文面试[多线程]——synchronized 和lock的区别

其他差别&#xff1a; synchronized是隐式的加锁,lock是显式的加锁; synchronized底层采用的是objectMonitor,lock采用的AQS; synchronized在进行加锁解锁时,只有一个同步队列和一个等待队列, lock有一个同步队列,可以有多个等待队列; synchronized使用了object类的wait和noti…

经典问题解析四

关于动态内存分配 new 和 malloc 的区别是什么&#xff1f; delete 和 free 的区别是什么&#xff1f; new 关键字与 malloc 函数的区别 new 关键字是 C 的一部分 malloc 是由 C 库函数提供的函数 new 是以具体类型为单位进行内存分配 malloc 以字节为单位进行内存分配 …

利用R作圆环条形图

从理念上看&#xff0c;本质就是增加了圆环弧度的条形图。如上图2。 需要以下步骤&#xff1a; 数据处理&#xff0c;将EXCEL中的数据做成3*N的表格导入系统&#xff0c;代码如下&#xff1a;library(tidyverse) library(stringr)library(ggplot2)library(viridis) stuper &…

【前车之鉴】: 2023最新教程-将java程序打包到maven私服的正确打开方式,详细流程介绍不怕你掌握不了

文章目录 为什么看这篇整体流程1. 注册账号【首次需要】2. 工单申请【新项目必须】3. 项目配置【新项目必须】4. 授权认证【新项目必须】5. 一键发布 最后也很重要 为什么看这篇 一是当前网络上一些博客有遗漏部分&#xff0c;这里做补充&#xff0c;二是网上思路没错&#xff…

DC/DC开关电源学习笔记(一)开关电源技术概述

&#xff08;一&#xff09;开关电源技术概述 1.什么是开关电源&#xff1f;2.开关电源技术概述2.1 小型化、薄型化、轻量化、高频化2.2 高可靠性2.3 低噪声2.4 采用计算机辅助设计和控制 1.什么是开关电源&#xff1f; 开关模式电源&#xff08;Switch Mode Power Supply&…

JVM解密: 解构类加载与GC垃圾回收机制

文章目录 一. JVM内存划分二. 类加载机制1. 类加载过程2. 双亲委派模型 三. GC垃圾回收机制1. 找到需要回收的内存1.1 哪些内存需要回收&#xff1f;1.2 基于引用计数找垃圾(Java不采取该方案)1.3 基于可达性分析找垃圾(Java采取方案) 2. 垃圾回收算法2.1 标记-清除算法2.2 标记…

【QT】信号和槽(15)

前面的内容说了很多不同的控件如何使用&#xff0c;今天来看下QT的核心&#xff0c;信号与槽&#xff08;Signals and slots&#xff09;&#xff01; 简单理解一下&#xff0c;就是我们的信号与槽连接上了之后&#xff0c;发射一个信号给到槽&#xff0c;槽函数接收到了这个信…

0103水平分片-jdbc-shardingsphere-中间件

文章目录 1 准备服务器1.1 创建server-order0容器1.2 创建server-order1容器 2、基本水平分片2.1、基本配置2.2、数据源配置2.3、标椎分片表配置2.4、行表达式2.5、分片算法配置2.6、分布式序列算法 3、多表关联3.1、创建关联表3.2、创建实体类3.3、创建Mapper3.4、配置关联表3…

Linux 桌面上的 Firefox 面临着大问题

导读毫无疑问&#xff0c;无论是在桌面、笔记本电脑还是移动设备上&#xff0c;浏览器都是任何操作系统中最重要的应用之一。 如果没有一个功能强大、快速且稳定的浏览器&#xff0c;操作系统的实用性将大幅度降低&#xff0c;以至于我相当确定&#xff0c;如果一个操作系统没有…

哈佛商学院教授:每个老板使用ChatGPT之类AI工具的理由

哈佛商学院教授Karim Lakhani表示&#xff0c;每个老板都应该使用生成式人工智能工具&#xff0c;生成式AI为老板提供了一种更高效的工作方式&#xff0c;在提高生产力、提高规模、与客户沟通以及促进销售、社交媒体内容更新和新产品开发等方面都有积极意义。 Karim Lakhani表…