C++11学习笔记
C++11
1.long long新类型
2.列表初始化
int t=0;
int t={0};
int t(0);
int t{0};
注意:如果我们使用列表初始化有丢失信息的风险,则编译器报错
long double ld=3.1415926536;
int a{ld},b={ld};//错误
int c(ld),d=ld;//正确,会丢失数据
3.空指针nullptr
int *p1=nullptr;
int *p2=0;
int *p3=NULL;//尽量避免
4.constexpr类型
const类型不一定是常量表达式,常量表达式指的是在编译的时候就知道值大小
如:
constint sz=str.size();
str的长度如果在编译的时候无法确定,则sz就不是常量表达式
而声明为constexpr的变量一定是个常量:
constexprint mf=20;//20是常量表达式
constexprint limit=mf+1;//mf+1是常量表达式
constexprint sz=size();//只有size()是一个constexpr函数时才能编译通过
指针与constexpr
如果用constexpr声明了一个指针,则constexpr仅对指针有效(注意与const的区别)
constint *p=nullptr;//p是一个指向整型常量的指针
constexprint *q=nullptr;//q是一个指向整形的常量指针
如果要声明一个指向常量的常量指针,使用:
constexprconstint * q=&i;
5.类型别名
传统方法是使用typedef
typedefunsignedint UINT;
C++11规定一种新方法:
using UINT=unsignedint;
注意:指针、常量、类型别名
typedefchar *pstring;
const pstring cstr=0;//指向char的常来那个指针
const pstring *ps;//ps是一个指针,对象是一个指向char的常量指针
6.auto类型说明符
auto定义的类型必须要有初始值,因为编译器要分析实际类型
auto i=0;//i为int
auto j=3.14;//j为double
auto k;//错误
auto可以同时声明多个变量,但是同时声明的所有变量类型要相同
auto i=0,*p=&i;//正确
auto sz=0;pi=3.14;//错误
复合类型、常量和auto
引用:
int i=0,&r=i;
auto a=r;//a为整型
const,auto会忽略顶层const,底层const会保留
constint ci=i,&cr=ci;
auto b=ci;//b为整型(ci顶层const特性被忽略)
auto c=cr;//c为整型(cr是ci的别名)
auto d=&i;//d是一个整型指针(i不为常量)
auto e=&ci;//e是一个指向整型常量的指针(常量对象取地址是底层const)
如果希望推断出的auto类型是顶层const,需要明确指出:
constauto f=ci;//ci的推演类型为int,而f为const int
将引用类型设为auto,原来的初始化规则仍然适用
auto &g=ci;//g是整型常量引用,绑定到ci
auto &h=42;//错误,不能为非常量引用绑定字面值(忽略顶层const)
constauto &j=42;//正确,常量引用绑定字面值
注意,声明多个变量时,*与&是声明符的一部分,而不是基本数据类型的一部分
auto k=ci,&l=i;//正确
auto &m=ci,*p=&ci;//正确
auto &n=i,*p2=&ci;//错误,i是int,&ci是const int *
7.decltype类型指示符
decltype推断变量(函数)类型
decltype(f()) sum=x;//sum的类型就是f的返回值类型
注意,decltype与auto对const处理的区别:auto会忽略顶层const,而decltype会保留顶层const
constint ci=0,&cj=ci;
decltype(ci) x=0;//x为const int
decltype(cj) y=x;//y为const int&
decltype(cj) z;//错误,z为const int&,必须绑定值
引用一直是以其所指对象的同义词出现,但是对于decltype并非如此(会保留引用类型)
如果表达式的内容是解引用操作,decltype会得到引用类型
int i=42,*p=&i,&r=i;
decltype(r+0) b;//加法的结果是int,所以b是一个未初始化的int
decltype(*p) c;//错误,c是int&
注意:decltype((variable))(注意是双层括号)的结果是引用,而decltype(variable)的结果只有当variable本身是一个引用是才是引用
decltype((i)) d;//错误,d是int&,需要绑定值
decltype(i) e;//正确,e是一个未初始化的int
8.类内初始值
C++11规定,可以为类内数据成员提供一个初始值用于初始化,如果没有提供,将进行默认初始化
注意:不能使用圆括号,可以使用花括号或等号
9.使用auto或decltype缩短类型长度
同时可以避免无符号数与有符号数可能带来的问题
10.范围for
for(declaration:expression)
statement
如果想要用范围for改变对象中的值,需要把循环变量定义为引用
string s("hello world");
for(auto &c:s){
c=toupper(c);
}
cout<<s<<endl;
11.定义vector对象的vector
在C++11标准之前,如果要定义一个vector的vector,必须在两个>之间添加空格:
vector<vector<int> > arr;
而新标准不用这样操作:
vector<vector<int>> arr;//C++11合法
12.vector的列表初始化
注意区分圆括号与花括号
vector<int> v1(10);//10个元素,都是0
vector<int> v1{10};//一个元素,10
vector<int> v1(10,1);//10个元素,都为1
vector<int> v1{10,1};//两个元素,10,1
使用花括号时,初始化过程会尽可能把花括号内的值当成元素初始值的列表来处理,只有在无法执行列表初始化时才会考虑其他初始化方式。
vector<string> v5{"hi"};//列表初始化
vector<string> v6("hi");//错误
vector<string> v7{10};//转化为圆括号
vector<string> v7(10);//10个空字串
13.容器的cbegin与cend函数
cbegin和cend返回const迭代器,当不需要对元素进行修改时,最好使用const迭代器
14.标准库函数begin与end
在容器中,有两个同名函数,功能类似,但是标准库begin与end不是成员函数,它的参数为数组
int ia[]={1,2,3,4,5,6,7,8};
int *beg=begin(ia);
int *last=end(ia);
这两个函数定义在iterator中
15.使用auto和decltype简化声明
int ia[3][4];//大小为3的数组,每个元素都是含有4个整数的数组
int (*p)[4]=ia;//p指向含有4个整数的数组
p=&ia[2];//p指向ia的尾元素
在上述声明中圆括号必须有
int *ip[4];//整型指针的数组
int (*p)[4];4个整数的数组的指针
可以用auto自动推断类型,避免出错
16.除法舍入规则
C++11规定整数除法一律向0取整(即切除小数部分)
17.初始值列表赋值
int k=0;
vector<int> vi;
k={5};//正确
k={3.14};//错误,窄化转换
vi={0,1,2,3,4};//正确
18.sizeof用于类成员
sizeof可用于类成员而不需要类的对象(想知道类成员大小并不需要真的获取类成员)
19.范围for 2
不能通过范围for添加或删除元素,因为范围for存储了end()的值,增加或删除会使迭代器失效
20.标准库initializer_list类
如果实参数量未知但是类型都相同,可以使用initializer_list类型形参
void error_msg(initializer_list<string> il){
for(auto p:il){
cout<<p<<endl;
}
}
error_msg({"hello","world"});
error_msg({"I","love","you"});
使用vector等也可以
21.列表初始化返回值
vector<string> func(){
return {"hello","world"};
}
22.尾置返回类型
int func(int a){
return0;
}
如果使用尾置返回类型则可以写作:
auto func(int a)->int{
return0;
}
对于返回值类型比较复杂的函数最有效,如:返回数组指针的函数
int (*func(int i))[10]{
//函数体
}
等价于
auto func(int i)->int(*)[10]{
//函数体
}
23.使用decltype简化返回类型定义
我们知道返回的类型指向哪个数组,就可以使用decltype关键字声明返回类型
int odd[]={1,3,5,7,9};
int even[]={0,2,4,6,8};
decltype(odd) *arrPtr(int i)}{
return (i%2)?&odd:&even;
}
注意:decltype并不负责吧数组转换为对应的指针,所以要在函数声明时加一个*
24.constexpr函数
constexpr函数是指能用于常量表达式的函数,函数的返回值以及所有的参数都必须是字面值类型,函数体中必须有且只有一条return语句
但是,C++11允许constexpr返回值并非一个常量
constexprint new_sz(){
return42;
}
constexpr size_t scale(size_t cnt){//如果cnt是常量,则返回常量
return new_sz()*cnt;
}
int arr[scale(2)];//正确
int i=2;
int a2[scale(i)];//错误,scale(i)不是常量表达式
注意:inline、constexpr可以在程序中多次定义,但是多次定义必须完全一致,所以内联函数和constexpr函数通常放在头文件中
25.使用=default生成默认构造函数
如果自定义了类的带参构造函数,则编译器不会生成默认构造函数,这时可以用=default生成默认构造函数
class A{
A(int t);
A()=default;//会生成默认构造函数
};
=default可以在类内部声明定义,也可以在类外部定义,如果在类内部,则默认构造函数是内联的,否则是非内联的(其他成员函数也是如此)
26.类内成员初始化
当我们提供一个类内初始化值时,必须使用等号或花括号表示(不能使用圆括号)
27.委托构造函数
委托构造函数是指把部分(或全部)职责委托给其他构造函数
class A{
A(int a);
A():A(0){};
};
28.constexpr构造函数
含有constexpr构造函数的类叫constexpr类,constexpr构造函数既要满足constexpr函数的要求又要满足构造函数的要求,,所以constexpr构造函数的函数体一般是空的
constexpr构造函数用于生成constexpr对象以及constexpr函数的参数或返回类型
29.用string对象作为文件名
在旧的C++标准中,文件名只能是C风格字符串
cosnt char * const fileName="a.txt";
ifstream fi(fileName);
string fileName2="b.txt";
ifstream fi(fileName2.c_str());
在新的C++标准中,文件名既可以是string对象,也可以是C风格字符串
string fileName3="c.txt";
ifstream fi(fileName3);//C++11中正确
30.array与forward_list
array与forward是C++新标准添加的内容,array替代内置数组,大小固定,但更安全,更易使用,forward替代手写链表,不支持size()
C++程序应该使用标准库容器而不是原始数据结构,如:内置数组
通常,使用vector是最好的选择,除非有更好的理由选择其他容器
31.容器的cbegin与cend
以前的C++标准中,const对象的begin()返回const类型的迭代器,否则返回普通iterator,如果要使用const迭代器,必须显示指定。由于新标准中添加了auto类型,所以同时添加了cbegin和cend,用于返回一个非const(const同样适用)对象的const迭代器
当不需要写访问时,尽量使用cbegin和cend
32.容器的列表初始化
容器用列表初始化时,除了array外,初始化列表隐含制定了容器大小
33.标准库swap
成员函数swap会交换内部数据结构,不会交换元素(array除外),除string外,指向容器的迭代器、引用和指针在swap操作后都不会失效
array对象swap时间与array对象的元素数量成正比
标准库swap在泛型编程中非常重要
统一使用非成员swap(标准库swap)是个好习惯
34.insert的返回值
insert操作后,会返回插入元素的迭代器
35.容器的emplace成员
emplace_back使用已有空间重新构造
push_back创建一个临时对象并且把它压入容器
36.shrink_to_fix
shrink_to_fix将capacity()减小为与size()相同的大小
37.string数值转换
标准库函数:
to_string(val);
stoi(s,p,b);
stol(s,p,b);
stoul(s,p,b);
stoll(s,p,b);
stoull(s,p,b);
stof(s,p,b);
stod(s,p);
stold(s,p);
val为要转换的数字,s为要转换的字符串,p为s中第一个非数值字符的下标,b是转换基数
38.lambda表达式
[捕获列表](参数列表)->返回值类型{函数体}
lambda表达式可以使用捕获列表中列出的局部变量
直接写变量名是值捕获,加上&后是引用捕获,类似函数参数传递,使用引用捕获时,必须确保lambda执行时变量是存在的
尽量保持捕获简单化,尽量避免指针捕获与引用捕获
捕获列表中如果是=或&表示根据代码推断要捕获哪些内容,其中=是值捕获,&是引用捕获
捕获有以下形式,=和&必须是列表中的第一个,表示隐式捕获的捕获类型
[]
[names]
[&names]
[&]
[=]
[&,name]
[=,&name]
可用mutable形成可变lambda,这样的lambda表达式即使是值捕获也可以改变变量的值
一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型
39.lambda表达式必须使用尾置返回类型
如果lambda表达式中只有一个单体return或无返回语句,可以省略返回值类型,否则必须使用尾置返回类型
40.标准库bind函数
bind定义在<functional>中
bind可以看作一个通用的函数适配器
一般形式为:
auto newCallable=bind(callable,arg_list);
newCallable是一个新的可调用对象,arg_list是一个以逗号分隔的参数列表
当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list参数
arg_list中的参数可能包含_n的名字,其中n为整数,这些参数是占位符,表示newCallable的参数,其中_1表示第一个参数,_2表示第二个参数,以此类推
占位符定义在placeholder命名空间中,而placeholders定义在std中,所以需要使用usingnamespace std::placeholders;
void func(string str1,int num1);
auto newFunc=bind(func,_1,5);
newFunc("hello");//等价于调用func("hello",5);
基于bind的特点,可以用它重排参数顺序
bind拷贝其参数,如果参数需要是引用类型,则需要ref或cref(也定义在<functional>中)
ostream &print(ostream &os,const string&s,char c){
return os<<s<<c;
}
for_each(words.begin(),words.end(),bind(print,ref(os),_1<' '));
41.关联容器的列表初始化
map<int,int> mp={
{1,3},
{2,4}
};
42.pair返回值
新标准中,可以使用列表对返回值进行初始化
pair<string,int> process(vector<string> &v){
if(!v.empty()){
return {v.back,v.back().size()};
}else{
return pair<string,int>();
}
}
43.pair的列表初始化
对map进行insert时,要记住类型是pair,构成pair最简单的办法就是参数列表中使用花括号初始化
map<string,int> mp;
mp.insert({"hello",5});
44.无序容器
新标准定义了4个无序容器:unordered_map,unordered_set,unordered_multimap,unordered_multiset
如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器
无序容器对关键字的要求:
可以使用任意内置类型,但不能使用自定义类型,不过可以通过通过函数来替代==运算符和哈希值计算函数。
hash<key_type>是标准库提供的hash值生成函数
unordered_set<Foo,decltype(FooHash)*>fooset(10,FooHash);//使用FooHash生成哈希值,Foo必须有==运算符
45.智能指针
新标准提供了两种智能指针:
shared_ptr:允许多个指针指向同一个对象
unique_ptr:“独占”所指对象
标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象
这三个类型都定义在<memory>头文件中
shared_ptr会自动释放管理的内存,但是有两种情况例外:
(1).没有销毁不用的shared_ptr对象,如将其作为返回值却没有使用
(2).将shared_ptr放在一个容器中,却没有释放容器中的资源
46.动态分配内存列表初始化
int *arr=newint[5]{1,2,3,4,5};
47.auto与动态分配
如果我们提供了一个用括号包围的初始化器,就可以使用auto,但是只有括号中仅有单一初始化对象时才可以使用auto
auto p1=newauto{5};
auto p2=newauto{5,6};//错误,无法推断类型
48.unique_ptr类
在同一时刻,只能有一个unique_ptr指向一个对象,当unique_ptr被销毁时,他所指的对象也被销毁了
unique_ptr不支持赋值
unique_ptr<string> p1(new string("hello world"));
unique_ptr<string> p2(p1);//错误,unique_ptr不支持拷贝
unique_ptr<string> p3;
p3=p1;//错误,unique_ptr不支持赋值
不能拷贝的规则有一个例外,可以拷贝一个将要被销毁的unique_ptr(函数返回一个局部的unique_ptr)
unique_ptr<int> close(int p){
return unique_ptr<int>(newint(p));
}
49.weak_ptr类
weak_ptr指向一个shared_ptr管理的对象,但是不会为shared_ptr增加引用计数,所以weak_ptr可能指向一个一个已经被释放的内存区
由于weak_ptr指向的内存区可能已经被释放,所以要通过lock成员函数判断对象是否还存在
if(shared_ptr<int> np=wp.lock()){//如果np不为空条件成立
//在if中,np与p共享对象
}
50.不能用范围for处理动态数组中的元素
new分配的所谓的数组其实不是数组,而是一个指向一块内存区的指针,由于不是数组类型,所以不能对动态数组调用begin和end(标准库函数),这些函数使用维度来返回指向首元素和尾后元素的指针,出于相同原因,也不能用范围for处理(所谓的)动态数组中的元素
动态数组不是数组类型
51.动态数组的初始化
可以采用列表初始化
int *p=newint[10]{1,2,3,4,5,6,7,8,9,0};
52.auto不能用于动态数组
可以用空括号对数组中的元素进行值初始化,但是不能栽括号中给出初始化器,这意味着不能用auto分配数组(auto无法推断类型)
53.allocator::construct调用任意构造函数
allocator(定义在<memory>中)可以使内存分配和对象构造分开
int n=10;
allocator<string> alloc;//可以分配string对象的allocator对象
autoconst p=alloc.allocate(n);//分配n个未初始化的string
auto q=p;
alloc.construct(q++);// *q为空串
alloc.construct(q++,10,c);// *q为"cccccccccc"
alloc.construct(q++,"hello");//*q为"hello"
在分配内存后,必须使用construct构造对象
对象的销毁使用destroy(只能对已经构造了的对象使用destroy)
54.将=default用于拷贝控制成员
通过将拷贝控制成员定义为=default来显式地要求编译器生成合成的版本
55.使用=delete阻止拷贝
在新标准下,我们可以通过将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝。
删除的函数是这样一种函数:虽然我们声明了他们,但是不能以任何方式使用它们。
在函数参数列表后面加上=delete来指出我们希望它定义为删除的
struct NoCopy{
NoCopy()=default;//使用默认构造函数
NoCopy(const NoCopy&)=delete;//阻止拷贝
NoCopy &operator(const NoCopy&)=delete;//阻止赋值
~NoCopy();//默认析构函数
};
56.移动构造函数和std::move
move定义在<utility>中
直接调用std::move而不是move(因为move同时是模板函数,与标准库冲突)
用移动类对象代替拷贝类对象
57.右值引用
右值引用是指必须绑定到右值的引用,通过&&获取右值引用
右值引用有一个重要性质:只能绑定到一个将要销毁的对象
int i=42;
int &r=i;//正确
int &&s=i;//错误,不能将右值引用绑定到左值上
int &t=i+5;//错误,i+5为右值
int &&u=i+5;//正确
constint &v=i+5;//正确,const引用可以绑定到右值上
变量是左值,所以不能将一个右值引用绑定在一个变量上,即使变量本身是右值引用也不行
58.标准库move函数
虽然不能将一个右值引用绑定在一个变量上,但是可以通过标准库move函数将左值转换为右值引用类型
int rr1=42;
int &&rr3=std::move(rr1);//正确
我们可以销毁一个移后源对象,也可以对它进行重新赋值,但不能使用一个移后源对象的值
59.移动构造和移动赋值
为了让我们自己的类支持移动和拷贝,我们需要为其定义移动构造函数和移动赋值运算符,移动构造后,移后源不再指向被移动资源,所以要保证对源的析构是安全的
class StrVec{
public:
StrVec(StrVec&&) noexcept;//移动构造函数,不应该抛出异常
//其他函数
};
StrVec::StrVec(StrVec&&s) noexcept:/*成员初始化器*/
{
/*构造函数体*/
}
60.移动构造函数通常应该是noexcept
两个相互关联的事实:
移动操作通常不抛出异常,但是抛出异常是允许的
标准库容器能对异常发生时其自身的行为提供保障
只有在确保移动操作是安全的,容器才会调用移动构造函数(及移动赋值运算符),否则就会调用拷贝构造函数
61.移动迭代器
一般来说,一个迭代器的解引用返回一个指向元素的左值。与其他迭代器不同,移动迭代器的解引用运算符生成一个右值引用。
通过make_move_iterator函数将一个普通迭代器转换为一个移动迭代器。此函数接受一个迭代器参数,返回一个移动迭代器
标准库不保证哪些算法适用于移动迭代器,哪些不适用,由于移动一个对象可能销毁源对象,因此你只有在确信算法在为一个元素赋值或将其传递给一个用户定义的函数后不再访问它时,才能将移动迭代器传递给算法
建议:不要随意使用移动操作
在类代码中小心地使用move,可以大幅提升性能,而如果随意在普通用户代码中使用移动操作,可能导致莫名其妙、难以查找的错误。
在移动构造函数和移动移动赋值运算符这些类实现代码之外的地方,只有当你确信需要进行移动操作且移动操作是安全的,才可以使用std::move
62.引用限定成员函数
string s1,s2;
s1+s2="hello";
这种右值使用方式令人惊讶,但是编译通过
我们希望在自己的类中阻止这种用法,我们希望强制左侧运算对象(即,this指向的对象)是一个左值
我们指出this的左值/右值属性与const相同:
class Foo{
public:
Foo &operator=(const Foo&)&;//只能向可修改的左值赋值
//其他
};
Foo &Foo::operator=(const Foo&rhs)&{
return *this;
}
引用限定符可以是&或&&,分别指出this可以指向一个左值或右值,类似const,引用限定符只能用于(非static)成员函数,并且必须同时出现在函数的声明和定义中
对于&限定的函数,只将它用于左值,对于&&限定的函数,只用于右值
一个函数可以同时用const和引用限定,在这种情况下,const限定符必须在引用限定符之前
如果一个成员函数有引用限定符,则具有相同参数列表的所有版本都必须有引用限定符
63.function类模板
function类型的对象存储可被调用对象,对于重载函数,应该存储函数指针(可以使用lambda表达式)
64.explicit类型转换运算符
int i=42;
cin<<i;//cin将被转换为bool,然后被提升为int,int<<int合法!
为了防止这种异常情况发生,C++11引入了显示类型转换运算符
class SmallInt{
public:
explicitoperatorint() const {return val;}//编译器不会自动转换
//其他
};
SmallInt si=3;//正确
si+3;//错误,不会隐式转换
static_cast<int>(si)+3;//正确
该规定存在一个例外:表达式被用作条件时会将显式的类型转换自动应用于它,如:if,while,do,for,!,||,&&,? :等
向bool的类型转换通常在条件部分,所以operator bool一般定义成explicit的
65.虚函数的override指示符
派生类可以在它覆盖的函数前加virtual关键字,但不是非得这么做,C++11允许我们在函数后添加一个override关键字标明(形参后面,const后面,引用限定符后面)
override会让编译器检测是否覆盖了虚函数,没有将会报错,便于调试
66.使用final关键字阻止继承
当我们不希望某个类被继承时,可以用final关键字
class NoDerived final{/* */};//不能作为基类
class Base{/* */};
class Last final:Base{/* */};//正确
class Bad:NoDerived{/* */};//错误
class Bad2:Last{/* */};//错误
67.虚函数override和final指示符
虚函数可以通过对override阻止覆盖
struct D2:B{
void f1(int)const final;//不允许被覆盖
};
struct D3:D2{
void f2();
void f1(int) const;//错误,D2将f1声明为final
};
68.派生类中删除的拷贝控制与基类的关系
基类删除的函数在派生类也是删除的(因为派生类无法调用基类相关函数)
69.继承的构造函数
派生类可以重用基类构造函数
class B:public A{
public:
using A:A;
//其他
};
或者
class B:public A{
public:
B(int a):A(){
//函数体
}
};
70.令模板自己类型的参数成为友元
template<typename Type> class Bar{
friend Type;//将访问权限授予用来实例化Bar的类型
//...
};
71.模板类型别名
新标准允许我们为类模板定义一个类型别名
template<typename T> using twin=pair<T,T>
twin<string> authors;//authors的类型是pair<string,string>
72.模板默认实参
我们可以提供默认模板实参
template<typename T,typename F=less<T>>
int compare(const T &v1,const T &v2,F f==F()){
if(f(v1,v2))return -1;
if(f(v2,v1))return1;
return0;
}
F表示可调用对象,定义了一个F类型f,并绑定到可调用对象上
73.模板显式实例化
模板在使用时才会被实例化,所以可能在编译单元中实例化相同的模板,这种开销在大系统中将变得很严重
对每个实例化声明,在程序的某个位置必须有显式的实例化定义
externtemplateclass Blob<string>;//实例化声明
templateint compare (constint&,constint&);//实例化定义
74.尾置返回类型
显式指定模板实参会给用户增加额外的负担,而不会带来好处。
template<typename It>
auto func(It beg,It end)->decltype(*beg){
//处理
return *beg;
}
func的返回类型与解引用beg参数的结果相同,解引用返回一个左值。如果用int序列调用,就返回int&
75.引用折叠规则
template<typename T> void f3(T&&);
f3(42);//实参是一个int类型的右值,模板参数T是int
int i=42;
f3(i);//为何能编译通过??
两个例外规则:
将左值传递给函数的右值引用参数,且此右值指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。所以当我们调用f3(i)时,编译器推断T的类型为int&,而不是int(这意味着参数是int& &&),通常我们不能直接定义引用的引用,但是可以通过类型别名完成
引用的引用会发生折叠
对于一个给定的类型X:
X& &
X&& &
X& &&
将会被折叠为X&
X&& &&
将会被折叠为X&&
所以参数类型int& &&被折叠为int&,因此编译通过
76.使用static_cast将一个左值转为右值引用
虽然可以通过static_cast将左值强制转为右值引用,但是尽量使用std::move
77.标准库forward函数
forward函数可以保持原始参数类型
当用于一个指向模板参数类型的右值引用函数参数(T&&)时,forward会保持实参类型的所有细节
尽量使用std::forward,标准库冲突问题
78.可变参数模板
一个可变参数模板就是一个可以接受可变数目参数的函数模板或模板类,可变数目的参数被称为参数包
两种参数包:模板参数包、函数参数包
用一个省略号来指出一个参数包
template<typename T,typename... Args>
void foo(const T&,const Args& ... args);
79.sizeof...运算符
sizeof...运算符用于获取参数包中参数数目
template<typename ... Args>
void g(Args ... args){
cout<<sizeof...(Args)<<endl;//类型参数数目
cout<<sizeof...(args)<<endl;//函数参数数目
}
80.可变参数模板与转发
结合forward与参数包
work(std::forward<Args>(args)...);
通常模板函数的参数应该是右值引用(&&),根据引用折叠,我们可以传递任何形式的参数
81.标准库tuple模板
tuple类似pair,但是它能关联多个类型的对象,tuple的构造函数是explicit的,所以
tuple<int,int,char,string> tu={0,1,'h',"hello"};//错误,除非显式转换
tuple<int,int,char,string> tu{0,1,'h',"hello"};//正确,会被转换为圆括号
make_tuple函数可以生成一个tuple
tuple一般用于快速定义结构体数据类型和返回多个不同类型的值
82.新的bitset运算
all:所有位置1返回true
count:1的个数
size:bitset的大小
IO运算符:
bitset<16> bits;
cin>>bits;//输入16个0或1
cout<<"bits:"<<bits<<endl;//打印刚输入的内容
83.正则表达式库
RE库定义在<regex>中
具体使用参见:http://www.cplusplus.com/reference/regex/regex/
84.随机数库<random>
新C++标准定义了随机数引擎类default_random_engine
为了获取指定范围的随机数,可以用分布类型对象uniform_int_distribution(无符号整数),uniform_real_distribution(实数),bernoulli_distribution(布尔)
当我们说随机数发生器时,是指分布对象和引擎对象的组合
85.浮点数格式化
hexfloat强制浮点数使用16进制
defaultfloat恢复默认状态
86.noexcept异常说明
noexcept说明指定某个函数不会抛出异常
void recoup(int) noexcept;//不会抛出异常
void alloc(int);//可能抛出异常
noexcept的位置在const以及引用限定符后,在final、override、=0之前
void recoup(int) noexcept;
void recoup(int) throw();
等价
87.noexcept运算符
noexcept(recoup(i));//如果recoup不抛出异常为true,否则为false
void f()noexcept(noexcept(g()));//f与g的异常说明一致
函数指针及该指针所指的函数必须有一致的异常说明
88.内联命名空间
//Place.h
inlinenamespace Place{
//...
}
namespace Place2{
#include"Place.h"
}
Place2::直接可以访问Place中的数据
89.继承构造函数与多重继承
C++11允许从多个类继承构造函数,但是如果从多个基类继承了喜爱那个同的构造函数(形参列表相同),就会发生错误,解决办法是定义相同版本的构造函数
struct B1{
B1()=default;
B1(string);
};
struct B2{
B2()=default;
B2(string);
};
struct D:public B1,public B2{
using B1::B1;
using B2::b2;
D()=default;//必须定义
D(string);
};
析构从下往上进行
90.有作用域的枚举类型
C++包含两种枚举,限定作用域的和不限定作用域的
带有作用域的枚举:
enumclass color{red,yellow,green};
访问时:
color t=color::green;
不限定作用域的枚举可以自动转换为int,而限定作用于的不会自动转换
91.枚举类型的前置声明
C++11中,我们可以声明一个enum,enum的前置声明(无论是隐式还是显式)必须指定成员大小
enum intValues:unsigned long;//必须指定
enum class color;//默认int
限定作用域的可以使用默认的int大小,而非限定作用域的必须显式指定
92.标准库mem_fun类模板
mem_fn定义在<functional>中
mem_fn接受一个成员指针,生成一个可调用对象,这个对象可以通过对象调用,也可以通过指针调用
auto f=mem_fn(&string::empty);
f(*svec.begin());//传入对象
f(&svec[0]);//传入指针
93.类类型的union成员
C++11中,union可以包含类成员,但是,当我们将union的值改为类类型成员对应的值,必须调用构造函数,相反,如果我们将一个类类型成员改为其他值时,必须运行该类型的析构函数
如果一个union含有类类型成员,并且定义了默认构造函数和拷贝控制成员,编译器会为union合成对应版本并将其声明为删除的
如果一个类有union数据成员,union中又有删除的拷贝控制成员,则该类与之对应的拷贝控制操作也将是删除的
94.C++11对多线程的支持
C++11已经从语言层面支持多线程了,不再需要调用Win API或者linux的相关函数
具体参见: http://www.cplusplus.com/reference/thread/thread/?kw=thread
http://www.cplusplus.com/reference/mutex/mutex/?kw=mutex
等资料
~完~