文章目录
- 前言
- [1. auto关键字(C++11)](#1. auto关键字(C++11))
-
- [1.1 为什么要有auto关键字](#1.1 为什么要有auto关键字)
- [1.2 auto关键字的使用方式](#1.2 auto关键字的使用方式)
- [1.3 auto的使用细则](#1.3 auto的使用细则)
- [1.4 auto不能推导的场景](#1.4 auto不能推导的场景)
- [2. 基于范围的for循环(C++11)](#2. 基于范围的for循环(C++11))
-
- [2.1 范围for的语法](#2.1 范围for的语法)
- [2.2 范围for的使用条件](#2.2 范围for的使用条件)
- [3. 指针空值nullptr(C++11)](#3. 指针空值nullptr(C++11))
-
- [3.1 为什么会有nullptr这个关键字?](#3.1 为什么会有nullptr这个关键字?)
前言
本文我了解一下C++11新特性的auto、范围for以及nullptr给我们的编程带来了什么样的好处,以及我们在特定的场景该如何使用它们。
温馨提示:本文所讲到的C++11(2011年)和C++98(1998年)均为C++编译器的版本。
OK,让我们一起探索这些auto、范围for以及nullptr背后的秘密。
1. auto关键字(C++11)
这里需要说明的一点是,在C++98就已经有auto这个关键字了。不过在C++98的做法中,它将auto关键字视作一个存储类型的指示符。换句话说,只要是在C++98中使用auto关键字定义的变量就是一个具有自动存储器功能的局部变量 -- 待补充
1.1 为什么要有auto关键字
这就要往类型别名的方向去思考这个问题。
想一个现象,随着我们越学到后面,代码就会变得愈加复杂,伴随的是声明类型的长度也会增加,这个就会导致两个问题:
- 类型难以拼写;
- 类型含义不明确导致出错。
这么说可能有点干巴,下面我来展示一段代码(这个是大家以后学习C++要用到的):
cpp
#include<iostream>
#include<string>
#include<map>
#include<vector>
using namespace std;
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>
和 std::map<std::string, std::string>::iterator
,这两个类型够长吧,即使你能记得住,如果有很多地方都要定义的话,我估计你的键盘可能会敲冒烟。
那有的人就会这么想,那我可以用typedef
来给这些长的类型起一个别名,比如下面这样:
cpp
#include<iostream>
#include<string>
#include<map>
#include<vector>
using namespace std;
typedef std::map<std::string, std::string> Map;
int main()
{
Map m{ {"apple","苹果"},{"orange","橙子"},{"pear","梨"} };
Map::iterator it = m.begin();
while (it != m.end())
{
//...
}
return 0;
}
这个方法确实是可以的,但是你能确保在庞大的代码量面前,你能十分的明确Map这个类型所代表的具体含义吗?本人觉得这是一件很难的事,另外用typedef
关键字,还有个重要的细节:
cpp
#include<iostream>
using namespace std;
typedef int* int_ptr;
int main()
{
int num1 = 66,num2 = 88;
//写法1:
int_ptr a = &num1 , b = &num2;
//写法2:
int_ptr a = &num1 , *b = &num2;
//以上两种写法那个是正确的?
}
答案:写法一是正确的。
cpp
#include<iostream>
using namespace std;
int main()
{
int num1 = 66,num2 = 88;
//写法1:
int* a = &num1 , b = &num2;
//写法2:
int* a = &num1 , *b = &num2;
//以上两种写法那个是正确的?
}
答案:写法二是正确的。
如果你上面两道题目做对了一道的话,那我想auto关键字就很适合你使用了!
1.2 auto关键字的使用方式
🍉auto 变量名 = 值;
🍇编译器在编译的过程看到auto就会根据赋值符号右边的表达式推导出出变量名的类型!
cpp
#include<iostream>
using namespace std;
int main()
{
auto a = 's';
auto b = 66;
auto c = 520.13f;
auto d = 0.1314;
//auto也可以推导出表达式的值的数据类型
auto tmp = b + d;
//我们可以用typdeid(变量名).name()
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
cout << typeid(tmp).name() << endl;
return 0;
}
[注意] :使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种"类型"的声明,而是一个类型声明时的"占位符",编译器在编译期会将auto替换为变量实际的类型。
1.3 auto的使用细则
- auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
cpp
//auto*和auto用来代表指针类型都是一样的,但是如果我们要用引用的话,就必须在auto后面加上&。
int a = 10;
int b = 66;
int c = 88;
auto* pa = &a;
auto pb = &b;
auto& ic = c;
cout << typeid(pa).name() << endl;
cout << typeid(pb).name() << endl;
cout << typeid(ic).name() << endl;
return 0;
- 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
cpp
void TestAuto()
{
auto a = 10, b = 20;
auto c = 3, d = 4.0; //该行代码会编译失败,因为c和d的初始表达式类型不同
}
1.4 auto不能推导的场景
- auto不能作为函数的形参
cpp
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
- auto不能直接声明数组
cpp
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6}; //会报错
}
好了,到这里auto关键字的用法也了解的差不多了。那么接下来我们再来看看C++的一个"语法糖"------"范围for"!
2. 基于范围的for循环(C++11)
2.1 范围for的语法
我们在C++98中如果要遍历一个数组,是这样做的:
cpp
void TestFor()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
for(int i = 0 ; i < sizeof(arr) / sizeof(int); ++i)
{
cout<< arr[i] << ' ';
}
cout << endl;
for(int* p = arr; p < arr + sizeof(arr)/sizeof(int); p++)
{
cout << *p << ' ';
}
cout << endl;
}
==对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。==因此C++11中引入了基于范围的for循环。for循环后的括号由冒号" :"分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
cpp
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
2.2 范围for的使用条件
- for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定
cpp
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
- 迭代的对象要实现++和==的操作。(关于迭代器这个问题,以后会讲,现在提一下,没办法讲清楚,现在大家了解一下就可以了)
范围for比较简单,只要求会用就可以了。那接下来再来讲讲另一个关键字"nullptr"!
3. 指针空值nullptr(C++11)
3.1 为什么会有nullptr这个关键字?
有的读者可能会诧异,不是说C++兼容C语言吗?那我们就直接用C语言的NULL作为来表示指针空值就行了啊,为什么C++还要单独再弄一个nullptr关键字出来呢?
我们可以查看C++下的NULL:
在main函数中敲一个NULL,之后点击鼠标右键,然后点击"转到定义"。
NULL定义的地方:
可以看到的是NULL在cpp文件中是字面常量0
如果我们要是在C++中用NULL,可能会遇到一些麻烦,比如下面的这段代码:
cpp
void f(int)
{
cout << "void f(int)" << endl;
}
void f(int*)
{
cout << "void f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
}
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
注意:
- 🍉在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 🍉 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 🍉为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
好了,到这里,你感受到了auto、范围for和nullptr的魅力了吗?
如果觉得本文写的不错的话,麻烦给偶点个赞吧!!!