C++11(也称C++2.0、Modern C++) 是继C++98(C++1.0) 之后的第二个国际标准规格,其对C++的改变是全方位的,有人也说C++11是一门新的语言。C++11增加了非常多的新特性,而这些新特性对于开发效率的提升非常明显。
C++11支持的编程范式:
关于C++11支持的编程范式初步考虑会写一篇博客进行介绍,而其中的函数式编程是以前C++版本所不具备的,并且C++11对泛型编程及元编程都有所改进。
- 面向过程式编程
- 基于对象编程
- 面向对象编程
- 泛型编程
- 函数式编程
- 元编程
C++11的新关键字
- auto
auto严格意义上说不是C++11新增的关键字,但由于C++11之前使用非常少,而C++11给了auto新的含义,用于类型推断。在C++14中使用auto和decltype可以编写功能非常强大的函数,而对于auto和decltype会专门写一篇文章用于阐述C++11中的类型推断。在C++11后,优先使用 auto 而非显式类型声明,原因有二,一是有些值的类型不是那么明显,程序员自己花很多精力去写类型有可能是错误的,不如让编译器去判断,二是可以消除变量没有初始化而是用的隐藏bug出现。对比下面两份代码,自然能体会auto的作用。它不仅让程序员不需要纠结其类型,还少写了很多代码。
template<typename It>
void dwim(It b, It e)
{
while(b != e){
typename std::iterator_traits<It>::value_type currValue = *b;
...
}
}
template<typename It>
void dwim(It b, It e)
{
while(b != e){
auto currValue = *b;
...
}
}
decltype
用于编译时推断类型,decltype有点像auto的逆过程,使用auto可以声明一个变量,而decltype可以通过一个变量得到类型。在C++11可以使用trailing-return-type指定返回值类型,而在C++14中可以使用decltype(auto)这种更强大的语法,让程序员不再纠结返回值类型。decltype 最主要用来声明一个函数模板,并且返回值的类型取决于参数的类型using
using不是C++11的新增关键字,C++11对其功能进行了扩充,除了对命名空间、函数等引入外,还可以用于定义模板别名(具体的类型别名也可以),而typedef只能作用于具体类型而不能用于模板别名的定义。nullptr
C++11前用NULL来表示空指针,但由于0可以被隐式类型转换为整型,这就会存在一些问题。C++11种引入nullptr来表示空指针。constexpr
constexpr在C++11中用于声明常量表达式,constexpr可作用于函数返回值、函数参数、数据声明以及类的构造函数等。常量表达式指值不会改变并且在编译时期就得到计算结果的表达式。const语义是指运行时常量,而constexpr不但要求运行时常量,还是编译期常量,即编译时即可计算其结果。noexcept
C++11中通过noexcept说明符来对外宣称处理不会抛出异常,调用者可以不用考虑异常处理机制,但是如果声明为noexcept的函数内部抛出了异常,则程序就会终止。final & override
final与override与JAVA的语义一致,final可以用于修饰类或者虚函数,用于修饰类时,意味着该类不能被继承,用于修饰虚函数时,意味着该函数不能被重写。override的引进是C++对自身安全性的增强(笔者看来C++虚函数设计对比JAVA还是弱一些)。该关键字用于表明函数是对父类函数的重写,用于编译器进行检查,而在C++11之前这块的设计完全缺失。static_assert
用于编译时的静态断言:若指定的表达式为false则编译失败。default & delete
C++11前代码对类会隐式产生多个函数:默认构造函数、默认析构函数、拷贝构造函数、赋值运算符函数(Big Three),在C++11种引入右值及move操作后,又会隐式产生移动构造函数、移动赋值函数。而当类中存在指针变量时,这种隐式产生的函数往往会引入bug,在C++11中引入default关键字,可以显示强制的要求编译器生成默认版本,引入delete关键字用于禁止编译器生成默认版本的函数。在C++编码中,对类的Big Three,C++11后对Big Five的处理是一个C++程序员技术能力的一个重要体现。alignas与alignof
C++11为了支持内存对齐,引入了两个关键字,对齐描述符alignas与操作符alignof。alignas用于指定类型的对齐字节数,alignof用于获取类型的对齐字节数。thread_local
程序中的对象都具有下列存储期之一:
存储期 | implication |
---|---|
自动 automatic
|
对象的存储在外围代码块开始时分配,而在结束时解分配。未声明为 static 、extern 或 thread_local 的所有局部对象均拥有此存储期。 |
静态 static
|
对象的存储在程序开始时分配,而在程序结束时解分配。只存在对象的一个实例。所有声明于命名空间作用域(包含全局命名空间)的对象,加上声明带有 static 或 extern 的对象均拥有此存储期。 |
动态 dynamic
|
对象的存储通过使用动态内存分配函数来按请求进行分配和释放的。 |
线程 thread
|
对象的存储在线程开始时分配,而在线程结束时释放。每个线程拥有其自身的对象实例。唯有声明为 thread_local 的对象拥有此存储期。 |
thread_local可以用于修饰三种变量:全局变量、类的static成员变量及本地变量,此时变量即拥有线程生命周期及线程可见性的变量。
C++11的重要新特性
笔者认为对C++有重要变革的新特性有:多线程编程、lambda表达式、可变参数模板、正则表达式、智能指针及右值引用。
多线程编程
C++11前没有提供对多进程并发的原生支持,所以C++的多进程并发要依赖平台相关的API实现。C++11 标准的推出在语言层面对多线程编程提供了原生支持,极大的提高了程序的可移植性。
C++11 新标准中引入了几个头文件来支持多线程编程:
< thread > :包含std::thread类以及std::this_thread命名空间。主要负责运行及管理线程。
< atomic > :包含std::atomic和std::atomic_flag类,定义了一些原子操作。
< mutex > :包含了与互斥量相关的类以及其他类型和函数用于线程同步。互斥避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。
< future > :包含两个Provider类(std::promise和std::package_task)和两个Future类(std::future和std::shared_future),promise和future用于在线程间传递异步结果,packaged_task来封装可以产生这种异步结果的函数调用.。
< condition_variable > :包含与条件变量相关的类,包括std::condition_variable和std::condition_variable_any。条件变量是允许多个线程相互交流的同步原语。它允许一定量的线程等待(可以定时)另一线程的唤醒,然后再继续。条件变量始终关联到一个互斥。
多线程编程后续会在博客中专门阐述。
lambda表达式
Lambda表达式在C++11中引入,利用Lambda表达式,可以方便的定义和创建匿名函数。lambda表达式在其他语言中都有提供,lambda表达式的引入也使得C++可以用于函数式编程范式。lambda表达式的形式如下:
[capture list] (params list) mutable exception-> return type { function body }
name | implication |
---|---|
capture list | 捕获外部变量列表,可以值捕获、引用捕获,也可以混合捕获 |
params list | 形参列表 |
mutable指示符 | 表明函数体内的代码可以修改值捕获的变量 |
exception | 异常设定 |
return type | 返回类型 |
function body | 函数体 |
可变参数模板
C++11之前类模版和函数模版中只能含固定数量的模版参数,可变模版参数(variadic templates)对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。
template <class... T>
void f(T... args);
可变参数模板的定义一般都会使用模版偏特化和递归方式两种技巧。C++11引入的元组tuple就是一种可变参数模板的应用。
正则表达式
C++11提供了正则表达式库,正则表达式是一种用于在字符串中匹配模式的微型语言。提供主要3种算法,regex_match确定正则表达式是否匹配整个目标字符序列;regex_search 确定正则表达式和目标字符序列中的某个子序列间是否有匹配;regex_replace以格式化的替换文本来替换正则表达式匹配的出现位置 。
name | implication |
---|---|
目标序列 | 可以是二个迭代器所指定的范围、空终止字符串或一个 std::string 。 |
模式 | 正则表达式自身。它确定构成匹配者。它是从带特定语法的字符串构成的 std::basic_regex 类型对象。受支持的语法变体的描述见 syntax_option_type 。 |
匹配的数组 | 关于匹配的信息可作为 std::match_results 类型对象获取。 |
替换字符串 | 确定如何替换匹配的字符串,受支持的语法变体的描述见 match_flag_type 。 |
智能指针
C++从业者在使用指针时,都会遇到的两类问题,一是悬空指针,二是内存泄漏。这些都是在使用指针时经常遇到的问题,而C++11引入的智能指针能很好的应对以上问题,让C++从业者对内存管理不再谈虎色变。
name | implication |
---|---|
unique_ptr | 独占指针对象,并保证指针所指对象生命周期与其一致。<所有权唯一> |
shared_ptr | 可共享指针对象,可以赋值给shared_ptr或weak_ptr。所有关联的shared_ptr生命周期结束时对象生命周期结束。<共享所有权> |
weak_ptr | 它不能决定所指对象的生命周期,引用所指对象时,需要lock()成shared_ptr才能使用。<为了解决shared_ptr循环引用问题引入> |
右值引用
右值引用是C++11中引入的,它是为了提高运行效率避免一些无意义的深拷贝而引入的。C++中所有的值都必然属于左值、右值二者之一。关于左值和右值的最初的定义是,左值既可以出现在赋值符号的左侧,也可以出现在赋值符号的右侧的值,右值是只能出现在赋值符号右侧的值。而目前区分左值和右值的一个方法就是看能对不能对其进行取地址,如果能则为左值,如果不能则为右值。对于右值引用,其与move和完美转发联系非常紧密,move与RVO、universal reference后面会专门写博客对其阐述。
C++11的其他增强
初始化列表initializer_list
初始化列表{}的引入使得变量的初始化更加优雅,特别对于STL容器,C++11可以和其他类型一样进行初始化,另外在前面的博客<The Most Vexing Parse In C++>中,可以看到使用初始化列表方式可以消除C++中很多令人困惑的解析。
基于范围的for循环
在C++11之前对于容器类的遍历会写一大坨代码,而C++11新入的auto和基于范围的for循环,可以很优雅的对容器元素进行遍历。
强类型枚举
在C++11之前枚举不是类型安全的,它实际是整数,并且具体使用哪种整数类型是由编译器定义的,此外枚举值是暴露在外层作用域的,所以即便是两种不同的枚举类型也不能使用相同的名字。C++11引入的enum class是类型安全的,不会隐式转化为整数,并且其作用域是枚举类内部。
C++11其他的小特性在关键字章节有所提及,后续会对几个重要的特性专门阐述。
WalkeR_ZG