当前位置: 首页 > news >正文

【重走C++学习之路】22、C++11语法

目录

一、列表初始化

1.1 {}初始化

1.2  std::initializer_list

二、变量类型推导

2.1 auto

2.2 decltype

三、右值引用和移动语义

3.1 左值与左值引用

3.2 右值与右值引用 

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

3.4 右值引用使用场景和意义

3.5 move 

3.6 完美转发和万能引用

四、新的类功能

4.1 新增两个默认成员函数

4.2  default和delete

五、可变参数模板

5.1 基本知识

5.2 容器中emplace接口

六、 lambda表达式

6.1 基本结构

6.2 捕获列表详解

1. 值捕获

2. 引用捕获(By Reference)

3. 混合捕获

4. 隐式捕获

6.3  函数对象与lambda表达式

七、包装器

7.1 function包装器

7.2 bind包装器

八、线程库

8.1 基本知识

8.2 原子性操作库

8.3 lock_guard与unique_lock

1. std::lock_guard

2. std::unique_lock 

 3. 核心区别对比

8.4  condition_variable

等待函数

通知函数

结语


一、列表初始化

1.1 {}初始化

在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。

struct Point
{int _x;int _y;
};int main()
{// 数组类型int arr1[] = { 1,2,3,4 };int arr2[6]{ 1,2,3,4,5,6 };// 结构体类型Point p = { 1, 2 };
}

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

class Point
{
public:Point(int x, int y):_x(x),_y(y){}private:int _x;int _y;
};int main()
{// 内置类型变量int a{ 2 };int b = { 3 };int c = { a + b };// 动态数组 C++98不支持int* arr = new int[5]{ 1,2,3,4,5 };// 容器使用{}进行初始化// vector<int> v = { 1,2,3 };vector<int> v{ 1,2,3 };        // 等号可以省略不写map<int, int> m{ {1,1},{2,2},{3,3} };// 自定义类型列表初始化  Point p{ 1, 2 };return 0;
}

1.2  std::initializer_list

initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=的参数,这样就可以用大括号赋值对应多个对象的列表初始化,但必须支持一个带有initializer_list类型参数的构造函数。

注意: 这种类型的对象由编译器根据初始化列表声明自动构造,初始化列表声明是用{}和,

容器使用initializer_list作为构造函数的参数的例子:

int main()
{vector<int> v = { 1,2,3,4 };list<int> lt = { 1,2 };// 这里{"sort", "排序"}会先初始化构造一个pair对象map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };// 使用大括号对容器赋值v = {10, 20, 30};return 0;
}

模拟实现vector支持initializer_list初始化和赋值:

template<class T>
class Vector
{
public:Vector(initializer_list<T> l):_size(0),_capacity(l.size()){_a = new T[_capacity];for (auto e : l){_a[_size++] = e;}}Vector<T>& operator=(initializer_list<T> l){vector<T> tmp(l);std::swap(_a, tmp._a);_size = l._size;_capacity = l._capacity;return *this;}private:T* _a;size_t _size;size_t _capacity;
};int main()
{Vector<int> v1 = { 1,2,3 };Vector<int> v2 = { 3,5,7,9 };v2 = { 1,2,3 };return 0;
}

二、变量类型推导

2.1 auto

auto在本系列最开始时就讲解了,还想了解的可以去我的文章中查看。【重走C++学习之路】2、C++基础知识

2.2 decltype

作用:根据表达式的实际类型推演出定义变量时所用的类型。且还可以使用推导出来的类型进行变量声明。

template<class T1, class T2>
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret;cout << typeid(ret).name() << endl;
}int Add(int x, int y)
{return x + y;
}int main()
{const int x = 1;double y = 2.2;// 用decltype自动推演变量的实际类型decltype(x * y) ret;     // ret的类型是doubledecltype(&x) p;          // p的类型是int*cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;// 带参数,推导函数返回值类型,注意这里不会调用函数cout << typeid(decltype(F(1, 'a'))).name() << endl;// 不带参数,推导函数类型cout << typeid(decltype(Add)).name() << endl;return 0;
}

注意:

  1. 运行时类型识别的缺陷是降低程序运行的效率
  2. typeid只能推导类型,但是不能使用类型声明和定义变量

三、右值引用和移动语义

在最开始介绍C++的基础知识时,就已经介绍过了引用的语法,只不过那里的引用都是左值引用。C++11新增了右值引用的语法特性,给右值取别名。左值引用和右值引用都是给对象取别名,只不过二者对象的特性不同。

3.1 左值与左值引用

左值:

一个表示数据的表达式(如变量名或解引用的指针),可以取地址和赋值,且左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边,例如:普通变量、指针等。

const修饰后的左值不可以赋值,但是可以取地址,所以还是左值。

左值引用:

给左值的引用,给左值取别名 ,符号为&,例如:int& ra = a;

示例:

int main()
{// 以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;// 以下几个是对上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& pvalue = *p;return 0;
}

左值总结:

  • 普通类型的变量,可以取地址
  • const修饰的常量,可以取地址,也是左值

3.2 右值与右值引用 

右值:

一个表示数据的表达式,右值不能取地址,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等 

右值引用:

 给右值的引用,给右值取别名,符号为&&,例如:int&& ra = Add(a,b)

示例:

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;
}

右值总结:右值分为两种

  • 纯右值:基本类型的常量或临时对象
  • 将亡值:自定义类型的临时对象用完自动完成析构,如:函数以值的方式返回一个对象

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

左值引用:

  • 左值引用不可以引用右值
  • 加了const的左值引用既可以引用左值也可以引用右值

示例:

    int main()
    {// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a; // ra为a的别名//int& ra2 = 10; // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
    }

    右值引用:

    • 右值引用不可以引用左值
    • 右值引用可以引用move后的左值

    示例:

    int main()
    {// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
    }

    3.4 右值引用使用场景和意义

    右值引用的出现时为了解决左值引用的一些短板,那么我们首先就要明确左值引用的作用。

    左值引用做参数和做返回值都可以提高效率。但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回,且传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

    图解:

    局部变量->tmp常量->接收变量,这其中发生了两次拷贝构造,有些编译器会优化成一次。且每一个拷贝都是深拷贝,性能损失很大。

    很显然这样是不符合的,因此右值引用就出现了,连带着右值引用的移动语义也出现了。

    移动语义:

    将一个对象中资源移动到另一个对象中的方式,可以有效减少拷贝,减少资源浪费,提供效率。

    实现代码: 

    String(String&& s)
    : _str(nullptr)
    , _size(0)
    , _capacity(0)
    {// 对于将亡值,内部做移动拷贝cout << "移动拷贝" << endl;swap(s);
    }

    to_string函数结束返回的临时对象是一个右值,用这个右值构造str,所以会优先调用移动构造,这里就是一个移动语义。本质是资源进行转移,此时tmp指向的是一块空的资源。可以看出的是这里解决的是减少接受函数返回对象时带来的拷贝,移动构造中没有新开空间,拷贝数据,所以效率提高了。

    除了移动构造,我们还可以增加移动赋值,具体如下:

    String& operator=(String&& s)
    {cout << "移动赋值" << endl;swap(s);return *this;
    }

    如果运行下面的代码会出现什么结果?

    int main()
    {MeiYu::string ret1;ret1 = MeiYu::to_string(1234);return 0;
    }

    这里运行后,会调用了一次移动构造一次移动赋值,要注意这里并没有发生编译器优化。因为如果是用一个已经存在的对象接收,编译器就没办法优化了。

    实际运行过程:

    to_string函数中会先用str生成一个临时对象,编译器在这里把str识别成右值,调用了移动构造。然后在把这个临时对象做为to_string函数调用的返回值赋值给ret1,这里调用的移动赋值。 

    注意:

    1. 移动构造和移动赋值函数的参数不能设置成const类型的右值引用,因为资源无法转移而导致移动语义失效。
    2. 在C++11中,编译器会为类默认生成一个移动构造和移动赋值,该移动构造和移动赋值为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造和移动赋值。
    3. 右值引用本身没有多大意义,引入了移动构造和移动赋值就有意义了。

    右值引用和左值引用减少拷贝的场景:

    • 作参数时: 左值引用减少传参过程中的拷贝。右值引用解决的是传参后,函数内部的拷贝构造。
    • 作返回值时: 如果出了函数作用域,对象还存在,那么可以使用左值引用减少拷贝。如果不存在,那么产生的临时对象可以通过右值引用提供的移动构造生成,然后通过移动赋值或移动构造的方式将临时对象的资源给接受返回值者。

    3.5 move 

    当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

    注意:

    • 被转化的左值,其生命周期并没有随着左值的转化而改变,即std::move转化的左值变量value不会被销毁。move告诉编译器:我们有⼀个左值,但我们希望像⼀个右值⼀样处理它。
    • STL中也有另一个move函数,就是将一个范围中的元素搬移到另一个位置。

    使用示例:

    int main()
    {String s1("123");String s2(move(s1));return 0;
    }

    3.6 完美转发和万能引用

    • 完美转发

    在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。

    • 万能引用

    模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。

    void Fun(int& x) { cout << "左值引用" << endl; }
    void Fun(const int& x) { cout << "const 左值引用" << endl; }
    void Fun(int&& x) { cout << "右值引用" << endl; }
    void Fun(const int&& x) { cout << "const 右值引用" << endl; }// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
    template<typename T>
    void PerfectForward(T&& t)
    {Fun(std::forward<T>(t));
    }int main()
    {PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
    }

    右值引用在传参的过程中移动要进行完美转发,否则会丢失右值属性,利用std::forward<T>(t)在传参的过程中保持t的原生类型属性

    四、新的类功能

    4.1 新增两个默认成员函数

    在C++11中由新增了两个默认成员函数:

    • 移动构造函数
    • 移动赋值运算符重载

    需要注意: 

    • 如果没有实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。
    • 默认生成的移动构造函数,对于内置类型成员会按照字节序进行浅拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
    • 如果没有实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。
    • 默认生成的移动构造函数,对于内置类型成员会按照字节序进行浅拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。

    4.2  default和delete

    C++11可以让你更好的控制要使用的默认函数。你可以强制生成某个默认成员函数,也可以禁止生成某个默认成员函数,分别用的的关键字是——defaultdelete。

    示例:

    class Person
    {
    public:Person(const char* name = "", int age = 0):_name(name),_age(age){}Person(Person&& p) = default;// 强制生成默认的Person& operator=(Person&& p) = default;Person(Person& p) = delete;// 强制删除默认的~Person(){}private:string _name;int _age;
    };
    

    五、可变参数模板

    5.1 基本知识

    C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板。具体格式:

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

    args前面有省略号,所以它们是可变参数模板,带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数;Args是一个模板参数包,args是一个函数形参参数包。

    如何获得参数包的值?

    • 递归函数方式展开参数包
    void ShowList()
    {cout << endl;
    }template<class T, class ...Args>
    void ShowList(T value, Args ...args)
    {cout << value << " ";// 递归调用ShowList,当参数包中的参数个数为0时,调用上面无参的ShowList// args中第一个参数作为value传参,参数包中剩下的参数作为新的参数包传参ShowList(args...);
    }
    int main()
    {ShowList(1, 'A');ShowList(3, 'a', 1.23);ShowList('a', 4, 'B', 3.3);return 0;
    }
    • 逗号表达式展开参数包

    什么是逗号表达式?

    优先级别最低,将两个及其以上的式子联接起来,从左往右逐个计算表达式,整个表达式的值为最后一个表达式的值。如:(3+5,6+8)称为逗号表达式,其求解过程先表达式1,后表达式2,整个表达式值是表达式2的值。

    如果一个参数包中都是同一个的类型,那么可以使用该参数包对数组进行列表初始化,参数包会展开,然后对数组进行初始化。例如:

    void ShowList(Args ...args)
    {int arr[] = { args... };// 列表初始化
    }
    int main()
    {ShowList(1, 2, 3, 4, 5);return 0;
    }

    如果参数包中的参数不是同一类型就会报错。但是我们可以利用列表初始化数组时,展开参数包的特性,再与一个逗号表达式结合使用,可以展开都会表达式中的参数,如下: 

    template<class T>
    void PrintArg(T value)
    {cout << value << " ";
    }template<class ...Args>
    void ShowList(Args ...args)
    {int arr[] = { (PrintArg(args), 0)... };cout << endl;
    }
    

    这里的表达式会按顺序执行,先执行第一个函数,然后把最后的0值赋给数组,这样就把参数包展开了。这个数组的目的纯粹是为了在数组构造的过程展开参数包

    (PrintArg(arg1), 0),(PrintArg(arg2), 0),(PrintArg(arg3), 0),(PrintArg(arg4), 0)

    5.2 容器中emplace接口

    上面的模板参数既支持可变参数,又支持万能引用。

    emplace_back和push_back进行对比:

    用法上:

    list<pair<int, string>> lt;
    lt.emplace_back(1, "hehe");
    list<pair<int, string>> lt;
    lt.push_back({ 2, "haha" });

    原理上:

    emplace_back是直接构造了,push_back是先构造,再移动构造。

    六、 lambda表达式

    Lambda 表达式是一种内联定义匿名函数对象的强大方式,它为 C++ 带来了函数式编程的特性,极大地提高了代码的简洁性和灵活性。

    6.1 基本结构

    [capture list](parameter list)mutable  -> return type { function body }
    
    • 捕获列表capture list):用于捕获外部变量,可按值(=)或引用(&)捕获。
    • 参数列表parameter list):与普通函数的参数列表类似,可省略(但括号不能省)。
    • 可变参数(mutable) :默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
    • 返回类型return type):使用尾置返回类型,可省略(编译器自动推导)。
    • 函数体function body):与普通函数的函数体相同。

    注意:

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

    6.2 捕获列表详解

    捕获列表允许 Lambda 表达式访问外部作用域的变量,有以下几种形式:

    1. 值捕获

    int x = 10;
    auto lambda = [x]() { return x * 2; };  // 捕获 x 的副本
    x = 20;
    std::cout << lambda();  // 输出 20(捕获的是 x 的旧值)
    

    2. 引用捕获(By Reference)

    int x = 10;
    auto lambda = [&x]() { return x * 2; };  // 捕获 x 的引用
    x = 20;
    std::cout << lambda();  // 输出 40(引用的是当前 x 的值)
    

    3. 混合捕获

    int x = 10, y = 20;
    auto lambda = [x, &y]() { return x + y++; };  // x 按值,y 按引用
    

    4. 隐式捕获

    • [=]:默认按值捕获所有外部变量。
    • [&]:默认按引用捕获所有外部变量。
    • [=, &x]:默认按值捕获,但 x 按引用捕获。
    • [&, x]:默认按引用捕获,但 x 按值捕获。

    注意:

    1. 父作用域指包含lambda函数的语句块
    2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
    3. 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
    4. 在块作用域以外的lambda函数捕捉列表必须为空。
    5. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
    6. lambda表达式之间不能相互赋值,即使看起来类型相同

    6.3  函数对象与lambda表达式

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

    class Rate
    {
    public:Rate(double rate): _rate(rate){}double operator()(double money, int year){ return money * _rate * year;}private:double _rate;
    };int main()
    {// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lamberauto r2 = [=](double monty, int year)->double{return monty * rate * year;};r2(10000, 2);return 0;
    }
    

    使用方式上来看,函数对象与lambda表达式完全一样。
    函数对象将rate作为其成员变量,在定义对象时给出初始值即可,而lambda表达式通过捕获列表可
    以直接将该变量捕获到。

    实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
    果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

    七、包装器

    7.1 function包装器

    C++ 中的 std::function 是一个通用的多态函数包装器,它可以存储、复制和调用任何可调用对象(如函数、函数指针、成员函数指针、Lambda 表达式、仿函数等)。使用包装器之前需要包含头文件:#include <functional>

    使用格式:function<返回类型(参数列表)>

    注意:

    如果std::function对象未包装可调用对象,使用std::function对象将抛出std::bad_function_call异常。

    示例:

    #include <iostream>
    #include <functional>
    #include <string>
    using namespace std;//1、普通的函数
    void func(const string& str)
    {cout << str << endl; 
    }//2、仿函数
    class myclass1
    {
    public:void operator()(const string& str){   cout << str << endl; }
    };//3、类中普通成员函数
    class myclass2
    {
    public:void func(const string& str){cout << str << endl; }
    };//4、类中静态成员函数
    class myclass3
    {
    public:static void func(const string& str){cout << str << endl; }
    };int main()
    {// 普通函数func("我是直接调用的普通函数");// 由函数指针调用void(*fp1) (const string& ) = func;fp1("我是由函数指针调用的普通函数");// 用包装器调用function<void(const string&)> ff1 = func;ff1("我是由包装器调用的普通函数");// 仿函数myclass1 aa;aa("我是由仿函数对象调用的函数");// 用包装器调用function<void(const string&)> ff2 = myclass1();ff2("我是由包装器调用的仿函数");// 用函数指针调用的类的非静态成员函数myclass2 bb;void(myclass2::*fp3) (const string& ) = &myclass2::func; (bb.*fp3)("我是由函数指针调用的类的非静态成员函数");// 用包装器调用,传入类名function<void(myclass2, const string&)> ff3 = &myclass2::func;// 需要传入this指针ff3(bb, "我是由包装器调用类的静态非成员函数");// 用函数指针调用类的静态成员函数myclass3 cc;void(*fp4)(const string& ) = myclass3::func; fp4("我是函数指针调用类的静态成员函数");// 用包装器调用function<void(const string&)> ff4 = myclass3::func;ff4("我是由包装器调用类的静态成员函数");// 匿名函数auto f = [](const string& str){cout << str << endl; };f("我是lambda函数");// 用包装器调用function<void(const string&)> ff5 = f;			ff5("我是由包装器调用的lambda函数");										return 0;
    }
    • 除了类的非静态成员函数,其他的可调用对象通过包装器的包装,得到了一个统一的格式,包装完成得到的对象相当于一个函数指针,和函数指针的使用方式相同。
    • 类的非静态成员函数还需要传入this指针,所以单独使用std::function是不够的,还需要结合使用std::bind函数绑定this指针以及参数列表。

    7.2 bind包装器

    std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

    std::bind()返回std::function的对象,调用bind的一般格式:

    auto newCallable = bind(callable, arg_list);

    其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
    arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。 

    示例:

    void func(int i, const string& str)
    {while(i--){cout << str << endl;}cout << endl;
    }
    int main()
    {function<void(int, const string&)> fn1 = //bind(可调用对象,参数列表)bind(func, placeholders::_1,placeholders::_2);//placeholders::_1/_2是参数占位符fn1(2, "普通函数");return 0;
    }

    八、线程库

    线程是操作系统中的一个概念,如果大家还没有学到Linux系统编程的话,这一部分可以先放放。

    在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件。

    8.1 基本知识

    相关成员函数:

    • thread()

    构造一个线程对象,没有关联任何线程函数,即没有启动任何线程

    • thread(fn,args1, args2,...)

    构造一个线程对象,并关联线程函数fn,args1,args2,...为线程函数的参数

    • get_id()

    获取线程id

    • jionable()

    线程是否还在执行,joinable代表的是一个正在执行中的线程。

    • jion()

    该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行

    • detach()

    在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关

     注意:

    1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
    2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
    3. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一般情况下可按照以下三种方式提供:函数指针、lambda表达式、函数对象
    4. thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不影响线程的执行。
    5. 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效:
    • 采用无参构造函数构造的线程对象
    • 线程对象的状态已经转移给其他线程对象
    • 线程已经调用jion或者detach结束

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

    8.2 原子性操作库

    多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问
    题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数
    据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。

    例如:

    #include <iostream>
    using namespace std;#include <thread>unsigned long sum = 0L;void fun(size_t num)
    {for (size_t i = 0; i < num; ++i)sum++;
    }int main()
    {cout << "Before joining,sum = " << sum << std::endl;thread t1(fun, 10000000);thread t2(fun, 10000000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;return 0;
    }

    这里线程1和线程2的执行并不是一前一后执行的,而是并发执行的,那么就会出现数据最后不是我们想要的结果,学过系统编程的同学很容想到加锁来确保sum的正确性。例如:

    #include <iostream>
    using namespace std;#include <thread>
    #include <mutex>std::mutex m;
    unsigned long sum = 0L;void fun(size_t num)
    {for (size_t i = 0; i < num; ++i){m.lock();sum++;m.unlock();}
    }int main()
    {cout << "Before joining,sum = " << sum << std::endl;thread t1(fun, 10000000);thread t2(fun, 10000000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;return 0;
    }
    

    虽然加锁可以解决,但是加锁有一个缺陷就是:只要一个线程在对sum++时,其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。 

    因此C++11中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入的原子操作类型,使得线程间数据的同步变得非常高效。

    那么上述代码就变成了:

    #include <iostream>
    using namespace std;#include <thread>
    #include <atomic>atomic_long sum{ 0 };
    void fun(size_t num)
    {for (size_t i = 0; i < num; ++i)sum ++; // 原子操作
    }int main()
    {cout << "Before joining, sum = " << sum << std::endl;thread t1(fun, 1000000);thread t2(fun, 1000000);t1.join();t2.join();cout << "After joining, sum = " << sum << std::endl;return 0;
    }

    程序员可以使用atomic类模板(例如:atmoic<T> t) 定义出需要的任意原子类型。

    原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。

    8.3 lock_guard与unique_lock

    为了防止使用锁时出现异常而导致锁没有释放出现死锁,因此:C++11采用RAII的方式对锁进行了封装,即lock_guard和unique_lock。std::lock_guard 和 std::unique_lock 是 C++11 引入的用于管理互斥锁(Mutex)的工具,它们都能自动释放锁资源,避免手动管理锁带来的潜在风险(如忘记解锁导致死锁)。

    1. std::lock_guard

    特性:

    • 简单粗暴:构造时锁定互斥量,析构时自动解锁,不支持手动解锁。
    • 不可移动 / 复制:设计为栈上的局部对象,生命周期结束时自动释放锁。
    • 轻量级:无额外开销,性能接近手动管理锁。

    示例:

    #include <mutex>std::mutex mtx;void func() 
    {std::lock_guard<std::mutex> lock(mtx);  // 加锁// 临界区代码
    }  // 离开作用域时自动解锁

    适用场景

    • 简单的临界区保护,锁的持有时间短。
    • 不涉及锁的转移或条件变量的场景。

    2. std::unique_lock 

    特性:

    • 灵活但开销略高:支持手动锁定 / 解锁,可移动(不可复制),可与条件变量配合。
    • 延迟锁定:构造时可选择不立即锁定,通过 lock() 和 unlock() 手动控制。
    • 所有权转移:可通过 std::move 转移锁的所有权,适用于复杂场景。

    示例:

    #include <mutex>std::mutex mtx;void func() 
    {std::unique_lock<std::mutex> lock(mtx, std::defer_lock);  // 延迟加锁// 其他操作...lock.lock();  // 手动加锁// 临界区代码lock.unlock();  // 手动解锁// 非临界区代码lock.lock();  // 再次加锁// 临界区代码
    }  // 离开作用域时自动解锁(若锁被持有)

     3. 核心区别对比

    特性std::lock_guardstd::unique_lock
    手动锁定 / 解锁不支持 支持 lock()unlock()try_lock()
    延迟锁定构造时必须锁定 可通过 std::defer_lock 延迟锁定
    所有权转移不可移动支持 std::move 转移所有权
    条件变量支持不能直接配合条件变量使用必须与条件变量配合使用
    性能轻量级,无额外开销略高(因支持更多特性)
    适用场景简单临界区,锁持有时间短复杂场景(如锁的转移、条件变量)

    8.4  condition_variable

    std::condition_variable 是 C++11 引入的线程同步原语,用于线程间的等待 - 通知机制。它允许一个线程阻塞(等待),直到另一个线程通过 notify_one() 或 notify_all() 唤醒它。这种机制常用于生产者 - 消费者模型、任务队列等场景。

    核心原理:

    • 等待线程:通过 wait() 释放锁并阻塞,直到被唤醒。
    • 通知线程:通过 notify_one() 或 notify_all() 唤醒等待线程。
    • 互斥锁:必须与 std::unique_lock 配合使用,保证原子性。

    关键成员函数:

    等待函数

    • wait(lock):释放锁并阻塞,直到被通知,唤醒后自动重新加锁。
    • wait(lock, predicate):等价于 while(!predicate()) wait(lock),避免虚假唤醒。
    • wait_for(lock, timeout):限时等待,返回 std::cv_status::timeout 或 no_timeout
    • wait_until(lock, time_point):等待到指定时间点。

    通知函数

    • notify_one():唤醒一个等待中的线程(若有)。
    • notify_all():唤醒所有等待中的线程。

    通过一个实例来来见见怎么使用:

    (两个线程交替打印奇偶数)

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <condition_variable>void two_thread_print()
    {std::mutex mtx;condition_variable c;int n = 100;bool flag = true;thread t1([&](){int i = 0;while (i < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool{return flag; });cout << i << endl;flag = false;i += 2; // 偶数c.notify_one();}});thread t2([&](){int j = 1;while (j < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool{return !flag; });cout << j << endl;j += 2; // 奇数flag = true;c.notify_one();}});t1.join();t2.join();
    }int main()
    {two_thread_print();return 0;
    }

    结语

    这一篇文章内容较多,右值引用部分需要着重理解,其他的如果暂时不理解也可以先放一放,这篇文章后半部分基本上都是现在很难用上的,可能现在记住理解了,过段时间也会忘记了。等到学习Linux的时候会用的比较多,那个时候再来回顾,保证会有一个新的理解。

    下一篇将会介绍C++中处理异常的方法,有兴趣的朋友可以关注一下。

    相关文章:

    【重走C++学习之路】22、C++11语法

    目录 一、列表初始化 1.1 {}初始化 1.2 std::initializer_list 二、变量类型推导 2.1 auto 2.2 decltype 三、右值引用和移动语义 3.1 左值与左值引用 3.2 右值与右值引用 3.3 左值引用与右值引用比较 3.4 右值引用使用场景和意义 3.5 move 3.6 完美转发和万能引…...

    Spring Security授权管理

    授权是Spring Security的核心功能之一&#xff0c;是根据用户的权限来控制用户访问资源的过程&#xff0c;拥有资源的访问权限则可正常访问&#xff0c;没有访问的权限时则会被拒绝访问。认证是为了保证用户身份的合法性&#xff0c;而授权则是为了更细粒度地对隐私数据进行划分…...

    2025A卷-正整数到Excel编号之间的转换

    题目描述 用过 excel 的都知道excel的列编号是这样的&#xff1a; a b c … z aa ab ac … az ba bb bc … yz za zb zc … zz aaa aab aac … 分别代表以下编号&#xff1a; 1 2 3 … 26 27 28 29 … 52 53 54 55 … 676 677 678 679 … 702 703 704 705 … 请写个函数&…...

    算法设计与分析(期末试卷)

    目录 一、频度计算&#xff08;15 分&#xff09; 二、项目工期问题&#xff08;20 分&#xff09; 三、TSP 问题的贪心算法&#xff08;15 分&#xff09; 四、“秤心如意”&#xff08;15 分&#xff09; 五、工作指派问题&#xff08;20 分&#xff09; 六、计算复杂度…...

    springboot(2.6.13)自定义用户授权管理

    1.自定义用户访问控制 a.重写configure(HttpSecurity http)方法 在自定义配置类SecurityConfig中重写 Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/deta…...

    JavaWeb:vueaxios

    一、简介 什么是vue? 快速入门 <!-- 3.准备视图元素 --><div id"app"><!-- 6.数据渲染 --><h1>{{ msg }}</h1></div><script type"module">// 1.引入vueimport { createApp, ref } from https://unpkg.com/vu…...

    uniapp常用

    1.下载文件带进度提示 <template> <view> <button click"startDownload">下载文件</button> <progress :percent"progress" stroke-width"3" /> </view> </template> <…...

    etcd 的安装及使用

    介绍 Etcd 是一个 golang 编写的分布式、高可用的一致性键值存储系统&#xff0c;用于配置共享和服务发现等。它使用 Raft 一致性算法来保持集群数据的一致性&#xff0c;且客户端通过长连接 watch 功能&#xff0c;能够及时收到数据变化通知&#xff0c;相较于 Zookeepe…...

    uni-app vue3 实现72小时倒计时功能

    功能介绍 &#xff0c;数组项有一个下单时间 &#xff0c;比如今天下单在72小时内可以继续支付&#xff0c;超过则默认取消订单 页面按钮处 加上倒计时 <!-- 倒计时 --> <text v-if"item.timeLeft > 0">{{ formatTime(item.remaining) }}</text&g…...

    【C语言】初阶算法相关习题(二)

    个人主页&#xff1a;夜晚中的人海 文章目录 ⭐一、两数之和&#x1f3e0;二、珠玑妙算&#x1f3a1;三、寻找奇数&#x1f680;四、截取字符串&#x1f389;五、寻找峰值 ⭐一、两数之和 题目描述&#xff1a;两数之和 解题思路&#xff1a; 1.先创建一个动态分配的数组ret&a…...

    Flutter 学习之旅 之 Flutter 和 Android 原生 实现数据交互的MethodChanel和EventChannel方式的简单整理

    Flutter 学习之旅 之 Flutter 和 Android 原生 实现数据交互的MethodChanel和EventChannel方式的简单整理 目录 Flutter 学习之旅 之 Flutter 和 Android 原生 实现数据交互的MethodChanel和EventChannel方式的简单整理 一、简单介绍 二、Flutter 和 Android 原生之间的数据…...

    STM32的SysTick

    SysTick介绍 定义&#xff1a;Systick&#xff0c;即滴答定时器&#xff0c;是内核中的一个特殊定时器&#xff0c;用于提供系统级的定时服务。该定时器是一个24位的递减计数器&#xff0c;具有自动重载值寄存器的功能。当计数器到达自动重载值时&#xff0c;它会自动重新加载…...

    【JS事件循环机制event-loop】

    目录 0、总结1、Event-Loop 概念2、宏任务-微任务3、事件循环执行机制4、调用栈5、示例 0、总结 Tasks execute in order, and the browser may render between them 【宏任务按序执行&#xff0c;浏览器可以在它们之间进行渲染】Microtasks execute in order, and are execut…...

    对比N+1查询和关联聚合查询

    通常我们管第一种模式叫 “N1 查询”&#xff0c;第二种叫 “关联聚合查询”。下面从几个角度来比较&#xff0c;帮助你做出选择。 1. 性能与资源消耗 方案SQL 语句数网络往返次数数据库负载Java 处理N1 查询&#xff08;先查项目&#xff0c;再遍历项目查设备状态数&#xff…...

    优化 Flutter 应用启动:从冷启动到就绪仅需 2 秒

    冷启动序列剖析&#xff1a;冷启动时&#xff0c;Flutter 应用需经历引擎和 Dart VM 初始化、启动 Dart Isolate、渲染第一帧等步骤。Android 和 iOS 系统分别通过启动屏幕和 Storyboard 缓解启动延迟。应用大小、初始化工作、调试模式下的 JIT 编译等因素会影响冷启动时间。优…...

    牟乃夏《ArcGIS Engine 地理信息系统开发教程》学习笔记 4-空间分析与高级功能开发

    目录 一、核心组件与接口回顾 &#xff08;一&#xff09;空间分析基础架构 &#xff08;二&#xff09;网络分析模块 二、矢量数据空间分析实战 &#xff08;一&#xff09;缓冲区分析 &#xff08;二&#xff09;叠加分析&#xff08;以裁剪为例&#xff09; 三、栅格…...

    UE 滚动提示条材质制作

    需要两个贴图 先制作条纹屏闪 这里RGB输出连到alpha&#xff0c;0为白色&#xff0c;到1就为黑色了 因为这个图片是RGB输出代表三个图片&#xff0c;看贴图颜色就知道了&#xff0c;然后把这三个相加一下&#xff1b;链接自发光颜色&#xff0c; 这里设置速度变量 通过网盘分…...

    金融业数字化转型——深入解读77页2024年中国金融体系指标大全【附全文阅读】

    本文主要介绍了金融业通行宝典中国金融体系指标大全的内容,包括央行体系、商业银行体系、非银金融机构与地方金融组织的各项指标。文章详细分析了美联储资产负債表的结构,并概述了美日欧等主要经济体资产负债表状况。 重点内容: 1. 央行体系是金融分析的重点。 2. 美联储资产…...

    研究:大模型输出一致性:确定性与随机性的场景化平衡

    大模型在相同输入下的输出是否一致,本质上取决于其设计目标、任务性质以及技术实现方式。这一问题需要从技术原理、应用场景、用户需求三个维度进行深度分析: 一、技术实现:确定性与随机性的平衡 模型架构的确定性基础 大模型的核心参数(如权重矩阵)在训练完成后是固定的…...

    数据分析1

    一、常用数据处理模块Numpy Numpy常用于高性能计算&#xff0c;在机器学习常常作为传递数据的容器。提供了两种基本对象&#xff1a;ndarray、ufunc。 ndarray具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组。 ufunc提供了对数组快速运算的标准数学函数。 ndar…...

    vmare pro安装报错用户在命令行上发出了EULAS_AGREED=1,表示不接受许可协议的错误解决方法

    问题现状和原因 用户在命令行上发出了EULAS_AGREED1&#xff0c;表示不接受许可协议的错误。 以上错误主要原因是因为机器安装过了vmare 卸载时没有卸载干净导致的。 解决方法&#xff1a; 1、控制面板-程序和功能-卸载程序。找到vamre卸载掉。 2、打开开始菜单输入注册表 …...

    《Linux篇》基础开发工具——vim详细介绍

    文章目录 1.软件包管理1.1 什么是软件包1.2 Linux软件生态 2.编辑器vim2.1 vim的正常/命令模式2.2 vim的末行模式2.3 vim的插入模式 3.配置vim 1.软件包管理 我们先来看一下再Linux是那个如何安装软件&#xff1f; 源码安装&#xff1a;软件是存在相互依赖的关系的&#xff0…...

    AI图片跳舞生成视频,animate X本地部署。

    本期内容打包限时免费下载https://www.kdocs.cn/l/cnQ5lNU5DFZB 对比不同算法&#xff0c;使用同一组图片和舞蹈视频。animate X官网&#xff0c;下载项目解压。按照官方教程下载模型&#xff0c;项目包和命名好的模型包已上传网盘&#xff0c;放到解压目录下即可。 安装好cond…...

    Web技术与Apache网站部署

    一、Web 基础与 HTTP 协议 1.1 静态网页与动态网页 静态网页 定义&#xff1a;由纯 HTML、CSS、JavaScript 构成&#xff0c;文件扩展名为 .htm 或 .html。内容在服务器生成后固定不变&#xff0c;仅通过客户端脚本&#xff08;如 JS&#xff09;实现视觉动态效果&#xff08…...

    第七章:Server/Client Communication

    Chapter 7: Server/Client Communication 从工具集成到服务器通信&#xff1a;如何让AI“远程协作”&#xff1f; 在上一章的工具与LLM集成中&#xff0c;我们已经能让AI调用真实世界的工具。但你是否想过&#xff1a;如果多个用户同时请求天气查询&#xff0c;或者需要远程控…...

    Linux调试器 - gdb使用指南

    目录 一、背景知识 二、开始使用 gdb &#xff08;一&#xff09;查看源代码相关指令 &#xff08;二&#xff09;程序执行控制指令 &#xff08;三&#xff09;断点相关指令 &#xff08;四&#xff09;变量操作相关指令 &#xff08;五&#xff09;其他常用指令 在Li…...

    C++面试常青客:LRUCache最近最少使用算法

    C面试常青客&#xff1a;LRUCache最近最少使用算法 文章目录 C面试常青客&#xff1a;LRUCache最近最少使用算法1.背景&#x1f3c6;2.原理&#x1f680;2.1基本原理2.2核心特性 3.结构3.1为什么需要 list<pair<int,int>>&#xff08;双向链表&#xff09;&#xf…...

    【含文档+PPT+源码】基于微信小程序的社交摄影约拍平台的设计与实现

    项目介绍 本课程演示的是一款基于微信小程序的社交摄影约拍平台的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系…...

    jetson nano上Ubuntu系统调用摄像头bug

    今天在做一个比赛的时候&#xff0c;通过调用摄像头做检测并输出目标角度和距离。刚开始用的是 cv::VideoCapture cap; cap.open("/dev/video0");没有任何问题&#xff0c;使用pnp解算得到的角度和距离都是正确的&#xff0c;画面也是小画面。 后面加了一些功能&…...

    用Python做有趣的AI项目5:AI 画画机器人(图像风格迁移)

    这个项目将使用 PyTorch 实现图像风格迁移&#xff08;Neural Style Transfer&#xff09;&#xff0c;让一张图片看起来具有另一张图片的“艺术风格”。 &#x1f527; 开发环境建议 Python 3.8 PyTorch&#xff08;pip install torch torchvision&#xff09; PIL&#x…...

    一种用于从视网膜图像中识别疾病的 BERT 式自监督学习 CNN

    大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 在医学成像领域&#xff0c;深度学习的出现&#xff0c;尤其是卷积神经网络 &#xff08;CNN&#xff09; 的应用&#xff0c;彻底改变了医学影像的分析和解释。然而&#xff0c;深度学习方法通常依…...

    OpenCV 图形API(68)图像与通道拼接函数------垂直拼接两个图像/矩阵的函数concatVert()

    操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 对给定的矩阵执行垂直拼接。该函数将两个 GMat 矩阵&#xff08;列数相同&#xff09;垂直连接&#xff1a; GMat A { 1, 7,2, 8,3, 9 }; GMat…...

    重测序关系矩阵构建方式汇总

    样本间亲缘关系矩阵&#xff08;kinship matrix&#xff09;和同源性矩阵&#xff08;IBS matrix&#xff09;构建的方式 1. 可以使用plink的–make-rel计算个体之间的亲缘关系&#xff08;强调个体之间的遗传相似性&#xff09; /opt/software/plink --bfile vcf_bfile--mak…...

    OpenCV 图形API(70)图像与通道拼接函数-----创建一个图像或矩阵(GMat)的副本的操作函数copy()

    操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 制作输入图像的一个副本。请注意&#xff0c;这个副本可能不是实际存在的&#xff08;没有实际复制数据&#xff09;。使用此函数来维护图的契约…...

    30天通过软考高项-第六天

    30天通过软考高项-第六天 任务&#xff1a;项目质量管理 思维导图阅读 知识点集锦阅读 知识点记忆 章节习题练习 知识点练习 手写回忆ITTO 听一遍喜马拉雅关于范围的内容 质量管理 -背 1. 过程定义 龟管控 要求标准规划定&#xff0c;计划转化看过程&#xf…...

    JUC中各种锁机制的应用和原理及死锁问题定位

    JUC中各种锁机制的应用和原理及死锁问题定位 在互联网大厂Java求职者的面试中&#xff0c;经常会被问到关于JUC&#xff08;Java Util Concurrency&#xff09;中的各种锁机制及其应用和原理的问题。本文通过一个故事场景来展示这些问题的实际解决方案。 第一轮提问 面试官&…...

    区块链vs实体经济:一场金融、医疗、政务与物流的“效率革命”

    区块链技术作为一种去中心化、不可篡改的分布式账本技术&#xff0c;正在重塑多个行业的运行模式。从金融交易的透明化到医疗数据的安全共享&#xff0c;从政务服务的效率提升到物流供应链的全程可追溯&#xff0c;区块链的跨行业应用展现出巨大的潜力与价值。以下是其在金融、…...

    FTP-网络文件服务器

    部署思路 单纯上传下载ftp系统集成间的共享 samba网络存储服务器 NFS 网络文件服务器&#xff1a;通过网络共享文件或文件夹&#xff0c;实现数据共享 NAS &#xff08; network append storage):共享的是文件夹 FTP&#xff1a;文件服务器samba&#xff1a;不同系统间的文件…...

    嵌入式RTOS实战:uC/OS-III最新版移植指南(附项目源码)

    文章目录 前言一、uC/OS简介二、工程移植2.1 下载ucos源码2.2 创建空白工程2.3 拷贝ucosiii源码文件2.3.1 UC-CONFIG2.3.2 UC-CPU2.3.3 UC-LIB2.3.4 UC-OS3 2.3 添加工程文件分组及路径2.4 代码首次编译2.5 源码修改2.5.1 cpu_cfg.h2.5.2 os_cpu_c.c2.5.3 lib_cfg.h2.5.4 sys.h…...

    10.Excel:快速定位目标值

    一 批量删除 1.如何使用 快捷键 CTRLG 补充&#xff1a;直接选择定位条件。 2.作用 1.批量删除工作表中的图片 补充&#xff1a;无法通过框选的方式选中这些图片进行删除。 这样只框选了表格&#xff0c;无法框选图片。因为图片在excel中被认为是一个对象&#xff0c;对象无法通…...

    状态模式 (State Pattern)

    状态模式(State Pattern)是一种行为型设计模式,它允许对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。该模式将状态封装成独立的类,并将请求委托给当前的状态对象,当对象的内部状态发生变化时,其行为也会随之改变。 一、基础部分 1. 意图 允许一个…...

    【Java面试题04】MySQL 篇

    文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、MySQL 篇&#xff1a;☀️☀️☀️1、MySQL 是如何实现事务的? 后序还在更新中~~~三、总结&#xff1a;&#x1f353;&#x1f353;&#x1f353; 一、前言&#x1f680;&#x1f680;&#x1f680; ☀️ 你每一…...

    同时安装多个版本的golang

    https://golang.google.cn/dl/ go install golang.org/dl/go1.20latest 这样就会将 go1.20.exe 下载到 GOPATH/bin&#xff0c;但是此时并没有 go1.20 的源码包&#xff0c;也就不能正常执行 build/run 等指令。 然后执行 go1.20 download下载源码包 > go1.20 download …...

    【Web应用服务器_Tomcat】三、Tomcat 性能优化与监控诊断

    在企业级 Java Web 应用的运行过程中&#xff0c;Apache Tomcat 作为广泛使用的 Servlet 容器和 Web 服务器&#xff0c;其性能表现直接影响用户体验和业务稳定性。本篇文章将深入探讨 Tomcat 性能优化的实用技巧&#xff0c;以及如何通过有效的监控诊断手段&#xff0c;及时发…...

    stm32week13

    stm32学习 九.stm32与HAL库 4.时钟树 stm32f103所拥有的时钟源&#xff1a; 外部时钟的稳定性比内部的高&#xff0c;但是成本高&#xff0c;需要在外部额外接 关于上述时钟树的简图&#xff1a; 右下四个是HAL库中的初始化函数 F4的时钟树简图&#xff1a; F7的时钟树简图…...

    深入探究C++ 中的stack、queue和deque

    目录 一、stack&#xff08;栈&#xff09; 二、queue&#xff08;队列&#xff09; 三、deque&#xff08;双向队列&#xff09; 四、容器适配器总结 在C 的标准模板库&#xff08;STL&#xff09;中&#xff0c;stack、queue和priority_queue是非常实用的容器适配器&…...

    第十二节:性能优化高频题-shallowRef/shallowReactive使用场景

    适用场景&#xff1a;大型对象/列表仅需第一层响应式变化&#xff08;如JSON配置数据&#xff09; Vue3 浅层响应式 API&#xff08;shallowRef/shallowReactive&#xff09;使用场景深度解析 一、核心使用场景与性能优化原理 大型 JSON 配置数据管理 • 场景特征&#xff1a;…...

    openGauss DB4AI与scikit-learn模块对比探究

    openGauss当前版本支持了原生DB4AI能力&#xff0c;引入原生AI算子&#xff0c;简化操作流程&#xff0c;充分利用数据库优化器、执行器的优化与执行能力&#xff0c;获得高性能的数据库内模型训练能力。 本文介绍了笔者采用鸢尾花数据集&#xff0c;对openGauss DB4AI功能进行…...

    基于大模型的公安预审办案笔录分析的挑战与应对策略-3

    引言 &#xff1a;在基于大模型的公安预审办案笔录分析应用过程中&#xff0c;虽然取得了一定的成果&#xff0c;但也面临着诸多挑战。本文将分析这些挑战&#xff0c;并提出相应的应对策略&#xff0c;以推动该技术在公安领域的更好地发展和应用。 引文&#xff1a;https://c…...

    ubantu18.04(Hadoop3.1.3)之Flink安装与编程实践(Flink1.9.1)

    说明&#xff1a;本文图片较多&#xff0c;耐心等待加载。&#xff08;建议用电脑&#xff09; 注意所有打开的文件都要记得保存。 第一步&#xff1a;准备工作 本文是在之前Hadoop搭建完集群环境后继续进行的&#xff0c;因此需要读者完成我之前教程的所有操作。 注意本次实…...