【C++】C++11

简介: 【C++】C++11

C++11

1. 统一的列表初始化

1.1 {}初始化

C++11扩大了用大括号括起来的列表的使用范围。使其适用于所有的内置类型和用户自定义的类型。使用初始化列表时,可以添加等号(=),也可以不添加

2. 声明

2.1 auto

C++11当中将其用于实现自动类型推断,这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

2.2 decltype

将变量的类型声明为表达式指定的类型

2.3 nullptr

#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

3. 右值引用和移动语义

3.1 左值引用和右值引用

传统的C++语法当中就有引用的概念,C++11当中增加了右值引用语法特性。无论是左值引用还是右值引用都是给变量起别

什么是左值?什么是右值?

左值表示一个数据的表达式(如变量名或者解引用的指针),我们可以获取它的地址+可以对它赋值。左值可以出现在赋值符号的左边,右值不能出现在赋值符号的左边

int main() {
    // 以下都是左值
    int* p = new int(0);
    int b = 1;
    const int c = 2;
    // 一下都是左值的引用
    int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& value = *p;
    return 0;
}

什么是右值?什么是右值引用?

右值也是一个数据的表达式。如: 字面常量、表达式返回值、函数返回值(这个不能是左值引用返回)。右值不能出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值起别名。

int main()
{
    double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
    10;
    x + y;
    fmin(x, y);
// 以下几个都是对右值的右值引用
    int&& rr1 = 10;
    double&& rr2 = x + y;
    double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
    10 = 1;
    x + y = 1;
    fmin(x, y) = 1;
    return 0;
}

需要注意的是右值是不能取地址的,但是给右值取别名之后,会导致右值存储到特别的位置,并且可以取到该位置的地址。

int main() {
    double x = 1.1, y = 2.2;
    int&& rr1 = 10;
    const double&& rr2 = x + y;
    rr1 = 20;
    rr2 = 5.5; // 报错
    return 0;
}

3.2 左值引用与右值引用比较

左值引用总结:

  1. 左值引用只能引用左值,不能引用右值
  2. const左值引用既可以引用左值,也可以引用右值

右值引用总结:

  1. 右值引用只能引用右值,不能引用左值
  2. 右值引用可以引用move以后的左值

3.3 右值引用使用的场景和意义

左值引用的短板:

只能使用传值返回,传值返回会导致至少一次拷贝构造

移动构造本质是将参数右值的资源窃取过来,占为已有。那么就不用做深拷贝了,所以叫做移动构造。就是窃取别人的资源来构造自己

不仅仅有移动构造,还有移动赋值

3.4 完美转发

模板当中的&& 不代表右值引用。而是万能引用。既能接收左值又能接收右值。模板的万能引用只是提供了同时接收左值引用又能接收右值引用。但是引用类型的唯一作用就是限制了接收的类型,后续引用中都退化成为了左值。如果在传递的过程当中保持左值或右值的属性,就需要这个完美转发了

forward保证了传参过程中的原生类型属性

强制生成默认函数的关键字default

比如我们提供了拷贝构造,就不会生成移动构造了,我们可以使用default关键字显示指定移动构造生成

禁止生成默认构造函数的关键字delete

4. 可变参数模板

template<class...Args>
void show(Args...args) {
}

递归方式展开参数包:

// 递归终止函数
template <class T>
void ShowList(const T& t)
{
    cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
    cout << value <<" ";
    ShowList(args...);
}
int main()
{
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', std::string("sort"));
    return 0;
}

5. lambda表达式

lambda表达式实际上是一个匿名函数

lambda表达式的书写格式:

[capture-list] (parameters) mutable->return-type{statement}

int main() {
    int a = 3, b = 4;
    [=]{return a + b;};
    auto fun1 = [&](int c){
        b = a + c;
    };
    fun1(10);
    cout << b << " " << a << endl;
    // 复制捕捉x
    int x = 20;
    auto add_x = [x](int a) mutable {
        x *= 2;
        return a + x;
    };
    cout << add_x(10) << endl;
    cout << typeid(a).name() << endl;
    cout << typeid(add_x(10)).name() << endl;
}
  • [var]表示值传递方式,捕捉变量var
  • [=]:表示值传递方式,捕捉所有父作用域中的变量
  • [&var]:表示引用传递捕捉变量var
  • [&] : 表示引用传递所有父作用域中的变量
  • [this]:表示值传递方式捕捉当前的this指针

5.1 函数对象与lambda表达式

函数对象又称为仿函数,是可以像函数一样使用的对象。就是在类中重载了operator()运算符的类对象

6. 线程库

6.1 thread类的简单介绍

在C++11之前,涉及到多线程的问题,都是和平台相关的。window和Linux都有自己的接口,这就让代码的移植性比较差。C++11当中最重要的特性就是对线程进行了支持,使C++在编程时不需要依赖第三方库

注意:

  1. 线程是操作系统中的概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态
  1. 当创建一个线程时, 没有提供线程函数,该对象实际没有对应任何线程
  2. 当创建一个线程对象后,并且线程关联线程函数,这个线程就被启动。与主线程一起运行。线程函数一般按照如下三种方式提供:
  1. 函数指针lambda表达式
  1. 函数对象

6.2 线程函数参数

线程函数参数是以值拷贝的方式拷贝到线程栈空间当中。因此即使线程参数为引用类型,在线程当中修改之后也还是不能修改外部实参。因为实际引用的是线程栈中的拷贝,不是外部实参

两个线程交替打印奇书偶数

#include "iostream"
#include "condition_variable"
#include "mutex"
#include "thread"
 using namespace std;
 //支持两个线程交替打印,t1打印奇数,t2一个打印偶数
int main(){
  mutex mtx;
  condition_variable cv;
  int n = 100;
  int x = 1;
  // 问题1:如何保证t1先运行,t2阻塞?
  // 问题2:如何防止一个线程不断运行?
  thread t1([&, n]() {
    while (true){
      unique_lock<mutex> lock(mtx);
      if (x >= 100)
        break;
      if (x % 2 == 0) // 偶数就阻塞
      {
        cv.wait(lock);
      }
//      cv.wait(lock, [&x]() {return x % 2 != 0; });
      cout << this_thread::get_id() << ":" << x << endl;
      ++x;
      cv.notify_one();
    }
    });
  thread t2([&, n]() {
    while (true){
      unique_lock<mutex> lock(mtx);
      if (x > 100)
        break;
      if (x % 2 != 0) // 奇数就阻塞
      {
        cv.wait(lock);
      }
//      cv.wait(lock, [&x](){return x % 2 == 0; });
      cout << this_thread::get_id() << ":" << x << endl;
      ++x;
      cv.notify_one();
    }
    });
  t1.join();
  t2.join();
  return 0;
}

7. 包装器

function包装器也叫适配器,我们来看看为什么需要包装器?本质上是一个类模板,也是一个包装器。

包装器的一个使用例子:

使用包装器之前的:

class Solution {
public:
    static int evalRPN(vector <string> &tokes)
    {
        stack<int> s;
        for (auto&str : tokes)
        {
            if (str == "+" || str == "-" || str == "*" || str == "/")
            {
                int right = s.top();
                s.pop();
                int left = s.top();
                s.pop();
                switch(str[0])
                {
                    case '+':
                        s.push(left + right);
                        break;
                    case '-':
                        s.push(left - right);
                        break;
                    case '*':
                        s.push(left * right);
                        break;
                    case '/':
                        s.push(left / right);
                        break;
                    default:
                        break;
                }
            }
            else
            {
                s.push(stoi(str));
            }
        }
    }
};

使用包装器之后:

class Solution {
public:
    static int evalRPN(vector <string> &tokes)
    {
        stack<int> st;
        map<string, function<int(int, int)>> opFuncMap ={
                {"+", [](int i, int j){return i + j;}},
                {"-", [](int i, int j){return i - j;}},
                {"*", [](int i, int j){return i * j;}},
                {"/", [](int i, int j){return i / j;}}
        };
        for (auto& str : tokes)
        {
            if (opFuncMap.find(str) != opFuncMap.end())
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                st.push(opFuncMap[str](left, right));
            }
        }
    }
};

8.bind

bind函数定义在头文件当中,是一个函数模板,它就像一个函数包装器(适配器)接收一个可调用对象生成一个新的可调用对象来“适应”原对象的参数列表。

一般而言,我么可以原本接收N个函数的参数的函数fn,通过绑定一些参数,返回一个接收M个参数(M可以大于N)

调用bind的一般形式是:auto newCallable = bind(callable, arg_list)

//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1,placeholders::_2);
相关文章
|
4天前
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
6月前
|
存储 程序员 C++
【C++小知识】基于范围的for循环(C++11)
【C++小知识】基于范围的for循环(C++11)
|
6月前
|
编译器 C语言 C++
【C++关键字】指针空值nullptr(C++11)
【C++关键字】指针空值nullptr(C++11)
|
6月前
|
存储 编译器 C++
【C++关键字】auto的使用(C++11)
【C++关键字】auto的使用(C++11)
|
7月前
|
存储 算法 编译器
【C++11】C++11深度解剖(下)
【C++11】C++11深度解剖(下)
54 0
|
7月前
|
存储 安全 程序员
【C++11】C++11深度解剖(上)
【C++11】C++11深度解剖(上)
51 0
|
7月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
8月前
|
算法 安全 编译器
【C++航海王:追寻罗杰的编程之路】C++11(四)
【C++航海王:追寻罗杰的编程之路】C++11(四)
61 0
|
8月前
|
编译器 C++ 容器
【C++航海王:追寻罗杰的编程之路】C++11(三)
【C++航海王:追寻罗杰的编程之路】C++11(三)
47 0
|
8月前
|
存储 编译器 C++
【C++航海王:追寻罗杰的编程之路】C++11(二)
【C++航海王:追寻罗杰的编程之路】C++11(二)
61 0