前言
1.演进、环境与资源
你应该具备的基础
- C++语法,语义
我们的目标
较全面地认识C++2.0新特性,并且从实例中得到体验。
C++ Standard之演化
- C++98(1.0)
- C++03(TR1,Technical Report1)
- C++11(2.0)
- C++14
Header files
C++2.0新特性包括语言和标准库两个层面,后者一header files形式呈现。
- C++标准库的header files不带后缀名(.h),例如
#include<vector>
- 新式C header files不带副名.h,例如
#include<cstdio>
- 旧式C header files(带有副名.h)仍可用,例如
#include<stdio.h>
#include <type_traits>
#include <array>
#include <thread>
using namespace std;
了解你的编译器对C++2.0的支持度
书籍
第一步,确认支持C++11:macro __cplusplus
cout<<__cplusplus<<endl;
重磅出击
语言:
- Variadic Templates
- move Semantics
- auto
- Range-basr for loop
- Initializer list
- Lambdas
- …
标准库:
- type_traits
- Unordered容器
- forward_list
- array
- tuple
- Con-currency
- RegEx
- …
2.Variadic Templates
数量不定的模板参数
void print() //必须存在一个空的供调用
{
}
template <typename T,typename... Type>
void print (const T& firstArg,const Types... args)
{
cout<<firstArg<<endl;
print(args...);
}
如果需要测试包里面的大小,需要使用sizeof...(args)
void print() //必须存在一个空的供调用
{
}
template <typename T,typename... Type> //2
void print (const T& firstArg,const Types... args)
{
cout<<firstArg<<endl;
print(args...);
}
template <typename... Types>//3
void print(const Types&... args)
{
/*...*/
}
2和3可否并存?(可)
若可,谁较泛化?谁较特化?
可以很方便地完成recursive function call.
(递归函数调用)
特别符合就用特化,否则调用泛化。
3.Spaces in Template Expression、nullptr and std–nullptr_t、Automatic Type Deduction with auto
Spaces in Template Expressions
C++11之前需要空格,现在不需要了。
vector<list<int> >; //OK in each C++ version
vector<list<int>>; //OK since C++11
nullptr and std::nullptr_t
C++11允许你使用nullptr
代替0
或者NULL
。
void f(int);
void f(void*);
f(0); //calls f(int)
f(NULL); //calls f(int) if NULL is 0,ambiguous otherwise
f(nullptr); //calls f(void*)
Automatic Type Deduction with auto
C++11,能够使用auto进行类型推导。
auto i = 42; //i has type int
double f();
auto d = f(); //d has type double
auto keyword
标准库的使用:
4.Uniform Initialization
C++11中统一初始化的时候都是使用{}
,当然使用之前的也不会出错。
int values[] {1,2,3};
vector<int > v {2,3,5,7,11,13,17};
vector<string> cities{
"Berlin","New York","London"
}
//这里形成一个initializer_list<string>,背后有个array<string,3>调用vector<string> ctors时编译器找到一个vector<string> ctor接受initializer_list<string>、所有容器皆有如此ctor。
complex<double> c{4.0,3.0}; //等价于 c(4.0,3.0);
//这里形成一个initializer_list<double>,背后有个array<double,2>调用vector<double> ctors时该array内的2个元素被分解传给ctor。complex<double>无论如何ctor接受initializer_list。
其实是利用一个事实,编译器看到{t1,t2,…,tn}便做出一个initializer list<T>,它关联至一个array<T,n>。调用函数(例如ctor)时该array内的元素可被编译器分解逐一传给函数。但若函数参数是个initializer_list<T>,调用者却不能给予数个T参数然后以为它们会自动转为一个initializer_list<T>传入。(简单理解,如果没有initializer_list编译器会自动分解;如果是initializer_list,编译器直接传入initializer_list;如果参数是initializer_list,调用者不能尝试传入数个T参数让编译器转换为initializer_list)
5.Initializer_list
下列表示为未设置初值或者设置初值。
从下列图片可以看出,{}
定义初值不会默认进行类型转换,强类型要求;()
要求较为宽泛。
initializer_list<>
C++11提供了类模板:
std::initializer_list<>
它能够支持初始化一个列表值或者任意你想要处理一个列表值的过程,例如:
void print(std::initializer_list<int> vals)
{
for(auto p = vals.begin(); p != vals.end();++p) //a list of val
{
std::cout<<*p<<"\n";
}
}
print({12,3,5,6,11,14}); //pass a list of values to print()
//传给initializer_list者,一定必须也是个initializer_list{....}形式
四个调用动作,谁调用谁。如果没有版本2的话,P q{77,5}
会被拆分成两个参数,调用版本1,但是r
存在3个参数不会被调用。
需要注意的initializer_list是浅拷贝。
使用initializer_list之后,min
和max
函数可以比较任意个参数。
6.Explicit for ctors taking more than one argument
explicit用来针对构造函数,并且只接受一个以上的实参。
C++2.0之前
explicit
关键字表示让编译器不要自作聪明进行转换,一般使用在构造函数内。
7.Range-based for statement
语法规则:
for(decl:coll){
statement
}
实例:
for(int i:{2,3,5,7,9,13,17,19}){
cout<<i<<endl;
}
vector<double> c;
...
for (auto elem: vec){
cout<<elem<<endl;
}
for(auto& elem: vec){ //引用(可改变)
elem *= 3;
}
当元素在for循环中初始化为decl时,不可能进行显式类型转换
。因此,下面的代码不能编译:
8. =default,=delete
如果你自行定义了一个ctor,那么编译器就不会再给你一个default ctor。
如果你强制加上=default,就可以重新获得并时候default ctor。
class Zoo
{
public:
Zoo(int i1,int i2):d1(i1),d2(i2) { }
Zoo(const Zoo&)=delete;
Zoo(const Zoo&&)=default;
Zoo& operator=(const Zoo&)=default;
Zoo& operator=(const Zoo&&)=delete;
virtual ~Zoo(){ }
private:
int d1,d2;
};
delete
标准库的使用。
delete表示不想要这个函数。
default
=default,=delete
class Foo
{
public:
Foo(int i):_i(i) { }
Foo()=default; //于是和上一个并存(ctor可以多个并存)
Foo(const Foo& x): _i(x._i) { }
//! Foo(const Foo&)=default; //[Error] Foo:Foo(const Foo&)' cannot be overloaded(拷贝构造函数只能有一份)
//! Foo(const Foo&)=delete; //[Error] Foo::Foo(const Foo&)' cannot be overloaded(已经赋值构造了,编译器无法解释)
Foo& oeprator=(const Foo& x) {_i = x.i;return *this; }
//! Foo& operator=(const Foo& x)=default; //[Error] 'Foo& Foo::operator=(const Foo&)' cannot be overloaded(赋值构造函数只能有一份)
//! Foo& operator=(const Foo& x)=delete; //[Error] 'Foo& Foo::operator=(const Foo&)' cannot be overloaded
//!void func1()=default; //[Error] 'void Foo::func1' cannot be defaulted(一般函数没有默认办版本)
void func2()=delete; //ok
//!~Foo()=delete; //这会造成使用Foo object时出错=>[Error] use of deleted function 'Foo::~Foo()'
~Foo()=delete;
private:
int _i;
}
=default;
用于上述之外函数是何意义?——>无意义,编译报错。
=delete;
可用于任何函数身上。(=0只能放在virtual函数)
了解c++无声地编写和调用哪些函数
什么时候empty class不再是empty呢?当C++处理过它之后。是的,如果你自己没声明,编译器就会为它声明一个copy ctor、一个copy assignment operator和一个dtor(都是所谓synthesized version合成版本)。如果你没有声明任何ctor,编译器也会为你声明一个default ctor。所有这些函数都是public且inline。
class Empty{};
唯有当这些函数被需要(被调用),它们才会编译器合成,以下造成对应的函数被编译器合成。
这些函数做了什么?唔,default ctor和dtor主要是给编译器一个地方用来放置『藏深幕后』的code,像是唤起base classes以及non-static members的ctors和dtors。
编译器产出的dtor是non-virtual,除非这个class的base class本身宣告有virtual dtor。
至于copy ctor和copy assignment operator,编译器合成版只是单纯将source object的每一个non-static data members拷贝到destination object。
complex有所谓的Big-There吗?
string有所谓的Big-Three吗?
当然有,而且有Big-Five
(字符串)指针只有浅拷贝,没有深拷贝。
No-copy and Private-Copy
boost::noncopyable
涉及这个类的原因在于继承这个类,继承这个类就可以拥有这个性质,一般的类是没办法进行copy的,只有成员函数和有元函数。
9.Alias Template(template typedef)
Alias化名或者别名
//使用macro用法达到相同效果(define)
#define Vec<T> template<typename T> std::vector<T,MyAlloc<T>>;
//于是:
Vec<int> coll;
//表示为:
template<typename int> std::vector<int,MyAlloc<int>>; //这不是我要的
使用typedef亦无法达到相同效果,因为typedef是不接受参数的。
我们至多变成这样:
typedef std::vector<int,MyAlloc<int>> Vec; //这不是我要的(没办法指定参数)
难道使用化名只是为了少打几个字。
我的实战经验如下~~
探用function template + iterator + traits
//iterator+traints会在C++标准库里面谈,这门课程不会涵盖。
容器一定有迭代器,有迭代器就知道迭代器的类型。(没有万一的情况)
10.Template template parameter
使用模板模板参数进行解决。
使用模板模板参数加alias别名。
11.Type Alias,noexcept,oerride,final
noexcept
当出现成长时搬动才是用noexcept。
Exception是一门大学问,另课探讨
swap也是一门大学门,另课探讨
你需要通知C++(特别是std::vector),移动构造器和析构函数不会抛出。当vector成长
时被称为移动构造函数,如果构造函数不是noexcept,std::vector就不能使用它,因为它不能保证标准所要求的异常保证。
注意:growable conatiners(会派生memory reallocation)只有一种:vector和deque(成长的容器)
vector(大规模成长)
deque
override
改写,运用在虚函数身上。子类重新改写父类的函数,但是签名需要保证一致。使用override明确告诉它需要改写。
final
使得类不能够被继承或者虚函数不能被改写。
12.decltype
使用decltype关键字,可以让编译器知道表达式的类型
。并且这种类型还可以接着进行编程使用。
(我们在使用对象的时候不知道类型,可以进行使用)
map<string,float> coll;
decltype(coll)::value_type elem;
//在C++11之前这么写
map<string,float>::value_type elem;
我们应用于decltype主要在三个方面:
- 声明返回类型
- 元编程
- 一个lambda表达式传递类型
decltype,used to declare return types
template <typename T1,typename T2>
decltype(x+y) add(T1 x,T2 y);//最后能不能用看结果
//换种写法
template <typename T1,typename T2>
auto add(T1 x,T2 y)->decltype(x+y);
//这使用了与lambdas相同的语法来声明返回类型,auto的返回类型是decltype(x+y)
decltype,used it in metaprogramming
注:以下代码必须传入容器,因为只有容器才有迭代器。
template <typename T>
void test18_decltype(T obj)
{
//当我们手上有type,可取其inner typedef,没问题
map<string,float>::value_type elem1;
//面对obj取其class type的inner typedef,因为如今我们有了工具decltype
map<string, float> coll;
decltype(coll)::value_type elem2;
//瞧我故意设计本测试函数为template function,接受任意参数T obj
//如今有了decltype我可以这样:
typedef typename T::iterator iType;
decltype(obj)anotherObj(obj);
}
//test18_decltype(complex<int>()); //编译失败
decltype,used to pass the type of a lambda
auto cmp = [](const Person& p1,const Person& p2){
return p1.lastname() < p2.lastname() ||
(p1.lastname() == p2.lastname() && p1.lastname() < p2.lastname())
};
...
std::set<Person,decltype(cmp)> coll(cmp);//设置比较大小
面对lambda,我们手上往往只有object,没有type.
要获得其type就得借助于decltype。
13.lambdas
定义一个inline函数作为参数或者局部对象。
[]{
std::cout<<"hello lambda"<<std::endl;
}
可以直接这样调用它:
[]{
std::cout<<"hello lambda"<<std::endl;
}(); //输出hello lambda
或者通过它的对象去调用:
auto I = []{
std::cout<<"hello lambda"<<std::endl;
}; //输出hello lambda
...
I(); //输出hello lambda
(声音有些问题,直接跳过!)
14.Variadic Templates
谈的是template
- function template
- class template
变化的是template parameters - 参数个数
利用参数格式逐一递减的特性,实现递归函数调用,使用function template完成。 - 参数类型
利用参数个数逐一递减导致参数类型也逐一递减的特性,实现递归继承或递归复合,以class template完成。
注意:记住...的位置。
void func() {}
template <typename T,typename... Types>
void func(const T& firstArg,const Types&... args)
{
//处理firstArg
func(args...);
}
以下介绍七大例子。
例1
void printX()
{
}
template <typename T,typename... Types>
void printX(const T& firstArg,const Types&... args)
{
cout<<firstArg<<endl;
printX(args...);
}
//调用
printX(7.5,"hello",bitset<16>(377),42);
//输出
//7.5
//hello
//0000000101111001
//42
例2
使用variables tempaltes重写printf()
原方式(没怎么看懂)
void printf(const char *s)
{
while(*s)
{
if(*s == '%' && (++s) != '%')
throw std::runtime_error("invalid format string:missing arguments");
std::cout<<*s++;
}
}
重写后
template<typename T,typename...Args>
void printf(const char*s ,T value,Args... args)
{
while(*s){
if(*s == '%' && *(++s) != '%'){
std::cout<<value;
printf(++s,args...);
return;
}
std::cout<<*s+=;
}
throw std::logic_error("extra argument provided to printf");
}
测试案例
int* pi = new int;
printf("%d%s%p%f\n",
15,
"This is Ace",
pi,
2.141592653);
例3
在一堆集合中找最大的一个。
若参数types皆同,则无需动用variadic templates,使用initializer_list足矣。
cout<<max({57,48,60,100,20,18})<<endl;
(复杂部分未记录)
例4
cout<<maximum({57,48,60,100,20,18})<<endl;
int maximum(int n)
{
return n;
}
template<typename...Args>
int maximum(int n,Args...args)
{
//利用不断调用std::max()而完成最大值之获取;其实std::max()已可接受任意函数之参数值
return std::max(n,maximum(args...));
}
例5——以异于一般的方式处理first元素和last元素
以异于一般的方式处理first元素和last元素。拿酒必须知道现在处理的元素的index。本例以sizeof...()
获得元素个数。
以get<index>(t)
取出元素并将index++,…
实现功能:
cout<<make_tuple(7.5,string("hello"),bitset<16>(377),42)
//输出以下结果:
[7.5,hello,0000000101111001,42]
实现源码部分
template<typename...Arg>
ostream& operator<<(ostream& os,const tuple<Args..>& t){
os<<"[";
PRINT_TUPLE<0,sizeof...(Args),Args...>::print(os,t);
return os<<"]";
}
template<int IDX,int MAX,typename...Arg>
struct PRINT_TUPLE{
static void print(ostream& os,const tuplr<Args...>& t){
os<<get<IDX>(t)<<(IDX+1==MAX?"":",");
PRINT_TUPLE<IDX+1,MAX,Args...>::print(os,t);
}
}
template<int MAX,typename... Args>
struct PRINT_TUPLE<MAX,MAX,Args...>{
static void print(std::ostream& os,const tuple<Args...>& t){
}
}
例6——用于递归继承
递归调用 处理的都是参数,使用function template
递归继承 处理的是类型(type),使用class template
原始版存在bug
template<typename... Values> class tuple;
template<> class tuple<> {};
template<typename Head,typename... Tail>
class tuple<Head,Tail...>
: private tuple<Tail...>
{
typedef tuple<Tail...> inherited;
public:
tuple() {}
tuple(Head v,Tail... vtail)
: m_head(v),inherited(vtail...) { }
//vtail调用base ctor并予参数
//注意这是initialization list(调用父类得构造函数)
typename Head::type head() {return m_head;}
inherited& tail() {return *this; }
//return 后转型维inherited 获得得是
protected:
Head m_head;
}
//例:tuple<int,float,string>
t(41,6.3,"nico");
t.head(); //获得41
t.tail(); //获得一个指针
t.tail().head(); //获得6.3
&(t.tail())
修改版
template<typename... Values> class tuple;
template<> class tuple<> {};
template<typename Head,typename... Tail>
class tuple<Head,Tail...>
: private tuple<Tail...>
{
typedef tuple<Tail...> inherited;
protected:
Head m_head;//如果没移上来,编译器会说不认得m_head,这太离谱了吧!
public:
tuple() {}
tuple(Head v,Tail... vtail)
: m_head(v),inherited(vtail...) { }
//vtail调用base ctor并予参数
//注意这是initialization list(调用父类得构造函数)
auto head()->decltype(head) {return m_head;}
inherited& tail() {return *this; }
//return 后转型维inherited 获得得是
}
//测试代码:
tuple<int,float,string> t(41,6.3,"nico");
cout<<sizeof(t)<<endl; //12
cout<<t.head()<<endl; //41
cout<<t.tail().head()<<endl; //6.3
cout<<t.tail().tail().head()<<endl; //nico
例7——用于递归复合,recursive composition
template<typename... Values> class tup;
template<> class tup<> {};
template<typename Head,typename... Tail>
class tup<Head,Tail...>
{
typedef tup<Tail...> composited;
protected:
composited m_tail;
Head m_head;
public:
tup() {}
tup(Head v,Tail... vtail)
: m_tail(vtail...),m_head(v) { }
Head head() {return m_head;}
composited& tail() {return m_tail; }
}
//测试代码
tup<int,float,string> it1(41,6.3,"nico");
cout<<sizeof(it1)<<endl; //16
cout<<it1.head()<<endl; //41
cout<<it1.tail().head()<<endl; //6.3
cout<<it1.tail().tail().head()<<endl; //nico
tup<string,complex<int>,biset<16>,double> it2('Ace',complex<int>(3,8),bitset<16>(377),3.1415926)
cout<<sizeof(it2)<<endl;//40
cout<<it2.head()<<endl; //Ace
cout<<it2.tail().head()<<endl; //(3,8)
cout<<it2.tail().tail().head()<<endl; //0000000101111001
cout<<it2.tail().tail().tail().head()<<endl; /3.1415926
15.C++ Keywords
17.Rvalue references and Move semantics(右值引用和移动语义)
右值引用是一种新的引用类型,可以用来解决不必要的复制和可实现的完美转发问题。当赋值的右侧为右值时,则左侧对象可以从右侧对象中窃取资源,而不是执行单独的操作,因此这称为移动语义
。
Lvalue:可以出现于operator=左侧者
Rvalue:只能出现于operator=右侧者
//以int为例:
int a = 9;
int b = 4;
a = b;
b = a;
a = a + b;
a + b = 42; //错误
//以string为例:
string s1("Hello");
string s2("World");
s1 + s2 = s2; //竟然通过编译
cout<<"s1:"<<s1<<endl; //s1:Hello
cout<<"s2:"<<s2<<endl; //s2:World
string()="World"; //竟然可以对temp obj赋值
//以complex为例:
complex<int> c1(3,8),c2(1,0);
c1 + c2 = complex<int>(4,9); //c1+c2可以当作Lvalue吗(可以)
cout<<"c1:"<<c1<<endl; //c1:(3,8)
cout<<"c2:"<<c2<<endl; //c2:(1,0)
complex<int>()=complex<int>(4,9);//竟然可以对temp obj赋值
C++及其用户定义的类型(string和complex)引入了一些关于可修改性和可分配性的细微差别,从而导致了这个定义的不正确。
Rvalue references
Rvalue:只能出现于operator=右侧者
int foo() { return 5; }
...
int x = foo(); //ok
int* p= &foo(); //[Error] 对于Rvalue取其reference,不可以。没有所谓Rvalue reference(before C++ 0x)
foo()=7; //[Error]
当Rvalue出现于operator=(copy assignment)的右侧,我们认为对其资源进行偷取/搬移(move)而非拷贝(copy)是可以的,是合理的。
那么:
- 必须有语法让我们在
调用端
告诉编译器,“这是个Rvalue”。 - 必须有语法让我们在
被调用端
18.Perfect Forwarding
19.写一个Move-aware class
20.Move-aware class对容器的效能测试
21.容器—结构与分类_旧与新的比较—关于实现手法
22.容器array
23.容器Hashtable
24.Hash function
25.Tuple
后记
持续更新!