【C++】C++11【下】lambda表达式|thread线程库

目录

1、lambda表达式

1.1 lambda表达式的引入

 1.2 lambda表达式的语法

1.3 lambda表达式的原理

 2、线程库

2.1thread类的介绍

2.2 线程函数参数

2.3 原子性操作库(atomic)

2.4 使用场景

应用场景1:

应用场景2:

应用场景3:

应用场景4:


本文为C++【下】

C++11【上】链接:【C++】C++11【上】列表初始化|声明|新容器|右值引用|完美转发|新的类功能-CSDN博客

1、lambda表达式

1.1 lambda表达式的引入

C++98中,如果想要对一个数据集合中的元素进行排序,可利用sort

下面利用sort的使用来引出lambda表达式,因为sort是个函数模板,故第三个参数可以接受函数指针,函数对象(仿函数),lambda表达式,这里先利用前两个来实现

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

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:  

struct Goods
{string _name;  // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
struct ComparePriceLess
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());
}
随着 C++ 语法的发展, 人们觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法, 都要重新去写一个类(仿函数或函数),如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名, 一般需要根据功能来命名(你瞎命名别人很难看懂) 。因此,在 C++11 语法中出现了 Lambda 表达式

 1.2 lambda表达式的语法

lambda表达式书写格式:

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

1. lambda表达式各部分说明:

①、[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]

判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用。

②、(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以

连同()一起省略

③、mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量

性。使用该修饰符时,参数列表不可省略(即使参数为空)。

④、->returntype返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回

值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导

⑤、{statement}函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获

到的变量。

注意:

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为

。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情

int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[] {};// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=] {return a + 3; };// 省略了返回值类型,无返回值类型auto fun1 = [&](int c) {b = a + c; };fun1(10);cout << a << " " << b << endl; //3 13// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int {return b += a + c; };cout << fun2(10) << endl; //26// 捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl; //30return 0;
}

2. 捕获列表说明:

捕捉列表描述了上下文中哪些数据可以被 lambda 使用 ,以及 使用的方式传值还是传引用
【捕捉列表就是捕捉跟我同一个作用域的对象】
传值捕捉:[a]捕捉a [a,b]捕捉a,b  [=]捕捉同一作用域中的所有对象
传值捕捉的对象是不能被改变的,加上mutable就可以改变
传引用捕捉:[&a]捕捉a [&a,&b]捕捉a,b  [&]捕捉同一作用域中的所有对象
  • [var]:   表示值传递方式捕捉变量var
  • [=]:      表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:     表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:  表示值传递方式捕捉当前的this指针(其实=和&就已经包含this指针了)

下面利用lambda表达式实现相加和交换两数操作,了解lambda表达式的使用

int add1(int a, int b)
{return a + b;
}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;cout << ::add1(a, b) << endl;//调用全局域的add1,即主函数之外的//auto add1 = [](int x1, int x2)->int {return x1 + x2 + a; };//没捕捉a导致无法使用auto add1 = [a](int x1, int x2)->int {return x1 + x2 + a; };//捕捉a后可以正常使用auto add1 = [a,b]()->int {return a + b; };//捕捉a,b,只用于a + b//实现a和b交换auto swap1 = [](int& x, int& y){	int z = x;x = y;y = z; };swap(a, b);//不正确的用法,即使加上mutable可以修改传值捕捉了,但传值捕捉的//是a和b的拷贝,只是把a和b的拷贝交换了而已,所以这里是没意义的auto swapab = [a, b]()mutable{int c = a;a = b;b = c;};swapab();return 0;
}
注意:
a. 父作用域指包含 lambda 函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割
比如:
[=, &a, &b] :以引用传递的方式捕捉变量 a b ,值传递方式捕捉其他所有变量
[& a, this] :值传递方式捕捉变量 a this ,引用方式捕捉其他变量
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误
比如: [=, a] = 已经以值传递方式捕捉了所有变量,捕捉 a 重复
d. 在块作用域以外的 lambda 函数捕捉列表必须为空
e. 在块作用域中的 lambda 函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者
非局部变量都会导致编译报错。
f. lambda 表达式之间不能相互赋值 ,即使看起来类型相同
void (*PF)();
int main()
{auto f1 = [] {cout << "hello world" << endl; };auto f2 = [] {cout << "hello world" << endl; };// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了//f1 = f2;   // 编译失败--->提示找不到operator=()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针PF = f2;PF();return 0;
}

3.使用场景:

struct Goods
{string _name;	// 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };//因为sort是个函数模板,故第三个参数可以传仿函数,函数指针和lambda表达式sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate < g2._evaluate; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate; });//一般我们不会像下面这么用,我们一般都是构造lambda表达式的匿名对象/*auto price_greater = [](const Goods& g1, const Goods& g2)->bool {return g1._price <g2._price; };sort(v.begin(), v.end(), price_greater);*/
}

1.3 lambda表达式的原理

先看以下用仿函数和lambda表达式实现的代码:

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);//仿函数// lambdaauto r2 = [=](double monty, int year)->double {return monty * rate * year;};r2(10000, 2);//lambda表达式return 0;
}
从使用方式上来看,函数对象与 lambda表达式完全一样。函数对象将 rate 作为其成员变量,在定义对象时给出初始值即可, lambda表达式通过捕获列表可以直接将该变量捕获到。
实际在底层编译器对于 lambda 表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个 lambda 表达式,编译器会自动生成一个类,在该类中重载了 operator()
int main()
{int a = 1, b = 2;// 对象 = 对象(编译器生成的lambda_uuid仿函数的对象)auto add = [](int x, int y)->int {return x + y; };add(a,b); //call lambda_uuid仿函数的operator()//底层还是靠仿函数来实现,也就是说你定义了一个lambda表达式,实际上//编译器会全局域生成一个叫lambda_uuid类,仿函数的operator()的参数和实现//就是我们写的lambda表达式的参数和实现//仿函数和lambda表达式的关系如同迭代器和范围for的关系,也就是lambda表达式存在即合理return 0;
}

 2、线程库

2.1thread类的介绍

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

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++11(2011)才更新的,且到现在也没有更新一个官方的封装好的靠谱网络库,其次一堆使用意义不大的语法一堆,学习成本高

  • thread()构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
  • thread(fn,args1,args2,参数…)构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数的参数
  • get_id()获取线程id
  • jionable()线程是否还在执行,joinable代表的是一个正在执行中的线程。
  • jion()该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
  • detach()在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关
注意:
1. 线程是操作系统中的一个概念, 线程对象可以关联一个线程,用来控制线程以及获取线程的
状态
2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
#include <thread>
int main()
{std::thread t1;cout << t1.get_id() << endl;return 0;
}
get_id()的返回值类型为id 类型 id 类型实际为 std::thread 命名空间下封装的一个类,该类中
包含了一个结构体:
// vs下查看
typedef struct
{ /* thread identifier for Win32 */void *_Hnd; /* Win32 HANDLE */unsigned int _Id;
} _Thrd_imp_t;
3. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。
线程函数一般情况下可按照以下三种方式提供:
  • 函数指针
  • lambda表达式
  • 函数对象
#include <iostream>
using namespace std;
#include <thread>
void ThreadFunc(int a)
{cout << "Thread1" << a << endl;
}
class TF
{
public:void operator()(){cout << "Thread3" << endl;}
};
int main()
{// 线程函数为函数指针thread t1(ThreadFunc, 10);// 线程函数为lambda表达式thread t2([]{cout << "Thread2" << endl; });// 线程函数为函数对象TF tf;thread t3(tf);t1.join();t2.join();t3.join();cout << "Main thread!" << endl;return 0;
}
4. thread 类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个
线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
5. 可以通过 jionable() 函数判断线程是否是有效的,如果是以下任意情况,则线程无效
  • 采用无参构造函数构造的线程对象
  • 线程对象的状态已经转移给其他线程对象
  • 线程已经调用jion或者detach结束

2.2 线程函数参数

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的 ,因此:即使线程参数为引用类型,在
线程中修改后也不能修改外部实参,因为 其实际引用的是线程栈中的拷贝,而不是外部实参
#include <thread>
void ThreadFunc1(int& x)
{x += 10;
}
void ThreadFunc2(int* x)
{*x += 10;
}
int main()
{int a = 10;//在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际//引用的是线程栈中的拷贝thread t1(ThreadFunc1, a);t1.join();cout << a << endl;// 如果想要通过形参改变外部实参时,必须借助std::ref()函数thread t2(ThreadFunc1, std::ref(a));t2.join();cout << a << endl;// 地址的拷贝thread t3(ThreadFunc2, &a);t3.join();cout << a << endl;return 0;
}
注意:如果是类成员函数作为线程参数时,必须将 this 作为线程函数参数。

2.3 原子性操作库(atomic)

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

#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;
}
C++98 中传统的解决方式:可以对共享修改的数据可以加锁保护
#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中引入了原子操作。
问:锁的串行效率高还是并行效率高?
答:串行效率高
mutex mtx;
int x = 0;//两个线程一起对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();}*///实际上串行更快。为什么?因为这里锁的力度太小了,时间都花到切换上下文了。
}
原子操作:即不可被中断的一个或一系列操作, C++11引入 的原子操作类型,使得线程间数据的同步变得非常高效。
注意:需要使用以上原子操作变量时,必须添加头文件

 在 C++11 中, 程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的
访问
更为普遍的,程序员可以 使用 atomic 类模板,定义出需要的任意原子类型
atmoic<T> t;    // 声明一个类型为T的原子类型变量t

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

#include <atomic>
int main()
{atomic<int> a1(0);//atomic<int> a2(a1);   // 编译失败atomic<int> a2(0);//a2 = a1;               // 编译失败return 0;
}

2.4 使用场景

应用场景1:

两个线程一起对x加n次:thread各配合函数指针、仿函数、lambda表达式使用
①、函数指针
atomic<int> x = 0; //支持整形/浮点的原子++,--
//扩展学习:atomic支持CAS->无锁编程//两个线程一起对x加n次
void Add(int n)
{for (int i = 0; i < n; ++i){++x;}
}int main()
{//利用函数指针配合threadthread t1(Add, 1000000);//加锁之后每次计算的出的数据就准确了thread t2(Add, 1000000);//要在主线程结束前对两个线程join一下才行t1.join();t2.join();cout << x << endl;return 0;
}

②、仿函数

atomic<int> x = 0;
struct Add
{int operator()(int n){for (int i = 0; i < n; ++i){++x;}return x;}
};
int main()
{Add add;//利用仿函数对象配合threadthread t1(add, 1000000);thread t2(add, 1000000);//也可以写为Add(),即用匿名对象//要在join前获取线程idcout << t1.get_id() << endl;//每次获取到的ID不一cout << t2.get_id() << endl;//每次获取到的ID不一t1.join();t2.join();cout << x << endl;//2000000return 0;
}

③、lambda表达式

int main()
{atomic<int> x = 0;auto add = [&x](int n) {for (int i = 0; i < n; ++i){++x;}};thread t1(add, 1000000);thread t2(add, 1000000);//也可以写为Add(),即用匿名对象//要在join前获取线程idcout << t1.get_id() << endl;//每次获取的ID不一cout << t2.get_id() << endl;//每次获取的ID不一t1.join();t2.join();cout << x << endl;//2000000return 0;
}

应用场景2:

m个线程对x加n次

int main()
{atomic<int> x = 0;//m个线程对x加n次int m, n;cin >> m >> n;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));}for (auto& t : vthreads){cout << t.get_id() << "join" << endl;t.join();}cout << x << endl;return 0;
}//写法二
int main()
{atomic<int> x = 0;//m个线程对x加n次int m, n;cin >> m >> n;//thread支持移动赋值和移动拷贝,不支持深拷贝的拷贝构造和拷贝赋值vector<thread> vthreads(m);for (int i = 0; i < m; ++i){//移动赋值【thread构造的是匿名对象,返回右值】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;
}

应用场景3:

使用两个线程打印0~n之间的数,一个线程打印奇数,一个线程打印偶数,要求依次打印。
//以下代码存在一个问题:竞争终端,每次结果都不一样,且都不对
int main()
{int n = 100;thread t1([n](){for (int i = 0; i < n; ++i){//偶数if (i % 2 == 0)cout << this_thread::get_id() << ":" << i << endl;}});thread t2([n](){for (int i = 0; i < n; ++i){//奇数if (i % 2)cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到id}});t1.join();t2.join();return 0;
}

​​​​​​改善后:

#include<mutex>
#include<condition_variable>//偶数和奇数依次打印的过程类似于生产者消费者模型
//互斥锁和条件变量同时使用
int main()
{int n = 100;mutex mtx1, mtx2;condition_variable cv1, cv2;//条件变量(一个是不行的,要让偶数先走),必须配合锁用thread t1([&](){for (int i = 0; i < n; i+=2){	if (i != 0)cv1.wait(unique_lock<mutex>(mtx1));//偶数cout << this_thread::get_id() << ":" << i << endl;cv2.notify_one(); //t1打印偶数以后,通知t2}});thread t2([&](){for (int i = 0; i < n; i+=2){cv2.wait(unique_lock<mutex>(mtx2));//要传互斥锁来保护条件变量//奇数cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到idcv1.notify_one(); //t2}});t1.join();t2.join();return 0;
}//法二
int main()
{int n = 100;mutex mtx1, mtx2;condition_variable cv1, cv2;//条件变量(一个是不行的,要让偶数先走),必须配合锁用thread t1([&](){for (int i = 0; i < n; i += 2){//偶数cout << this_thread::get_id() << ":" << i << endl;cv2.notify_one(); //t1打印偶数以后,通知t2cv1.wait(unique_lock<mutex>(mtx1));}});thread t2([&](){for (int i = 0; i < n; i += 2){cv2.wait(unique_lock<mutex>(mtx2));//要传互斥锁来保护条件变量//奇数cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到idcv1.notify_one(); //t2}});t1.join();t2.join();return 0;
}

应用场景4:

线程池

//扩展:线程池
struct tack
{template<class fn>tack(fn){}
};class thread_poo1
{
public:thread_poo1(int n = 8):vthreads(n){}private:vector<thread> vthreads;//queue<task> _q;
};

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Qt中解决opencv的putText函数无法绘制中文的一种解决方法

文章目录 1.问题2.查阅资料3.解决办法 1.问题 在opencv中&#xff0c;假如直接使用putText绘制中文&#xff0c;会在图像上出现问号&#xff0c;如下图所示&#xff1a; 2.查阅资料 查了一些资料&#xff0c;说想要解决这个问题&#xff0c;需要用到freetype库或者用opencv…

Java--多线程--Thread类+Runnable接口

1.多进程与多线程 1.1多进程&#xff1a; 一个进程是一个包含自身地址的程序&#xff0c;每个独立执行的程序都称为进程&#xff0c;也就是正在执行的程序&#xff0c;系统可以分配给每个进程一段有限的使用CPU的时间&#xff08;CPU时间片&#xff09;&#xff0c;CPU在这个时…

Django实战项目-学习任务系统-查询列表分页显示

接着上期代码框架&#xff0c;6个主要功能基本实现&#xff0c;剩下的就是细节点的完善优化了。 接着优化查询列表分页显示功能&#xff0c;有很多菜单功能都有查询列表显示页面情况&#xff0c;如果数据量多&#xff0c;不分页显示的话&#xff0c;页面展示效果就不太好。 本…

pytorch 笔记:GRU

1 介绍 对于输入序列中的每个元素&#xff0c;每一层都计算以下函数&#xff1a; ht​ 是t时刻 的隐藏状态xt​ 是t时刻 的输入ht−1​ 是 t-1时刻 同层的隐藏状态或 0时刻 的初始隐藏状态rt​,zt​,nt​ 分别是重置门、更新门和新门。σ 是 sigmoid 函数∗ 是 Hadamard 乘积。…

OpenCV 笔记(4):图像的算术运算、逻辑运算

Part11. 图像的算术运算 图像的本质是一个矩阵&#xff0c;所以可以对它进行一些常见的算术运算&#xff0c;例如加、减、乘、除、平方根、对数、绝对值等等。除此之外&#xff0c;还可以对图像进行逻辑运算和几何变换。 我们先从简单的图像加、减、逻辑运算开始介绍。后续会有…

【Git企业开发】第四节.Git的分支管理策略和bug分支

文章目录 前言一、Git的分支管理策略 1.1 Fast forward 模式和--no-ff 模式 1.2 企业分支管理策略二、bug分支三、删除临时分支四、总结总结 前言 一、Git的分支管理策略 1.1 Fast forward 模式和--no-ff 模式 通常合并分支时&#xff0c;如果可能&#xff0c;Git 会…

61. 旋转链表、Leetcode的Python实现

博客主页&#xff1a;&#x1f3c6;李歘歘的博客 &#x1f3c6; &#x1f33a;每天不定期分享一些包括但不限于计算机基础、算法、后端开发相关的知识点&#xff0c;以及职场小菜鸡的生活。&#x1f33a; &#x1f497;点关注不迷路&#xff0c;总有一些&#x1f4d6;知识点&am…

基于GEE云平台一种快速修复Landsat影像条带色差的方法

这是之前关于去除遥感影像条带的另一篇文章&#xff0c;因为出版商推迟了一年发布&#xff0c;所以让大家久等了。这篇文章的主要目的是对Landsat系列卫星因为条带拼接或者镶嵌产生的条带来进行的一种在线修复方式。 原文连接 一种快速修复Landsat影像条带色差的方法 题目&a…

ffmpeg命令帮助文档

一&#xff1a;帮助文档的命令格式 ffmpeg -h帮助的基本信息ffmpeg -h long帮助的高级信息ffmpeg -h full帮助的全部信息 ffmpeg的命令使用方式&#xff1a;ffmpeg [options] [[infile options] -i infile] [[outfile options] outfile] 二&#xff1a;将帮助文档输出到文件 …

【IDEA】设置sql提示

第一步&#xff1a;注入SQL语言 1.首先选择任意一条sql语句&#xff0c;右击&#xff0c;选择 ‘显示上下文操作’ 2.选择 ‘注入语言或引用’ 3. 往下翻&#xff0c;找到MySQL 第二步&#xff1a;配置MySQL数据库连接 1.首先点击侧边的数据库&#xff0c;再点击上面的加号 2…

中兴路由器、小米路由器无线信号强度对比

最近小米新推出的路由器小米AX3000T非常火&#xff0c;在网上看到有好多人都在安利&#xff0c;引起了我的兴趣&#xff0c;刚好老家的路由器用了这么久也是时候要换一个了&#xff0c;毕竟我妈老说上网卡??所以我立马就在PDD搞了一台回来&#xff0c;打算和我现在家里用的中…

二叉树进阶 - (C++二叉搜索树的实现)

二叉树进阶 - &#xff08;二叉搜索树的实现&#xff09; 二叉搜索树1. 二叉搜索树概念2. 二叉搜索树操作2.1 二叉搜索树的查找2.2 二叉搜索树的插入2.3 二叉搜索树的删除(重点) 3. 二叉搜索树的(代码)实现 二叉搜索树 1. 二叉搜索树概念 二叉搜索树又称二叉排序树&#xff0…

腾讯云域名备案后,如何解析到华为云服务器Linux宝塔面板

一、购买域名并且进行备案和解析&#xff0c;正常情况下&#xff0c;购买完域名&#xff0c;如果找不到去哪备案&#xff0c;可以在腾讯云上搜索“备案”关键词就会出现了&#xff0c;所以这里不做详细介绍&#xff0c;直接进行步骤提示&#xff1a; 二、申请ssl证书&#xff0…

diffusers-Load adapters

https://huggingface.co/docs/diffusers/main/en/using-diffusers/loading_adaptershttps://huggingface.co/docs/diffusers/main/en/using-diffusers/loading_adapters 有几种训练技术可以个性化扩散模型&#xff0c;生成特定主题的图像或某些风格的图像。每种训练方法都会产…

关于嵌入式rtthread系统与单片机芯片

简介 我估计已经有很久没更新了&#xff0c;近一年都在某个国企里工作&#xff0c;我做的就是嵌入式工程师的岗位&#xff0c;最近才刚刚退出来&#xff0c;想来说说自己的工作使用的软件和系统。 本身进公司的时候&#xff0c;其实做的就是写单片机的板子的程序的工作&#x…

mysql迁移data目录(Linux-Centos)

随着时间的推移&#xff0c;mysql的数据量越越大&#xff0c;使用yum默认安装的目录为系统盘 /var/lib/mysql&#xff0c;现重新挂载了一个硬盘&#xff0c;需要做数据目录的迁移到 /mnt/data/。以解决占用系统盘过高情况。 1.强烈建议这种操作。镜像一个一样的Centos系统&…

基于springboot实现游戏分享网站系统项目【项目源码+论文说明】

基于springboot实现游戏分享网站演示 摘要 网络的广泛应用给生活带来了十分的便利。所以把游戏分享管理与现在网络相结合&#xff0c;利用java技术建设游戏分享网站&#xff0c;实现游戏分享的信息化。则对于进一步提高游戏分享管理发展&#xff0c;丰富游戏分享管理经验能起到…

canvas实现环形进度条

与setTimeout和setInterval不同&#xff0c;requestAnimationFrame不需要设置时间间隔。 效果图 源代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Canvas progress</title> </head&g…

软件测试/测试开发丨ChatGPT能否成为PPT最佳伴侣

点此获取更多相关资料 简介 PPT 已经渗透到我们的日常工作中&#xff0c;无论是工作汇报、商务报告、学术演讲、培训材料都常常要求编写一个正式的 PPT&#xff0c;协助完成一次汇报或一次演讲。PPT相比于传统文本的就是有布局、图片、动画效果等&#xff0c;可以给到观众更好…

Qt 窗口无法移出屏幕

1 使用场景 设计一个缩进/展开widget的效果&#xff0c;抽屉效果。 看到实现的方法有定时器里move窗口&#xff0c;或是使用QPropertyAnimation。 setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint |Qt::X11BypassWindowManagerHint&#xff09;&#xff1b; 记得在移…