【C++】19.C++11

news2024/12/25 12:40:10

1.C++11

  • auto 范围for 新容器 线程库
  • 列表初始化
  • 右值引用和移动语义 lambda表达式
  • 容器支持花括号列表初始化 本质是增加一个initializer_list的构造函数
  • initializer_list支持花括号

2.列表初始化

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <map>
using namespace std;
//列表初始化 不过我们要能看懂 实际中用的也不多
class Point
{
public:
    Point(int x = 0, int y = 0)
        :_x(x),
        _y(y)
    {}

private:
    int _x;
    int _y;
};


int main()
{
    int x = 1;
    //int y{ 2 };//C++11支持

    //C++11 花括号初始化
    vector<int> v1{ 1,2,3,4,5 };//C++11
    vector<int> v2 = { 1,2,3,4,5 };

    list<int> l1{ 1,2,3,4,5 };//C++11
    list<int> l2 = { 1,2,3,4,5 };

    map<string, int> m1{ {"苹果",1},{"西瓜",2},{"香蕉",3} };//C++11
    map<string, int> m2 = { {"苹果",1},{"西瓜",2},{"香蕉",3} };

    Point p1(1, 2);
    Point p2{ 1,2 };
    Point p3 = { 1,2 };
    //容器如何支持花括号? 构造函数

    return 0;
}

利用花括号进行初始化 容器支持花括号需要构造函数

3.类型推导

//类型推导 属于RTTI run time type identification
//程序运行起来对对象的类型识别
//(了解)
int main()
{
    int a = 10;
    double b = 20;
    auto c = a + b;

    //typeid(c)构造对象 .name 调用成员函数 拿到类型名称
    cout << typeid(c).name() << endl;

    string s;
    //拿到类型名称的字符串
    cout << typeid(s).name() << endl;

    //假设我想定义一个跟c和s一样类型的对象
    //decltype通过对象去推类型
    decltype(c) d;
    decltype(s) e;

    cout << typeid(d).name() << endl;
    cout << typeid(e).name() << endl;
    return 0;
}

auto 可推导

decltype 通过对象去推类型

typeid().name() 拿到类型名称的字符串

4.auto和范围for

//auto和范围for->简化代码的写法
//熟悉
int main()
{
    std::map<std::string, std::string> dict{ {"insert","插入"},{"sort","排序"} };
    std::map<std::string, std::string>::iterator it1 = dict.begin();
    auto it2 = dict.begin();
    //这里it1和it2可以认为是完全一个类型的对象
    //唯一差别是it2的类型是编译器自动推导出来
    //auto的优势就是可以把在类型比较复杂地方 简化代码的写法

    //这里要注意当容器村对象比较大 或者这个对象要做深拷贝 如string
    //最好给&和const 可以减少拷贝提高效率
    //容器支持范围for原理 范围for会被编译器替换成迭代器 也就是意味着支持迭代器就支持范围for
    //for(auto e : dict)
    for (const auto& e : dict)
    {
        cout << e.first << ":" << e.second << endl;
    }
    //auto不能去做形参和返回值的
    //正常是这样的
    //for (std::pair<const std::string,std::string>& e: dict)
    //{
    //	cout << e.first << ":" << e.second << endl;
    //}

    //除了STL的容器使用范围for 数组也可以(原生指针可以认为也是天然迭代器 比如
    //vector/string的迭代器就是原声指针)
    int a[] = { 1,2,3,3,5,4 };
    for (auto e : a)
    {
        cout << e << " ";
    }
    cout << endl;
    return 0;
}

5.final和override

final与override(了解)

这两个关键字分别的作用是什么?

final修饰类 类就变成了最终类 不能被继承

final还可以修饰虚函数 这个虚函数不能被重写

override是子类重写虚函数 检查是否完成重写

不满足重写的条件 则报错

6.C++11新容器

新容器

C++98容器

string/vector/list/map/set + stack/queue/priority_queue

C++11新容器

array(定长数组)/forword_list(单链表)

**array(**定长数组):实际中用的很少

缺点:定长+存储数据的空间在栈上 栈的空间本来就不大

forword_list(单链表):实际中用得少

缺点:不支持尾插尾删 + insert数据也是在当前位置的后面

unordered_map/unordered_set:推荐

因为他们的效率高于map/set

#include <array>

int main()
{
    array<int, 100> a;
    return 0;
}
//默认成员函数控制
class A
{
public:
    A() = default;//指定显示去生成默认构造函数

    A(const int& a)//拷贝构造 写了拷贝构造不会有默认构造函数
        :_a(a)//没有构造函数用不了 
    {}

private:
    int _a = 10;
};

int main()
{
    A aa1;
    A aa2(aa1);
    aa1 = aa2;
    return 0;
}

要求A的对象不能拷贝和赋值(防拷贝)

//C++98
class A
{
public:
    A() = default;

    //C++98
    //只声明 不定义 这样就没办法用(拷贝对象)
    //缺陷:别人可以在类外面定义
    //为了解决这个缺陷 private限定
    //类外面也没办法了
private:
    A(const A& aa);
    A& operator=(const A& aa);

private:
    int _a = 10;
};

//类外面定义
A::A(const A& aa)
{}

int main()
{
    A aa1;
    A aa2(aa1);
    aa1 = aa2;
    return 0;
}
//C++11做法
class A
{
public:
    A() = default;

    //C++11
    //delete搞成删除函数
    A(const A& aa) = delete;
    A& operator=(const A& aa) = delete;
private:
    int _a = 10;
};

int main()
{
    A aa1;
    A aa2(aa1);
    aa1 = aa2;
    return 0;
}

C++11做法更加优秀

7.右值引用

1°概念和语法

C++98就提出了引用的概念 引用就是给一个对象取别名

C++98 左值引用

C++11 右值引用

不管是左值引用还是右值引用 他们都是给对象取别名

左值引用主要给左值取别名 右值引用主要给右值取别名

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

左边就是左值?右边就是右值?

注意这个是一个C语法留下的坑 就像左移和右移一样

这里左右不是方向 左边的值不一定是左值 右边的不一定是右值

int x1=10;int x2=x1;这里x1是左值 10是右值 x2是左值

可以修改就可以认为是左值 左值通常是变量

右值通常是常量 表达式或者函数返回值(临时对象)

int main()
{
    int x = 1, y = 2;
    //左值引用的定义
    int a = 0;
    int& b = a;
    
    //左值引用不能引用右值 const左值引用可以
    //int& e = 10;
    //int& f = x + y;
    const int& e = 10;
    const int& f = x + y;

    //右值引用的定义
    int&& c = 10;
    int&& d = x + y;

    //右值引用不能引用左值 但是可以引用move后左值
    //int&& m = a;
    int&& m = move(a);

    return 0;
}
//C++98
template<class T>
void f(const T& a)
{
    cout << "void f(const T& a)" << endl;
}

template<class T>
void f(const T&& a)
{
    cout << "void f(const T&& a)" << endl;
}

int main()
{
    int x = 10;
    f(x);//这里会匹配左值引用
    f(10);//这里会匹配右值引用
    return 0;
}

 

C++11又将右值区分为:纯右值和将亡值

纯右值:基本类型的常量或者临时对象

将亡值:自定义类型的临时对象

结论:所有深拷贝类(vector/list/map/set) 都可以加两个右值引用移动拷贝和移动赋值

2°移动构造和移动赋值

//MyString
class String
{
public:
    String(const char* str = "")
    {
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    
    //s2(s1) 左值
    String(const String& s) noexcept
    {
        cout << "String(const String& s)-深拷贝-代价大-效率低" << endl;
        _str = new char[strlen(s._str) + 1];
        strcpy(_str, s._str);
    }

    //s3(右值-将亡值) 右值
    String(String&& s) noexcept
        :_str(nullptr)
    {
        std::cout << "String(String&& s)-移动拷贝-效率高" << endl;
        swap(_str, s._str);
    }

    ~String()
    {
        delete[] _str;
    }

    // s3 = s4
    String& operator=(const String& s) noexcept
    {
        cout << "String& operator=(const String& s)-拷贝赋值" << endl;
        if (this != &s)
        {
            char* newstr = new char[strlen(s._str) + 1];
            strcpy(newstr, s._str);
            delete[] _str;
            _str = newstr;
        }
        return *this;
    }

    //s3 = 右值-将亡值
    String& operator=(String&& s) noexcept
    {
        cout << "String& operator=(String&& s)-移动赋值-效率高" << endl;
        swap(_str, s._str);
        return *this;
    }

    //s1 + s2  s1不变
    String operator+(const String& s2)
    {
        String ret(*this);//拷贝
        //ret.append(s2._str);
        return ret;//拷贝 返回的是右值 临时对象
    }

    //s1 += s2 s1会变 可以引用返回
    String& operator+=(const String& s2)
    {
        //this->append(s2);
        return *this;//这里无拷贝 返回的是左值
    }

private:
    char* _str;
};

String f(const char* str)
{
    String tmp(str);
    return tmp;//这里不会返回tmp tmp生命周期到了 这里返回的实际是tmp的临时对象
}

int main()
{
    String s1("左值");
    String s2(s1);				//参数是左值
    String s3(f("右值-将亡值"));//参数是右值-将亡值(传递给你用 用完我就析构) move
    String s4(move(s1));        //move以后就是将亡值
    String s5("左值");
    s5 = f("右值-将亡值");
    return 0;
}

 

3°传值返回

int main()
{
    String s1("s1");
    String s2("s2");
    String s3 = s1 += s2;//拷贝构造
    String s4 = s1 + s2; //拷贝构造+移动构造

    //现实中不可避免存在传值返回的场景 传值返回的拷贝返回对象的临时对象
    //如果vector只实现参数为const左值引用深拷贝 那么下面的代价就很大
    //vector(const vector<T>& v)->深拷贝
    //但是如果vector实现了参数右值引用的移动拷贝 那么这里效率就会很高
    //vector(vector<T>&& v)->移动拷贝
    //结论:右值引用本身没太多意义 右值引用实现了移动构造和移动赋值
    //那么面对接收函数传值返回对象(右值)等等场景 可以提高效率
    //3.当传值返回值 返回时右值 结合前面学的移动构造和移动赋值 可以减少拷贝
    return 0;
}
#include <string>

string f(const char* str)
{
    string tmp(str);
    return tmp;//这里不会返回tmp tmp生命周期到了 这里返回的实际是tmp的临时对象
}

int main()
{
    string s1("左值");
    string s2(s1);
    string s3(f("右值")); 
    return 0;
}

4°右值引用作函数参数

std::vector::push_back void push_back(const value_type& val); void push_back(value_type&& val);

std::list::push_back void push_back(const value_type& val); void push_back(value_type&& val);

std::set::insert void push_back(const value_type& val); void push_back(value_type&& val);

std::vector::emplace_back template<class.. Args> void emplace_back(Args&&... args) //模板的可变参数(了解) 网上有人说:emplace版本比push和insert高效 这句话不准确 没有深入去分析

...其他容器插入数据结构也基本都是两个重载实现 一个左值引用 一个右值引用

int main()
{
    vector<string> v;
    string s1("左值");
    int val = 1234;

    //push_back中调用的是string的拷贝构造
    v.push_back(s1);//调左值

    //push_back中调用的是string的移动构造
    v.push_back("右值");//临时对象 隐式类型转成string 调右值
    v.push_back(to_string(val));//to_string是传值返回 调右值
    
    v.emplace_back(s1);
    v.emplace_back("右值");

    vector<pair<string, string>> vp;
    vp.push_back(make_pair("右值", "右值"));//调的右值
    
    pair<string, string> kv("左值", "左值");//调的左值
    vp.push_back(kv);

    vp.emplace_back(make_pair("右值", "右值"));//右值
    vp.emplace_back(kv);//左值
    vp.emplace_back("右值", "右值"); // 体现emplace_back模板可变参数的特点的地方 右值

    return 0;
}

5°总结

右值引用作参数和作返回值减少拷贝的本质是移动构造和移动赋值

左值引用和右值引用本质的作用都是减少拷贝 右值引用本质可以认为是弥补左值引用不足的地方

相辅相成 都是减少拷贝

左值引用:解决的时传参过程中和返回值过程中的拷贝

作参数:void f1(T x) -> void f1(T& x) 解决的是传参过程中减少拷贝

作返回值:T f2() -> T& f2() 解决的返回值过程中的拷贝 但是无法解决接收返回值时的拷贝 比如T ret = f2();

但是要注意这里有限制 如果返回对象出了作用域不在了就不能传引用返回 这个左值引用无法解决

右值引用:解决的是传参后 push/insert函数内部将对象移动到容器空间上的问题

传值返回接收返回值的拷贝

作参数:void push(const T&& x) 解决的push内部不再使用拷贝构造x到容器空间上 而是移动构造过去

作返回值:T f2() 解决的外面调用接收f2()返回对象的拷贝 T ret = f2() 这里就是**右值引用的移动构造 减少了拷贝

8.完美转发

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用

的另外一个函数。

forward完美转发

//完美转发
void Fun(int& x) { cout << "lvalue ref" << endl; }
void Fun(const int& x) { cout << "const lvalue ref" << endl; }
void Fun(int&& x) { cout << "rvalue ref" << endl; }
void Fun(const int&& x) { cout << "const rvalue ref" << endl; }

template<typename T>
void PerfectForward(T&& t) 
{ 
    //Fun(t); //右值引用会在第二次之后的参数传递过程中属性丢失 下一层调用会全部识别为左值
    //加上forward进行完美转发 此时左值还是左值 右值还是右值
    Fun(std::forward<T>(t));
}

int main()
{
    PerfectForward(10); // rvalue ref 走右值

    int a;
    PerfectForward(a); // lvalue ref 走左值
    PerfectForward(std::move(a)); // rvalue ref 走右值
    const int b = 8;
    PerfectForward(b); // const lvalue ref 走const左值
    PerfectForward(std::move(b)); // const rvalue ref 走const右值
    
    string s0("hello");
    string s1 = to_string(11111);


    return 0;
}

9.lambda表达式

1°排序

//整型排序
#include <algorithm>
#include <functional>

template<class T>
struct Greater
{
    bool operator()(const T& x1, const T& x2)
    {
        return x1 > x2;
    }
};


bool g2(const int& x1, const int& x2)
{
    return x1 > x2;
}

int main()
{
    int array[] = { 4,1,8,5,3,7,0,9,2,6 };

    // 默认按照小于比较,排出来结果是升序
    std::sort(array, array + sizeof(array) / sizeof(array[0]));

    // 如果需要降序,需要改变元素的比较规则
    std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
    
    Greater<int> g1;//给一个类的对象g1
    g1(1, 2);//g1是一个对象 调operator()实现的比较
    g2(1, 2);//g2是一个函数指针 这里是的调用他指向的函数
    //他们是完全不同的类型对象 但是他们用起来是一样的
    std::sort(array, array + sizeof(array) / sizeof(array[0]), g1);
    std::sort(array, array + sizeof(array) / sizeof(array[0]), g2);
    
    return 0;
}

仿函数和函数指针

#include <algorithm>
#include <functional>
struct Goods
{
    string _name; // 名字
    double _price;// 价格
    int    _num;  // 数量
    //...
};

//那么这里如果去重载Goods的operator>/operator<是不好的
//因为你不知道需要按哪一项成员去比较

struct ComparePriceGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price > gr._price;
    }
};

struct CompareNumGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._num > gr._num;
    }
};

struct CompareNameGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._name > gr._name;
    }
};

int main()
{
    Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), ComparePriceGreater());
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), CompareNumGreater());
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), CompareNameGreater());
    return 0;
}

此时需要写三个仿函数 很重复 lambda表达式可优化

2°使用

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。

(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略

mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。

->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

int main()
{
    [] {};//没有参数 没有返回值可以不写

    //定义在函数中的匿名函数
    int a = 3, b = 4;
    //实现a+b的lambda表达式

    //不捕捉
    auto add1 = [](int x1, int x2)->int {return x1 + x2; };
    add1(a, b);

    //直接捕捉
    //auto add2 = [a, b]()->int {return a + b; };
    auto add2 = [=]()->int {return a + b; };//全部捕捉
    add2();

    //不捕捉
    auto swap1 = [](int& x1, int& x2) {
        int x = x1;
        x1 = x2;
        x2 = x;
    };
    swap1(a, b);

    //捕捉 不用传参
    auto swap2 = [&a, &b]() {
        int x = a;
        a = b;
        b = x;
    };
    swap2();

    //全部引用捕捉
    auto swap3 = [&]() {
        int x = a,
        a = b;
        b = x;
    };
    swap3();

    return 0;
}

改进后:

int main()
{
  //原来
    Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), ComparePriceGreater());
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), CompareNumGreater());
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), CompareNameGreater());
    
    //lanbda表达式
    auto price_greater = [](const Goods& g1, const Goods& g2) {
        return g1._price > g2._price;
    };
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), price_greater);
    
    //lanbda表达式直接作参数
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2) {
        return g1._price > g2._price;});
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2) {
        return g1._price < g2._price; });
    
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2) {
        return g1._name > g2._name; });
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2) {
        return g1._name < g2._name; });
    //我们会发现在这里使用lambda表达式更方便一些
    return 0;
}

第三个参数直接传lambda表达式

通过对象+.+成员可以直接看到比较的是什么 仿函数命名好也可以

lambda表达式其实定义函数内存的匿名函数函数

int main()
{
    //[捕捉列表](参数)->返回值类型[函数体]
    int a = 0, b = 1;
    //实现一个a+b的lambda表达式
    auto add1 = [](int x1, int x2)->int {return x1 + x2; };
    cout << add1(a, b) << endl;

    //捕捉列表就是捕捉跟我一个作用域的对象
    //传值捕捉 [a] 捕捉a [a,b] 捕捉a b [=]捕捉所有对象
    //传引用捕捉 [&a] 捕捉a [&a,&b] 捕捉a b [&] 捕捉同一作用域中的所有对象
    //传值捕捉的对象是不能改变的 有const属性(加上mutable就可以改变了)
    auto add2 = [a, b]()->int {return a + b; };
    add2();

    //实现a和b交换
    //auto swap1 = [](int& a, int& b) {int c = a;a = b;b = c;};
    auto swap1 = [](int& x, int& y) {
        int z = x; 
        x = y; 
        y = z; 
    };
    
    swap1(a, b);
    cout << a << " " << b << endl;

    //实际还是不能完成交换 
    //auto swap2 = [a,b]()mutable {
    //	int z = a;
    //	a = b;
    //	b = z;
    //};
    //swap2();

    //只交换a和b
    auto swapab = [&a, &b]() {
        int z = a;
        a = b;
        b = z;
    };
    swapab();

    cout << a << " " << b << endl;
    return 0;
}

仿函数很依赖命名 命名号就很容易看懂

如果命名写的不好 要具体看仿函数内部的代码才知道按什么比较的

lambda优势就是会让代码可读性强 一看就知道按照什么比的

int main()
{
    int a = 1, b = 2;
    //对象 = 对象(编译器生成的lambda_uuid仿函数的对象) auto推出对象
    auto add = [](int x, int y)->int {return x + y; };
    add(a, b);
    //底层还是依靠仿函数来实现 也就是说你定义了一个lambda表达式
    //实际上编译器会生成一个叫lambda_uuid类 仿函数的operator()的参数和实现
    //就是我们写的lambda表达式的参数和实现
    //跟范围for一样 范围for的本质是迭代器 写范围for是为了简便
    //lambda表达式的本质是仿函数 写lambda表达式更简便
    return 0;
}

10.线程库

1°C++11线程库

C++11 线程库

windows 自己的一套API 如:CreateThread

Linux 使用posix的pthread 如:pthread_create

C++98中 如果你想写多线程的程序 既可以在windows下跑 也可以在Linux下跑

那么怎么办?

用条件编译

#ifdef _WIN32

        CreateThread(...)

#else

        pthread_create(...)

#endif

C++11线程库

特点:跨平台 面向对象封装的类(每个线程是一个类对象)

实现原理:封装库时使用了条件编译 也就是说他的底层还是分别调用了不同平台的线程API

扩展:吐槽一下C++ C++缺点之一:就是更新有用的东西太慢了 比如线程库C++11(2011)才更新的

而且现在也没有更新一个官方的封装好的靠谱网络库 其次一些不痛不痒的语法更新了一堆 增加学习成本

2°函数指针配合thread

#include <thread>
#include <mutex>
#include <atomic>

mutex mtx;//只允许一个线程进
//int x = 0;
atomic<int> x = 0;//++调的operator++
//支持整型/浮点型的原子++或者-- 不需要加锁
//扩展学习:CAS->无锁编程

//两个线程去一起对x加n次
void Add(int n)
{
    //加到外面和里面 哪个效率高
    //锁加到外面 串行 一个线程跑完了 另一个线程接着跑
    mtx.lock();
    for (int i = 0; i < n; i++)
    {
        ++x;
    }
    mtx.unlock();
    
    //锁加到里面 并行 
    /*for (int i = 0; i < n; i++)
    {
        mtx.lock();//t2刚切出去 t1就解锁了 马上就把t2切回来
        ++x;
        mtx.unlock();
    }*/
    //串行更快
    //思考为什么串行还快一些?
    //因为这里锁的粒度太小了 时间都花到切换上下文
}

int main()
{
    thread t1(Add, 10000);//函数名 参数
    thread t2(Add, 10000);//次数一多就会出问题
    //同时加的时机不一样 最后结果不一定是20000
    //解决:加锁 注意线程安全

    //main是主线程 t1 t2没走完 主线程会走完 直接抛异常
    t1.join();
    t2.join();
    //join就是等t1 t2先走完

    cout << x << endl;
    return 0;
}

 

3°仿函数配合thread

atomic<int> x = 0;

struct Add
{
    void operator()(int n)
    {
        for (int i = 0; i < n; ++i)
        {
            ++x;
        }
    }
};

int main()
{
    Add add;//对象
    thread t1(add, 100000);//对象调operator() 后面为operator()的参数
    thread t2(add, 100000);
    //thread t2(Add(),100000);//匿名对象
 
    cout << t1.get_id() << endl;
    cout << t2.get_id() << endl;

    t1.join();
    t2.join();

    cout << x << endl;
}

 

4°lambda表达式配合thread

int main()
{
    atomic<int> x = 0;

    auto add = [&x](int n) {
        for (int i = 0; i < n; ++i)
        {
            ++x;
        }
    };

    thread t1(add, 100000);//接收对象作为函数名 然后传参 
    thread t2(add, 100000);

    cout << t1.get_id() << endl;
    cout << t2.get_id() << endl;
   
    t1.join();
    t2.join();

    cout << x << endl;
    
    return 0;
}

  

int main()
{
    atomic<int> x = 0;

    //m个线程对x加n次
    int m, n;
    cin >> m >> n;
    //法1
    //vector<thread> vthreads;
    //for (int i = 0; i < m; ++i)
    //{
    //	vthreads.push_back(thread([&x](int count) {
    //		for (int i = 0; i < count; ++i)
    //		{
    //			++x;
    //		}
    //	},n));
    //}
    //法2 注意的是thread支持移动拷贝和移动赋值 不支持深拷贝的拷贝构造和拷贝赋值
    vector<thread> vthreads(m);
    for (int i = 0; i < m; ++i)
    {
        //移动赋值 右边是匿名对象(将亡值) 
        vthreads[i] = thread([&x](int count) {
            for (int i = 0; i < count; ++i)
            {
                ++x;
            }
            }, n);
    }

    for (auto& t : vthreads)
    {
        cout << t.get_id() << ".join()" << endl;
        t.join();
    }

    cout << x << endl;

    return 0;
}

 

5°交替打印

//使用两个线程打印0-n之间的数 一个线程打印奇数 一个线程打印偶数 要求依次打印 打印1后打印2
#include <mutex> //互斥锁
#include <condition_variable> //条件变量

int main()
{
    int n = 100;
    mutex mtx1, mtx2;
    condition_variable cv1, cv2;
    //1代表线程1 2代表线程2

    thread t1([&]()
    {
        //偶数
        for (int i = 0; i < n; i+=2)
        {
            unique_lock<mutex> locker1(mtx1);
            //不加条件 两边都在等
            if (i != 0)
            {
                cv1.wait(locker1);
            }
            if (i % 2 == 0)
                cout << this_thread::get_id() << ":" << i << endl;

            cv2.notify_one();// t1打印偶数以后 通知t2
        }
    });

    thread t2([&]()
    {
        //奇数
        for (int i = 1; i < n; i+=2)
        {
            unique_lock<mutex> locker2(mtx2);

            cv2.wait(locker2);

            if (i % 2)
                cout << this_thread::get_id() << ":" << i << endl;
        
            cv1.notify_one(); // t2打印奇数以后 通知t1
        }
    });

    t1.join();
    t2.join();

    return 0;
}

 

【C++】19.C++11 完 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/526937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

​数字化转型升级之工业元宇宙与AIGC

月説小飞象交流会 生活就是&#xff0c;面对复杂&#xff0c;保持欢喜。心烦时&#xff0c;记住三句话&#xff1a;1、算了吧。2、没关系。3、会过去的。 内部交流│24期 数字化转型升级 工业元宇宙与AIGC data analysis ●●●● 分享人&#xff1a;李铁军 ‍ 现如今数字化不再…

定风波、渡重山、至未来:2023中国数字能源生态大会开启的新旅程

全球碳中和的时代背景下&#xff0c;面向3060发展目标&#xff0c;新使命、新技术、新应用的到来&#xff0c;都给能源产业带来了持续变革的必要性与可能性。 2023年5月11日&#xff0c;在2023中国数字能源生态大会上&#xff0c;华为数字能源技术有限公司总裁侯金龙发表了“融…

【微电网】含风、光、储联合发电的微电网优化调度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

2023 年 IntelliJ IDEA 下载安装教程,超详细图文教程,亲测可用

. IDEA 下载 1、打开浏览器输入https://www.jetbrains.com/&#xff0c;进入 Jetbrains官网&#xff0c;点击 Developer Tools&#xff0c;再点击 Intellij IDEA 2、点击中间的 Download&#xff0c;进入IDEA下载界面 3、选择左边的 Ultimate 版本进行下载安装。Ultimate 版…

心法利器[84] | 最近面试小结

心法利器 本栏目主要和大家一起讨论近期自己学习的心得和体会&#xff0c;与大家一起成长。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。 2022年新一版的文章合集已经发布&#xff0c;累计已经60w字了&#xff0c;获取方式看这里&…

让Chat-GPT成为你的微信小助理

前言 最近公司裁员风波&#xff0c;很不幸成为了裁员名单中的一员&#xff1b;此时又恰逢一波AIGC浪潮&#xff0c;首当其冲的就是GPT这样的大语言模型的诞生&#xff0c;是整个AI领域的一个质的飞跃。正好在这样一个空挡期&#xff0c;我就基于Chat-GPT 做了一些深入的实践&a…

ChatGPT是什么?

ChatGPT是什么&#xff1f; ChatGPT是一个基于人工智能技术的聊天机器人平台&#xff0c;旨在为用户提供智能化、高效率的交互体验。ChatGPT能够理解用户输入的自然语言&#xff0c;根据语义分析和机器学习算法生成相应的回答。它可以回答用户的问题、提供建议、进行闲聊等&am…

前端技术搭建井字游戏(内含源码)

The sand accumulates to form a pagoda ✨ 写在前面✨ 功能介绍✨ 页面搭建✨ 样式设置✨ 逻辑部分 ✨ 写在前面 上周我们实通过前端基础实现了飞机大战游戏&#xff0c;今天还是继续按照我们原定的节奏来带领大家完成一个井字游戏游戏&#xff0c;功能也比较简单简单&#x…

路径规划 | 图解快速随机扩展树RRT算法(附ROS C++/Python/Matlab仿真)

目录 0 专栏介绍1 什么是RRT算法&#xff1f;2 图解RRT算法原理3 算法仿真与实现3.1 ROS C实现3.2 Python实现3.3 Matlab实现 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细介绍全局规划(图搜索、采样法、…

〖Python网络爬虫实战㉕〗- Ajax数据爬取之Ajax 案例实战

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付…

Gradle下载、安装、配置

1. Gradle下载 1.1 Gradle下载地址&#xff1a;https://docs.gradle.org/current/userguide/installation.html#installing_manually 1.2 点击Download 1.3 选择想要下载的版本&#xff0c;点击binary-only即可下载 2. Gradle安装&#xff08;注意&#xff1a;安装gradle之前…

【C语言】三子棋小游戏的思路及实现(内附代码)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 分文件实现 2.分步骤实现 2.1 游戏菜单 2.2 创建棋盘 2.3 初始化棋盘 2.4 打印棋盘 2.5 玩家下棋 2.6 电脑下棋 2.7 判断输赢 3. 附完整代码 3.1 test.c 3.2 game.h 3.2 game.c 1. 分文件实现 当我…

对称加密、非对称加密、数字签名、消息摘要的简单学习

对称加密、非对称加密、数字签名、消息摘要的简单学习 前言对称加密算法DES特点&#xff1a;为什么不使用&#xff1a; 3DES&#xff08;Triple DES 或者 DESede&#xff09;特点&#xff1a;使用场景&#xff1a;为什么不用&#xff1a; AES&#xff08;Advanced Encryption S…

聊一聊模板方法模式

统一抽取&#xff0c;制定规范&#xff1b; 一、概述 模板方法模式&#xff0c;又叫模板模式&#xff0c;属于23种设计模式中的行为型模式。在抽象类中公开定义了执行的方法&#xff0c;子类可以按需重写其方法&#xff0c;但是要以抽象类中定义的方式调用方法。总结起来就是&…

c语言实现栈(顺序栈,链栈)

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:讲解用c语言实现:“数据结构之"栈”,分别从"顺序栈…

区间预测 | MATLAB实现QRCNN-BiGRU卷积双向门控循环单元分位数回归时间序列区间预测

区间预测 | MATLAB实现QRCNN-BiGRU卷积双向门控循环单元分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRCNN-BiGRU卷积双向门控循环单元分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 1.Matlab实现基于QRCNN-BiGRU分位数回…

MySQL视图与联集

一、VIEW&#xff08;视图&#xff09; 1、 概念 可以被当作是虚拟表或存储查询 视图跟表格的不同是&#xff0c;表格中有实际储存资料&#xff0c;而视图是建立在表格之上的一个架构&#xff0c;它本身并不实际储存资料。 临时表在用户退出或同数据库的连接断开后就自动消…

DIY技巧:微星B760主板13600K降压教程 CPU温度暴降25℃

前段时间微星B600/700系主板更新了最新的BIOS&#xff0c;最新的BIOS更新&#xff1b;额105微码&#xff0c;让用户能直接在BIOS中对13代带K处理器进行降压&#xff0c;十分方便&#xff0c;今就带大家体验一下微星B760迫击炮主板的降压流程&#xff0c;其他微星B600/700系主板…

43岁,年薪200万的高管,被裁了!这4条职场潜规则,你越早知道越好

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) 我的一位老朋友S总&#xff0c;是某世界500强外企中国区运营总监&#xff0c;光年薪就200万&#xff0c;还不包括福利、股票的部分&#xff0c;他比我略长一两岁&#xff0c;我们人生经历相似&#xf…

一文搞懂Go错误链

0. Go错误处理简要回顾 Go是一种非常强调错误处理的编程语言。在Go中&#xff0c;错误被表示为实现了error接口的类型的值&#xff0c;error接口只有一个方法&#xff1a; type error interface {Error() string } 这个接口的引入使得Go程序可以以一致和符合惯用法的方式进行错…