1 原始字面量
有时候在输出一个路径字符串时,编译器会将其中的部分内容识别成转义字符进行输出,可以用R “xxx(原始字符串)xxx”
其中()两边的字符串可以省略。原始字面量R可以直接表示字符串的实际含义,而不需要额外对字符串做转义或连接等操作。
string str2 = R"(D:\hello\world\test.text)";
2 nullptr
在之前的C++中,为指针进行初始化的时候通常用NULL,在C语言中NULL的本质是一个(void*)0
,而在C++中NULL的本质就是0,一个int类型值,底层实现为:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
因为C++中不允许一个void *指针隐式转换为其它类型指针,比如:
void *ptr1 = NULL;
int *ptr2 = ptr1;//错误
int *ptr2 = (int *)ptr1;//正确
C++11没有对这一特性进行修改,而是引入了nullptr
专门用于初始化空类型指针,其可以隐式转换为其它类型的指针,从而避免了野指针(没有明确指向的指针)
3 constexpr
const在C++11之前就有,用处在于变量只读
以及修饰常量
,但要注意两者是不一样的
变量只读:比如函数中我们不希望传入的参数被修改
void func(const int num)
修饰常量:有时提前定义一个常量,并用于描述数组大小
const int a = 20;
int arr[a];
C++11中用constexpr修饰常量表达式,常量表达式指的就是由多个(≥1)常量(值不会改变)组成并且在编译过程中就得到计算结果的表达式。其效率高于运行时计算的非常量表达式,其不需要每次运行都计算
要注意的时是,constexpr不能取代const,在表示常量的时候,两者是相同的,但是在用于变量只读的时候,比如引用传参的时候设置只读属性还是用const,constexpr达不到修饰变量只读的效果。
4 auto自动类型推导
使用auto声明的变量必须要进行初始化,以让编译器推导出它的实际类型,在编译时将auto占位符替换为真正的类型。
auto的使用限制
- 不能作为函数参数使用。因为只有在函数调用的时候才会给函数参数传递实参,auto要求必须要给修饰的变量赋值,因此二者矛盾。
- 不能用于类的非静态成员变量的初始化
- 不能使用auto关键字定义数组
- 无法使用auto推导出模板参数
auto的应用
- 用于泛型编程,在使用模板的时候,很多情况下我们不知道变量应该定义为什么类型就用auto
- 用于stl容器的遍历
#include <map>
int main()
{
map<int, string> person;
map<int, string>::iterator it = person.begin();
for (; it != person.end(); ++it)
{
// do something
}
return 0;
}
//简化后为:
map<int, string> person;
// 代码简化
for (auto it = person.begin(); it != person.end(); ++it)
{
// do something
}
5 decltype类型推导
使用auto只能进行初始化变量的推导,而decltype可以进行表达式的推导,其也是在编译阶段实现的
推导规则
- 表达式为普通变量或者普通表达式或者类表达式,在这种情况下,使用decltype推导出的类型和表达式的类型是一致的。
- 表达式是函数调用,使用decltype推导出的类型和函数返回值一致。
- 表达式是一个左值,或者被括号( )包围,使用 decltype推导出的是表达式类型的引用(如果有const、volatile限定符不能忽略)。
6 final和override
C++中增加了final关键字来限制某个类不能被继承,或者某个虚函数不能被重写,如果使用final修饰函数,只能修饰虚函数,并且要把final关键字放到类或者函数的后面。
override关键字确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数,这样就可以保证重写的虚函数的正确性,也提高了代码的可读性,和final一样这个关键字要写到方法的后面。使用了override关键字之后,假设在重写过程中因为误操作,写错了函数名或者函数参数或者返回值编译器都会提示语法错误
7 函数模板允许默认参数
8 using
在C++中using用于声明命名空间,使用命名空间也可以防止命名冲突。在程序中声明了命名空间之后,就可以直接使用命名空间中的定义的类了。C++11中用using可以来定义类型的别名,和typedef的效果一样
typedef 旧的类型名 新的类型名;
// 使用举例
typedef unsigned int uint_t;
using 新的类型 = 旧的类型;
// 使用举例
using uint_t = int;
其好处体现在函数指针的定义上
// 使用typedef定义函数指针
typedef int(*func_ptr)(int, double);
// 使用using定义函数指针
using func_ptr1 = int(*)(int, double);
使用using定义函数指针别名的写法看起来就非常直观了,把别名的名字强制分离到了左边,而把别名对应的实际类型放在了右边,比较清晰,可读性比较好。
但using最主要的特性还是可以通过使用using来为一个模板定义别名,比如:
template <typename T>
using mymap = map<int, T>;
使用typedef重定义类似很方便,但是它有一点限制,比如无法重定义一个模板,比如我们需要一个固定以int类型为key的map,它可以和很多类型的value值进行映射,如果使用typedef这样直接定义就非常麻烦:
typedef map<int, string> m1;
typedef map<int, int> m2;
typedef map<int, double> m3;
9 基于范围的for循环
基本语法
for (declaration : expression)
{
// 循环体
}
在上面的语法格式中declaration表示遍历声明,在遍历过程中,当前被遍历到的元素会被存储到声明的变量中。expression是要遍历的对象,它可以是表达式、容器、数组、初始化列表等。
通常建议以引用的方式进行修改,不仅可以对其进行写操作,而且还避免了每次遍历的拷贝操作。
#include <iostream>
#include <vector>
using namespace std;
int main(void)
{
vector<int> t{ 1,2,3,4,5,6 };
cout << "遍历修改之前的容器: ";
for (auto &value : t)
{
cout << value++ << " ";
}
cout << endl << "遍历修改之后的容器: ";
for (auto &value : t)
{
cout << value << " ";
}
cout << endl;
return 0;
}
对容器的遍历过程中,如果只是读数据,不允许修改元素的值,可以使用const定义保存元素数据的变量,在定义的时候建议使用const auto &,这样相对于const auto效率要更高一些。
#include <iostream>
#include <vector>
using namespace std;
int main(void)
{
vector<int> t{ 1,2,3,4,5,6 };
for (const auto& value : t)
{
cout << value << " ";
}
return 0;
}
使用普通的for循环方式(基于迭代器)遍历关联性容器, auto自动推导出的是一个迭代器类型,需要使用迭代器的方式取出元素中的键值对(和指针的操作方法相同):
- it->first
- it->second
使用基于范围的for循环遍历关联性容器,auto自动推导出的类型是容器中的value_type,相当于一个对组(std::pair)对象,提取键值对的方式如下: - it.first
- it.second
普通循环与基于范围循环的区别:
对应基于范围的for循环来说,冒号后边的表达式只会被执行一次。在得到遍历对象之后会先确定好迭代的范围,基于这个范围直接进行遍历。如果是普通的for循环,在每次迭代的时候都需要判断是否已经到了结束边界。