内容简介:C++11特性划分为七1.保持语言的稳定性和兼容性2.更倾向于通用的而不是特殊化的手段来实现特性
C++11特性划分为七 大类,分别是:
1.保持语言的稳定性和兼容性
2.更倾向于通用的而不是特殊化的手段来实现特性
3. 专家新手都支持
4.增强类型的安全性
5. 增强性能和操作硬件的能力
6. 开发能够改变人们思维方式的特性
7.融入编程现实
1、保持语言的稳定性和兼容性
(1)新增关键字
alignas
alignofdecltype
auto重新定义
static_assert
using重新定义
noexcept
export
nullptr
constexpr
thread_local
(2)返回函数名字
const char* hello(){return __func__;};
(3)#pragma once 等价于
#ifndef THIS_HEADER
#define THIS_HEADER
//...
#endif
(4)变长函数参数的宏定义,例子:
#define LOG(...) {\
fprintf(stderr, "%s: line %d: \t", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n") ; \
}
使用方式:
LOG("x=%d", x)
注意加上编译选项 g++ -std=c++11 xx.cpp
(5)__cplusplus 判断编译器类型 , 其等于 199711L(c++03) 201103L(c++11)
#if __cplusplus< 201103L
#error "should use c++11 implementation"
#endif
(6)运行时检查用 assert, 编译时检测用 static_assert(1==1, "information")
在 c++11之前可以通过除 0导致编译器出错的方式来模拟一个静态断言
#define assert_static(e) \
do{\
enum{assert_static__ == 1/(e)}; \
}while(0)
(7)noexcept
如果 c++11中 noexcept修饰的函数抛出了异常,编译器可以选择直接调用 std::terminate()来终止程序运行
c++11默认将 delete函数设置成 noexcept
类析构函数默认也是 noexcept(false)的
这个用法可以推广到函数模板
template<class T>
void fun() noexcept(noexcept( T() ) ) ){}
这样可以根据条件决定“函数是否抛出异常”
(8)快速初始化成员变量
可以通过 =、 {}对非静态成员尽快就地初始化,以减少多个构造函数重复初始化变量的工作,注意初始化列表会覆盖就地初始化操作
class Group{
public:
Group(){}
Group(int a) : data(a) {}
Group(Mem m) : mem(m) {}
Group(int a, Mem m, string n) : data(a), mem(m), name(n) {}
private:
int data = 1;
Memmem{0};
string name("Group");
};
(9) sizeof可以对 People::hand这样的类的非静态成员进行计算,而不用先新建一个对象 People p了
(10)类模板可以声明友元了
template<typename T> class People[
friend T;
}
(11)通过 final和 override让编译器辅助做一些函数是否禁止重载或函数是否是重载函数的判断,
这样做的目的是避免——类继承者的编写者有时想重载某个基类的函数,结果由于参数写错或者函数名写错,导致意想不到的误会
(12)c++98中函数有默认参数,模板类有默认模板参数,但是函数模板没有, c++11增加了这一特性,但与类模板有区别
template<T1, T2=int> class xxx; //OK
template<T1 = int, T2> class xxx2; //error
template<T, int i=0> class xxx3; //OK
template<inti=0, T> class xxx4; //error
template<T1=int, T2> void fun(T1 a, T2 b); //OK
template<inti=0, T> void fun2(T a); //OK
(13)外部模板加快编译器编译速度 ,如下(当然不加也无妨)
extern template void fun<int>(int);
2、更倾向于通用的而不是特殊化的手段来实现特性
(1)继承构造函数
目的:由于类构造函数是不可继承的,为了避免派生类逐个实现基类的构造函数(透传)
例子:
struct A{
A(int i){}
A(double d, int i){}
A(float f, int i, const char* c){}
}
struct B:A{
using A::A;
}
等价于
struct B{
B(int i){}
B(double d, int i ){}
B(float f, int i, const char* c){}
}
(2)委派构造函数
目的:减少多构造函数的类编写时间,让复杂的构造函数调用简单的构造函数(通用函数),实现这个目的在以前通常采用一个通用的 init的类成员函数来实现
例子:有点像变量初始化列表
class Info{
public:
Info() {InitRest(); }
Info(int i) : Info() {type = i;}
Info(char e) : Info() {name = e;}
private:
void InitRest() { //其他初始化操作}
int type{1};
char name{'a'};
};
需要注意的是, : Info()不能与初始化列表一起使用
如下面这段是错误的:
Info(int i) : Info(), type(i){}
(3)移动语义
目的:解决深拷贝问题,同时减少函数返回类对象时拷贝构造的次数,注意这里移动构造函数只对临时变量起作用,在之前的解决中都是通过手工定制拷贝构造函数来解决的
概念:
左值——可以取地址的、有名字的就是左值,如 a = b+c中的 a
右值——不能取地址的、没有名字的就是右值,如 a = b+c中的 b+c
右值引用——就是对一个右值进行引用的类型,因为右值没有名字,下面代码是从右值表达式获取其引用 :
T && a = ReturnRvalue();
可以理解左值引用是具有名字的变量名的别名,右值引用则是没有名字的变量名的别名,所以必须立即初始化(如上)
在 c++98中,常量左值引用( const T&) 是一个“万能”的引用类型,它可以接受非常量左值( T a) ,常量左值 (const T a),右值 (T fun() )进行初始化
使用常量左值引用可以减少临时对象的开销,如下
struct Copyable
{
Copyable() {}
Copyable(const Copyable& o)
{
cout<< "Copied" <<endl;
}
};
Copyable ReturnRvalue() { return Copyable(); }
void AcceptVal(Copyable) {}
void AcceptRef(const Copyable& cp) {}//Copyable c = std::move(cp);}
void AcceptRRef(int&& i) {i+=3; cout<< (char)i<<endl;}
int main()
{
cout<< "Pass by value: " <<endl;
AcceptVal(ReturnRvalue()); // 临时值被拷贝传入
cout<< "Pass by reference: " <<endl;
AcceptRef(ReturnRvalue()); // 临时值被作为引用传递
AcceptRRef('c'); // 临时值被作为引用传递
}
例子:
①移动构造函数
#include <iostream>
using namespace std;
class HasPtrMem {
public:
HasPtrMem(): d(new int(3)) {
cout<< "Construct: " << ++n_cstr<<endl;
}
#if 0 //拷贝一份内存,并用h.d的值赋值
HasPtrMem(const HasPtrMem& h): d(new int(*h.d)) {
cout<< "Copy construct: " << ++n_cptr<<endl;
}
#endif
HasPtrMem(HasPtrMem&& h): d(h.d) { // 移动构造函数
h.d = nullptr; // 将临时值的指针成员置空
cout<< "Move construct: " << ++n_mvtr<<endl;
}
~HasPtrMem() {
delete d;
cout<< "Destruct: " << ++n_dstr<<endl;
}
int * d;
static int n_cstr;
static int n_dstr;
static int n_cptr;
static int n_mvtr;
};
int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_dstr = 0;
int HasPtrMem::n_cptr = 0;
int HasPtrMem::n_mvtr = 0;
const HasPtrMem GetTemp() {
const HasPtrMem h;
cout<< "Resource from " << __func__ << ": " << hex <<h.d<<endl;
return h;
}
int main() {
const HasPtrMem&& a = GetTemp(); //延长临临时变量的生命周期
cout<< "Resource from " << __func__ << ": " << hex <<a.d<<endl;
return 0;
}
② <utility>的函数 std::move(T) 可以调用类 T的移动构造函数,来完成对象的深度拷贝,在类没有定义移动构造函数的时候,会默认调用该类的拷贝构造函数
一个高性能的置换函数如下,调用时使用移动构造函数,移动赋值函数
class T
{
T(T&& t); //移动构造函数
T& operator=(T&& t); //移动赋值函数
T(const T& t); //拷贝构造函数
T& operator=(const T& t); //赋值函数
}
template<class T>
void swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
(4)完美转发
目的:完美转发的意思就是在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数,如:
template<typename T> void Iamforwording(T t){
IrunCodeActually(t);
}
为了使这个函数能够支持左值引用和右值引用, c++11里面定义了新的引用折叠规则(属于模板推导规则的一部分),详细的推导规则可以参见《深入理解 c++11》一书 P86的表 3-2(属于模板的推导规则之一)
简单例子如下:
void IamForwording(X&&& t){
IrunCodeActually(static_cast<X&&&>(t));
}
应用引用折叠规则,则是
void IamForwording(X& t){
IrunCodeActually(static_cast<X&>(t));
}
例子:
void RunCode(int&& m) { cout<< "rvalue ref" <<endl; }
void RunCode(int& m) { cout<< "lvalue ref" <<endl; }
void RunCode(const int&& m) { cout<< "constrvalue ref" <<endl; }
void RunCode(const int& m) { cout<< "constlvalue ref" <<endl; }
template<typename T>
void PerfectForward(T&& t) { RunCode(forward<T>(t)); }
int main() {
int a;
int b;
const int c = 1;
const int d = 0;
PerfectForward(a); // lvalue ref
PerfectForward(move(b)); // rvalue ref
PerfectForward(c); // constlvalue ref
PerfectForward(move(d)); // constrvalue ref
}
(5)初始化列表
允许以下的初始化代码,而 c++98只能通过第一行的编译
int a[] = {1,2,3};
int b[]{1,2,3};
vector<int> c{1,2,3};
map<int, float> d= {{1,1.0f}, {2, 2.0f}};
总结起来,以下初始化结果是等价的
int a= 3+4;
int a= {3+4};
int a(3+4);
int a{3+4};
int *pa = new int(3+4);
初始化列表也可以用于类的构造函数
enum Gender {boy, girl};
class People {
public:
People(initializer_list<pair<string, Gender>> l) // initializer_list的构造函数
{
autoi = l.begin();
for (;i != l.end(); ++i)
data.push_back(*i);
}
private:
vector<pair<string, Gender>> data;
};
People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}};
(6)POD( plain old data)类型
POD通常是用户自定义的普通类型,体现了与 C的兼容性,可以用最老的 memcpy进行复制,可以用 memset进行初始化
c++11将 POD划分为两个基本概念的合集,即:平凡的( trivial) 和 标准布局的( standard layout)
平凡的类或结构体应符合以下定义:
①拥有平凡的默认构造函数和析构函数,一旦定义了构造函数,即使是一个无参数的空函数,该构造函数也不再是的平凡的。
②拥有平凡的拷贝构造函数和移动构造函数
③拥有平凡的拷贝赋值运算符和移动赋值运算符
④不能包含虚函数以及虚基类
可以通过
cout<<is_trivial<sturct XXX>::value <<endl
来检验 ,头文件 <type_traits>
标准布局的类或结构体应符合以下定义:
①所有非静态成员有相同的访问权限
②在类或者结构体继承时,满足以下条件之一:
*派生类中有非静态成员,且只有一个仅包含静态成员的基类
*基类有非静态成员,而派生类没有非静态成员
③类中第一个非静态成员的类型与其基类不同
④没有虚函数和虚基类
⑤所有非静态数据成员均符合标准布局类型,其基类也符合标准布局
可以通过
cout<<is_standard<sturct XXX>::value<<endl
来检验 ,
头文件 <type_traits>
(7)用户自定义常量
struct Watt{ unsigned int v; };
Watt operator "" _w(unsigned long long v) {
return {(unsigned int)v};
}
int main() {
Watt capacity = 1024_w;
}
(8)模板别名
typedef unsignd int UINT 等价于 using UINT = unsigned int 同样适用于模板 template<typename T> using MapString = std::map<T, char*> MapString<int> test;
3、专家新手都支持
(1)右尖括号改进, Y<X<1>>不再编译失败了。
(2)auto类型推导
重新定义了 auto的语意,通过编译器实现对类型的自动推导,使 c++有点类似 python等动态类型语言
简单例子:
int main() {
double foo();
auto x = 1; // x的类型为int
auto y = foo(); // y的类型为double
struct m { int i; }str;
auto str1 = str; // str1的类型是struct m
auto z; // 无法推导,无法通过编译
z = x;
}
作用如下:
①简化代码
std::vector<std::string> t;
for(auto i=t.begin(); i<t.end(); i++){}
②免除类型声明麻烦
class PI {
public:
double operator* (float v) {
return (double)val * v; // 这里精度被扩展了
}
const float val = 3.1415927f;
};
int main() {
float radius = 1.7e10;
PI pi;
auto circumference = 2 * (pi * radius);
}
③跨平台
④模板推导,需要配合 decltype
template<typename T1, typename T2>
auto Sum(T1& t1, T2& t2) ->decltype(t1+t2){
return t1+t2;
}
⑤用在宏定义上,避免多次变量展开
#define Max1(a, b) ((a) > (b)) ? (a) : (b)
#define Max2(a, b) ({ \
auto _a = (a); \
auto _b = (b); \
(_a > _b) ? _a: _b; })
int main() {
int m1 = Max1(1*2*3*4, 5+6+7+8);
int m2 = Max2(1*2*3*4, 5+6+7+8);
}
上述代码避免了, 1*2*3*4被多次计算
(3)decltype,同样也是类型推导,区别在于
decltype的类型推导并不是像 auto一样是从变量声明的初始化表达式获得变量的类型,其总是以一个普通表达式为参数,返回该表达式的类型。
作用如下:
①增加代码可读性
vector<int> vec;
typedef decltype(vec.begin()) vectype;
vectype i; // 这是auto无法做到的
for (i = vec.begin(); i<vec.end(); i++) {
}
for (decltype(vec)::iterator i = vec.begin(); i<vec.end(); i++) {
}
②恢复匿名结构体
sturct{
int d;
}ttt;
decltype(ttt) ok;
③模板利器
// s的类型被声明为decltype(t1 + t2)
template<typename T1, typename T2>
void Sum(T1 & t1, T2 & t2, decltype(t1 + t2) & s) {
s = t1 + t2;
}
④基于 decltype的 result_of
#include <type_traits>
using namespace std;
typedef double (*func)();
int main() {
result_of<func()>::type f; // 由func()推导其结果类型
}
⑤追踪函数的返回类型
下面两个代码是等价的,第二个代码可读性稍好
int (* (*pf()) () ) () { return 0;}
auto pf1() -> auto (*) () ->int (*) () { return 0; }
可用于函数转发 :
#include <iostream>
using namespace std;
double foo(int a) {
return (double)a + 0.1;
}
int foo(double b) {
return (int)b;
}
template<class T>
auto Forward(T t) ->decltype(foo(t)){
return foo(t);
}
int main(){
cout<< Forward(2) <<endl; // 2.1
cout<< Forward(0.5) <<endl; // 0
}
推导法则:
首先定义标记符表达式:除去关键字、字面量等编译器需要使用的标记之外的 程序员 自定义的标记都可以是标记符。而耽搁标记符对应的表达式就是标记符表达式。
通过下面的例子和注释可以了解推导的四个法则。
int i = 4;
int arr[5] = {0};
int *ptr = arr;
struct S { double d; } s;
void Overloaded(int);
void Overloaded(char); // 重载的函数
int&& RvalRef();
const bool Func(int);
// 规则 1: 单个标记符表达式以及访问类成员,推导为本类型
decltype(arr) var1; // int[5], 标记符表达式
decltype(ptr) var2; // int*, 标记符表达式
decltype(s.d) var4; // double, 成员访问表达式
decltype(Overloaded) var5; // 无法通过编译,是个重载的函数
// 规则 2: 将亡值,推导为类型的右值引用
decltype(RvalRef()) var6 = 1; // int&&
// 规则 3: 左值,推导为类型的引用
decltype(true ? i : i) var7 = i; // int&, 三元运算符,这里返回一个i的左值
decltype((i)) var8 = i; // int&, 带圆括号的左值
decltype(++i) var9 = i; // int&, ++i返回i的左值
decltype(arr[3]) var10 = i; // int& []操作返回左值
decltype(*ptr) var11 = i; // int& *操作返回左值
decltype("lval") var12 = "lval"; // const char(&)[9], 字符串字面常量为左值
// 规则 4:以上都不是,推导为本类型
decltype(1) var13; // int, 除字符串外字面常量为右值
decltype(i++) var14; // int, i++返回右值
decltype((Func(1))) var15; // constbool, 圆括号可以忽略
(4)基于范围的 for循环关键字改进
int arr[5] = {1,2,3,4,5};
for(auto i : arr)
cout<<i;
要求迭代的对象实现 ++和 ==等操作符,且范围是确定的。同时注意迭代器对象在 for中是解引用的
vector<int> v = {1, 2, 3, 4, 5};
for (auto i = v.begin(); i != v.end(); ++i)
cout<< *i<<endl; // i是迭代器对象
for (auto e: v)
cout<< e <<endl; // e是解引用后的对象
4、增强类型的安全性
(1)强类型枚举
(2)堆内存管理
5、增强性能和操作硬件的能力
(1)常量表达式
(2)变长模板
(3)原子类型与原子操作 aotmic<float>af{1.2f}
(4)线程局部存储, 原来的 __thread interrCode 可以写成 intthread_localerrCode;
(5)快速退出: quick_exit与 at_quick_exit 不执行析构函数而只是让程序终止,与 exit同属于正常退出
用法类似下面:
#include <cstdlib>
#include <iostream>
using namespace std;
void openDevice() { cout<< "device is opened." <<endl; }
void resetDeviceStat() { cout<< "device stat is reset." <<endl; }
void closeDevice() { cout<< "device is closed." <<endl; }
int main() {
atexit(closeDevice);
atexit(resetDeviceStat);
openDevice();
exit(0);
}
输出:
device is opened
device stat is reset
device is closed
6、开发能够改变人们思维方式的特性
(1)nullptr
(2)=default =deleted
(3)lambda
lambda语法
[捕捉列表 ] (参数 ) mutable修饰符 -> 返回类型 {}
如果不需要参数,其括号可以省略
捕捉列表用于捕捉上下文的变量,形式如下 :
[var]表示值传递方式捕捉变量 var
[=] 表示值传递方式捕捉所有父作用域的变量(包括 this)
[&var] 表示引用传递捕捉变量 var
[&] 表示引用传递捕捉所有父作用域的变量(包括 this)
[this] 表示值传递捕捉当前的 this指针
① lambda 显然比仿函数( operator() (参数 ) )好用
class AirportPrice{
private:
float _dutyfreerate;
public:
AirportPrice(float rate): _dutyfreerate(rate){}
float operator()(float price) {
return price * (1 - _dutyfreerate/100);
}
};
int main(){
float tax_rate = 5.5f;
AirportPrice Changi(tax_rate);
auto Changi2 =
[tax_rate](float price)->float{ return price * (1 - tax_rate/100); };
float purchased = Changi(3699);
float purchased2 = Changi2(2899);
}
②代码可读性强
int Prioritize(int i);
int AllWorks(int times){
int i;
int x;
try {
for (i = 0; i < times; i++)
x += Prioritize(i);
}
catch(...) {
x = 0;
}
const int y = [=]{
int i, val;
try {
for (i = 0; i < times; i++)
val += Prioritize(i);
}
catch(...) {
val = 0;
}
return val;
}();
}
③捕捉列表不同会导致不同的结果
#include <iostream>
using namespace std;
int main() {
int j = 12;
auto by_val_lambda = [=] { return j ;};
auto by_ref_lambda = [&] { return j;};
cout<< "by_val_lambda: " <<by_val_lambda() <<endl; //12
cout<< "by_ref_lambda: " <<by_ref_lambda() <<endl; //12
j++;
cout<< "by_val_lambda: " <<by_val_lambda() <<endl; //12, 注意这里,j传入的时候是12
cout<< "by_ref_lambda: " <<by_ref_lambda() <<endl; //13
}
④ mutable的作用
int main(){
int val;
// 编译失败, 在const的lambda中修改常量
auto const_val_lambda = [=]() { val = 3;};
// 非const的lambda,可以修改常量数据
auto mutable_val_lambda = [=]() mutable { val = 3;};
// 依然是const的lambda,对引用不影响
autoconst_ref_lambda = [&] { val = 3;};
// 依然是const的lambda,通过参数传递val
auto const_param_lambda = [&](int v) { v = 3;};
const_param_lambda(val);
return 0;
}
⑤与 STL结合
#include <vector>
#include <algorithm>
using namespace std;
vector<int>nums;
vector<int>largeNums;
const int ubound = 10;
inline void LargeNumsFunc(inti){
if (i>ubound)
largeNums.push_back(i);
}
void Above() {
// 传统的for循环
for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
if (*itr>= ubound)
largeNums.push_back(*itr);
}
// 使用函数指针
for_each(nums.begin(), nums.end(), LargeNumsFunc);
// 使用lambda函数和算法for_each
for_each(nums.begin(), nums.end(), [=](inti){
if (i>ubound)
largeNums.push_back(i);
});
}
7、融入编程现实
(1)algnof和 alognas
(2)[[ attibute-list ]]
(3)Unicode的库支持
(4)原生字符串字面量
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 『互联网架构』软件架构-redis特性和集群特性(中)(49)
- 『互联网架构』软件架构-redis特性和集群特性(上)(48)
- 『互联网架构』软件架构-redis特性和集群特性(下)(50)
- JDK 14 功能特性
- C# 特性(Attribute)
- python—高级特性
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。