初识C++的时候,以为会个STL就差很少了,后来发现了C++11这个东西,以及C++14,C++17QAQ,看了一下,好高深不学,emmmm真香= =node
这里就只讲一下对ACM写代码有很高帮助的部分特性,由于大部分OJ和比赛只支持11,因此14和17就不讲了,而后还有C++11增长的元组tuple和几个容器另谈。python
在以前版本的c++中,NULL的值其实就是0,由于其实就是#define NULL 0,有些时候是((void *) 0)emmm,不说那些废话了。因此这些就会遇到一个问题,c++
例以下面这两个函数正则表达式
1 int fun(char* ch){} 2 int fun(int num){}
当char ch = NULL,这样一个值去代入函数时,编译会调用下面那个函数,而不是第一个。而nullptr类型是nullptr_t,就是专门为了区别空指针和0,因此之后写代码nullptr代替NULL就能行了。算法
constexpr变量必须是一个常量,必须用常量表达式来初始化,例以下面代码安全
1 const int a = 10; 2 int b = 10; 3 constexpr int d = 10; //正确 4 constexpr int c = a + 10; //正确 5 constexpr int e = b; //错误
值得一提的是若是成员函数标记为constexpr,则默认其是内联函数,若是变量声明为constexpr,则默认其是const,这个会在构造函数中用到。多线程
其实之前的c++中就有auto,可是emmm,废话不说,直接说内容吧,有了auto就很方便了。好比下面这个代码闭包
1 map<int,int>::iterator it = mp.begin(); 2 auto it = mp.begin();
谁方便一眼便知道了吧,auto能够把自动推导成变量或者函数。dom
好比函数
1 int fun(int i){ 2 cout << i << endl; 3 } 4 5 function<int(int)> f; 6 auto a = fun;
这里auto其实就和自动推导成了function<int(int)>类型。值得注意的时,定义auto类型变量必须赋初值,否则则会编译错误。还有就是函数的返回值不能直接用auto代替,例如
auto fun(int i){ }
这是会报错的,可是不是函数的返回值就不能是auto类型了呢,实际上是能够的,但只是不能直接这样定义,下面咱们会讲到如何把函数返回值定义为auto。
顾名思义,就是推导一个变量的类型是啥,和sizeof用法相似,它的出现就是为了弥补auto的缺陷。
auto a = 1,b = 2; decltype(a+b) z;
上面不是说怎么把函数值弄为auto吗,其实在之前得c++中也能够用typename,好比
1 template<typename R, typename T, typename U> 2 R add(T x, U y) { 3 return x+y 4 }
当有多个这种函数的时候,就会显得代码很冗长,很不人性化qwq,因此就出现了拖尾返回类型,例以下面这个代码
1 auto fun(int i) -> bool{ 2 return i&1; 3 }
这个代码编译是没有问题的,后面的->bool就是说明函数的返回类型是布尔类型,因此就可使用auto做为函数类型,固然也能够和decltype连用写出下面的代码。
1 template<typename T> 2 auto fun(T i) -> decltype(i*i){ 3 return i*i; 4 }
简洁易懂,并且看着特别舒服有木有。
在其余语言里面常常能够看到for(a : b)这样的循环用法,而在c++里面,你要是想对一个容器进行区间迭代,你得这样写
1 for(vector<int>::iterator iter = vec.begin(); iter != vec.end(); iter++)
虽然看上去很整洁,但也太不人性化了吧,因此11就引用了:进行区间迭代,如今你能够直接用auto和:写出下面的代码
1 for(auto &iter : vec)
这真的很coool
当多个容器叠加的时候,咱们都是用空格将>>区分开,由于在之前版本的c++中>>是会当作右移操做符号的,可是11更新后,你彻底不用担忧这个问题,例以下面代码,在11中编译是没有任何问题的
1 vector<vector<int>> q;
在之前的c++版本中,当咱们须要对一个结构体或者类赋初值,咱们得写一个函数,例如
1 struct node{ 2 int a,b; 3 node(int _a,int _b){this->a = _a,this->b = _b;} 4 node(int _a,int _b):a(_a),b(_b){} 5 };
但如今,c++11有五种不一样的方式对各类变量初始化,你能够直接用大括号赋值,下面提供了几种不一样的结构体赋值方法。
1 struct node{ 2 int a,b; 3 }; 4 5 node asd{1,2}; 6 node as = {1,2};
对于其余变量和容器也适用。
typedef你们确定使用过,通常将一个类或者结构体作别名,但对于容器,好比下面用法
1 template<typename T> 2 typedef vector<T> qwq;
是不合法的,因此c++11引入了using,和typedef功能相同。
1 template<typename T> 2 using asd = vector<T>;
当须要使用vector时候,就直接调用便可。
1 asd<long long> vec;
对于某个函数
1 template<typename T1,typename T2> 2 auto fun(T1 a,T2 b)->bool{ 3 return a < b; 4 }
可是若是你想制定默认模板参数类型怎么办,c++11提供了一种便利,能够直接指定默认参数
1 template<typename T1 = int,typename T2 = int> 2 auto fun(T1 a,T2 b)->bool{ 3 return a < b; 4 };
Lambda表达式是用来建立匿名函数的。什么叫作匿名函数,就是没有名字qwq,那么为何要用到匿名函数呢,举个例子,好比sort的第三个参数,谓词函数,通常咱们会写一个比较函数,但这样的后果就是,若是有多个sort你就得,每一个函数去找他的比较函数,实属麻烦。各位看下面这个例子
1 sort(a.begin(),a.end(),[](int a,int b)->bool{return a < b;});
就算你不懂什么是lambda表达式,但你也能猜到这个sort就是按照a<b比较吧。那么lambda表达式是什么样子的呢。大体就是
1 [capture](parameters)opt->return-type{body}
emmm 后面那一截和普通函数没什么区别,参数,返回值类型,函数体。和函数最大的不一样就是匿名函数是临时函数,没有函数名,及拿及用,固然你也能够用function保存一个匿名函数。
至于opt是个什么东西= =下面说,这个通常不会用上。
其中:
1.返回值类型->return-type能够省略,由语言自动推导,但只限于lambda表达式语句简单。
2.引入lambda表达式的前导符是一对方括号,叫作lambda引入符。lambda表达式可使用与其相同范围内的变量,一共有下面这么几种方式捕获变量
[]//不捕获任何外部变量
[=]//以值形式捕获全部外部变量
[&]//以引用方式捕获全部外部变量
[x,&y]//x以值形式捕获,y以引用形式捕获
[=,&x]//x以引用形式捕获,其他变量以值捕获
[&,x]//x以值形式捕获,其他变量以引用形式捕获
[this]捕获当前类的指针。捕获this的目的是能够在lambda中使用当前类的成员函数和成员变量。
对于[=]和[&],lambda能够直接使用this指针,可是对于[]的形式,若是要使用this指针,必须显式传入
3.opt函数选项
能够选填mutable,exception,attribute。
mutable说明lambda表达式体内的代码能够修改被捕获的变量,而且能够访问被捕获的对象的non-const方法。
exception说明lambda表达式是否抛出异常以及何种异常
attribute用来声明属性
4.lambda表达式不能直接被赋值,闭包类型禁用了赋值操做符号,可是能够用lambda表达式去初始化另外一个lambda表达式,例如
1 auto a = []{cout <<1 << endl;}; 2 auto b = a;
也能够吧lambda表达式赋值给相对应的函数指针,例如
1 function<int(int)> fun = [](int a){return a;};
那么就能够很方便的利用lambda表达式填充各类谓词函数了,例以下面即是一个斐波那契数列
1 array<int,10> a; 2 auto f0 = 0, f1 = 1; 3 generate(a.begin(),a.end(),[&f0,&f1]()->int{int v = f1;f1 += f0, f0 = v;return v;}); 4 for_each(a.begin(),a.end(),[](int v){cout << v << " ";}); 5 cout << endl;;
代码一眼看上去就能知道意思,无需定义额外函数。大部分的STL算法,均可以搭配lambda表达式来实现想要的效果。
什么是右值引用,先看一个例子。
1 string a(x); 2 string b(x+y); 3 string c(fun()); 4 5 //若是使用如下拷贝构造函数 6 string(const string& str){ 7 size_t size = strlen(str.data)+1; 8 data = new char[size]; 9 memcpy(data,str.data,size); 10 }
则只有第一行的x深度拷贝有必要,由于其余地方还可能会用到x,x就是一个左值。但第二行和第三行的参数则是右值,由于表达式产生的匿名string对象,以后无法再用。
c++11引入了一种机制“右值引用”,用&&来表示右值引用,以便咱们经过重载直接使用右值参数,例以下面这个构造函数:
1 string(string&& that){ 2 data = that.data; 3 that.data = 0; 4 }
咱们没有深度拷贝堆内存中的数据,而是仅仅复制了指针,并把源对象的指针置空。事实上,咱们“偷取”了属于源对象的内存数据。因为源对象是一个右值,不会再被使用,所以客户并不会觉察到源对象被改变了。在这里,咱们并无真正的复制,因此咱们把这个构造函数叫作“转移构造函数”,他的工做就是把资源从一个对象转移到另外一个对象,而不是复制他们。
那么赋值操做符就能够写成
1 string& operator=(string that){ 2 std::swap(data, that.data); 3 return *this; 4 }
注意到咱们是直接对参数that传值,因此that会像其余任何对象同样被初始化,那么确切的说,that是怎样被初始化的呢?对于C++ 98,答案是复制构造函数,可是对于C++ 11,编译器会依据参数是左值仍是右值在复制构造函数和转移构造函数间进行选择。
若是是a=b,这样就会调用复制构造函数来初始化that(由于b是左值),赋值操做符会与新建立的对象交换数据,深度拷贝。这就是copy and swap 惯用法的定义:构造一个副本,与副本交换数据,并让副本在做用域内自动销毁。这里也同样。
若是是a = x + y,这样就会调用转移构造函数来初始化that(由于x+y是右值),因此这里没有深度拷贝,只有高效的数据转移。相对于参数,that依然是一个独立的对象,可是他的构造函数是无用的(trivial),所以堆中的数据没有必要复制,而仅仅是转移。没有必要复制他,由于x+y是右值,再次,从右值指向的对象中转移是没有问题的。
转移左值是十分危险的,可是转移右值倒是很安全的。若是C++能从语言级别支持区分左值和右值参数,我就能够彻底杜绝对左值转移,或者把转移左值在调用的时候暴露出来,以使咱们不会不经意的转移左值。
复制构造函数执行的是深度拷贝,由于源对象自己必须不能被改变。而转移构造函数却能够复制指针,把源对象的指针置空,这种形式下,这是安全的,由于用户不可能再使用这个对象了。
有时候,咱们可能想转移左值,也就是说,有时候咱们想让编译器把左值看成右值对待,以便能使用转移构造函数,即使这有点不安全。出于这个目的,C++ 11在标准库的头文件< utility >中提供了一个模板函数std::move。实际上,std::move仅仅是简单地将左值转换为右值,它自己并无转移任何东西。它仅仅是让对象能够转移。
1 unique_ptr<Shape> a(new Triangle); 2 unique_ptr<Shape> b(a); //false 3 unique_ptr<Shape> c(move(a)); //true
请注意,第三行以后,a再也不拥有Triangle对象。不过这没有关系,由于经过明确的写出move(a),咱们很清楚咱们的意图:亲爱的转移构造函数,你能够对a作任何想要作的事情来初始化c;我再也不须要a了,对于a,您请自便。
固然,若是你在使用了mova(a)以后,还继续使用a,那无疑是搬起石头砸本身的脚,仍是会致使严重的运行错误。
总之,move(val)将左值转换为右值(能够理解为一种类型转换),使接下来的转移成为可能。
正则表达式描述了一种字符串匹配的模式。通常使用正则表达式主要是实现下面三个需求:
1) 检查一个串是否包含某种形式的子串;
2) 将匹配的子串替换;
3) 从某个串中取出符合条件的子串。
C++11 提供的正则表达式库操做 string 对象,对模式 std::regex (本质是 basic_regex)进行初始化,经过 std::regex_match 进行匹配,从而产生 smatch (本质是 match_results 对象)。
咱们经过一个简单的例子来简单介绍这个库的使用。考虑下面的正则表达式:
[a-z]+.txt: 在这个正则表达式中, [a-z] 表示匹配一个小写字母, + 可使前面的表达式匹配屡次,所以 [a-z]+ 可以匹配一个及以上小写字母组成的字符串。在正则表达式中一个 . 表示匹配任意字符,而 . 转义后则表示匹配字符 . ,最后的 txt 表示严格匹配 txt 这三个字母。所以这个正则表达式的所要匹配的内容就是文件名为纯小写字母的文本文件。
regex_match 用于匹配字符串和正则表达式,有不少不一样的重载形式。最简单的一个形式就是传入string 以及一个 regex 进行匹配,当匹配成功时,会返回 true,不然返回 false。例如:
1 string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};//`\` 会被做为字符串内的转义符,须要对 `\` 进行二次转义,从而有 `\\.` 2 regex txt_regex("[a-z]+\\.txt"); 3 for (const auto &fname: fnames) 4 cout << fname << ": " << regex_match(fname, txt_regex) << endl;
另外一种经常使用的形式就是依次传入 string/smatch/regex 三个参数,其中 smatch 的本质实际上是 match_results,在标准库中, smatch 被定义为了 match_results,也就是一个子串迭代器类型的 match_results。使用 smatch 能够方便的对匹配的结果进行获取,例如:
1 regex base_regex("([a-z]+)\\.txt"); 2 smatch base_match; 3 for(const auto &fname: fnames) { 4 if (regex_match(fname, base_match, base_regex)) { 5 // sub_match 的第一个元素匹配整个字符串 6 // sub_match 的第二个元素匹配了第一个括号表达式 7 if (base_match.size() == 2) { 8 string base = base_match[1].str(); 9 cout << "sub-match[0]: " << base_match[0].str() << endl; 10 cout << fname << " sub-match[1]: " << base << endl; 11 } 12 } 13 }
代码运行结果为
foo.txt: 1 bar.txt: 1 test: 0 a0.txt: 0 AAA.txt: 0 sub-match[0]: foo.txt foo.txt sub-match[1]: foo sub-match[0]: bar.txt bar.txt sub-match[1]: bar
C++11 引入了委托构造的概念,这使得构造函数能够在同一个类中一个构造函数调用另外一个构造函数,从而达到简化代码的目的:
1 class Base { 2 public: 3 int value1; 4 int value2; 5 Base() { 6 value1 = 1; 7 } 8 Base(int value) : Base() { // 委托 Base() 构造函数 9 value2 = 2; 10 } 11 };
在继承体系中,若是派生类想要使用基类的构造函数,须要在构造函数中显式声明。
倘若基类拥有为数众多的不一样版本的构造函数,这样,在派生类中得写不少对应的“透传”构造函数。以下:
1 struct A 2 { 3 A(int i) {} 4 A(double d,int i){} 5 A(float f,int i,const char* c){} 6 //...等等系列的构造函数版本 7 }; 8 struct B:A 9 { 10 B(int i):A(i){} 11 B(double d,int i):A(d,i){} 12 B(folat f,int i,const char* c):A(f,i,e){} 13 //......等等好多个和基类构造函数对应的构造函数 14 };
若是想要使得函数拥有编译时计算的能力,则使用关键字constexpr
1 class Square { 2 public: 3 constexpr Square(int e) : edge(e){}; 4 constexpr int getArea() {return edge * edge;} 5 private: 6 int edge; 7 }; 8 9 int main() { 10 Square s(10); 11 cout << s.getArea() << endl; 12 return 0; 13 }
itoa等等字符串和数值类型的转换成为历史。
提供了to_string和stox方法,将字符串和数值自由转换;
1 //数值转字符串 2 std::string to_string(int value); 3 std::string to_string(long int value); 4 std::string to_string(long long int value); 5 std::string to_string(unsigned int value); 6 std::string to_string(unsigned long long int value); 7 std::string to_string(float value); 8 std::string to_string(double value); 9 std::wstring to_wstring(int value); 10 std::wstring to_wstring(long int value); 11 std::wstring to_wstring(long long int value); 12 std::wstring to_wstring(unsigned int value); 13 std::wstring to_wstring(unsigned long long int value); 14 std::wstring to_wstring(float value); 15 std::wstring to_wstring(double value); 16 17 //字符串转数值 18 std::string str = "1000"; 19 int val = std::stoi(str); 20 long val = std::stol(str); 21 float val = std::stof(str);
c++11还提供了字符串(char*)转换为整数和浮点类型的方法:
1 atoi: 将字符串转换为 int 2 atol: 将字符串转换为long 3 atoll:将字符串转换为 long long 4 atof: 将字符串转换为浮点数
生成随机数,免去了之前须要自行调用srand初始化种子的步骤,由于有时候忘记初始化致使结果错误。
std::random_device rd;
int randint = rd();
获取时间函数,比之前方便许多。
1 std::chrono::duration<double> duration //时间间隔 2 3 std::this_thread::sleep_for(duration); //sleep 4 5 LOG(INFO) << "duration is " << duration.count() << std::endl; 6 7 std::chrono::microseconds //微秒 8 9 std::chrono::seconds //秒 10 11 end = std::chrono::system_clock::now(); //获取当前时间
1 #include<algorithm> 2 #include<numeric> 3 4 all_of(first,first+n,ispositive());//false 5 6 any_of(first,first+n,ispositive());//true 7 8 none_of(first,first+n,ispositive());//false 9 10 int source[5]={0,12,34,50,80}; 11 int target[5]; 12 //从source拷贝5个元素到target 13 copy_n(source,5,target); 14 15 //iota()算法能够用来建立递增序列,它先把初值赋值给 *first,而后用前置 ++ 操做符增加初值并赋值到给下一个迭代器指向的元素,以下: 16 17 inta[5]={0}; 18 charc[3]={0}; 19 iota(a,a+5,10);//{10,11,12,13,14} 20 iota(c,c+3,'a');//{'a','b','c'}
std::atomic<XXX>
用于多线程资源互斥操做,属c++11重大提高,多线程原子操做简单了许多。
C正则(regex.h)和boost成为历史
新增基于hash的无序容器。
做用于容器,区别于push、insert等,如push_back是在容器尾部追加一个容器类型对象,emplace_back是构造1个新对象并追加在容器尾部
对于标准类型没有变化,如std:;vector<int>,push_back和emplace_back效果同样
如自定义类型class A,A的构造函数接收一个int型参数,
那么对于push_back须要是:
std::vector<A> vec; A a(10); vec.push_back(a);
对于emplace_back则是:
std::vector<A> vec; vec.emplace_back(10);
避免无用临时变量。好比上面例子中的那个a变量。
这个改进仍是有点意义的,平常程序应该能减小很多无心义的内存空间占用
push、insert这类操做会触发容器的capacity,即预留内存的扩大,实际开发时每每这些扩大的区域并无用途
std::vector<int> v{1, 2, 3, 4, 5}; v.push_back(1); std::cout << "before shrink_to_fit: " << v.capacity() << std::endl; v.shrink_to_fit(); std::cout << "after shrink_to_fit: " << v.capacity() << std::endl;
能够试一试,减小了不少。
C++98标准库中提供了一种惟一拥有性的智能指针std::auto_ptr,该类型在C++11中已被废弃,由于其“复制”行为是危险的。
auto_ptr 的危险之处在于看上去应该是复制,但实际上确是转移。调用被转移过的auto_ptr 的成员函数将会致使不可预知的后果。因此你必须很是谨慎的使用auto_ptr ,若是他被转移过。
C++ 11中,std::auto_ptr< T >已经被std::unique_ptr< T >所取代,后者就是利用的右值引用。
<utility>
<unordered_map>
<unordered_set>
<random>
<tuple>
<array>
<numeric>
须要深刻了解的话就本身去找资料了吧qwq,我以为用的最多的可能仍是lambda表达式和区间迭代了吧,愈来愈像python了,qwq但愿有所帮助。