2.1.1 long long类型
除字符和布尔类型外,其他整形用于表示不同尺寸的整形,一个long long 至少和一个long一样大,其中,数据类型long long 是在C++11中新定义的
2.2.1 列表初始化
作为C++11新标准的一部分,用花括号来初始化变量 得到了全面应用,这种初始化的形式被称为列表初始化
std::vector<string> sv = { "a","aa","aaa" }; //std::vector<string> sv { "a","aa","aaa" }; std::cout << sv[1];
2.3.2 nullptr常量
得到空指针的最直接的办法就是用字面值nullptr来初始化指针,这也是C++11新标准刚刚引入的一种方法,nullptr是一种特殊类型的字面值,它可以被转换成任意其他的指针类型
int *p = nullptr; //等价于 int *p = 0;
2.4.4 constexpr变量
C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量只是否是一个常量表达式,声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化,新标准允许定义一种特殊的constexpr函数
int arr[] = {1,2,3,4,5,6 }; constexpr int sz = sizeof(arr)/sizeof(int); std::cout << sz;
2.5.1 类型别名声明
类型别名是一个名字,它是某种类型的同义词,传统的方法是使用关键字typedef,含有typedef的声明语句定义的不再是变量而是类型别名,新标准规定了一种新的方法,使用别名声明来定义类型的别名:
using NodePtr = struct node* ; //typedef struct node* NodePtr;
2.5.2 auto类型指示符
编程时常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚地知道表达式的类型,然而要做到这一点并非那么容易,有时甚至根本做不到,C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型
auto p = []{cout << "lambda" << endl; }; // 执行 p();
2.5.3 decltype类型指示符
有时会遇到这种情况:希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量,C++11标准引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型
auto p = []()->int { return 123; }; decltype( p() ) x = 456.7; // declare type std::cout << x << typeid(x).name() ;
2.6.1 类内初始化
C++11新标准规定,可以为数据成员提供一个类内初始值,创建对象时,类内初始值将用于初始化数据成员
struct node{ int x = 256; };
3.2.2 使用auto或decltype缩写类型
在C++11新标准中,允许编译器通过auto或者decltype来推断变量的类型
std::string line; getline(std::cin, line ); //getline是string类的非成员函数重载 auto len = line.size(); //auto std::cout << len;
3.2.3 范围for语句
范围for语句,遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式是:
for(declaration: expression ) statement,其中expression部分是一个对象,用于表示一个序列,declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素,每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值
string str("Hello world"); for(auto c: str ) std::cout << c;
3.3 定义vector对象的vector(向量的向量)
过去必须在外层vector的右尖括号和其元素类型之间添加一个空格,如 vector<vector<int> >,C++11新标准则不需要
3.3.1 vector对象的列表初始化(见列表初始化)
3.4.1 容器的cbegin和cend函数
为了便于专门得到const_iterator类型的返回值,C++11新标准引入了两个新函数,分别是cbegin和cend
vector<int> cv {1,2,3,4,5,6 }; auto it = cv.cbegin(); //it类型是vector<int>::const_iterator
3.5.3 标准库begin和end函数
C++11新标准引入了两个名为begin和end的函数,这两个函数不是成员函数与容器中的两个同名成员功能类似
vector<int> cv {1,2,3,4,5,6 }; auto it = begin(cv);
3.6 使用auto和decltype简化声明
C++11新标准,通过使用auto或者decltype能尽可能地避免在数组前面加上一个指针类型了
#include <iostream> using namespace std; int main () { int ia[3][2] { 1,2, 3,4, 5,6 }; int (*ip)[2] = ia; //ip行指针 ip = &ia[2]; //ia的尾元素 /* for(auto p = ia; p!=ia+3; ++p ){ for(auto q = *p; q!=*p+2; ++q){ std::cout << *q << ' '; } std::cout << endl; } */ for(auto p = begin(ia); p!=end(ia); ++p ){ for(auto q = begin(*p); q!=end(*p); ++q){ std::cout << *q << ' '; } std::cout << endl; } return 0; }
4.2 除法的舍入规则
C++11新标准规定商一律向0取整(即直接切除小数部分),如果m和n是整数且n非0,除了-m导致溢出的特殊情况,其他时候(-m)/n 和 m/(-n)都等于-(m/n),m%(-n)等于 m%n, (-m)%n 等于 -(m%n)
4.4 用大括号包围的值列表赋值
C++新标准允许使用花括号括起来的初始值列表作为赋值语句的右侧运算对象,如果左侧运算对象是内置类型,那么初始值列表最多只能包含一个值,而且该值即使转换的话其占用空间也不应该大于目标类型空间,对于类类型来说,赋值运算的细节由类本身决定,对于vector来说,vector模板重载了赋值运算符并且可以接收初始值列表,当赋值发生时,用右侧对象的元素替换左侧运算对象的元素
int k; vector<int> vi{1,2,3,4,5,6 }; //k = {3.14 }; //错误:窄化转换 vi = {7,8}; // 替换
4.9 将sizeof用于类成员
C++11新标准允许我们使用作用域运算符来获取类成员的大小
5.4.3 范围for语句(见3.2.3 范围for语句)
6.2.6 标准库initializer_list类
为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型;如果实参类型不同,可以编写一种特殊的函数,也就是所谓的可变参数模板
initializer_list是一种标准库类型,用于表示某种特定类型的值的数组,initializer_list类型定义在同名的头文件中,是一种模板类型,initializer_list对象中的元素永远是常量值,无法改变initializer_list对象中的值,含有initializer_list形参的函数也可以同时拥有其他形参
#include <initializer_list> #include <iostream> using namespace std; void f (initializer_list<string> il ) { for(auto it = il.begin(); it!=il.end(); ++it) std::cout << *it; } int main () { initializer_list<string> il{ "Hello", "world" }; f(il ); return 0; }
6.3.2 列表初始化返回值
C++11新标准规定,函数可以返回花括号包围的值的列表,如果函数返回的是内置类型,则花括号包围的列表最多包含一个值,而且该值所占空间不应该大于目标类型的空间,如果函数返回的是类类型,由类本身定义初始值如何使用
#include <vector> #include <iostream> using namespace std; vector<string> func ( ) { string str = "!"; return { "Hello","world",str }; //返回列表初始化的对象 // 可以返回一个空的vector对象:{ } } int main () { vector<string> v = func(); for(auto i:v) std:: cout << i; return 0; }
6.3.3 定义尾置返回类型
C++11新标准使用尾置返回类型,即把函数的返回类型放在了形参列表之后,任何函数的定义都能使用尾置返回,对于返回类型比较复杂的函数最有效,比如返回类型是数组的指针(行指针)或数组的引用
#include <iostream> using namespace std; int arr[2][3] = {1,2,3, 4,5,6 }; /* int(*func( ) )[3] { return &arr[1]; } */ auto func( )->int(*)[3]{ //尾置返回类型 return &arr[1]; } int main () { int(*p)[3] = func(); for(auto q=*p; q<*p+3; ++q ) std::cout << *q; return 0; }
6.3.3 使用decltype简化返回类型定义
使用decltype关键字声明返回类型
#include <iostream> using namespace std; int odd[] = {1,3,5,7,9 }; int even[] = {0,2,4,6,8}; /* decltype并不负责把数组类型转换成对应的指针, 所以decltype的结果是一个数组, 要想表示函数返回指针还必须在函数声明时加一个*符号 */ decltype(odd)* arrPtr(int i){ return (i%2) ? &odd : &even; } int main () { int(*p)[5] = arrPtr(16); for(auto q=*p; q<*p+5; ++q ) std::cout << *q; return 0; }
if(typeid(even)==typeid(decltype(odd)) ) // if(typeid(p)==typeid(decltype(odd)*) ) std:: cout << "yes";
6.5.2 constexpr函数
constexpr函数是指能用于常量表达式的函数,函数的返回类型及所有形参的类型都得是字面值类型
#include <iostream> using namespace std; constexpr int new_size() { return 42; } constexpr size_t scale(size_t cnt ) { //函数的返回类型及形参必须是字面值类型 return new_size() * cnt; } int main () { constexpr int x = new_size(); //执行该初始化任务时,编译器把对constexpr函数的调用替换成其结果值 //为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数 //当scale的实参是常量表达式时,它的返回值也是常量表达式,反之则不然 std:: cout << scale( 2 ) << "," << scale( x ); int i = 2; //int arr[scale(i )];//错误!constexpr函数不一定返回常量表达式 return 0; }
7.1.4 使用=default生成默认构造函数
在C++11新标准中,如果需要默认的行为,可以通过在参数列表后面写上=default来要求编译器生成构造函数,如果=default在类的内部则默认构造函数是内联的
7.3.1 类对象成员的类内初始化
在C++11新标准中,最好的方式就是把这个默认值声明成一个类内初始值,类内初始值必须使用=的初始化形式或者花括号括起来的直接初始化形式
7.5.2 委托构造函数
C++11新标准扩展了构造函数初始值的功能,可以定义委托构造函数,一个委托构造函数使用它所属类的其他构造执行它自己的初始化过程,或者说它把它自己的一些或全部职责委托给了其他构造函数
7.5.6 constexpr构造函数
除了算术、引用和指针外,某些类也是字面值类型
字面值类型的类可能含有constexpr函数成员,这样的成员必须符合constexpr函数的所有要求,它们是隐式const的
数据成员都是字面值类型的聚合类是字面值常量类,如果一个类不是聚合类,但它符合下述要求,则它也是一个字面值常量类:数据成员都必须是字面值类型;类必须至少含有一个constexpr构造函数;如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式,或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数;类必须使用析构函数的默认定义,该成员负责销毁类的对象
尽管构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr函数,constexpr构造函数可以声明成=default的形式或者删除函数的形式,否则,constexpr构造函数就必须既符合构造函数的要求:不能包含返回语句,又符合constexpr函数的要求:能拥有的唯一可执行语句就是返回语句,综合这两点,constexpr构造函数一般来说应该是空的
constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数,或者是一条常量表达式,constexpr构造函数用于生成constexpr对象以及constexpr函数的参数或返回类型
8.2.1 用string对象处理文件名
想要读写一个文件,可以定义一个文件流对象,并将对象与文件关联起来:ifstream in(ifile);,在新C++标准中,文件名既可以是库类型string对象,也可以是C风格字符数组,旧版本的标准库只允许C风格字符数组
9.1 array和forward_list容器
forward_list和array是新C++标准增加的类型:forward_list单向链表,只支持单向顺序访问,在链表的任何位置进行插入或删除操作速度都很快,没有size操作;array固定大小数组(不能改变容器大小的操作),支持快速随机访问,不能添加或删除元素
9.2.3 容器的cbegin和cend函数
以C开头的版本是C++新标准引入的,用以支持auto与begin和end函数结合使用,当auto 与 begin或end结合使用时,获得的迭代器类型依赖于容器类型,与想要如何使用迭代器毫不相干,但以c开头的版本还是可以获得const_iterator的,而不管容器的类型是什么
list<string> a = {"Hello","world"}; auto it = a.begin(); //仅当a是const时,it是const_iterator auto cit = a.cbegin(); //cit 是 const_iterator
9.2.4 容器的列表初始化(见2.2.1 列表初始化)
9.2.5 容器的非成员函数swap
在新标准库中,容器既提供成员函数版本的swap,也提供非成员函数版本的swap,而早期标准库版本只提供成员函数的swap,统一使用非成员版本的swap是一个好习惯
swap操作交换两个相同类型容器的内容,调用swap之后,两个容器中的元素将会交换:调用swap之后,除了array外,交换两个容器内容的操作保证会很快--元素本身并未交换,swap只是交换了两个容器的内部数据结构(元素不会被移动的事实意味着,除了string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效)
#include <initializer_list> #include <iostream> #include <typeinfo> #include <list> #include <algorithm> #include <array> #include <vector> #include <string> //using namespace std; int main () { /* std::list<std::string> v1 {"H","e","l","l","o"}; std::list<std::string> v2{"w","o","r","l","d"}; */ std::string v1 = "Hello"; std::string v2 = "world"; auto it = v1.cbegin(); //!!! swap(v1,v2 ); //对于一个string调用swap会导致迭代器、引用和指针失效 for(auto i:v1) std::cout << i; std::cout << std::endl; for(auto i:v2) std::cout << i; std::cout << std:: endl; std::cout << *it; return 0; }
9.3.1 容器insert成员的返回类型
在新标准下,接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器(在旧版本的标准库中,这些操作返回void),如果范围为空,不插入任何元素,insert操作会将第一个参数返回
#include <iostream> #include <list> #include <vector> #include <string> //using namespace std; int main () { std::vector<std::string> sv {"Hello" }; auto it = sv.insert(sv.end(),10,"Anna" ); //接受元素个数 for(auto i:sv ) std:: cout << i; std::cout << std::endl; std::cout << *it << std::endl; std::list<std::string> sl {"world"}; auto lit = sl.insert(sl.end(),sv.begin(),sv.end() ); //接受元素范围 //auto lit = sl.insert(sl.end(),sv.begin(),sv.begin() ); //范围为空 for(auto i:sl ) std:: cout << i; std::cout << std::endl; lit != sl.end() ? (std:: cout << *lit ) : (std:: cout << *(--lit) ) ; return 0; }
9.3.1 容器的emplace成员的返回类型
新标准引入三个新成员emplace_front、emplace、emplace_back,这些操作构造而不是拷贝元素,这些操作分别对应push_front、insert和push_back,允许将元素放置在容器头部、一个指定位置之前或容器尾部
当调用push或insert成员函数时,将元素类型的对象传递给它们,这些对象被拷贝到容器中,而当调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数,emplace函数在容器中直接构造元素,传递给emplace函数的参数必须与元素类型的构造函数相匹配
#include <thread> #include <iostream> #include <list> #include <vector> #include <string> //using namespace std; void func(size_t id){ std::cout << "thread: " << id << " run,"; } int main () { std::vector<std::thread> tv; for(size_t i=0; i<5; i++ ) tv.emplace_back(func, i ); for(auto &t:tv ){ t.join(); } return 0; }
9.4 shrink_to_fit
在新标准中,可以调用shrink_to_fit来要求dque、vector或string退回不需要的内存空间,此函数指出不再需要任何多余的内存空间,但是具体的实现可以选择忽略此请求,也就是说,调用shrink_to_fit也并不保证一定退回内存空间
9.5.5 string的数值转换函数
新标准引入了多个函数,可以实现数值数据与标准库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)、stod(s,p)、stold(s,p),p是size_t指针,用来保存s中第一个非数值字符的下标,b是转换所用的基数
int i = 42; std::string s = to_string( size_t(&i) ); //把i的地址(int*)转成string对象s size_t pi = stoi(s); //把string对象转成size_t std::cout << *(reinterpret_cast<int*>(pi) ); //把size_t强制转换成int*
10.3.2 Lambda表达式
一个lambda表达式表示一个可调用的代码单元,可以将其理解为一个未命名的内联函数,与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体,但与函数不同,lambda可能定义在函数内部,一个lambda表达式具有如下形式:
[capture list](parameter list) -> return type { function body }
在lambda中忽略括号和参数列表等价于指定一个空参数列表,忽略返回类型,lambda 根据函数体中的代码推断出返回类型,如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void,一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量(非static变量),才能在函数体中使用该变量(lambda可以直接使用局部static变量和它所在函数之外声明的名字)
auto f = [] {return 43; };//可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体
如果希望能 改变一个被捕获的变量的值,必须在参数列表首加上 关键字mutable
void func() { size_t v = 42; auto f = [v]()mutable {return ++v; }; v = 0; auto j = f(); //std::cout << j; }
10.3.3 Lambda表达式中的尾置返回类型
lambda体是单一的return语句,无须指定返回类型,需要为一个lambda定义返回类型时,必须使用尾置返回类型( 见6.3.3 定义尾置返回类型 )
10.3.4 标准库bind函数
C++11 bind,占位符 using namespace std::placeholders;
#include <functional> #include <iostream> //using namespace std; template<typename T> void f1( T func ){ func( ); } template<typename T> void f2( T func ){ func( 10 ); } void add(size_t a, size_t b){ std:: cout << a + b; } struct A{ public: void operator()(){ std:: cout << "A()"; } }; void test(){ std:: cout << "test"; } int main () { auto function1 = std::bind(add, 1, 2 ); f1(function1 ); using namespace std::placeholders; //占位符 auto function2 = std::bind(add, _1, 2 ); //占位符 f2(function2 ); //// f1(test ); f1(A() ); return 0; }
bind拷贝其参数,ostream不能拷贝,传递给bind一个对象而又不拷贝它,就必须使用标准库ref函数,ref返回一个对象,包含给定的引用,此对象是可以拷贝的,标准库中还有一个cref函数,生成一个保存const引用的类
#include <algorithm> #include <string> #include <functional> #include <iostream> //using namespace std; std::string words[] {"Hello","world" } ; std::ostream& print(std::ostream& os, const std::string s, char c ){ return os << s << c; } /* template<class InputIterator, class Function> Function for_each(InputIterator first, InputIterator last, Function fn) { while (first!=last) { fn (*first); // 占位符_1,即*first ++first; } return fn; // or, since C++11: return move(fn); } */ int main () { using namespace std::placeholders; //占位符 for_each(words, words+2, bind(print, std::ref(std::cout), _1, ' ' ) ); return 0; }
#include <functional> #include <iostream> struct A{ void func(){ std:: cout << "void A:: func()"; } A() = default; A(const A& ) = delete; //禁止复制 A& operator=(const A& ) = delete; private: //A(const A& ); //禁止复制 //A& operator=(const A& ); }; template<typename T> void test( T t){ t( ); } int main () { A a; test(std::bind( A::func, std::ref(a)) ); //bind会复制参数 return 0; }
lambda 表达式捕获变量,减少参数
#include <algorithm> #include <string> #include <iostream> int main () { std::string words[] {"Hello","world" } ; std::ostream& os = std::cout; char c = ' '; for_each(words, words+2, [&os,c](const std::string&s){ os << s << c; } ); return 0; }
bind是函数模板,绑定成员函数时,遇到成员函数重载,bind不能以绑定的成员函数的参数推定具体绑定的重载函数,必须定义绑定的成员函数的类型及变量
#include <functional> #include <iostream> struct A{ void func(){ std:: cout << "void A:: func()"; } void func(int ){ std:: cout << "void A:: func(int)"; } void func(int,int ){ std:: cout << "void A:: func(int,int)"; } void func()const{ std:: cout << "void A:: func()const"; } }; namespace bind_func{ void (A::*zero_func)( ) = &A::func; void (A::*int_func)(int) = &A::func; void (A::*int2_func)(int,int) = &A::func; void (A::*const_func)()const = &A::func; } int main () { auto function = std::bind( bind_func::zero_func, std::placeholders::_1); //auto function = std::bind( bind_func::int_func, std::placeholders::_1 , 6 ); //auto function = std::bind( bind_func::int2_func, std::placeholders::_1 , 5, 6 ); //auto function = std::bind( bind_func::const_func, std::placeholders::_1); function( A() ); return 0; }
11.2.1 关联容器的列表初始化
在新标准下,可以对关联容器进行值初始化
std::set<std::string > iset { "Hello", "world" }; std::map<std::string, int > word_count {{"Hello",3} , {"world", 6 } }; std::multimap<std::string, int > mp {{"Hello",1},{"Hello",2} };
11.2.3 列表初始化pair的返回类型
在新标准下,如果函数需要返回一个pair,可以对返回值进行列表初始化
#include <utility> //pair #include <vector> #include <string> #include <iostream> std::pair<std::string, int > func( std::vector<std::string> &v ){ if(!v.empty() ) return {v.back(),v.back().size() }; //列表初始化 return std::pair<std::string, int >(); //隐式构造返回值 } int main () { std::vector<std::string> v {"Hello","world" }; auto sip = func(v ); std::cout << sip.first << sip.second; return 0; }
11.3.2 pair的列表初始化
在新标准下,创建一个pair最简单的方法是 在参数列表中 使用花括号初始化
#include <utility> //pair #include <string> #include <iostream> #include <map> int main () { std::map<std::string,int > word_count; word_count.insert({"Hello",3} ); //! word_count.insert({"world",5} ); for(auto i:word_count ) std::cout << i.first << i.second << std::endl; return 0; }
11.4 无序容器
新标准定义了4个无序关联容器(unordered_map、unordered_multimap、unordered_set、unordered_multiset),这些容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符,除了哈希管理操作之外,无序容器还提供了与有序容器相同的操作(find、insert等)
无序容器在存储上组织为一组桶,每个桶保存零个或多个元素,无序容器使用一个哈希函数将元素映射到桶,为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶,无序容器的性能依赖于哈希函数的质量和桶的数量和大小,无序容器提供了一组管理桶的函数,不能直接定义关键字类型为自定义类类型的无序容器,必须提供自己的hash模板版本
12.1 智能指针
为了更容器(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象:shared_ptr允许多个指针指向同一个对象;unique_ptr则独占所指向的对象,标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象
12.1.1 shared_ptr类
智能指针是模板,默认初始化的智能指针中保存着一个空指针,解引用一个智能指针返回它指向的对象,make_shared函数是最安全的分配和使用动态内存的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr,shared_ptr在进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象
#include <string> #include <iostream> #include <memory> int main () { std::shared_ptr<std::string> p; //p(&s)不安全 { std::string s{ "Hello" }; p = std::make_shared<std::string >(s ); //安全的 //p指向以s初始化的动态分配的对象 //p = std::shared_ptr<std::string >(&s ); //不安全 //p指向&s //s出了作用域析构 s = "world"; } std:: cout << *p; return 0; }
#include <string> #include <iostream> #include <memory> class A{ public: A(){ std:: cout << "A()"; } ~A(){ std:: cout << "~A()"; } }; int main () { std::shared_ptr<A> p; { A a; p = std::shared_ptr<A>( &a ); } //std::shared_ptr<A> p{ new A }; std::cout << p.use_count(); return 0; }
如果将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的哪些元素
程序使用动态内存出于以下三种原因之一:程序不知道自己需要多少对象,程序不知道所需对象的准确类型,程序需要在多个对象间共享对象
12.1.2 动态分配对象的列表初始化
在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针,默认情况下,动态分配的对象是默认初始化的,内置类型或组合类型的对象的值是未定义的,而类类型对象将用默认构造函数进行初始化;可以使用直接初始化来初初始化一个动态分配的对象,可以使用传统的构造方式(使用圆括号),在新标准下,可以使用列表初始化(使用花括号)
int *pi = new int(1024 ); //传统构造方式,直接初始化对象 std::vector<int> *pv = new std::vector<int> { 0,1,2,3,4,5,6,7,8,9 }; //使用列表初始化,直接初始化对象
12.1.2 auto和动态分配
如果提供了一个括号包围的初始化器,就可以使用auto,只有当括号中仅有单一初始化器时才可以使用auto
auto p = new auto(obj );
12.1.5 unique_ptr类
只能有一个unique_ptr指向一个给定对象,当unique_ptr被销毁时,它所指向的对象也被销毁,初始化unique_ptr必须采用直接初始化形式,unique_ptr不支持普通的拷贝或赋值操作,可以通过调用release或reset将指针所有权从一个(非const)unique_ptr转移给另一个unique
#include <iostream> #include <memory> class A{ public: A(){ std:: cout << "A()"; } ~A(){ std:: cout << "~A()"; } }; int main () { std::unique_ptr<A> u1; //空unique_ptr //std::unique_ptr<A> u1(new A()); //初始化必须采取直接初始化 std::unique_ptr<A> u2; auto p = new A(); u1.reset(p ); //令u1指向p所指对象 //u1.release(); //释放对p的控制权,返回指针,并将u1置空 //u1.reset( );//u1 = nullptr; //释放u1所指对象 //std::unique_ptr<A> ; u2.reset(u1.release() ); //u1释放对p的控制权返回p,令u2指向p //u2 = nullptr; /* std::unique_ptr<A> u1(new A()); std::unique_ptr<A> u2; u2.reset(u1.release() ); u2 = nullptr; */ std:: cout << "Here"; return 0; }
12.1.6 weak_ptr类
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,不能使用weak_ptr直接访问对象,而必须调用lock,此函数检查weak_ptr指向的对象是否存在,如果存在返回一个指向共享对象的shared_ptr
#include <iostream> #include <memory> class A{ public: A(){ std:: cout << "A()"; } ~A(){ std:: cout << "~A()"; } }; int main () { auto p = std::make_shared<A>( A() ); //初始化shared_ptr指针p( make_ptr函数返回shared_ptr指针 ) std::weak_ptr<A> wp(p ); //wp弱共享p //wp绑定到p //wp1=p wp2=wp1 //p = nullptr; if(std::shared_ptr<A> np = wp.lock() ) std::cout << "yes:" << np.use_count(); //std::cout << wp.use_count(); std:: cout << "Here"; return 0; }
12.2.1 范围for语句不能应用于动态分配数组
让new分配一个对象数组,由于分配的内存并不是一个数组类型,因此不能对动态数组调用begin或end:这些函数使用数组维度(维度是数组类型的一部分)来返回指向首元素和尾后元素的指针,相同的原因,也不能用范围for语句来处理动态数组中的元素,动态数组不是数组类型,这是很重要的
std::string *ps = new std::string[3] { "Hello","world",std::string(3,'!')}; for(auto p = ps; p!=ps+3; ++p ) std:: cout << *p << ' ';
12.2.1 动态分配数组的列表初始化
在新标准中,可以提供一个元素初始化器的花括号列表
12.2.1 auto不能用于分配数组
虽然用空括号对数组元素进行值初始化,但不能在括号中给出初始化器,这意味着不能用auto分配数组( 见12.1.2 auto和动态分配 )
int *p = new int[10](); //空括号对数组元素进行值初始化
12.2.2 allocator::construct可使用任意构造函数
标准库allocator类定义在头文件memory中,它将内存分配和对象构造分离开来,它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的,allocator是一个模板,为了定义一个allocator对象,必须指明这个allocator可以分配的对象类型
std::allocator<std::string> alloc; //可以分配string的allocator对象 auto const p = alloc.allocate(n); //分配n个未初始化的string
在新标准库中,construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造一个元素
auto q = p; //q指向最后构造的元素之后的位置 alloc.construct(q++ ); alloc.construct(q++,10,'c'); alloc.construct(q++,"Hello" );
13.1.5 将=default用于拷贝控制成员
通过将拷贝控制成员定义为=default来显式地要求编译器生成合成的版本,只能对具有合成 版本的成员函数使用=default即默认构造或拷贝控制成员,在类内用=default修饰成员的声明时,合成的函数将隐式地声明为内联的,不希望合成的成员是内联函数,应该只对成员的类外定义使用=default
13.1.6 使用=default阻止拷贝类对象
在新标准下,可以通过将拷贝构造和拷贝赋值运算符定义为删除的函数来阻止拷贝,删除的函数是这样一种函数:声明了它们,但不能以任何方式使用 它们,=delete通知编译器不希望定义这些成员,可以对任何函数指定=delete,对于析构函数已删除的类型,不能定义该类型的变量(可以动态分配该类型的对象)或释放指向该类型动态分配对象的指针
struct NoCopy{ NoCopy() = default; NoCopy(const NoCopy& ) = delete; //阻止拷贝 NoCopy &operator=(const NoCopy& ) = delete; //阻止赋值 ~NoCopy() = default; };
13.5 用移动类对象代替拷贝类对象
新标准库引入了两种机制,可以避免类对象的拷贝:第一种机制是有一些标准库类,都定义了所谓的“移动构造函数”,移动构造函数通常是将资源从给定对象“移动”而不是拷贝到正在创建的对象;第二种机制是一个名为move的标准库函数 ,定义在 utility头文件中,使用move时,直接调用std::move而不是move
13.6.1 右值引用
为了支持移动操作,新标准引入了一种新的引用类型--右值引用,所谓右值引用就是必须绑定到右值的引用,通过&&来获得右值引用,右值引用有一个重要的性质--只能 绑定 到一个将要销毁的对象
一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值,对于常规引用称之为左值引用,右值引用将其绑定到要求转换的表达式、字面值常量或返回右值的表达式,返回非引用类型的函数,连同算术、关系、位以及后置递增/递减运算符,都生成右值,可以将一个const的左值引用或一个右值引用绑定到这类表达式,右值引用指向将要被销毁的对象
变量表达式也有左值/右值属性,变量表达式都是左值,不能将一个右值引用绑定到一个右值引用类型的变量上
#include <iostream> struct A{ size_t x = 111; }; int main() { A &&rr = A(); //右值引用rr std:: cout << rr.x; return 0; }
13.6.1 标准库move函数
不能将一个右值引用直接绑定到一个左值上,可以显式地将一个左值转换为右值引用类型,通过调用std::move的新标准库函数来获得绑定到左值的右值引用,move调用告诉编译器:有一个左值,希望像一个右值一样处理它,调用move意味着:除了对左值(右值引用变量)赋值或销毁它外,将不再使用它
#include <iostream> #include <utility> //std::move() struct A{ size_t x = 111; }; int main() { A &&rr1 = A(); // 变量arr1是左值 A &&rr2 = std::move(rr1); //获得绑定到左值的右值引用//除了对rr1赋值或销毁它外,不再使用它 std:: cout << rr2.x; return 0; }
13.6.2 移动构造函数和移动赋值
移动构造函数的第一个参数是该类类型的一个右值引用,任何额外的参数必须有默认实参,移动构造函数必须确保移后源对象处于一个状态--销毁无害,移动资源完成移动,源对象必须不再指向被移动的资源--这些资源的所有权已经归属新创建的对象,
13.6.2 移动构造函数通常应该是noexcept
noexcept是新标准引入的,承诺一个函数不抛出异常的一种方法,在一个函数的参数列表后指定noexcept,在一个构造函数中,noexcept出现在参数列表和初始化列表开始的冒号之间
13.6.2 移动迭代器
新标准中定义了一种移动迭代器适配器,一个移动迭代器通过改变给定迭代器的解引用运算符的行为来适配此迭代器,移动迭代器的解引用运算符生成一个右值引用,调用标准库的make_move_iterator函数将一个普通迭代器转换为一个移动迭代器,此函数接受一个迭代器参数,返回一个移动迭代器
13.6.3 引用限定成员函数
新标准库任然允许向右值赋值,强制左侧运算对象(即this指向的对象)是一个左值,指出this的左值/右值属性的方式与定义const成员函数相同,即在参数列表后放置一个引用限定符(&或&&),引用限定符可以是&或&&,分别指出this可以指向一个左值或右值,引用限定符只能用于(非static)成员函数,且必须同时出现在函数的声明和定义中
14.8.3 function类模板
C++语言中有几种可调用对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类,两个不同类型的可调用对象却可能共享一种调用形式,调用形式 指明了调用返回的类型以及传递给调用的实参类型
function是一个模板,当创建一个具体的function类型时,必须提供额外的信息:function类型能够表示的对象的调用形式,function<int(int,int) >声明了一个function类型,表示接受两个int、返回一个int的可调用对象
function<int(int,int) > f1 = add; //函数指针 function<int(int,int) > f2 = divide(); //函数对象类的对象 function<int(int,int) > f3 = [](int i,int j){return i*j; }; //lambda
map<string, function<int(int,int)> > binops; //能把所有可调用对象,包括函数指针、lambda或函数对象在内,都添加到map中
不能将重载函数的名字存入function类型的对象中,可以用存储函数指针或使用lambda来消除二义性
14.9.1 explicit类型转换运算符
C++11新标准引入了显式的类型转换运算符(explicit conversion operator),当类型转换运算符是显式的时,也能执行类型转换,不过必须显式的强制类型转换才可以
当表达式出现在下列位置时,显式的类型转换将被隐式地执行:if、while及do语句的条件部分;for语句头的条件表达式;逻辑非、逻辑或、逻辑与的运算对象;条件运算符的条件表达式
15.2.2 虚函数的override指示符
C++新标准允许派生类显式地注明它使用某个成员函数覆盖了它继承的虚函数,具体做法是:在形参列表后面、或者在const成员函数的const关键字后面、或者在引用成员函数的引用限定符后面添加一个关键字override
15.2.2 通过定义类为final来阻止继承
C++11新标准提供了一种防止继承发生的方法,即在类名后跟一个关键字final
15.3 虚函数的override和final指示符
在C++新标准中可以使用override关键字来说明派生类的虚函数,如果使用override标记了某个函数,但该函数并没有覆盖已存在的虚函数,此时编译器将报错;能把某个函数指定为final,如果已经把函数定义成了final了,则之后任何尝试覆盖该函数的操作都将引发错误,final和override说明符出现在形参列表(包括任何const或引用修饰符)以及尾置返回类型之后
15.7.2 删除的拷贝控制和继承
15.7.4 继承的构造函数
通常情况下,using声明语句 只是令某个名字在当前作用域内可见,而当作用于构造函数时,using声明语句将令编译器产生代码:对于基类的每个构造函数,编译器都生成一个与之对应的派生类构造,换句话说,对应基类的每个构造函数,编译器都在派生类中生成一个形参列表完全相同的构造函数,这些编译器生成的构造函数形如:derived(parms): base(args){ },如果派生类含有自己的数据成员,则这些成员将被默认初始化,一个构造函数的using声明不会改变该构造函数的访问级别,不能指定explicit或constexpr
#include <iostream> struct B{ int x; B(int x):x(x) { } }; struct A:public B{ int y; A(int x,int y):B(x),y(y) { } using B::B; //继承B的构造函数 }; int main () { A a(1); //继承B的构造函数//如果派生类含有自己的数据成员,则这些数据成员将被默认初始化 std:: cout << a.y; //y默认初始化 return 0; }
16.1.2 声明模板类型形参为友元
在新标准中,可以将 模板类型参数 声明为友元
#include <iostream> template<typename T> class A{ friend T; //声明模板类型参数T为友元 private: A():x(111){ } int x{100}; }; class B{ public: void test(){ std:: cout << A<B>().x; //B为A友元 } }; int main () { B().test(); return 0; }
16.1.2 模板类型别名
新标准允许为类模板定义一个类型别名:template<typename T> using twin = pair<T,T>; ,一个模板类型别名是一族类的别名
#include <string> #include <iostream> template<typename T1, typename T2> struct Add{ auto operator()( T1 t1, T2 t2) ->decltype(t1 + t2) { return t1 + t2; } }; template<typename T1, typename T2> using Test = Add<T1,T2>; //模板类型Add 别名 /* template<typename T> using Test = Add<T,T>; //模板类型Add 别名 */ int main () { Test<std::string,char> t; std:: cout << t("Hello" ,'!' ); /* Test<int> t; std:: cout << t(3 ,5 ); */ return 0; }
16.1.3 模板函数的默认模板参数
指定显式模板实参:
#include <iostream> template<typename T1,typename T2, typename T3> T1 sum(T2 v2,T3 v3){ return v2 + v3; } int main () { /* 显式模板实参按由左向右的顺序与对应的模板参数匹配;第一个模板实参与一个模板参数匹配, 第二个实参与第二个参数匹配,以此类推,只有尾部(最右)参数的显式模板实参才可以忽略, 而且前提是它们可以从函数参数推断出来 */ auto val = sum<long long, long, int >(5,6 );//第一个对应T1 第二个对应T2 第三个对应T3 std:: cout << val; auto value = sum<long>(5,6 ); //T1是显式指定的,T2和T3是从函数实参类型推断而来的 std:: cout << value; return 0; }
在新标准中,可以为函数和类模板提供默认实参,更早的C++标准只允许为类模板提供默认实参,对于一个模板参数,只有当它右侧的所有参数都有默认参数时,它才可以由默认实参
#include <functional> //less greater #include <iostream> template<typename T, typename F = std::less<T> > //默认模板实参std::less<T> int compare ( const T& v1, const T& v2, F f = F() )//默认函数实参F() { if(f(v1,v2) ) return -1; if(f(v2,v1) ) return 1; return 0; } int main () { std:: cout << compare(0,40 ); //-1 0<40 //1 40<0 // 0 0==40 std:: cout << std::boolalpha << std::less<int>()(0,40 ); return 0; }
#include <string> #include <functional> //less greater #include <iostream> template<typename T = std::string, typename F = std::less<std::string> > //默认模板实参std::less<T> int compare ( const T& v1, const T& v2, F f = F() )//默认函数实参F() { if(f(v1,v2) ) return -1; if(f(v2,v1) ) return 1; return 0; } int main () { std:: cout << compare<>("Hello","world" ); std:: cout << compare<std::string>("Hello","world" ); return 0; }
#include <string> #include <iostream> template<typename T1, typename T2 = char > //默认模板实参char auto add ( T1 t1, T2 t2) ->decltype(t1 + t2) { return t1 + t2; } int main () { std:: cout << add<std::string>("Hello" ,'!' ); return 0; }
无论何时使用一个类模板,都必须在模板名之后接上尖括号,如果一个类模板为其所有模板参数都提供了默认实参,且希望使用这些模板实参,就必须在模板名之后跟一个尖括号
#include <iostream> #include <iomanip> //showpoint setprecision template<typename T = int> //T默认为int class Numbers{ public: Numbers(T v = 0 ):val(v) { } friend std::ostream& operator << (std::ostream& o, const Numbers<T>& t ){ return o << t.val << " "; } private: T val; //T 是自定义的类类型,应该重载 << 运算符 }; int main () { Numbers<> n1(1); //空尖括号对 Numbers<double> n2(2); std::cout << n1 << std::showpoint << std::setprecision(3) << n2; return 0; }
16.1.5 实例化的显式控制
当模板被使用时才会进行实例化,这意味着,相同的实例可能出现在多个对象文件中,在新标准中,可以通过显式实例化来避免这种开销,一个显式实例化有如下形式:
extern template declaration; //实例化声明 template declaration; //实例化定义
16.2.3 模板函数与尾置返回类型
编写一个模板函数,模板函数参数类型不相同,且不知道模板函数返回结果的准确类型,可以使用尾置返回类型,用decltype()来获取返回结果的类型
#include <string> #include <iostream> template<typename T1, typename T2> auto Add( T1 t1, T2 t2) ->decltype(t1 + t2) //尾置返回类型 { return t1 + t2; } int main () { std::cout << Add<std::string,char>("Hello", '!' ); return 0; }
16.2.5 引用折叠规则
如果我们间接创建了一个引用的引用,则这些引用形成了折叠,在新标准中,折叠规则扩展到右值引用,只有一种特殊情况下引用会折叠成右值引用:右值引用的右值引用,引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数
16.2.6 用static_cast将左值转换为右值
虽然不能隐式地将一个左值转换为右值引用,但可以用static_cast显式地将一个左值转换为一个右值引用,对于操作右值引用的代码来说,将一个右值引用绑定到一个左值的特性允许它们截断左值
16.2.7 标准库forward函数
forward返回该显式实参类型的右值引用,即,forward<T>的返回类型是T&&,forward定义在头文件utility中,对std::forward不使用using声明
16.4 可变参数模板
一个可变参数模板就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包,存在两种参数包:模板参数包,表示零个或多个模板参数;函数参数包:表示零个或多个函数参数
用一个省略号来指出一个模板参数或函数参数表示一个包,在一个模板参数列表中,class...或typename...指出接下来的参数表示零个或多个类型的列表;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包
template<typename T, typename... Args> void foo(const T &t, const Args &...rest);
可变参数模板与引用折叠
#include <iostream> template<typename Func, typename... Args> void test(Func f, Args&... args ) //& { f(args... ); // x& & ---> x& } void func(int a, int &b){ //&折叠(b) a++; b++; } int main () { int a=3; int b=5; test(func, a, b ); std:: cout << a << "," << b; return 0; }
可变参数模板与非可变参数模板
#include <iostream> template<typename T> //非可变参数模板 !!! std::ostream& print(std::ostream &o, const T &t){ return o << t; } //非可变参数模板必须声明在作用域中,否则可变参数模板会无限递归 template<typename T, typename... Args> //可变参数模板 std::ostream& print(std::ostream &o, const T &t, const Args &...args){ o << t << ","; return print(o,args...); //!!! } int main() { print(std::cout, 1.3, 2, "Hello", 4, 5 ); return 0; }
16.4 sizeof...运算符
需要知道参数包中有多少元素时,可以使用sizeof...运算符,返回一个常量表达式,不会对其实参求值
16.4.3 可变参数模板与转发
在新标准下,可以组合使用可变参数模板与forward机制来编写函数,实现将其实参不变地传递给其他函数,可变参数函数通常将它们的参数 转发给其他函数
template<typename...Args> void fun(Args&&...args ) //将Args扩展为一个右值引用的列表 { work(std::forward<Args>(args)... );//work实参既扩展Args又扩展args }
将fun的所有实参转发给另一个名为work的函数,假定由它完成函数的实际工作,由于fun的参数是右值引用 ,因此可以传递给它任意类型的实参,使用std::forward传递这些实参,因此它们的所有类型信息在调用 work时都会得到保持
17.1 标准库Tuple类模板
tuple是类似pair的模板,每个pair的成员类型不相同,但每个pair都恰好有两个成员,不同tuple类型的成员类型也不相同,但一个tuple可以有任意数量的成员,每个确定的tuple类型的成员数目是固定的,但一个tuple类型的成员数目可以与另一个tuple类型不同,希望 将一些数据组合成单一对象,但又不想麻烦地定义一个新数据结构来表示这些 数据,tuple是非常有用的
17.2.2 新的bitset运算
bitset操作定义了多种检测或设置一个或多个 二进制位的方法,bitset类还支持位运算符,这些运算符用于bitset对象的含义与内置运算符用于unsigned运算对象相同
17.3 正则表达式库
正则表达式是一种描述字符序列的方法,是一种极其强大的计算工具,C++正则表达式库(RE库),是新标准的一部分,RE库定义在头文件regex中,它包含多个组件
17.4 随机数库
C++程序不应该使用库函数rand,而 应使用 default_random_engine类 和 恰当的分布类 对象,一个引擎类可以生成unsigned随机数序列,一个分布类使用一个引擎类生成指定类型的、在给定范围内的、服从特定概率分布的随机数
随机数引擎 是函数对象类,它们定义了一个调用运算符,该运算符不接受参数并返回一个随机unsigned整数,可以通过调用一个随机数引擎对象来生成原始随机数,标准库定义了多个随机数引擎类,区别在于性能和随机性质量不同,每个编译器都会指定其中一个作为default_random_engine类型,此类型一般具有最常用的特性,随机数引擎的输出是不能直接使用的,称之为原始随机数
#include <random> #include <iostream> int main () { std::default_random_engine e; for(size_t i = 0; i<10; ++i) std:: cout << e() <<", "; //生成原始随机数 return 0; }
为了得到在一定指定范围内的数,使用一个分布类型的对象,分布类型也是函数对象类,分布类型定义一个调用运算符,它接受一个随机数引擎为参数
随机数发生器,是指分布对象和引擎对象的组合,会生成相同的随机数序列,为引擎设置种子 有两种方式:在创建引擎对象时提供种子,或者调用引擎的seed成员
#include <ctime> //std::time(0) #include <random> #include <iostream> int main () { std::default_random_engine e(std::time(0) ); //种子 std::time(0) std::uniform_int_distribution<unsigned> u(0,9); for(size_t i = 0; i<10; ++i) std:: cout << u(e) <<", "; //分布类对象 return 0; }
17.5.1 浮点数格式控制
在新标准中,通过使用 hexfloat 可以强制浮点数使用十六进制格式,新标准库还提供另一个名为 defaultfloat 的操作符,它将流恢复到默认状态--根据要打印的值选择计数法
18.1.4 noexcept异常指示符
在C++11标准中,可以通过提供noexcept说明指定某个函数不会抛出异常,其形式是关键字noexcept紧跟在函数的参数列表后面,用以标识该函数不会抛出异常
18.1.4 noexcept运算符
noexcept说明符的实参常常与noexcept运算符混合使用,noexcept运算符是一个一元运算符,它的返回值是一个bool类型的右值常量表达式,用于表示给定的表达式是否会抛出异常,noexcept不会求其运算对象的值
noexcept( e ) //当e调用的所有函数都做了不抛出说明且e本身不含有throw语句时,表达式为true否则返回false void f() noexcept(noexcept(g() ) ) //f和g的异常说明一致
18.2.1 内联名字空间
C++11新标准引入了一种新的嵌套命名空间,称为内联命名空间,和普通的嵌套命名空间不同,内联命名空间中的名字可以被外层命名空间直接使用,定义内联命名空间的方式是关键字namespace前添加关键字inline
18.3.1 继承的构造函数和多重继承
在C++11新标准中,允许派生类从它的一个或几个基类中继承构造函数,但是如果从多个基类中继承了相同的构造函数,则这个类必须为该构造函数定义它自己的版本,否则程序将产生错误
19.3 有作用域的enum
每个枚举类型定义了一种新的类型,枚举属于字面值常量类型,C++包含两种枚举:限定作用域的和不限定作用域的
C++11新标准引入了限定作用域的枚举类型,定义 限定作用域的枚举类型 的一般形式是:首先是关键字enum class(或enum struct)随后是枚举类型名字以及花括号括起来的以逗号分隔的枚举成员列表,最后是一个分号
定义不限定作用域的枚举类型时省略掉关键字class或struct(枚举类型是可选的)
#include <iostream> struct A{ enum color{ red, green, yellow }; enum struct COLOR{ red, green, yellow }; //枚举成员被隐藏 }; int main () { int i = A::color::red; //int j = A::COLOR::red; //不可以隐式转换为整形 A::COLOR R = A::COLOR::red; std:: cout << ( R == A::COLOR::red ); return 0; }
19.3 说明类型用于保存enum对象
C++在新标准中,可以在enum的名字后加上冒号以及在该enum中使用的类型,没有指定enum的潜在类型,则 默认情况下限定作用域的enum成员类型是int,对于不限定作用域的枚举类型来说,其枚举成员不存在默认类型,只知道成员的潜在类型足够大,肯定能够容纳枚举值,指定enum潜在类型的能力使得可以控制不同实现环境中使用的类型
enum intValues: unsigned long long { charType = 255, shortType = 65535, intType = 65535, longType = 4294967295UL,longlongType = 18446744073709551615ULL };
19.3 enum的提前声明
在C++11标准中,可以提前声明enum,enum的前置声明(无论隐式地还是显式地)必须指定其成员的大小
enum intValues: unsigned long long ; //不限定作用域的,必须指定成员类型 enum class open_modes; //限定作用域的枚举类型可以使用默认成员类型int
19.4.3 标准库mem_fn类模板
通过使用标准库功能mem_fn来让编译器负责推断成员的类型,mem_fn定义在functional头文件,可以从成员指针生成一个可调用对象,mem_fn可以根据成员指针的类型推断可调用对象的类型,无须用户显式指定
#include <functional> #include <iostream> struct A{ void f(){ std::cout << "A::f()"; } }; int main () { auto f = std::mem_fn(&A::f ); f(A() ); //传A对象或对象指针 return 0; }
19.6 类类型的联合成员
C++早期版本 规定,在union中不能含有定义了构造函数或拷贝控制成员的类类型成员,C++11标准取消了这一限制