C++11特性总结

C++11新特性总结

  • 一、语法
    • 1、auto
    • 2、decltype
    • 3、nullptr
    • 4、final
    • 5、override
    • 6、=default和=delete
    • 7、lambda表达式
    • 8、move
  • 二、STL新内容
    • 1、array
    • 2、forward_list
    • 3、unordered_map和unordered_set
  • 三、智能指针
    • 1、shared_ptr
    • 2、unique_ptr
    • 3、weak_ptr

一、语法

1、auto

自动推导变量数据类型,用于从初始化表达式中推断出变量的数据类型。类型确定后,不能重复初始化。

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
#include 
#include 
using namespace std;

struct testStruct
{
   int a;
   int b;
};

int main()
{
   // auto
   cout << "Hello World!\n";
   auto a = 5;                      	//int类型
   //a = "ding";                 	//错误,不能重复初始化,a已推到出int类型
   auto b = "testing";                	//不是string类型
   string c = "testing";
   //auto d = b.c_str();
   auto e = c;                      	//string类型
   cout << e.c_str() << endl;
   char* f = new char[5];
   auto g = f;                      	//char*类型
   f[5] = 0;
   g[5] = 0;
   f = NULL;
   g = NULL;
   auto* h = new testStruct;        	//自定义testStruct类型
   h->a = 5;

   vector<int> i;
   vector<vector<int>> j;
   map<int, string> k;
   k[1] = "aaa";
   map<int, string>::iterator it = k.begin();
   auto l = i;                      	//vector类型
   auto m = j;                      	//vector>类型
   auto n = k;                      	//map
   //auto ll = i, mm = j, nn = k;    	//错误写法,编译失败
   auto ll = 5, mm = 6, nn = 7;      	//正确写法,在声明符列表中,auto必须始终推到为同一类型
   auto iter = k.begin();          	//map::iterator类型

   double a2 = 3.144;
   const auto a3 = a2;                	//const double
   auto a4 = a2;                 	//double
   volatile int c2 = 3;
   auto c3 = c2;                 	//int

   return 0;
}

for循环迭代差异:

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
using namespace std;

int main()
{
    //C++98
    vector<int> vec(8, 1);
    cout << "C++98 range for:" << endl;
    for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++)
    {
        cout << *it << endl;
    }

    //C++11
    cout << "C++11 range for:" << endl;
    for (auto d : vec)   		//d中存储的是vec中的值
    {
        d = 2;				//不会改变vector中的值,d只是一个临时变量;
    }

    for (auto d : vec)			//d中存储的是vec中的值
    {
        cout << d << endl;
    }

    for (auto &d : vec)  		
    {
        d = 6;				//可以改变vector中的值
    }

    for (auto d : vec)   
    {
        cout << d << endl;
    }

    //数组for_each
    char arr[] = {'a','b','c','d'};
    for (auto &d : arr)
    {
        d -= 32;
    }
    for (auto d : arr)
    {
        cout << d << endl;
    }

    //遍历二维数组,注意迭代变量row必须为引用。如果你想用 range for 的方法,来遍历更高维的数组 (dim > 2),那么你只需要:除了最内层循环之外,其他所有外层循环都加入 '&' 即可。
    int array2[5][5] = {0};
    for (auto &row : array2)
        for (auto col : row)
            cout << col << endl;

    return 0;
}

2、decltype

通过一个变量或表达式得到变量的数据类型,并不会真正的执行表达式;

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
#include 
#include 
using namespace std;

int func(int a)
{
    cout << "this is func, int type param" << endl;
    return a;
}

string func(string b)
{
    cout << "this is func, string type param" << endl;
    return "this is func, string type param";
}

void func()
{
 cout << "this is func, without return value" << endl;
}

int main()
{
    // decltype
    map<int, int> aa;
    aa[5] = 6;
    decltype(aa) bb = aa;		//map类型
    bb[1] = 2;
    map<int, int>::iterator cc = aa.begin();	
    decltype(cc) dd = cc;		//map::iterator类型
    decltype(func(5)) ee = 5;     	//int类型,并不会执行func(5)
    decltype(func("5")) ff = "5";	//string类型,并不会执行func("5")
    // decltype(func()) gg = 6;		//编译错误,非法使用void类型
    
    return 0;
}

3、nullptr

nullptr是一个表示空指针的标识。NULL只是一个定义为常整数0的宏,而nullptr是C++11的一个关键字,一个內建的标识符。

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 

using namespace std;

void fun(int a)
{
    cout << "call function: [int]\n" << endl;
}

void fun(int *a)
{
    cout << "call function: [int*]\n" << endl;
}

int main()
{
    fun(NULL);   		//调用void fun(int a)
    fun(nullptr);  		//调用void fun(int *a)

    int* p = NULL;
    fun(p);  			//调用void fun(int *a)

    return 0;
}

4、final

final使用方法有以下三种:

  1. 修饰变量
    final修饰基本数据类型的变量时,必须赋予初始值且不能被改变,修饰引用变量时,该引用变量不能再指向其他对象。
  2. 修饰方法
    代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。
  3. 修饰类
    final类通常功能是完整的,它们不能被继承。

C++11中的final不能修饰变量,修饰方法和类与java中final功能相同。

class Base final             	//final修饰类
{
public:
    int a;

    //final int b;          	//final不能修饰变量

    virtual void func() final; 	//final修改函数,不能被子类重写
};

class Last final : Base       	//Base不能被继承
{

};

5、override

允许派生类显示的注明它将使用那个成员函数改写基类的虚函数,必须是virtual修饰的虚函数。
与java中的@override功能类似,标识重写(覆盖)。

struct B
{
    virtual void f1(int) const;
    virtual void f2();
    void f3();
};

struct D1 : public B
{
    void f1(int) const override;  		//ok
    void f2(int) override;   			//error,B中没有形如f2(int)的函数
    void f3() override;  			//error,f3不是虚函数
    void f4() override;  			//error,B中无f4函数
};

6、=default和=delete

对于 C++ 的类,如果程序员没有为其定义特殊成员函数,那么在需要用到某个特殊成员函数的时候,编译器会隐式的自动生成一个默认的特殊成员函数,比如拷贝构造函数,或者拷贝赋值操作符。

C++11允许我们使用=default来要求编译器生成一个默认构造函数,也允许我们使用=delete来告诉编译器不要为我们生成某个默认函数

class B
{
    B() = default; 			//显示声明使用默认构造函数
    B(const B&) = delete; 		//禁止使用类对象之间的拷贝
    ~B() = default;  			//显示声明使用默认析构函数
    B& operator=(const B&) = delete;  	//禁止使用类对象之间的赋值
    B(int a);  
};

7、lambda表达式

lambda的基础语法定义如下:

[capture](parameters) mutable ->return-type{statement}
  1. [capture]:捕获列表。它总是出现在lambda函数的开始位置。在编译器看来[]是lambda的引出符号,编译器正式通过它来判断接下来的代码是否是lambda函数。捕获列表能够捕捉当前上下文中的变量供给lambda函数使用。具体的capture列表中的语法,下面还会详细讲述。

  2. (parameters):参数列表。它跟一般函数的参数列表一样,使用规则也相同。在lambda中,如果不需要传入参数,可以省略。

  3. mutable:修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消它的常量属性。显示指定mutable修饰符的时候,参数列表不能省略。

  4. ->return-type:返回值类型。->这个同C++11新引入的追踪返回值类型的声明是一致的,语法也是一致的。不同的是,处于方便,lambda函数在没有返回值的情况下,可以省略掉(在某些编译器可以推导出返回值类型的情况亦可省略)。

  5. {statement}:函数体。与一般函数的函数体一致,额外可以使用捕获列表中捕获的变量。

直观感受下lambda的使用:

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 

using namespace std;

int main()
{
    auto f = []() {cout << "hello world!" << endl; };
    f();  				//hello world!

    int a = 123;
    auto f1 = [a] { cout << a << endl; };
    f1();  				//123

    auto f2 = [&a] {cout << a << endl; };
    a = 789;
    f2();  				//789

    //隐式捕获:让编译器根据函数体中的代码来推断需要捕获哪些变量
    auto f3 = [=] {cout << a << endl; };
    f3();  				//789

    auto f4 = [&] {cout << a << endl; };
    a = 990;
    f4();  				//990

    auto f5 = [](int a, int b)->int {return a + b; };
    printf("%d\n", f5(1, 2));  		//3

    return 0;
}

lambda函数相较普通函数调用最便捷之处就是其捕获列表,它可以通过值传递捕获或者引用传递方式捕获,直接在函数体内访问到上下文(一个代码块内)的变量。

如果是普通函数的话,这些都要以参数形式传递进去,使代码十分冗长。那么捕获列表的具体语法可以归纳如下:

  1. [a]表示值传递捕获变量a(多个参数可以用逗号分隔)

  2. [=]表示值传递捕获上下文所有变量

  3. [&a]表示引用传递捕获变量a

  4. [&]表示引用传递捕获上下文所有变量

  5. [this]表示值传递捕获当前的this指针

  6. [=, &a, &b]表示值传递捕获上下文所有变量,但是a、b变量以引用传递方式捕获。

  7. [&, a, this]表示引用传递捕获上下文所有变量,但是a和this指针以值传递方式捕获

    char c = 'a';
    float d = 1.11f;
    foo([=](int param)->void
    {
        std::cout << "lambda param is :"<< param <<std::endl;
        std::cout << "lambda cap is :" << c << std::endl;
        std::cout << "lambda cap2 is :" << d << std::endl;
    }, 123);

8、move

是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
#include 
#include 
int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;
    
    //调用常规的拷贝构造函数,新建字符数组,拷贝数据
    v.push_back(str);
    std::cout << "After copy, str is \"" << str << "\"\n"; //After move, str is "Hello"
    
    //调用移动构造函数,掏空str,掏空后,最好不要使用str
    v.push_back(std::move(str));
    std::cout << "After move, str is \"" << str << "\"\n";   //After move, str is ""
    std::cout << "The contents of the vector are \"" << v[0]
        << "\", \"" << v[1] << "\"\n";   //The contents of the vector are "Hello", "Hello"
}

二、STL新内容

1、array

  1. 使用 std::array保存在栈内存中,相比堆内存中的 std::vector,我们就能够灵活的访问这里面的元素,从而获得更高的性能;同时正式由于其堆内存存储的特性,有些时候我们还需要自己负责释放这些资源。

  2. 使用std::array能够让代码变得更加现代,且封装了一些操作函数,同时还能够友好的使用标准库中的容器算法等等,比如 std::sort。

std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array 很简单,只需指定其类型和大小即可:

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
#include 

void foo(int* p)
{

}

int main()
{
    std::array<int, 4> arr = {4,3,1,2};

    foo(&arr[0]);  				//OK
    foo(arr.data());  				//OK
    //foo(arr);  				//wrong
    std::sort(arr.begin(), arr.end());  	//排序

    return 0;
}

2、forward_list

std::forward_list 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率。

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
#include 
#include 
#include 

int main()
{
    std::forward_list<int> list1 = { 1, 2, 3, 4 };

    //从前面向foo1容器中添加数据,注意不支持push_back
    list1.pop_front();  		//删除链表第一个元素
    list1.remove(3);   			//删除链表值为3的节点
    list1.push_front(2);
    list1.push_front(1);
    list1.push_front(14);
    list1.push_front(17);

    list1.sort();

    for (auto &n : list1)
    {
        if (n == 17)
            n = 19;
    }

    for (const auto &n : list1)
    {
        std::cout << n << std::endl;  	//1 2 2 4 14 19
    }

    return 0;
}

3、unordered_map和unordered_set

无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant),在不关心容器内部元素顺序时,能够获得显著的性能提升。

C++11 引入了两组无序容器:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。
下面给出unordered_map和unordered_set的使用方法。

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
#include 
#include 
#include 
#include 

void foo(int* p)
{

}

int main()
{
    //unordered_map usage
    std::unordered_map<std::string, int> um = { {"2",2},{"1",1},{"3",3} };

    //遍历
    for (const auto &n : um)
    {
        std::cout << "key:" << n.first << "  value:" << n.second << std::endl;
    }

    std::cout << "value:" << um["1"] << std::endl;


    //unordered_set usage
    std::unordered_set<int> us = { 2,3,4,1};

    //遍历
    for (const auto &n : us)
    {
        std::cout << "value:" << n << std::endl;
    }

    std::cout << "value:" << us.count(9) << std::endl; 		//判断一个数是否在集合内,1存在0不存在
    std::cout << "value:" << *us.find(1) << std::endl;  	//查找一个特定的数是否在集合内,找到就返回该数的迭代器位置

    return 0;
}

三、智能指针

1、shared_ptr

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。赋值非原子操作,多线程使用需加锁

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
#include 

int main()
{
    //auto ptr = std::make_shared(10);
    std::shared_ptr<int> ptr(new int(10));
    std::shared_ptr<int> ptrC(ptr);

    auto ptr2 = ptr;

    {
        auto ptr3 = ptr2;
        std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  		//4
        std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  		//4
    }

    std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  		//3
    std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  		//3

    int *p = ptr.get(); //获取原始指针

    std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  		//3
    std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  		//3

    return 0;
}

2、unique_ptr

一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全。

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
#include 

int main()
{
    std::unique_ptr<int> ptr(new int(10));
    //auto ptr2 = ptr; 		//非法

    //虽说unique_ptr是不可复制的,但我们可以使用std::move将其独占权转移到其他的unique_ptr
    auto ptr2(std::move(ptr));
    std::cout << *ptr2 << std::endl;

    return 0;
}

3、weak_ptr

weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。

// testC++11.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include 
#include 
#include 
#include 
#include 
using namespace std;


#include 
#include 
using namespace std;

class A;
class B;

class A {
public:
    A() {
        cout << "A !" << endl;
    }
    ~A() {
        cout << "~~~~~A !" << endl;
    }
    void setBB(shared_ptr<B> val) {
        bb = val;
    }
private:
    //shared_ptr bb;
    weak_ptr<B> bb;             //用循环引用的指针之一为weak_ptr,来解决循环引用
};


class B {
public:
    B() {
        cout << "B !" << endl;
    }
    ~B() {
        cout << "~~~~~~B !" << endl;
    }
    void setAA(shared_ptr<A> val) {
        aa = val;
    }
private:
    shared_ptr<A> aa;
};

int main()
{
    shared_ptr<A> aa(new A());
    shared_ptr<B> bb(new B());
    aa->setBB(bb);
    bb->setAA(aa);

    return 0;
}

参考链接:
https://www.cnblogs.com/skyfsm/p/9038814.html
https://www.cnblogs.com/lenmom/p/9198126.html
https://www.jianshu.com/p/5ea6e6feeaf3
https://www.jianshu.com/p/234b818f289a

你可能感兴趣的:(C++)