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

【C++】C++11

目录

C++11简介

统一的列表初始化

{}初始化

std::initializer_list

声明

auto

decltype

nullptr

范围for循环

智能指针

STL中的一些变化

右值引用和移动语义

左值引用和右值引用

右值引用的意义

完美转发

lambda表达式

新的类功能

可变参数模版 

包装器

function包装器

bind

线程库

线程函数参数

lock_guard和unique_lock

原子性操作库

两个线程交替打印


C++11简介

相比于C++98/03,C++11发生了较大的变化,大约新增了约140个新特性,以及修正了约600个缺陷,这使得C++更加强大。

统一的列表初始化

{}初始化

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

struct Point
{int _x;int _y;
};int main()
{int array1[] = { 1,2,3,4,5 };Point p = { 1,2 };return 0;  
}

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

int arr1[]{ 1,2,3,4,5 };
int arr2[5]{ 0 };
Point p2{ 1,2 };

对于自定义类型创建对象时也可以用列表初始化方式调用构造函数初始化:

class A
{
public:A(int x, int y):_x(x), _y(y){}A(int x):_x(x), _y(x){}
private:int _x;int _y;
};
int main()
{A a5(6);//单参数的隐式类型转换A a3 = 1;A a4 {2};//多参数的隐式类型转换//C++支持的列表初始化,这里{1,2}先调用构造函数初始化,再调用拷贝构造赋值给a1   A a1 = { 1,2 };A a2 { 3,4 };return 0;
}

std::initializer_list

//vector可以这样初始化
vector<int> v1(10, 1);
//但是vector不能用{}初始化
vector<int> v2 = {1,2,3,4,5};

为什么vector用不了{}这样构造呢?{...}里面可能有1/2/3/4/....个元素,但是vector的构造函数参数列表不知道到底该设置多少个,要写无数个构造函数才行,就像下面这样:

所以,为了支持这样的初始化,引入了initializer_list,

因此,我们现在可以将{...}的常量数组转化为initializer_list。

initializer_list的本质是两个指针,一个first指向第一个元素,一个last指向最后一个元素的下一个,为了验证这样一个事实,我们算一下i1的大小,在32位平台下,i1的大小是8(2个指针的大小):

initializer_list也支持简单的迭代器begin和end,迭代器的类型是const T*,由于它指向常量数组,所以是const T*,不支持修改。 

所以,为了解决本节刚开始提出的问题,C++11使用了这样的方式解决:

vector(initializer_list<T> i1);

这个构造一劳永逸的解决了问题,不用像上面那样麻烦的方式解决。

initializer_list可以接收常量数组,本质是两个指针,一个指向数组的开始,一个指向数组最后的下一个。这样,就算{...}里有5个8个,都可以传给initializer_list。

当  X自定义 = Y类型,这个时候就会隐式类型转换,需要有 X(Y mm),即X支持Y为参数类型的构造就可以。

在上面图中,{1,2,3,4,5}会被识别成initializer_list,下面的那个,等号右侧生成一个临时对象,再去拷贝构造给v4。  

再举一个例子,创建map对象时,也可以用{}进行创建:

声明

auto

在C++11中,auto用于自动推断类型,这样要求必须进行显示初始化。

int main()
{int i = 10;auto p = &i;cout << typeid(p).name() << endl;map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();return 0;
}

decltype

关键字decltype将变量的类型声明为表达式指定的类型。它的功能和typeid有点像,typeid可以拿到对象的类型,但是得到的只是一个单纯的字符串,不能用来定义对象,如果想定义一个和typeid一样的值,这就做不到了。

但是decltype可以做到:

但是,有人可能会认为,我用一个auto不也可以吗?但是,我们换一个场景:

我想用ret3的返回值类型去初始化class B的模版参数,这时候typeid就不行了,但是可以用decltype来确定ret3的类型,

B<decltype(ret3)> b;

虽然auto和decltype虽然可以方便我们写代码,但是有些地方增加代码读起来的难度,所以慎用auto和decltype!

nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

范围for循环

我们在之前的学习中,已经多次使用了范围for,这里不再赘述。

只有一点需要注意,我们最好把&加上,即for(auto& e:v)或者for(const auto& e:v)(在不改变值的情况下加const),这样可以防止深拷贝,提高效率。

智能指针

我们之后单独学习这块。

STL中的一些变化

新容器 

array:对越界检查更严格,但是可以用vector来替代,所以用处不大,是一个挺失败的设计。

forward_list:单向链表,相比list节省一个指针,但是也不好用。

容器内部

几乎在每个容器内部,都增加了initializer_list、移动构造(这个很有用),

还有emplace、empalce_back:

右值引用和移动语义

左值引用和右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名

什么是左值和右值

左值和右值的区分关键,是能否取地址。可以取地址的是左值,不能取地址的是右值。

因此,a、b、c均是左值。

左值不一定是值,也可能是表达式。如*p也是左值,因为它可以取地址。

类似的,v[1]也是左值,它可以取地址。

右值也是一个表达数据的表达式,它不能取地址,如字面常量、匿名对象、临时对象。

什么是左值引用和右值引用

引用就是取别名,左值引用就是给左值取别名,右值引用就是给右值取别名,

右值引用就是用&&表示: 

自定义的类型右值主要有两种,一类是匿名对象,一类是传值返回的临时对象。

左值引用不能给右值取别名,但是const 左值引用可以,

const string& ref1 = string("1111");

右值引用也不能给左值取别名,但是可以给move以后得左值取别名。

左值引用总结:

1. 左值引用只能引用左值,不能引用右值。

2. 但是const左值引用既可引用左值,也可引用右值。

右值引用总结:

1. 右值引用只能右值,不能引用左值。

2. 但是右值引用可以move以后的左值。

右值引用的意义

引用的意义是减少拷贝,提高效率, 比如引用传参

void func1(const string& s);

如果不用引用,那传参时就要拷贝,像map这种大一点的容器拷贝的代价很大,

还有传引用返回

string& func2();

左值引用的场景主要就是引用传参和传引用返回。但是,左值引用返回值的问题没有彻底解决,因为如果是func2中局部对象,不能用引用返回,它的生命周期就在func2里面,出了作用域就销毁了,同时,也不能用右值引用返回,右值引用也不能解决生命周期的问题,

例如,bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

to_string的返回值用左值引用和右值引用都不太行,因为返回值str出了作用域都会被销毁,在C++11中,另辟蹊径,增加了一个参数为右值引用的构造函数--移动构造,(对比:参数为左值引用的构造函数为拷贝构造),移动构造拷贝构造构成函数重载。

在没有移动构造时,拷贝构造string(const string& s)既可以接收左值,又可以接收右值;但是在有了移动构造时,编译器会找最匹配的。

在C++11中,将右值做了区分,分为纯右值将亡值,内置类型的那种是纯右值,自定义类型的那种是将亡值,在移动构造中,可以这样写:

//移动构造
//右值(将亡值)
string(string&& s):_str(nullptr)
{cout << "string(string&& s) -- 移动构造" << endl;swap(s);
}

既然已经是将亡值,那么不妨把我的和将亡值交换一下,移动将亡值的资源,这样就不用拷贝构造了,效率就上来了    

有人说右值引用延长了str的生命周期,这种说法是不正确的,str出了作用域就会被销毁,确切的说是延长了资源的生命周期。

如果是下面这种形式,如果没有移动拷贝和移动赋值,那就是一次拷贝构造和一次赋值拷贝,编译器不敢将这两步合二为一(因为这是两个不同的操作),

如果有了移动拷贝和移动赋值,那就是一次移动拷贝和一次移动赋值,

移动构造、移动赋值和我们之前拷贝构造的现代写法有点像,但是这两种有本质的区别,现代写法是让一个临时对象去构造并转移它的资源,并没有提高效率,而移动构造、移动赋值给我的右值就是一个将亡值,直接转移这个将亡值的资源,代价很小。

我们再来看一些关于右值引用的其他问题:

我们知道,std::string("111111111111")本身是右值,但是右值引用本身(s1)是左值,因为只有右值引用本身处理成左值,才能实现移动构造和移动赋值,转移资源(右值不能转移资源)。这样的意思,是为了移动构造和移动赋值的转移资源的逻辑是自洽的。

我们来看一下C++11中其他地方用到右值引用的,

由于s1是左值,所以push_back不能调用移动拷贝,只能做深拷贝,

但是,如果想上图这样,将匿名对象放到push_back的参数中,就会调用移动构造。在push_back这类函数时,使用匿名对象就更好了,这样就不会设计到深拷贝,只需要移动构造,提高效率。可见,右值引用不仅在返回值有意义,也在参数值有意义

也可以这样,这样就会先发生隐式类型转换,将const char*转换为string,转换时会产生临时对象,是右值,临时对象具有常性,会调用移动构造。

最后一个问题,如果list里的值类型是int(内置类型)或者日期类(浅拷贝自定义类型),还会涉及到移动拷贝和移动构造吗?

不会涉及,上面效率提升,针对的是自定义类型的深拷贝的类,因为深拷贝的类才有转移资源的移动系列函数;对于内置类型和浅拷贝自定义类型,没有移动系列函数。

完美转发

模版中的&&万能转发

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; }//函数模版里面,这里叫做万能引用
template<typename T>
void PerfectForward(T&& t)
{Func(t);
}void PerfectForward(int& t)
{Fun(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;
}

函数模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力。

但是,我们看程序运行的结果,

因为在函数参数t接收后,后续都退化成了左值,所以都会调用Fun(int& x)和Fun(const int& x)这两个函数,但是这不是我们想要的结果,我们希望能够在传递的过程中保持它的左值或者右值的属性,那么就需要用到完美转发

std::forward 完美转发在传参的过程中保留对象原生类型属性。

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; }//函数模版里面,这里叫做万能引用
//实参传左值,就推成左值引用,实参传右值,就推成右值引用
template<typename T>
void PerfectForward(T&& t)
{//std::forward<T>(t)在传参的过程中保持了t的原生类型属性Fun(std::forward<T>(t));
}void PerfectForward(int& t)
{Fun(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;
}

什么时候用完美转发呢?在函数模版里,想要达到传什么就保持它的属性就用完美转发。这是一道常见的面试题。

lambda表达式

在C++98中,如果想要对一个自定义类型进行排序,需要用到仿函数,用户自定义排序的比较规则,比如,有这样一个自定义类型,

struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};

我们有存储Goods类型的vector,想要对这个vector按照价格排序,既可以排升序,又可以排降序,可以使用algorithm这个头文件中的sort算法,但是如果需要对商品按照它的某一项属性排序,如价格,就需要自己写一个类来定义仿函数,

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& g1, const Goods& g2){return g1._price < g2._price;}
};struct ComparePriceGreater
{bool operator()(const Goods& g1, const Goods& g2){return g1._price > g2._price;}
};int main()
{vector<Goods> v = { {"苹果",2.3,2},{"香蕉",3.2,4}, {"西瓜",0.9,3} };sort(v.begin(), v.end(), ComparePriceGreater());return 0;
}

但是,上面的方法有点复杂,每次都要自己写一个类,所以C++为了解决这样的问题,引入了lambda表达式

lambda表达式实际是一个匿名函数,lambda表达式书写格式:

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函数不能做任何事情。

对捕捉列表和mutable的进一步说明:

为了解决传值捕捉在函数内部改变不会影响外面的问题,引入了引用捕捉,引用捕捉是在捕捉列表变量前加&(这其实和取地址&有些冲突,但是在lambda表达式捕捉列表中我们认为&是引用捕捉) :  

 捕捉列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式传值还是传引用。

[var]:表示传值捕捉变量var

[=]:表示传值方式捕捉父作用域的所有变量

[&var]:表示引用方式捕捉变量var

[&]:表示引用方式捕捉父作用域的所有变量

[this]:表示传值方式捕捉当前的this指针

 除了上面传值和传引用捕捉方式以外,还有混合捕捉(一部分传值捕捉,一部分传引用捕捉):

因此,我们可以写lambda表达式作为一个匿名函数传给sort进行排序:

int main()
{vector<Goods> v = { {"苹果",2.3,2},{"香蕉",3.2,4}, {"西瓜",0.9,3} };auto priceLess = [](const Goods g1, const Goods g2)->bool {return g1._price < g2._price; };sort(v.begin(), v.end(), priceLess);//也可以直接传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;});sort(v.begin(), v.end(), [](const Goods g1, const Goods g2){return g1._name < g2._name;});sort(v.begin(), v.end(), [](const Goods g1, const Goods g2){return g1._name > g2._name;});return 0;
}

其中,priceLess的类型,我不知你不知只有编译器知,见下图,是随机生成的类型。

其实,lambda表达式就是仿函数,lambda编译时,会生成对应的仿函数,上面的随机名字就是仿函数类的名称。

实际上,lambda表达式和范围for很类似,范围for替代成了迭代器,lambda替代成了仿函数

新的类功能

在前面我们学习了右值引用,其实,右值引用的特点是和左值引用的特点进行了区分,是左值就匹配左值引用,是右值就匹配右值引用。移动语义是,当右值匹配到右值引用的时候,会调用移动构造和移动拷贝。它们针对的是深拷贝的自定义类型对象,如string、vector、list等,可以转移资源,提高效率。

原来的C++类中,有6个默认成员函数,构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载、const取地址重载,前4个最重要,后两个用处不大。

C++11 新增了两个:移动构造函数和移动赋值运算符重载。

对于移动构造函数和移动赋值运算符重载,有一些需要注意的:

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

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)。

如果提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

 我们调试以下代码可以得到验证:

class Person
{
public:Person(const char* name = "张三", int age = 18):_name(name), _age(age){}/*~Person(){}*/
private:ghs::string _name;int _age;
};int main()
{Person s1;//默认拷贝构造Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

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

C++可以更好地控制要使用的默认函数。假设要使用某个默认的函数,但是因为某些原因这个函数没有默认生成。比如:当我们提供了拷贝构造,就不会生成移动构造,那么就可以使用default关键字指定移动构造生成。

class Person
{
public:Person(const char* name = "张三", int age = 18):_name(name), _age(age){}//强制生成Person(Person& p) = default;Person& operator=(Person& p) = default;Person(Person&& p) = default;Person& operator=(Person&& p) = default;~Person(){}
private:ghs::string _name;int _age;
};int main()
{Person s1;//默认拷贝构造Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

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

 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

继承和多态中的final与override关键字
这个我们在继承和多态章节已经进行了详细学习。

可变参数模版 

在C++11之前,模版中的参数数量是固定的,但是在C++11中引入了可变参数模版,这无疑是一大进步。

我们先来看一个基本可变参数的函数模版:

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

其中,Args是一个模版参数包,代表0~N个类型,args是根据模版参数包定义的一个形参参数包。上面的参数Args前面有省略号,所以它是一个可变模版参数,我们把带省略号的参数成为参数包,包含0~N个模版参数。但是我们无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数。由于语法不支持args[i]的方式获取可变参数,所以我们用一些方法来获取参数包的每个值。

递归函数展开参数包

void _CppPrintf()
{cout << endl;
}
template<class T,class ... Args>
void _CppPrintf(const T& val, Args... args)
{cout << val << endl;_CppPrintf(args...);
}
template <class ... Args>
void CppPrintf(Args... args)
{_CppPrintf(args...);
}
int main()
{CppPrintf(1, 'A', std::string("sort"));return 0;
}

其编译时递归推导过程如下: 

还有另外一个奇葩的推导方式

STL中的emplace相关接口函数

可以看出,emplace_back对深拷贝类型有一定的优化,但是不那么明显,效率没有提升很多

其实,emplace_back对需要浅拷贝的效率更高,因为push_back浅拷贝类型,只能调用拷贝构造,即需要一次构造+一次拷贝构造,而emplace_back只需要调用一次构造就行。

总之,emplace_back可以直接构造,而无需调用拷贝构造!

当然,emplace_back除了上面的用多个参数进行构造,也可以用单参数构造,

因此,当使用emplace时,实参建议的选择顺序是:参数包 > 右值 > 左值。

包装器

function包装器

function包装器,也叫做适配器。C++中function本质是一个类模版,也是一个包装器。

ret = func(x);

上面的func可能是什么呢?可能是函数名、函数指针、仿函数或者lambda表达式,这些都是可调用对象

但是,它们都多少有些问题:

函数指针       ->    类型定义复杂

仿函数对象   ->    要定义一个类,用的时候有点麻烦,不适合类型统一

lambda         ->    没有类型概念     

template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}

运行以上程序,发现useF函数模版实例化了三份,这看起来有点麻烦,为了解决以上问题,引出了包装器。

std::function在头文件<functional>

//类模版原型
template <class Ret, class... Args>
class function<Ret(Args...)>;

模版参数说明:

Ret被调用函数返回值

Args被调用函数的形参

实际上,function的底层是仿函数,在调用时会调用operator()。

int f(int a, int b)
{return a + b;
}
struct Functor
{
public:int operator() (int a, int b){return a + b;}
};//不是定义可调用对象,而是包装可调用对象
int main()
{//空对象function<int(int, int)> fc1;//包装函数指针function<int(int, int)> fc2 = f;//包装仿函数对象function<int(int, int)> fc3 = Functor();//包装lambdafunction<int(int, int)> fc4 = [](int x, int y) {return x + y; };cout << fc2(1, 2) << endl;//fc2本质是调用了operator()cout << fc2.operator()(1, 2) << endl;cout << fc3(1, 2) << endl;cout << fc4(1, 2) << endl;return 0;
}

那我们不禁要问,为什么要给函数指针、仿函数、lambda外面套一个壳再使用呢,它们本身也是可以调用的啊。

包装器的一些玩法:逆波兰表达式求解

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;//命令->动作(函数)map<string,function<int(int,int)>> m={{"+",[](int x,int y){return x+y;}},{"-",[](int x,int y){return x-y;}},{"*",[](int x,int y){return x*y;}},{"/",[](int x,int y){return x/y;}},};for(auto& e : tokens){if(m.count(e)) {function<int(int,int)> f = m[e];//操作符运算int right = st.top();st.pop();int left = st.top();st.pop();st.push(f(left,right));}else{//操作数入栈st.push(stoi(e));}} return st.top();   }
};

另外,如果我们想要包装成员函数指针,需要&类型::函数名这样调用。成员函数指针又分静态成员函数和非静态成员函数:

int main()
{//成员函数的函数指针 &类型::函数名//包装静态成员函数function<int(int, int)> f1 = &Plus::plusi;cout << f1(1, 2) << endl;//包装非静态成员函数,不能直接Plus::plusd,需要在前面加取地址符&,静态函数前可加可不加&/*function<double(double, double)> f2 = &Plus::plusd;cout << f2(1.1, 2.2) << endl;*///包装器的参数要和成员函数的参数一致,成员函数第一个参数是一个隐含的this指针function<double(Plus*, double, double)> f3 = &Plus::plusd;Plus plus;cout << f3(&plus,1.1, 2.2) << endl;//但是,上面包装非静态的成员函数有点麻烦,还需要定义一个类对象//是通过指针&plus或者对象Plus()去调用plusd,所以这里传指针和对象都可以function<double(Plus, double, double)> f4 = &Plus::plusd;cout << f4(Plus(), 1.1, 2.2) << endl;return 0;
}

bind

std::bind是一个函数模版,用于调整可调用对象的参数个数或者顺序

bind函数可以看做一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。

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

 newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,给callable提供实参,当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数

fn是要绑定的函数,args是要对应的参数,包含像_1、_2这样的名字,这些名字是占位符,_1为newCallable中第一个参数,_2为newCallable中第二个参数,依次类推。Ret是返回值的类型,我们可以显示实例化bind,以此来控制返回值的类型。

但是,调整顺序的意义不大,了解即可。

除了调整顺序以外,我们还可以调整参数个数:

class Sub
{
public:int sub(int a, int b){return a - b;}
};int main()
{//绑定,调整参数个数,把第一个参数用Sub()绑定死auto f4 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);cout << f4(10, 5);cout << endl;auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2);cout << f5(10, 5);return 0;
}

这样,我们得到绑定后的对象为f4,调用f4时,只需要传两个未绑定的参数a和b即可。

再来看一个例子:

void fx(const string& s, int x, int y)
{cout << s << " -> [血量:" << x << "  蓝:" << y <<"]" << endl;
}
int main()
{fx("赵云", 80, 46);fx("赵云", 78, 34);fx("赵云", 54, 13);return 0;
}

我们可以这样调fx,这很正常,但是调fx时每次都要传“赵云”这个参数,其实,我们可以绑定第一个参数一直是“赵云”,这样只需要输入参数x和y即可:

int main()
{auto f1 = bind(fx1, "赵云", placeholders::_1, placeholders::_2);f1(100, 89);f1(98, 76);return 0;
}

除了绑定第一个参数,我们还可以绑定第二个参数,比如在游戏中开挂,无论使用哪个角色,血量一直保持在100:

int main()
{auto f2 = bind(fx1, placeholders::_1, 100, placeholders::_2);f2("孙尚香", 46);f2("关羽", 93);return 0;
}

我们需要记住的是,_1代表第一个实参,_2代表第二个实参。

实际上,bind的返回值是一个类,里面重载了operator(),实际上会调用仿函数。

bind的返回除了传给auto,也可以传给function(因为bind的返回值是一个类,里面重载了仿函数),

线程库

我们之前了解的多线程问题,都是和平台相关的,比如windows和linux下都有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在多线程编程时不需要依赖第三方库。要使用标准库中的线程,必须包含<thread>头文件。

注意:

1.当创建线程对象后,如果使用无参构造,没有提供线程函数,该对象没有对应任何线程。

get_id()的返回值类型是id类型,id类型为std::thread命名空间下疯转的一个类。

2.当创建一个线程对象后,并且给线程关联函数,该线程就会被启动,与主线程一起运行。线程函数一般有3种提供方式:

  • 函数指针
  • lambda表达式
  • 函数对象

函数指针:

void print(int n, int k)
{for (int i = k; i < n; i++){std::cout << i << " ";}std::cout << std::endl;
}int main()
{std::thread t1(print, 100, 0);std::thread t2(print, 200, 100);std::cout << t1.get_id() << std::endl;std::cout << t2.get_id() << std::endl;t1.join();t2.join();std::cout << std::this_thread::get_id << std::endl;return 0;
}

lambda表达式:

int main()
{int x = 0;std::mutex mtx;std::thread t1([&] {mtx.lock();for (int i = 0; i < 10000; i++){++x;}mtx.unlock();});std::thread t2([&]{mtx.lock();for (int i = 0; i < 10000; i++){++x;}mtx.unlock();});t1.join();t2.join();std::cout << x << std::endl;return 0;
}

可以使用this_thread类中的get_id来获取主线程id。

3.线程不支持拷贝,不允许拷贝构造和赋值,但是支持移动构造和移动赋值。

线程函数参数

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

如果想要通过形参改变外部实参,必须借助std::ref()函数或者传入指针。

lock_guard和unique_lock

在多线程中,为了保证线程安全,需要通过锁的方式控制。 

 如果把锁放到for循环里面,也是线程安全的,但是这样会导致线程之间频繁切换,效率低。

上述代码中,其缺陷是,锁控制不好时,可能会造成死锁,比如在锁中间代码返回,或者在锁的范围内抛异常。因此,C++采用RAII的方式对锁进行了封装,即lock_guard和unique_lock。

那如果在func中前半部分希望加锁,而后半部分不希望加锁,只需要用{}把前半部分括住,定义一个局部域,LockGuard的生命周期就在这个{}局部域了。

而unique_lock和lock_guard类似,只不过功能更丰富一些,支持手动加锁解锁。

原子性操作库

传统解决线程安全的方法是对共享资源进行加锁保护,虽然加锁可以解决问题,但是加锁的缺陷是,只要要有一个线程在对sum++,其他线程就会被阻塞,影响效率,而且可能造成死锁。

为此,C++11中引入了原子操作,需要包含<atomic>库。

在C++11中,程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥访问。程序员可以使用atomic类模版,定义出需要的任意原子类型。

atomic<T> t;  //声明一个类型为T的原子类型变量t

两个线程交替打印

要使两个线程交替打印,需要使用到条件变量: 

条件变量condition_variable用于进行进程之间的互相通知。

为了使得线程交替打印,要保证线程1先执行,线程2后执行(哪怕把线程2放到前面)。

这里利用了wait,

由于条件变量wait不是线程安全的,因此要给wait传互斥锁,调用wait的线程被阻塞,直到被notified,wait的作用是使进程间做到同步。在wait阻塞进程时,当前进程会先把锁解掉,允许在这个锁上阻塞的线程继续走。当这个进程被唤醒后,这个进程会解阻塞,并获取到这个锁。

notify_one会唤醒在这个条件变量上等待的一个线程,如果没有现成在上面等待,什么都不做,如果有多个线程在等待,会选择其中任意一个。

下面是两个线程交替打印的代码:

{std::mutex mtx;std::condition_variable c;int n = 100;bool flag = true;std::thread t1([&]() {int i = 0;while (i < n){std::unique_lock<std::mutex> lock(mtx);//flag=false t1就阻塞//flag=true t1就不会阻塞while (!flag){c.wait(lock);}std::cout << i << std::endl;flag = false;i += 2; // 偶数c.notify_one();}});std::thread t2([&]() {int j = 1;while (j < n){std::unique_lock<std::mutex> lock(mtx);//只要flag==true,t2就阻塞//只要flag==false,t2就不会阻塞while (flag)c.wait(lock);std::cout << j << std::endl;j += 2; // 奇数flag = true;c.notify_one();}});t1.join();t2.join();return 0;
}

相关文章:

【C++】C++11

目录 C11简介 统一的列表初始化 {}初始化 std::initializer_list 声明 auto decltype nullptr 范围for循环 智能指针 STL中的一些变化 右值引用和移动语义 左值引用和右值引用 右值引用的意义 完美转发 lambda表达式 新的类功能 可变参数模版 包装器 func…...

Intellij IDEA如何查看当前文件的类

快捷键&#xff1a;CtrlF12&#xff0c;我个人感觉记快捷键很麻烦&#xff0c;知道具体的位置更简单&#xff0c;如果忘了快捷键&#xff08;KeyMap&#xff09;看一下就记起来了&#xff0c;不需要再Google or Baidu or GPT啥的&#xff0c;位置&#xff1a;Navigate > Fi…...

CF 278A.Circle Line

题目分析 输入n个数据作为路径&#xff0c;求从a到b的最短距离&#xff0c;需要将其相成一个圆圈&#xff0c;既可以从小往大走又可以从大往小走 思路分析 依然将数据存为数组&#xff0c;通过下标进行操作&#xff0c;既然说了有两种方式那就计算两种方式哪个更快就输出谁 代…...

Naive UI去掉n-select下拉框边框,去掉n-input输入框边框

1、第一种通过js去掉 <template><div><div style"margin-top:10px;width: 100%;"><dade-descriptions><tr><dade-descriptions-item label"代理名称"><dade-input placeholder"代理名称"></dade-…...

(文末提供数据集下载)ML.NET库学习001:基于PCA的信用卡异常检查之样本处理与训练

文章目录 (文末提供数据集下载)ML.NET库学习001&#xff1a;基于PCA的信用卡异常检查之样本处理与训练目标项目概述代码结构概述1. **主要类和文件**2. **命名空间和使用指令**3. **数据类 (TransactionObservation)**4. **主程序入口 (Main 方法)**5. **数据预处理 (DataPrepr…...

疯狂SQL转换系列- SQL for Milvs2.4

鉴于Milvus仍在不停的迭代新版本&#xff0c;推出新功能&#xff0c;其SDK目前并不稳定。目前其2.4版本的SDK接口已与之前的2.2版本有了较大的差别&#xff0c;功能上也有了一定的调整。为此&#xff0c;我们重新提供了针对[Milvus2.4](https://github.com/colorknight/moql-tr…...

C++的 I/O 流

本文把复杂的基类和派生类的作用和关系捋出来&#xff0c;具体的接口请参考相关文档 C的 I/O 流相关的类&#xff0c;继承关系如下图所示 https://zh.cppreference.com/w/cpp/io I / O 的概念&#xff1a;内存和外设进行数据交互称为 I / O &#xff0c;例如&#xff1a;把数…...

基于ansible部署elk集群

ansible部署 ELK部署 ELK常见架构 &#xff08;1&#xff09;ElasticsearchLogstashKibana&#xff1a;这种架构是最常见的一种&#xff0c;也是最简单的一种架构&#xff0c;这种架构通过Logstash收集日志&#xff0c;运用Elasticsearch分析日志&#xff0c;最后通过Kibana中…...

4.Python字符串和列表:字符串输入、字符串输出、下标和切片、字符串常见函数、列表(list)、列表的循环遍历、列表的增删改查、列表的嵌套、列表的切片

1. Python 字符串 1.1 字符串输入 input() 函数用于从用户获取字符串输入。它总是返回一个字符串类型的值。 # 从用户输入字符串 name input("请输入你的名字&#xff1a;") print(f"你好, {name}")1.2 字符串输出 字符串的输出通常使用 print() 函数…...

51单片机之使用Keil uVision5创建工程以及使用stc-isp进行程序烧录步骤

一、Keil uVision5创建工程步骤 1.点击项目&#xff0c;新建 2.新建目录 3.选择目标机器&#xff0c;直接搜索at89c52选择&#xff0c;然后点击OK 4.是否添加起吊文件&#xff0c;一般选择否 5.再新建的项目工程中添加文件 6.选择C文件 7.在C文件中右键&#xff0c;添加…...

Redis - 全局ID生成器 RedisIdWorker

文章目录 Redis - 全局ID生成器 RedisIdWorker一、引言二、实现原理三、代码实现代码说明 四、使用示例示例说明 五、总结 Redis - 全局ID生成器 RedisIdWorker 一、引言 在分布式系统中&#xff0c;生成全局唯一ID是一个常见的需求。传统的自增ID生成方式在分布式环境下容易出…...

Linux ftrace 内核跟踪入门

文章目录 ftrace介绍开启ftraceftrace使用ftrace跟踪指定内核函数ftrace跟踪指定pid ftrace原理ftrace与stracetrace-cmd 工具KernelShark参考 ftrace介绍 Ftrace is an internal tracer designed to help out developers and designers of systems to find what is going on i…...

Visual Studio(VS)没有显示垂直滚轮or垂直滚轮异常显示

前言&#xff1a; 前段时间&#xff0c;我换上了新电脑。满心欢喜地安装好 VS&#xff0c;准备大干一场时&#xff0c;却发现了一个小麻烦 —— 垂直滚轮显示异常&#xff08;如图 1&#xff09;。这种显示方式实在让我难以适应&#xff0c;每一次操作都觉得别扭。 于是&#…...

大数据数仓实战项目(离线数仓+实时数仓)3

1.课程内容和课程目标 2.订单时间维度指标需求分析 根据时间数据&#xff0c;生成一个时间维度表&#xff0c;我们后面还可以去复用这个时间维度表 3.使用kettle生成日期维度数据 Hive创建日期维度表 使用Kettle构建以下组件结构图 使用kettle生成日期维度数据插入到我们的hi…...

通过acme生成与续签ssl证书,并部署到nginx

通过acme生成与续签ssl证书&#xff0c;并部署到nginx 介绍 官方介绍&#xff1a; acme.sh 实现了 acme 协议&#xff0c;可以从 ZeroSSL&#xff0c;Lets Encrypt 等 CA 生成免费的证书。 安装 acme.sh 1. curl方式 curl https://get.acme.sh | sh -s emailmyexample.com…...

c语言对应汇编写法(以中微单片机举例)

芯片手册资料 1. 赋值语句 C语言&#xff1a; a 5; b a; 汇编&#xff1a; ; 立即数赋值 LDIA 05H ; ACC 5 LD R01,A ; R01 ACC&#xff08;a5&#xff09;; 寄存器间赋值 LD A,R01 ; ACC R01&#xff08;读取a的值&#xff09; LD R02,A ; R02 ACC&…...

React基础内容(面试一)

React大厂常见的面试题目涉及多个方面&#xff0c;包括React的基本概念、组件、状态管理、生命周期、性能优化等。以下是对这些面试题目的详细解析&#xff1a; 一、React基本概念 解释React是什么以及它的主要特点 React是一个用于构建用户界面的JavaScript库&#xff0c;由F…...

2025年软件测试五大趋势:AI、API安全、云测试等前沿实践

随着软件开发的不断进步&#xff0c;测试方法也在演变。企业需要紧跟新兴趋势&#xff0c;以提升软件质量、提高测试效率&#xff0c;并确保安全性&#xff0c;在竞争激烈的技术环境中保持领先地位。本文将深入探讨2025年最值得关注的五大软件测试趋势。 Parasoft下载https://…...

常用工具类——Collections集合框架

常用工具类——Collections集合框架 Collections 是 JDK 提供的一个工具类&#xff0c;提供了一系列静态方法&#xff0c;分类来复习&#xff01; 1.排序操作 reverse(List list) :反转顺序shuffle(List list) &#xff1a; 洗牌&#xff0c;将顺序打乱sort(List list) &…...

【大数据技术】搭建完全分布式高可用大数据集群(ZooKeeper)

搭建完全分布式高可用大数据集群(ZooKeeper) apache-zookeeper-3.8.4-bin.tar.gz注:请在阅读本篇文章前,将以上资源下载下来。 写在前面 本文主要介绍搭建完全分布式高可用集群 ZooKeeper 的详细步骤。 注意: 统一约定将软件安装包存放于虚拟机的/software目录下,软件…...

Docker Desktop安装kubernetes时一直在Starting:Kubernetes failed to start

原因&#xff1a;由于墙的问题&#xff0c;导致拉取国外的K8s镜像失败 解决&#xff1a; 下载 k8s-for-docker-desktop 选中自己的kubernetes 版本 下载zip包 PowerShell运行load_images.ps1文件 重启docker kubernetes运行成功...

物流中的物联网:其含义、应用和优势

随着世界联系日益紧密&#xff0c;物流格局正经历重大变革。科技已成为供应链管理的支柱&#xff0c;推动物流公司迈入效率与连通性兼具的新时代。 物联网&#xff08;IoT&#xff09;是一股变革性力量&#xff0c;重塑着物流与运输行业的架构。物联网在物流领域并非昙花一现的…...

Axure设计教程:动态排名图(中继器实现)

一、开篇 在Axure原型设计中&#xff0c;动态图表是展示数据和交互效果的重要元素。今天&#xff0c;我们将学习如何使用中继器来创建一个动态的排名图&#xff0c;该图表不仅支持自动轮播&#xff0c;还可以手动切换&#xff0c;极大地增强了用户交互体验。此教程旨在提供一个…...

【人工智能】掌握图像风格迁移:使用Python实现艺术风格的自动化迁移

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 图像风格迁移(Image Style Transfer)是一种基于深度学习的计算机视觉技术,通过将一张图像的内容与另一张图像的艺术风格结合,生成一幅具…...

# C指针地址CUP寄存器访问IO内存映射

C指针地址&CUP寄存器访问&IO内存映射 在裸机编程中&#xff0c;C语言可以像汇编语言一样直接操作芯片寄存器地址进行读取和写入&#xff0c;主要是由于以下几个原因&#xff1a; 1. 裸机环境下没有操作系统的干预 裸机编程是指直接在硬件上运行程序&#xff0c;没有…...

UE5 蓝图学习计划 - Day 14:搭建基础游戏场景

在上一节中&#xff0c;我们 确定了游戏类型&#xff0c;并完成了 项目搭建、角色蓝图的基础设置&#xff08;移动&#xff09;。今天&#xff0c;我们将进一步完善 游戏场景&#xff0c;搭建 地形、墙壁、机关、触发器 等基础元素&#xff0c;并添加角色跳跃功能&#xff0c;为…...

浅尝yolo11全程记录1-准备环境+官网模型推理(个人备份)

准备工作&#xff08;虚拟环境、导入项目&#xff09; 安装Anaconda 主要是为了创建和管理虚拟环境&#xff0c;在pycharm里按照项目里的requirments.txt安装依赖的时候&#xff0c;使用虚拟环境会好很多&#xff08;我记得不用Anaconda也可以直接在pycharm的terminal里头创建…...

用 Python 给 Excel 表格截图(20250207)

我搜索了网络上的方案&#xff0c;感觉把 Excel 表格转换为 HTML 再用 platwright 截图是比较顺畅的路径&#xff0c;因为有顺畅的工具链。如果使用的是 Windows 系统则不需要阅读此文&#xff0c;因为 win32com 库更方便。这篇文章中 Excel 转 HTML 的方案&#xff0c;主要弥补…...

Office/WPS接入DS等多个AI工具,开启办公新模式!

在现代职场中&#xff0c;Office办公套件已成为工作和学习的必备工具&#xff0c;其功能强大但复杂&#xff0c;熟练掌握需要系统的学习。为了简化操作&#xff0c;使每个人都能轻松使用各种功能&#xff0c;市场上涌现出各类办公插件。这些插件不仅提升了用户体验&#xff0c;…...

智能化转型2.0:从“工具应用”到“价值重构”

过去几年&#xff0c;“智能化”从一个模糊的概念逐渐成为企业发展的核心议题。2024年&#xff0c;随着生成式AI、大模型、智能体等技术的爆发式落地&#xff0c;中国企业正式迈入智能化转型的2.0时代。这一阶段的核心特征是从单一场景的“工具应用”转向全链条的“价值重构”&…...

深度整理总结MySQL——索引工作原理

B树索引数据结构 前言什么样的索引数据结构是好的搜索速度要求支持范围查找寻求适合查找的算法寻求合适的数据结构二叉查找树自平衡二叉树B树B树数据结构B与B树比较 总结 前言 相信你在面试时&#xff0c;通常会被问到“什么是索引&#xff1f;”而你一定要能脱口而出&#xf…...

基于asr的所见即可说方案

年前写的文章对所见即可说方案进一步调研-CSDN博客&#xff0c;针对rk3568定制版&#xff0c;进行了Accessibility实现所见即可说功能的验证与调研&#xff0c;结论是不可行。 最终解决方案是&#xff1a;结合科大讯飞的AI大模型智能助手&#xff0c;使用rk3588板&#xff08;…...

【截图】selenium自动通过浏览器截取指定元素div的图片

【截图】selenium自动通过浏览器截取指定元素div的图片 思路 截取完整网页截图 通过元素的坐标 截图到指定位置的图片 前提是已经获取到 driver 了 # 定位目标divtarget_div driver.find_element(By.CLASS_NAME, headlines-right)# 获取div的位置和大小location target_div…...

CSS入门学习笔记(一)

学习视频&#xff1a;https://www.bilibili.com/video/BV1zN2UYoEEo/ 目录 基本介绍语法引入方式内联样式&#xff08;行内样式&#xff09;内部样式外部样式 选择器四种选择器全局选择器元素选择器类选择器id选择器 合并选择器选择器的优先级 字体属性colorfont-sizefont-weig…...

docker安装es及分词器ik

系统是macos&#xff0c;docker是docker-desktop 拉取镜像 docker pull bitnami/elasticsearch 启动docker镜像 docker create -e "discovery.typesingle-node" \ --name elasticsearch1 -p 9200:9200 -p 9300:9300 \ bitnami/elasticsearch:8.17.1 测试是否好…...

11. 9 构建生产级聊天对话记忆系统:从架构设计到性能优化的全链路指南

构建生产级聊天对话记忆系统:从架构设计到性能优化的全链路指南 关键词: 聊天对话记忆系统、多用户会话管理、LangChain生产部署、Redis记忆存储、高并发对话系统 一、服务级聊天记忆系统核心需求 多用户隔离:支持同时处理数千个独立对话持久化存储:对话历史不因服务重启丢…...

SpringBoot启动源码剖析:从入口到容器的诞生

文章目录 SpringBoot启动的核心入口SpringApplication的初始化SpringBoot的启动流程1. 准备环境&#xff08;Environment&#xff09;2. 创建应用上下文&#xff08;ApplicationContext&#xff09;3. 刷新应用上下文&#xff08;Refresh Context&#xff09;4. 调用Runner接口…...

Day38【AI思考】-彻底打通线性数据结构间的血脉联系

文章目录 **彻底打通线性数据结构间的血脉联系****数据结构家族谱系图****一、线性表&#xff08;老祖宗的规矩&#xff09;****核心特征** **二、嫡系血脉解析**1. **数组&#xff08;规矩森严的长子&#xff09;**2. **链表&#xff08;灵活变通的次子&#xff09;** **三、庶…...

虚拟鼠标MATVT:遥控器操控的安卓电视增强工具

虚拟鼠标MATVT&#xff1a;遥控器操控的安卓电视增强工具 matvt Virtual Mouse for Android TV that can be controlled via remote itself. 项目地址: https://gitcode.com/gh_mirrors/ma/matvt 项目基础介绍与编程语言 虚拟鼠标MATVT&#xff08;matvt&#xff09;是…...

优惠券平台(一):基于责任链模式创建优惠券模板

前景概要 系统的主要实现是优惠券的相关业务&#xff0c;所以对于用户管理的实现我们简单用拦截器在触发接口前创建一个单一用户。 // 用户属于非核心功能&#xff0c;这里先通过模拟的形式代替。后续如果需要后管展示&#xff0c;会重构该代码 UserInfoDTO userInfoDTO new…...

从零开始:OpenCV 图像处理快速入门教程

文章大纲 第1章 OpenCV 概述 1.1 OpenCV的模块与功能  1.2 OpenCV的发展 1.3 OpenCV的应用 第2章 基本数据类型 2.1 cv::Vec类 2.2 cv&#xff1a;&#xff1a;Point类 2.3 cv&#xff1a;&#xff1a;Rng类 2.4 cv&#xff1a;&#xff1a;Size类 2.5 cv&#xff1a;&…...

计算机网络-SSH基本原理

最近年底都在忙&#xff0c;然后这两天好点抽空更新一下。前面基本把常见的VPN都学习了一遍&#xff0c;后面的内容应该又继续深入一点。 一、SSH简介 SSH&#xff08;Secure Shell&#xff0c;安全外壳协议&#xff09;是一种用于在不安全网络上进行安全远程登录和实现其他安…...

数据库性能优化(sql优化)_统计信息_yxy

数据库性能优化_统计信息理解 1 什么是数据库统计信息?2 统计信息不准确3 统计信息分类3.1 表统计信息3.2 列统计信息3.3 索引统计信息4 统计方式4.1 频率直方图4.2 等高直方图5 总结1 什么是数据库统计信息? 数据库中同一个sql有非常多种执行方式,每种执行方式的代价肯定不…...

QT通过setProperty设置不同QSS样式

如上切换效果就是通过setProperty来实现切换不同颜色的。 实现以上效果第一步&#xff0c;需要在QSS中做属性处理。 QLabel{color:red;} QLabel[status"1"]{color:black;} QLabel[status"2"]{color:white;} QLabel[status"3"]{color:blue;} QLa…...

基础入门-算法解密散列对称非对称字典碰撞前后端逆向MD5AESDESRSA

知识点&#xff1a; 0、算法类型-单向散列&对称性&非对称性 1、算法识别加解密-MD5&AES&DES&RSA 2、解密条件寻找-逻辑特征&源码中&JS分析 应用场景&#xff1a; 1、发送数据的时候自动将数据加密发送&#xff08;只需加密即可&#xff09; 安全…...

VsCode创建VUE项目

1. 首先安装Node.js和npm 通过网盘分享的文件&#xff1a;vsCode和Node&#xff08;本人电脑Win11安装&#xff09; 链接: https://pan.baidu.com/s/151gBWTFZh9qIDS9XWMJVUA 提取码: 1234 它们是运行和构建Vue.js应用程序所必需的。 1.1 Node安装&#xff0c;点击下一步即可 …...

【DeepSeek】DeepSeek小模型蒸馏与本地部署深度解析DeepSeek小模型蒸馏与本地部署深度解析

一、引言与背景 在人工智能领域&#xff0c;大型语言模型&#xff08;LLM&#xff09;如DeepSeek以其卓越的自然语言理解和生成能力&#xff0c;推动了众多应用场景的发展。然而&#xff0c;大型模型的高昂计算和存储成本&#xff0c;以及潜在的数据隐私风险&#xff0c;限制了…...

ARM嵌入式学习--第十三天(I2C)

I2C --介绍 I2C&#xff08;Inter-intergrated Circuit 集成电路&#xff09;总线是Philips公司在八十年代初推出的一种串行、半双工的总线&#xff0c;主要用于近距离、低速的芯片之间的通信&#xff1b;I2C总线有俩根双向的信号线&#xff0c;一根数据线SDA用于收发数据&…...

js滚动到页面最底部

setTimeout(()> { //延后执行&#xff0c;等页面渲染结束let container document.querySelector(.raise-flag-content); //找到当前divif (container) {container.scrollTop container.scrollHeight - (container.clientHeight - 400 );}})container.scrollTop container…...

关于 SQL 内连接、外连接(左连接、右连接)的面试题

一、概念理解类 1. 请详细解释内连接&#xff08;INNER JOIN&#xff09;、左连接&#xff08;LEFT JOIN&#xff09;、右连接&#xff08;RIGHT JOIN&#xff09;在 SQL 中的概念和区别&#xff0c;并分别举例说明它们在实际查询场景中的应用。 在SQL中&#xff0c;内连接&a…...