C++11(中)
C++11(中)
- 1.可变参数模板
- 1.1.使用场景
- 2.lambda表达式(重要)
- 2.1.使用说明
- 2.2.函数对象与lambda表达式
- 3.线程库
- 3.1.thread
- 3.2.atomic原子库操作
- 3.3.mutex
- 3.3.1.mutex的种类
- 3.3.2.lock_guard
- 3.3.3.unique_lock
🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【C++的学习】
📝📝本篇内容:可变参数模板;可变参数使用场景;lambda表达式;lambda表达式使用说明;函数对象与lambda表达式;线程库;thread;atomic原子库操作;mutex;mutex的种类;lock_guard;unique_lock
⬆⬆⬆⬆上一篇:C++11(上)
💖💖作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-
1.可变参数模板
C++11的新特性可变参数模板能够让我们创建可以接受可变参数的函数模板和类模板
#include <iostream>
using namespace std;
template<class ...Args>
void ShowList(Args ...args)
{
//Args是一个模板参数包,args是一个函数形参参数包
//声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数
}
int main()
{ShowList();return 0;
}
上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点
下面举一个栗子
#include <iostream>
using namespace std;
void ShowList()
{
//递归终止函数
}
template<class T,class ...Args>
void ShowList(T value,Args... args)
{cout << value << endl;
//Args是一个模板参数包,args是一个函数形参参数包
//声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数ShowList(args...);
}
int main()
{ShowList(1,2,3,4,5,6,7,8,9,10);return 0;
}
还可以使用逗号表达式来展开参数包
#include <iostream>
#include <string>
using namespace std;
template<class T>
void Print(T value)
{cout << value << endl;
}
template<class ...Args>
void ShowList(Args... args)
{int arr[] = {(Print(args),0)...};
}
int main()
{ShowList(1,string("hello world"),3, 4.1, 5, 6, 7, 8, 9, 10);return 0;
}
我们的的数组元素其实会初始化,{(Print(args), 0)…}将会展开成((Print(arg1),0),(Print(arg2),0),(Print(arg3),0), etc… ),在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包
1.1.使用场景
在我们的很多STL容器中,也使用到了可变参数模板
上面两个就是最简单的例子,我们可以看到emplace_back函数就是使用了可变参数包,同时他还是万能引用
那它和我们的push_back有啥区别呢?
让我们来看一下它的底层先,我们在VS2022下演示
我们选中emplace_back,然后按下F12,此时会跳转到下图
此时再跳转到_Emplace_one_at_back
此时再进行跳转到_Emplace_reallocate
我们可以看见construct,继续跳转
到底了,无法再继续跳转了,其实它的底层就是使用了定位new,如果是不知道什么是定位new可以看一下我这篇博客☞C++内存管理
不知道大家有没有看过我们侯捷大佬的《STL源码剖析》,在这本书的第二章的空间配置器中就讲到了construct的底层,我可以给大家看一下,具体的编译器和具体的版本实现会不一样,但是大差不差
接下来我们回归主题,来研究一下,emplace_back有啥区别,我们首先用到我们自己模拟实现的string
#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <cassert>
using namespace std;
namespace lnb
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _arr;}iterator end(){return _arr + _size;}const iterator begin()const{return _arr;}const iterator end()const{return _arr + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(const char* str)" << endl;if (_size == 0){_capacity = 4;}_arr = new char[_capacity+1];strcpy(_arr, str);}string(const string& str){cout << "string(const string& str)——深拷贝" << endl;_arr = new char[str._capacity + 1];strcpy(_arr, str._arr);_size = str._size;_capacity = str._capacity;}string(string&& str):_arr(nullptr),_size(0),_capacity(0){cout << "string(const string&& str)——移动拷贝" << endl;swap(str);//这边之所以可以传递到左值引用,是因为当实参传给str后,有了左值的属性}const string& operator=(const string& str){cout << "const string& operator=(const string& str)—赋值重载" << endl;if (this != &str){char* tmp = new char[str._capacity + 1];delete[] _arr;_arr = tmp;strcpy(_arr, str._arr);_capacity = str._capacity;_size = str._size;}return *this;}const string& operator=(string&& str){cout << "const string& operator=(string&& str)——移动赋值" << endl;swap(str);return *this;}~string(){delete[] _arr;_size = _capacity = 0;}const char* c_str(void){return _arr;}size_t size()const{return _size;}size_t capacity()const{return _capacity;}char& operator[](size_t pos){assert(pos < _size);return _arr[pos];}char& operator[](size_t pos)const{assert(pos < _size);return _arr[pos];}void Print(const string& str){for (size_t i = 0; i < str._size; i++){cout << str[i] << " ";}cout << endl;iterator bg = str.begin();while (bg != str.end()){cout << *bg << endl;bg++;}}void reserve(size_t size){char* tmp = new char[size + 1];strcpy(tmp, _arr);delete _arr;_arr = tmp;_capacity = size;}void resize(size_t size, char c = '\0'){if (size <= _size){_arr[size] = '\0';_size = size;}else{if (size > _capacity){reserve(size);}size_t i = _size;while (i < size){_arr[i] = c;i++;}_size = size;_arr[_size] = '\0';}}void swap(string& str){std::swap(_arr, str._arr);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}private:char* _arr;size_t _size;size_t _capacity;static const size_t npos = -1;};
}
class Date
{
public:Date(int year,int month,int day):_year(year),_month(month),_day(day){cout << "Date(int year,int month,int day)" << endl;}Date(Date&& d){cout << "Date(Date&& d)——移动拷贝" << endl;}Date(const Date& d){cout << "Date(const Date& d)——拷贝构造函数" << endl;}private:int _year;int _month;int _day;
};
int main()
{/*list<lnb::string> mylist;mylist.push_back("1111");cout << "----------------------" << endl;mylist.emplace_back("1111");*//*list<Date> mylist;mylist.push_back({ 2024,11,18 });cout << "----------------------" << endl;mylist.emplace_back( 2024,11,18);*//*lnb::string str("hello world");vector<Date> myvector;myvector.push_back({2024,11,18});cout << "----------------------" << endl;myvector.emplace_back(2024,11,18);*//*list<pair<int,lnb::string>> mylist;mylist.push_back(make_pair(10,lnb::string("1111")));cout << "-------------------------------" << endl;mylist.emplace_back(1,lnb::string("1111"));*/return 0;
}
其实emplace_back依靠的是定位new的调用,实际效率差不多,可以无脑使用emplace
2.lambda表达式(重要)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
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());return 0;
}
每当我们需要使用各种算法时就会需要用到仿函数,但是次数一旦多了就会比较复杂和不便,因此出现了lambda表达式
2.1.使用说明
格式说明:
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement}
①lambda表达式各部分说明
[capture-list]:捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用
(parameters):参数列表,与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
mutable:默认情况下,lambda函数总是const函数,不能对其捕获的参数进行修改,mutable可以取消其常量性。使用该修饰符时,参数列表不能省略(即使参数为空)
->returntype:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值可以省略。返回值类型明确的情况下也可以省略,由编译器对返回类型进行推导
{}:函数体,在函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量
最简单的lambda函数:[]{}
②捕获列表说明
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕获变量var
[&]:表示引用传递捕获所有父作用域中的变量(包括this)
[this]:b表示值传递方式捕获当前的this指针
注意:
① 在块作用域以外的lambda函数捕捉列表必须为空
②语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:
[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
③捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:
[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
看接下来的栗子
#include <iostream>
using namespace std;
struct Date
{
public:Date(int year,int month,int day):_year(year),_month(month),_day(day){}Date(const Date& d){//for example 3[this,&d] {_year = d._year;_month = d._month;_day = d._day;}();}int _year;int _month;int _day;
};
int main()
{int out = 10;{//for example 1cout << "for example 1:";int x = 10;int y = 20;auto f1 = [=]()->int{return x + y; };//具体用什么类型接受后续会讲cout<<f1() << endl;//for example 2cout << "for example 2:";[=](int a, int b)mutable{a = 10;b = 20;//不使用mutable可以修改x = 11;//不使用mutable无法修改out = 10;//也可以捕获cout << "a=" << a << ",b=" << b << ",x=" << x << ",out=" << out << endl;}(100,200);//for example 3cout << "for example 3:";Date d1(2024, 11, 18);Date d2(d1);cout << d2._year << ":" << d2._month << ":" << d2._day << endl;}return 0;
}
#include <iostream>
using namespace std;
int main()
{auto f1=[]() {return 1; };auto f2(f1);//可以拷贝构造//f1 = f2;//不能进行相互赋值int(*fptr)(void) = f1;//可以赋值给相同类型的函数指针return 0;
}
可以看出lambda表达式实际是一个匿名函数
2.2.函数对象与lambda表达式
#include <iostream>
using namespace std;
struct Add
{int operator()(int x,int y){return x + y;}
};
int main()
{Add x;cout<<x(1, 2)<<endl;//3auto y= [](int x, int y) {return x + y; };cout<<y(1, 2)<<endl;//3return 0;
}
从使用方式上来看,函数对象与lambda表达式完全一样
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
如果在VS2019中的话,类的名称是lambda_uuid,但是在VS2022下就不是了,对于什么是uuid,简单来说就是通用唯一标识符 (UUID) 是一种特定形式的标识符,在大多数实际用途中可以安全地认为是唯一的。两个正确生成的 UUID 相同的可能性几乎可以忽略不计,即使它们是由不同的各方在两个不同的环境中创建的。
3.线程库
3.1.thread
现在C++11支持多线程了,以前的话会因为平台的的接口问题,导致移植性较差,现在就可以完全解决这个问题了
☞thread文档
我们首先来看一下thread类的基本使用以及常用的调用接口
首先是构造函数
如果构造函数不带任何参数,没有提供线程函数,该对象实际没有任何线程,没什么用处
在上面的代码中还用到了get_id()成员函数,它其实是一个std下封装的一个类,返回值为id类型
接下来,我们提供一下线程函数,来正式使用一下线程
线程函数一般有三种方式提供,分别是lambda表达式,函数对象,函数指针
#include <iostream>
#include <thread>
#include <future>
using namespace std;
void Func(int x,int y)
{cout << "x:" << x << ",y:" << y << endl;cout << "I am thread t2" << endl;
}struct Add
{int operator()(int x, int y){cout << "I am thread t3" << endl;return x + y;}
};int main()
{//for example 1-lambda表达式作为线程函数thread t1([]() {cout << "I am thread t1" << endl;});//for example 2-函数指针作为线程函数int a = 10;int b = 20;thread t2(&Func,a,b);//for example 3-函数对象作为线程函数thread t3(Add(), a, b);t1.join();//等待线程t2.join();t3.join();future<int> result = async(Add(),a,b);//获取线程的返回值cout <<"thread t3 return->"<<result.get() << endl;cout << "Main thread" << endl;return 0;
}
顺序看着比较乱,这是因为线程都是在同时进行的,没有进行同步互斥操作,并且我们代码最后需要等待线程,使用join,不然线程还没执行完,主线程就结束了
我们的thread是防拷贝的,不允许拷贝构造和赋值重载,但是可以移动构造和移动赋值,即将一个线程的状态转给其他线程对象,转移期间不影响线程的执行
#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;
int main()
{//模仿线程池thread thread_array[5];for (int i=0;i<sizeof(thread_array)/sizeof(thread_array[0]);i++){thread_array[i] = thread([i]()//移动赋值重载{printf("I am thread t%d\n", i+1);});}for (int i = 0; i < 5; i++){thread_array[i].join();}return 0;
}
我们可以通过joinable,来判断线程是否有效,对于一下任意情况,线程无效:
①采用无参构造函数构造的线程对象
②线程对象的状态已经转移给其他线程对象
③线程已经调用jionable或者detach结束
其中第三点可能不太理解detach,我们来演示一下,它其实就是不需要主线程等待了
#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;
void Func(int& x)
{while (1){cout << "thread 1,x="<<x<< endl;Sleep(1000);}
}int main()
{int a = 10;thread t1(Func,ref(a));//传引用就得用reft1.detach();//使用后主线程就不需要再等待了Sleep(10000);return 0;
}
在上面图中演示了detach的使用,同时还有一个关注点,就是如果线程函数的参数要使用引用,得需要使用ref(),也可以使用指针来改变对应的值,我们一般线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的
#include <iostream>
#include <thread>
using namespace std;
struct Date
{void print(){cout << _year << "/" << _month << "/" << _day << endl;}int _year=2024;int _month=11;int _day=19;
};
int main()
{Date d;thread t1(&Date::print,&d);//类成员函数作为线程函数时需要带上thist1.join();return 0;
}
3.2.atomic原子库操作
在我们日常使用多线程中最担心的就是碰到线程安全问题,因此我们C++11就有了原子操作库,
对于原子操作,简单来说就是一个事件只有两种情况,要么做完,要么没做,因此使用后原子操作库后就能保证安全
☞atomic原子库文档
原子类型只能从其模板参数中进行构造,不允许拷贝构造,移动构造以及赋值重载等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值重载等。
符重载默认删除掉了。
#include <iostream>
#include <thread>
using namespace std;
int num = 0;
void Func()
{int i = 100;while(i--)num++;
}int main()
{thread t1(Func);thread t2(Func);t1.join();t2.join();cout << num << endl;return 0;
}
上图中的内容为没有使用原子打印出的结果,我们的理想结果为200,但是结果显而易见,不过在多次尝试中,也很多次的达到了200,但这样是有线程安全问题的
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic<int> num=0;//使用原子操作
void Func()
{int i = 100;while(i--)num++;
}int main()
{thread t1(Func);thread t2(Func);t1.join();t2.join();cout << num.load() << endl;return 0;
}
如上代码就没问题了,我们如果需要知道num原本的类型值,就可以使用load成员函数,有的同学可能会考虑到使用锁,确实没问题,我们后面就会讲解,但是对于使用锁的话,效率比较低,而且锁一不注意就会造成死锁,而我们的原子操作可以保证高效率和安全。
3.3.mutex
3.3.1.mutex的种类
所有的锁对象之间不能进行拷贝,也不能进行移动赋值
C++11一共提供4种锁,分别是mutex,recursive_mutex,timed_mutex,recursive_timed_mutex
①mutex:
mutux是最常用的锁了,它常用的函数有:lock(),unlock(),try_lock()
lock()可能会发生的情况:
如果该锁没有被别的线程获取,则获取这个锁,直到unlock前,一直获得这个锁
如果当前锁被其他线程已经获取,那么调用线程就阻塞
如果当前锁被其他线程获取不释放,而当前线程又获取了其他线程所需要的锁,那么就会造成死锁
try_lock()可能会发生的情况:
- 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量
- 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉
- 如果当前互斥量被当前调用线程锁住,则会产生死锁
②recursive_mutex:
这个锁允许同一个线程对锁多次上锁(递归上锁),来获得对锁对象的多层所有权,释放锁时需要调用与该锁层次深度相同的unlock
其余两个就不多赘述了,可以查文档
③timed_mutex:timed_mutex文档
④recursive_timed_mutex:recursive_timed_mutex文档
3.3.2.lock_guard
在我们使用锁的过程中,难免会遇到忘记解锁,因此出现了lock_guard类模板,它主要是通过RAII的方式,对其管理的锁进行了封装,在需要加锁的地方,只需要用上述介绍的任意锁实例化一个lock_guard,调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
using namespace std;
int num=0;
mutex _m;
void Func()
{int i = 100;lock_guard<mutex> guard(_m);//使用while(i--)num++;
}int main()
{thread t1(Func);thread t2(Func);t1.join();t2.join();cout << num<< endl;return 0;
}
上面为使用了库中的lock_guard,其实它的写法很简单,我自己也写了一lock_guard模拟实现
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
using namespace std;
namespace lnb
{template<class M>class lock_guard//模拟实现{public:lock_guard(M& m):_m(m){_m.lock();}~lock_guard(){_m.unlock();}private:M& _m;};
}int num=0;
mutex mtx;
void Func()
{int i = 100;lnb::lock_guard<mutex> guard(mtx);//使用while(i--)num++;
}int main()
{thread t1(Func);thread t2(Func);t1.join();t2.join();cout << num<< endl;return 0;
}
3.3.3.unique_lock
与lock_gard类似,unique_lock类模板也是采用RAII的方式对锁进行了封装,并且也是以独占所有权的方式管理mutex对象的上锁和解锁操作,即其对象之间不能发生拷贝。在构造(或移动(move)赋值)时,unique_lock 对象需要传递一个 Mutex 对象作为它的参数,新创建的unique_lock 对象负责传入的 Mutex 对象的上锁和解锁操作,与lock_guard不同的是,unique_lock更加的灵活,提供了更多的成员函数
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
using namespace std;
int num=0;
mutex mtx;
void Func()
{int i = 100;unique_lock<mutex> uq(mtx);//使用while(i--)num++;
}int main()
{thread t1(Func);thread t2(Func);t1.join();t2.join();cout << num<< endl;return 0;
}
🌸🌸C++11(中)的知识大概就讲到这里啦,博主后续会继续更新更多C++的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪
相关文章:

C++11(中)
C11(中) 1.可变参数模板1.1.使用场景 2.lambda表达式(重要)2.1.使用说明2.2.函数对象与lambda表达式 3.线程库3.1.thread3.2.atomic原子库操作3.3.mutex3.3.1.mutex的种类3.3.2.lock_guard3.3.3.unique_lock 🌟&#x…...

下拉选择器,选择框,支持单选、多选、筛选和清空功能,支持vue2和vue3
下拉选择器,选择框,支持单选、多选、筛选和清空功能,支持vue2和vue3https://ext.dcloud.net.cn/plugin?id8159 点击即可。 注意数据来源: 选择的:valueName:选择下拉选择显示的显示屏...
HTTP中GET和POST的区别是什么?
HTTP定义: GET:用于获取资源,通常用于请求数据而不改变服务器的状态 POST:用于提交数据到服务器,通常会改变服务器的状态或产生副作用(如创建或更新资源) 参数传递方式: GET&…...

day04 企业级Linux安装及远程连接知识实践
1. 使用传统的网卡命名方式 在启动虚拟机时,按tab键进入编辑模式 添加命令: net.ifnames0 biosdevname0 这样linux系统会使用传统的网卡命名,例如eth0、eth1…… 2. 快照 做系统关键操作时,一定要使用快照(先将系统关机) 3.…...

jvm核心组件介绍
1. 类加载器(ClassLoader): • 想象它是一个快递员,负责把Java类(.class文件)这个“包裹”从磁盘这个“发货地”送到JVM内部这个“目的地”。类加载器确保每个类只被加载一次,并维护一个类的层级…...

uname -m(machine) 命令用于显示当前系统的机器硬件架构(Unix Name)
文章目录 关于 arm64 架构检查是否安装了 Rosetta 2其他相关信息解释:命令功能:示例: dgqdgqdeMac-mini / % uname -m arm64您运行的 uname -m 命令显示您的系统架构是 arm64。这意味着您的 Mac Mini 使用的是 Apple 的 M1 或更新的芯片&…...

Pgsql:json字段查询与更新
1.查询json字段的值 SELECT attribute_data->>设施类别 mycol, * FROM gis_coord_data WHERE attribute_data->>设施类别阀门井 查询结果如下: 2.更新json字段中的某个属性值 UPDATE gis_coord_data SET attribute_data(attribute_data::jsonb ||{&quo…...
类的加载机制
类加载的概念 类加载是 Java 虚拟机(JVM)把字节码文件(.class 文件)转变为 Java 类型的复杂且关键的过程。这就如同把一份详细的设计图纸(字节码文件)加工成一个可以实际运行和使用的软件模块(J…...

基于vite创建的react18项目的单元测试
题外话 最近一个小伙伴进了字节外包,第一个活就是让他写一个单元测试。 嗯,说实话,在今天之前我只知道一些理论,但是并没有实操过,于是我就试验了一下。 通过查询资料,大拿们基本都说基于vite的项目&…...
fiddler抓包工具与requests库构建自动化报告
一. Fiddler 抓包工具 1.1 Fiddler 工具介绍和安装 Fiddler 是一款功能强大的 HTTP 调试代理工具,能够全面记录并深入检查您的计算机与互联网之间的 HTTP 和 HTTPS 通信数据。其主界面布局清晰,主要包含菜单栏、工具栏、树形标签栏和内容栏。 1.2 Fid…...
Docker login 报证书存储错误的解决办法
文章目录 docker login 出现错误,提示:Error saving credentials: error storing credentials - err: exit status 1, out: Cannot autolaunch D-Bus without X11 $DISPLAY 环境 使用的是 Mint Linux ,容器为 docker-ce 最新版 1 2 3 4 $…...

【自动化Selenium】Python 网页自动化测试脚本(上)
目录 1、Selenium介绍 2、Selenium环境安装 3、创建浏览器、设置、打开 4、打开网页、关闭网页、浏览器 5、浏览器最大化、最小化 6、浏览器的打开位置、尺寸 7、浏览器截图、网页刷新 8、元素定位 9、元素交互操作 10、元素定位 (1)ID定位 &…...
什么是MyBatis?
MyBatis简介 MyBatis是一款优秀的持久层框架,用于简化Java应用程序对数据库的操作。它曾是Apache的一个开源项目,名为iBatis,2010年迁移到Google Code并改名为MyBatis,2013年11月又迁移到了GitHub。 一、MyBatis的作用 在JavaE…...

TortoiseGit 将本地已有仓库推送到远程
TortoiseGit 将本地已有仓库推送到远程 一、创建线上仓库二、创建本地仓库三、提交内容到本地仓库四、添加远程仓库地址补充 一、创建线上仓库 在gitlab管理面页面按这前讲过的步骤创建一个空仓库。(通常我们把服务器上这个仓库叫远程仓库,把我们自己电…...

腾讯云OCR车牌识别实践:从图片上传到车牌识别
在当今智能化和自动化的浪潮中,车牌识别(LPR)技术已经广泛应用于交通管理、智能停车、自动收费等多个场景。腾讯云OCR车牌识别服务凭借其高效、精准的识别能力,为开发者提供了强大的技术支持。本文将介绍如何利用腾讯云OCR车牌识别…...

TailwindCss 总结
目录 一、简介 二、盒子模型相关 三、将样式类写到一个类里面apply 四、一款TailWind CSS的UI库 一、简介 官方文档:Width - TailwindCSS中文文档 | TailwindCSS中文网 Tailwind CSS 的工作原理是扫描所有 HTML 文件、JavaScript 组件以及任何 模板中的 CSS 类…...
Java与C#
Java和C#(C Sharp)是两种流行的面向对象编程语言,它们在很多方面非常相似,因为它们都受到了类似的编程范式和语言设计理念的影响。然而,它们之间也存在一些重要的区别。 平台依赖性: Java:Java是…...

leetcode:222完全二叉树的节点个数
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。 完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最…...

[STM32]从零开始的STM32 FreeRTOS移植教程
一、前言 如果能看到这个教程的话,说明大家已经学习嵌入式有一段时间了。还记得嵌入式在大多数时候指的是什么吗?是的,我们所说的学习嵌入式大部分时候都是在学习嵌入式操作系统。从简单的一些任务状态机再到复杂一些的RTOS,再到最…...
java——Tomcat连接池配置NIO、BIO、APR
Tomcat连接池的配置涉及不同的IO模型,包括NIO(Non-blocking IO,非阻塞IO)、APR(Apache Portable Runtime,Apache可移植运行库)和BIO(Blocking IO,阻塞IO)。以…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...