1.auto
它的功能为类型推断。auto是一个类型的占位符,通知编译器去根据初始化代码推断所声明变量的真实类型。各种作用域内声明变量都可以用到它。
auto i=42; //i is an int auto l=42LL; //l is an long long auto p=new foo(); //p is a foo
在遍历STL容器时需要声明迭代器(iterator),现在不需要声明typedef就可以得到简洁的代码了。
std::map<std::string,std::vector<int>> map; for( auto it = begin(map); it != end(map); ++it ){}
需要注意的是,auto不能用来声明函数的返回值,但如果函数有一个尾随的返回类型时,auto可以出现在函数声明中返回值位置。在这种情况下,auto并不是告诉编译器去推断返回类型,而是指引编译器去函数的末端寻找返回值类型。
template< typename T1, typename T2> auto compose( T1 t1, T2 t2) -> decltype( t1 + t2 ) { return t1+t2; } auto v = compose( 2, 3.14); //v‘s type is double
2.nullptr
以前都是用0来表示空指针的,但由于0可以被隐式类型转换为整型,这就出现了一些问题。
void f( int ); void f( char * p );
如果存在以上两种方法,调用f(0)时,C++98编译失败,但在C++11中调用的是f(int)方法;想要调用f(char *p)调用方式为:f( nullptr )。
nullptr和任何指针类型以及类成员指针类型的空值之间可以发生隐式类型转换,同样也可以隐式转换为bool型(取值为false)。但是不存在到整型的隐式类型转换。
为了向前兼容,0仍然是个合法的空值指针。
3.Rang-based for loops(基于范围的for循环)
思考问题:对vector<int>中的每个元素加1。
void add( int & a) { a += 1; } int intArray[] = {1,2,3,4,5}; vector< int > intVector(intArray,intArray+5);
第一种原始做法:
for(vector<int>::iterator iter = intVector.begin(); iter != intVector.end(); iter++ ) { add(*iter); }
第二种使用boost的foreach:
#include <boost/foreach.hpp> BOOST_FOREACH(int& a,intVector) { add(a); }
第三种使用for_each:
#include <algorithm> for_each(intVector.begin(),intVector.end(),add);
第四种就是C++11扩展的for语句:
for( int& e : intArray ) { e = e+1; }
用这种新的写法可以遍历数组、初始化列表以及任何重载了的非成员的begin和end函数的类型。
4.Override和final
vitual关键字是可选的,这使得阅读代码变得很费劲。因为可能需要追溯到继承体系的源头才能确定某个方法是否是虚函数。为了增加可读性,我总是在派生类里也写上virtual关键字。即使这样,仍然会产生一些微妙的错误。所以C++11加入了两个新的标识符。
override标识符:指定在基类中的虚函数应该被重写。
final标识符:指定派生类中的函数不会重写基类中的虚函数。
class B { public: virtual void f(int) {std::cout << "B::f" << std::endl;} }; class D : public B { public: virtual void f(int)override final {std::cout << "D::f" << std::endl;} }; class F : public D { public: virtual void f(int) {std::cout << "F::f" << std::endl;} };
上述程序会报错:“D::f”声明为“final”的函数无法被“F::f”重写。
5.Stong-typed enums 强类型枚举
传统的C++枚举类型存在一些缺陷:它们会将枚举常量暴露在外层作用域中(这可能导致名字冲突,如果同一个作用域中存在两个不同的枚举类型,但是具有相同的枚举常量就会冲突),而且它们会被隐式转换为整型,无法拥有特定的用户定义类型。
enum A { a, b }; //默认从0开始赋值,依次加1 //enum B { a, c }; //报错a重定义:以前的定义为枚举数cout << a << "," << b ; //输出为0,1
在C++11中通过引入了一个称谓强类型枚举的新类型,修正了这种情况,强类型枚举由关键字enum class标识。它不会将枚举常量暴露到外层作用域中,也不会隐式的转换为整型,并且拥有用户指定的特定类型。
enum class A { a, b}; enum class B { a, c}; //不会报错 //cout << a <<endl; //报错:a未声明的标识符 //cout << A::a <<endl; //报错:<<没有找到接受A类型的右操作的运算符 cout << (int)A::a <<endl; //输出0
6.Smart Pointers 智能指针
1)unique_ptr:如果内存资源的所有权不需要共享,就用这个(它没有拷贝构造函数),但是它可以转让给另一个unique_ptr(存在move构造函数)。
void foo(int* p) { std::cout << *p << std::endl; } std::unique_ptr<int> p1(new int(42)); // 声明一个智能指针p1,初始化值为42 std::cout << * p1 << std::endl; // 输出42 std::unique_ptr<int> p2 = std::move(p1); // p1转让给p2,p1变为空,自动释放内存 //std::cout << * p1 << std::endl; // 出错:程序被中断,因为p1已被置为空 std::cout << p1.get() << std::endl; // 输出为00000000,get函数返回的是nullptr std::cout << * p2 << std::endl; // 输出42 (*p2)++; // p2指向内容加1 if(p2) foo(p2.get()); // 输出43
2)shared_ptr:如果内存资源需要共享,那么使用这个。
void foo( int * p ) { cout << *p <<endl; } void bar( std::shared_ptr< int > p) { ++(*p); } std::shared_ptr< int > p1( new int(42) ); //声明一个智能指针p2,初始化为42 std::shared_ptr< int > p2 = p1; //把p1共享给p2 bar( p1 ); //改变p1的值,同时也在改变p2的值 foo( p2.get() ); //输出为43
其中第一个声明也可以写成:
auto p3 = std::make_shared<int>(42);
make_shared<T>是一个非成员函数,使用它的好处是可以一次性分配共享对象和智能指针自身的内存。而显示地使用shared_ptr构造函数来构造至少需要两次内存分配。除了会产生额外的开销,还可能会导致内存泄漏。在下面的例子中,seed()抛出一个错误就会产生内存泄漏。
void foo( std::shared_ptr<int> p,int init ) { * p = init; } foo( std::shared_ptr<int>(new int(42)),seed());
3)weak_ptr:持有被shared_ptr所管理对象的引用,但是不会改变引用计数值。它被用来打破依赖循环(想象有一个tree结构中,父节点通过一个共享所有权的引用(chared_ptr)引用子节点,同时子节点又必须持有父节点的引用。如果这两个引用也共享所有权,就会导致一个循环,最终两个节点内存都无法释放)。
auto p = std::make_shared<int>(42); std::weak_ptr<int> wp = p; //wp只是获得了观测权,没有共享资源 auto sp = wp.lock(); //lock()从观测的shared_ptr获得一个可用的shared_ptr对象 std::cout << *sp << std::endl; p.reset(); //reset()销毁函数 if(wp.expired()) //expired()==true表示wp所指对象已经销毁 std::cout << "expired" << std::endl;
7.Lambdas(匿名函数)
语法形式:[函数对象参数](操作符重载函数参数) mutable或exception声明 -> 返回值类型{函数体}。
1)函数对象参数有以下几种形式:
空:没有使用任何函数对象参数。
=:函数体内可以使用Lambda所在作用范围内所有可见的局部变量,并且是值传递方式。
&:函数体内可以使用Lambda所在作用范围内所有可见的局部变量,并且是引用传递方式。
this:函数体内可以使用Lambda所在类中的成员变量。
a:将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的,要修改传递进来的a的拷贝,可以添加mutable修饰符。
&a:将a按引用进行传递。
a,&b:将a按值进行传递,b按引用进行传递。
=,&a,&b:除a和b按引用进行传递外,其他参数都按值进行传递。
&,a,b:除a和b按值进行传递外,其他参数都按引用进行传递。
2)操作符重载函数参数:没有参数时,这部分可以省略。 参数可以通过按值和按引用两种方试进行传递。
3)mutable或exception声明:这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)。
4)->返回值类型:标识函数返回值的类型。当返回值为void,或者函数体中只有一处return的地方,这部分可以省略。
5)函数体:标识函数的实现,这部分不能省略,但函数体可以为空。
vector<int> iv{5, 4, 3, 2, 1}; int a = 2, b = 1; for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<<endl;}); // 输出6 5 4 3 2 for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);}); // iv每个数乘以3for_each(iv.begin(),iv.end(),[](int &x){cout<<x<<endl;}); //输出15 12 9 6 3 for_each(iv.begin(), iv.end(), [=](int &x)->int{return x * (a + b);}); //只是返回每个数的3倍,但是不改变原来数组的数 for_each(iv.begin().iv.end(),[](int &x){cout<<x<<endl;}); //输出为15 12 9 6
再举一个例子:
std::vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); std::for_each(std::begin(v), std::end(v), [](int n) {std::cout << n << std::endl;}); //输出为1 2 3 auto is_odd = [](int n) {return n%2==1;}; //定义了一个函数is_odd,返回1(不能够被2整数)或0(能被2整数) auto pos = std::find_if(std::begin(v), std::end(v), is_odd); //find_if查找符和条件的数据,返回指向数据的指针 if(pos != std::end(v)) std::cout << *pos << std::endl; //第一个符合条件的数据为1,输出为1
更复杂的是递归lambda。
std::function<int(int)> lfib = [&lfib](int n) {return n < 2 ? 1 : lfib(n-1) + lfib(n-2);}; cout<<lfib(5)<<endl;;
(暂时更新到这里,后续会继续更新)