1. 新增扩展int类型:long long int,也称long long。
在C++11新特性中,long long一定是最容易被接受的一个。多数程序员看到它时甚至不会意识到这是一个新特性。
与 long long 整型相关的一共有3个:LONG_MIN、LONG_MAX 和ULONG_MAX, 它们分别代表了平台上最小的long long 值、最大的long long 值,以及最大的unsigned long long 值。long long int 64位 , unsigned long long 64位
int64_t用来表示64位整数,在32位系统中是long long int,在64位系统中是long int,所以打印int64_t的格式化方法是:
printf("%ld", value); // 64bit OS printf("%lld", value); // 32bit OS
2. noexcept
noexcept替代传统的throw抛异常,更大的作用就是保证应用程序的安全
比如: 一个类析构函数不应该抛异常,那么对于常被析构调用的delete,c++11默认将delete设置为noexcept,
使用: .h.cpp文件都需要写
自定义的函数可以如下:
void func() noexcept;
void func() noexcept(true/false);
3. 允许使用 = 或者 {} 进行就地初始化非静态数据成员
如果同时使用类的初始化列表(又称:冒号语法初始化),则初始化列表生效
如下,初始化后,m_high=10,m_strName=test
看起来,是就地初始化先执行,然后才是初始化列表。
class Father { public: Father() :m_high(10) ,m_strName("test") {} private: int m_high = 20; std::string m_strName{"name1"};
注意:
c++11中使用{}初始化,是唯一一种可以防止类型收窄,narrowing的初始化方式, 即:数据变化或者精度丢失,如float->int,高精度到低精度等
这也是{}与其它方式初始化不一样的地方
使用{}初始化,编译器会检查是否出现类型收窄。因此,建议能用{}初始化变量
4. final/override
final: 使派生类无法覆盖它所修饰的虚函数
override: 使用override,则派生类必须覆盖即重写所修饰的虚函数,建议能用就用,避免手一抖写错,新增了一个函数而无法识别
5. using
在c++11前,如果派生类要使用基类的成员函数,可以通过声明using来完成
c++11中,此想法被扩展到构造函数中,使用using来声明继承基类的构造函数,但只能初始化基类的成员,如果要初始化派生类自己的成员,可以使用就地初始化。
如: #include <iostream> #include <string> class A { public: A(int i); A(int i, char c); public: double f(double i) { std::cout << __FUNCTION__ << std::endl; return i; } private: int m_num = 0; char m_c = 'A'; }; class B : public A { public: using A :: A; //使用A的构造函数,C++11后支持 public: using A::f; //使用A的f函数,c++11前就支持 int f(std::string name) { std::cout << __FUNCTION__ << std::endl; return 0; } private: std::string m_strName{"testB"}; }; 使用: int main() { int i = 10; char c = 'B'; double d = 9.98f; B b{i,c}; //c++11中,使用using A :: A 表示使用基类A的构造, //如果没有使用using,此处B没有构造函数,编译报错,如果要实现与A一样的构造,需要全部重写类似B的构造,比较繁琐 //如:B(int i); B(int i, char c). //有了using,直接using,然后自己的变量,就地初始化,方便。 b.f(d); //使用using A::f; 表示使用基类的A的f(double)函数 //如果没有,此处基类A中的f(double)已经被B的f(string)的覆盖了,编译会报错。 }
6. 委派构造
看名字,就知道与构造函数有关
c++11中,所谓委派构造是指将构造的任务委派给了目标函数来完成类的构造
直白点,就是构造函数委派另外的构造函数进行构造。
注意: 当使用了委派构造后,就不能使用初始化列表进行构造了。只能选其一
class Info { public: Info():m_a(0),m_strName("") { Init(); } Info(int i):Info() //委派info构造
{ Init(); m_a = i; } Info(std::string name,int i) :Info(i) //委派info(i)构造
{ Init(); m_strName = name; } //error, if use initialize list, then can not use delegate constructor func // Info(std::string name) :Info(), m_a(i) { // Init(); // m_strName = name; // } private: void Init() { //do any other initialize std::cout << __FUNCTION__ << __FILE__ << __LINE__ << std::endl; } private: int m_a; std::string m_strName; };
7.左值,右值
c语言一个典型的说法,=左边的是是左值,右边的是右值
c++中,一个更广泛认同的说法:那就是可以取地址的,有名字的就是左值,反之就是右值。
更为细致的,c++中,右值分为:将亡值和纯右值
纯右值:
1. 函数的非引用返回的临时变量,
2. 一些计算表达式值,如2+3,
3. 不跟对象关联的纯值,如:1,c,d,
4. 类型转换的返回值
5. lambda表达式
将亡值:则是与右值引用 &&相关的
1. 返回右值引用的函数返回值
2. std::move
3. 类型转发后的&&
左值引用 &
右值引用 &&
c++中,由于右值通常不具有名字,所以我只能通过引用的方式找打它,因此就有了右值引用的表达:&&
比如: T&& a = returnValue();
returnValue返回临时对象,本该在函数返回后,生命周期结束,但是由于使用右值引用T&& a接收,因此其生命就被绑定到a上面,
相比于: T a = returnValue();
减少了一次对象的析构与一次对象的构建。
C++11之后引入了引用折叠规则,如:T &&,
当实参类型是一个左值引用,则会推导为X& &&,引用折叠规则最终为X&
当实参类型是一个右值引用,则会推导为X&& &&,引用折叠规则最终为X&&。
即,可以理解T&&为万能引用,无论左值引用右值引用,它都可以接收。
8 . 移动构造std::move
std::move 其实就是将左值强制转换为右值引用,继而我们可以继续通过右值引用使用这个值。而被转换的左值,其声明周期也没有因为转换而变化
static_cast<T&&>(lValue)
使用场景:
假如是个左值,如成员变量为指针或者引用,没有使用右值,通过左值构造对象或者传递参数,就会进行默认的拷贝构造,多一次析构与创建
但是如果使用std::move转换为右值,进入移动构造,则会减少了一次对象的析构与一次对象的构建。
如下: 使用move转为右值,强行转换进行移动构造
#pragma once #include <utility> class HugeMem { public: HugeMem(int size) :sz(size > 0 ? size:1) { c = new char[sz]; } //移动构造 HugeMem(HugeMem && hm) :sz(hm.sz), c(hm.c) { hm.c = nullptr; } virtual ~HugeMem() { delete [] c; c = nullptr; } private: char *c; int sz; }; class Moveable { public: Moveable() :c(new char[3]),h(1024) { } //强制使用移动构造,如果没有,会进入拷贝构造 Moveable(Moveable && hm) : c(hm.c),h(std::move(hm.h)) { hm.c = nullptr; } virtual ~Moveable() { delete[] c; c = nullptr; } private: char *c; HugeMem h; };
实际上: 程序员在编写移动构造函数的时候,应该总记得使用std::move将类似于指针,文件句柄等资源转为右值进行传参
这样的好处:
1. 如果成员支持移动构造,直接移动构造
2. 如果成员不支持移动构造,那么将接收的还是左值,实现拷贝构造,也不会引起什么大问题。
9. std::forward
背景:
void forwardTest(T t){ run(t);}
如上: 实际执行的是run,forwardTest只是对外包装一层,转发了调用而已。
似乎平常,都是这么写代码的,貌似是透传,但实际却没那么简单,
这中间在参数t传递给run的时候,就进行了一次临时对象的拷贝,尽管功能上实现了转发,但谈不上完美。
所以,通常需要做的是引用类型,引用不会有拷贝的开销,但如果碰上使用右值的地方,就没法使用了,
或者,run接收的是const t,那岂不是要为run重载几个版本?,
如此std::forward登场了
void forwardTest(T&& t){ run(std::forward<t>);}
结合折叠规则和自动推导
如果是左值, std::forward转化为static_cast<T&>(t)
如果是右值, std::forward转化为static_cast<T&&>(t)
总结:
move和forward在实现上差别并不大,都是强转,不过库既然这么设计,也许是为了让不同的名字有对应的用途,以对应将来的扩展
区别就是:move强行转换成了右值,而forward则是保留了原有的左右值,
使用场景:
但需要将一参数直接透传到另一个函数执行时,可以使用std::forward,减少一次对象的拷贝与析构
10. 显示类型转换 explict
c++11之前,explict用来修饰构造函数,显示指定构造函数类型,不能通过隐式转换
class A { publi: A(int i):m_data(i){} private: int m_data; }
没有使用explict,可以有如:A a = 10;隐式转换调用了构造函数。
加入了explic后,就会提示需要显示指定,编译通不过,只能A a(10);
c++11 以后,允许explict用来修饰类型转换操作符()上,意味着,只有通过直接构造或者强制类型转换才成功使用类型.
class B; class A { publi: explict operator B () const {return B();} } void Func(B b); void main() { A a; B b1(a); //直接构造初始化 B b2 = static_cast<B>(a); //强制类型转换 //其它都不行,拷贝构造 B b3 = a; Func(a); }
11. POD
12. 非首先联合体union
c++11之前,联合体成员数据类型有一些限制,比如:自定义的结构体类型若增加了构造函数,则是不允许作为联合体的成员的,
c++11之后,取消了对联合体成员数据类型的限制。标准规定,任何非引用类型都可以作为联合体的成员
需要自己写构造函数初始化一些带有构造函数的类成员。(默认的构造函数会被删掉)
13. inline namespace, 解决父子命名空间的繁锁
14. 使用using定义类型的别名,类似typdef
typedef unsigned int UINT
using UINT = unsigned int
15. 右 > 的改进
c++11之前,实例化模板类遇到两个>中间需要加空格,避免编译错误,因为会被当做右移>>
如:vector<vector<int> > vec
c++11后,没有了,不会报错,自动匹配解析了。
为了避免与右移动重复,真正右移动的时候,建议(不是强制)加括号避免被解析出错如:(3>>2)
16. typeid
c++11之前,就支持RTTI运行时类型识别,RTTI为每个类型生成type_info,typeid(类型)可以返回变量的类型type_info信息数据
而type_info.name()成员函数就可以返回类型的名字。
C++11之后,新增了hash_code()这个成员函数,返回类型的唯一hash,用于类型的比较=====C#有点像
17. auto与decltype
c++11新增的自动类型推导
auto从变量推导:
如:auto i=10;
不能使用auto四种情况:
1. func函数,auto不能作为形参
2. 结构体,非静态成员变量,不能为auto
3. 声明auto数组
4. 实例化模板的时候,不能使用auto,如std::vector<auto> v = {1};
decltype从表达式推导:
如:auto a = 10, b = 20;
decltype(a + b) c = 10; c的类型与a+b一样
decltype规则:decltype(e)
1. 如果e是不带括号的标记符表达式或者类成员访问表达式,那么decltype(e)就是e所命名的实体类型, e不能是被重载的函数
2. 假设e的类型是T,如果e是将亡值,则decltype(e) = T&&
3. 假设e的类型是T,如果e是左值,则decltype(e) = T&
4. 假设e的类型是T,则decltype(e) = T
最容易迷糊的是1和3,来我们看:
int i = 0;
decltype(i) a; ===规则1, i不带括号,就是i,int
decltype( (i) ) a ===规则3, i带括号,(i)不是一个表达式,确是一个左值可以取地址,因此是 int&
解释一下1中的标记符表达式:
基本上除去所有关键字,字面量等编译器标记的之外,所有自己定义的变量都是,成员变量也是
如: int arry[10];
int *prt = nullptr;
Struct S {double d;}s;
像array,prt,s.d都是,而类似a+b这种则不是,得归到规则4种。
decltype与auto不同的是:
auto不能带带走cv修饰符即:const/volatile
decltype是能带走的,即没法继承cv修饰符,被去掉了。
18. 追踪返回类型:自动推导返回值
解决泛型种如下问题:比如需要泛型返回值,自动推导,下面会编译不过,因为不认识t1和t2
template<typename T1, typename T2> decltype(t1 + t2) Sum(T1 t1, T2 t2){ return t1 + t2; } //c++11之后:返回值后置 auto Sum(T1 t1, T2 t2) -> decltype(t1 + t2) { return t1 + t2; } 如: old: int func(char *a, int b); new: auto func(char *a, int b)->int;
19. for循环
int array = {1,2,3}; for(auto i : array) {}
20. enum
c++11以前: enum的变量全局的,容易混,容易污染
如: enum Type{General, Light, Medium, Heavy}
enum Category {General, Pisotol, MachineGun, Cannon }
General有重复,需要自己加命名空间,加类封装等等
c++11 强类型枚举: enum class type name{。。。}
如: enum class char C {c1 = 1, c2= 2}
enum class int D {D1 = 1, D2 = 2, Dbig = 0xFFFF}
使用: C:c1 D:D1
加上了名称,强作用域,隔开,type可以为wchar_t的任意类型。
21. 智能指针
1. unique_ptr 看名字,独占的指针, 从实现上看,是一个删除了拷贝构造,保留了移动构造的指针封装
std::unique_ptr<int> p1(new int(11));
std::unique_ptr<int> p2 = p1; //编译不过,独占,不能被复制
std::unique_ptr<int> p3 = std::move(p1); //唯一,可以移动构造,完了之后,p1失效
2. share_prt 看名字,共享指针,有引用计数,只要有赋值,就是++,到0后自动删除
3. weak_ptr 不会增加引用技术,通过lock返回share_ptr,如果无效,返回空,交叉引用使用,父->子,share,子->父 weak, 避免相互,无法释放,内存泄漏。
share_ptr<int> sp(new int(11)); //计数1
weak_ptr<int> wp = sp// 计数1,不增加
share_ptr<int> sp1 = wp.lock() //转化为share,如果sp已经被删除,sp1为空
22. constexptr
由constexptr修饰的变量就是所谓的常量表达式值。
c++11中, constexptr是不能修饰自定义的变量的。
const int i = 10; //常量表达式
constexptr int j = i; //常量表达式值
二者大部分没啥区别,有一点:
如果i在全局声明,则编译器一定会为i产生数据。
而对于j,只要没有代码显示使用j的地址,编译器可以不为其产生数据,而仅仅作为编译数据
23. 变长模板,变长函数/参数
24. 多线程
std::thread, lock_guard, mutex, condition_variable
std::lock_guard
std::unique_lock
1. 正常情况,为了省去手动的lock/unlock,采用lock_guard包装,即可打到加锁保护的目的
std::lock_guard<std::mutex> lk(mQueMutex);
2. 线程在wait的时候,就得使用unique_lock,不能使用lock_guard
std::unique_lock<std::mutex> lk(mQueMutex);
mQueCondVar.wait_for(lk, std::chrono::milliseconds(10));
理由:
std:lock_guard中无法暂时释放锁和加锁,而unique_lock可以临时释放锁,枷锁
wait过程需要临时释放锁,如果一直锁着不释放,会永远无法捕捉变量得更新
std::recursive_mutex
std::mutex
1. 正常情况,mutex配合上述lock,直接使用
2. 同一个线程,重复加锁,使用mutex则会导致死锁,此时就需要使用std::recursive_mutex
recursive_mutex 递归锁
可以允许一个线程对同一互斥量多次加锁,解锁时,需要调用与lock()相同次数的unlock()才能释放使用权
如下也可行:std::lock_guard<std::recursive_mutex>
原子类型: atomic_bool, atomic_int,......
内存顺序: memory_order_relaxed...
c++11中,所有的原子操作都可以使用memory_order作为一个参数
int t = 1;
atomic<int> a;
a.store(1, memory_order_relaxed);
25. 线程局部存储
====c++11只做了语法统一,没有性能的规定
int thread_local errorCode;
一旦声明thread_local类型变量,其值从线程开始初始化,结束后不在有效。
如:两个线程T1,T2。
1. 每个定义一个全局errorCode,各自为战
2. 定义一个全局的errorCode,到底是哪个报的error,无法确定
26. nullptr
从0到NULL, 再从NULL到nullptr
27. 类的默认函数: 五大函数+析构
1. 构造
2. 拷贝构造
3. 移动构造
4. 拷贝赋值(operator =)
5. 移动赋值
6. 析构
28. ==default和==delete
29. lambda表达式
30. std::function和std::bind
31. 数据对齐:
操作符: alignof() --->查看对齐字节数
struct A{ int a; char c; }; //alignof(A) = 8 说明8字节对齐
对齐描述符: alignas() ---->指定使用几个字节对齐,既可以是类型,也可以是具体数值
struct alignas(4) A{ int a; char c; }; //使A按4字节对齐 //alignas(double)和alignas(8)一样, //stl:std::align等
32. unicode支持,常见的由UTF-8, UTF-16,UTF-32
Windows UTF-16
linux/mac UTF-8
UTF-8:变长编码unicode,英文通常1字节表示,与ASCII码兼容,
中文采用3字节,省空间,一个汉字就是 3+‘ ’= 10字节, 没有大小端问题。 没有u8string,需要函数与多字节转换
UTF-16:定长编码, 由字节序问题,LE和BE版本 有u16string,u32string,方便操作
c++11前: wchar_t表示,Windows下被实现位16位宽,理论长度可以为8位,16位,32位,
这样各个平台不一致,难以移植
c++11后: char16_t 16字节, 存储UTF-16编码的unicode数据
char32_t 32字节, UTF-32
char 8字节, UTF-8,
各个平台统一。
增加前缀来表示unicode字符
u8 UTF-8 u8"123"
u UTF-16 u“ab”
U UTF-32 U"唐"
之前的 L 表示宽字符wchar_t,四种前缀
L“123456”
普通的就不用加,默认的 “123456”
33. 原生字符串的支持 R
所见即所得,不转义,看见的就是输入的
cout << R("hello
world") << endl;
输出:
hello world
没有转义,是什么就输出什么。
unicode u8R, u16R, u32R也一样