c++修炼之路之C++11

目录

一:使用列表初始化

二:decltype和nullptr

三:右值引用和移动语义 

四:新的类功能 

五:可变参数模板 

 六:lambda表达式

七:包装器 

1.function包装器

 2.bind包装器

接下来的日子会顺顺利利,万事胜意,生活明朗-----------林辞忧 

一:使用列表初始化

1.统一使用{}初始化(对于内置类型和自定义类型)

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

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

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

struct Point
{int _x;int _y;
};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;}
private:int _year;int _month;int _day;
};//c++11支持一切使用列表初始化
int main()
{int x1 = { 1 };int array1[] = { 1, 2, 3, 4, 5 };Point p = { 1, 2 };Date d1={ 2022, 1, 2 };//多参数的构造函数的隐式类型转换const Date& d2 = { 2024,8,31 };//c++11支持可以不加=int x2{1};int array2[]{ 1, 2, 3, 4, 5 };Point p1{ 1, 2 };Date d3{ 2022, 1, 2 };
}

2.std::initializer_list(对于容器的多值构造)

 对于vector,list等容器支持多个值来构造初始化

对于这里的Date类是多参数的构造函数的隐式类型转换,必须跟对应构造函数的参数个数匹配,而对于这里的vector,list等容器则是使用了initializer_list的构造函数

 对于这里的initializer_list则是c++11新引入的一个类型,查阅官方文档介绍得

 c++11在这里把列表的值识别为一个initializer_list的对象,每个值的类型都是initializer_list

在底层方面

对于上述的多值构造,实际上是用迭代器遍历这个initializer_list,将每个值插入到vector等容器中或者使用对应的迭代器区间来构造

对于这里的列表初始化和 initializer_list构造结合起来使用的优秀例子为

二:decltype和nullptr

1.关键字decltype将变量的类型声明为表达式指定的类型

int i = 1;
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();
cout << typeid(i).name() << endl;
auto k = i;vector<decltype(it)> v;
decltype(it) it1;

对于typeid().name()来获取的类型是以字符串形式获取到的,获取到的这个类型不能用来创建变量或者使用的,此时我们就可以使用auto来创建变量

但auto不能做参数,这是就得用decltype(变量)来推导对象的类型,这个类型是可以用来做模板实参或者再定义对象

2.nullptr

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

三:右值引用和移动语义 

1.左值,左值引用,右值,右值引用

  // 以下的p、b、c、*p都是左值// 左值:可以取地址int* p = new int(0);int b = 1;const int c = b;*p = 10;string s("111111");s[0];cout << &c << endl;cout << &s[0] << endl;// 左值引用给左值取别名int& r1 = b;int*& r2 = p;int& r3 = *p;string& r4 = s;// 右值:不能取地址double x = 1.1, y = 2.2;// 以下几个都是常见的右值,常量临时对象,匿名对象10;x + y;fmin(x, y);string("11111");//cout << &10 << endl;// 右值引用给右值取别名int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x,y);string&& rr4 = string("11111");/////左值引用引用给右值取别名:不能直接引用,但是const 左值引用可以const int& rx1 = 10;const double& rx2 = x + y;const double& rx3 = fmin(x, y);const string& rx4 = string("11111");//适用场景// void push(const T& x);vector<string> v;string s1("1111");v.push_back(s1);v.push_back(string("1111"));v.push_back("1111");//右值引用引用给左值取别名:不能直接引用,但是move(左值)以后右值引用可以引用int&& rrx1 = move(b);int*&& rrx2 = move(p);int&& rrx3 = move(*p);string&& rrx4 = move(s);

这里实际在底层看来是没有左右值区分的,都是用指针来实现的,move的本质也就相当于强制类型转换

2.右值引用使用场景和意义

引用的意义为减少拷贝,在这里左值引用解决的场景:引用传参/引用传返回值

但当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回,这里就会有多次拷贝

因此提出使用右值引用的移动构造/赋值(移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己)来解决上述问题

但现在的编译器会做很大的优化处理,会影响观察到的结果,接下来就用一个实例来观察的

举例如下

namespace mjw
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}typedef const char* const_iterator;const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造// s2(s1)string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;reserve(s._capacity);for (auto ch : s){push_back(ch);}}// 赋值重载string& operator=(const string& s){cout << "string& operator=(const string& s) -- 深拷贝" << endl;if (this != &s){_str[0] = '\0';_size = 0;reserve(s._capacity);for (auto ch : s){push_back(ch);}}return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];if (_str){strcpy(tmp, _str);delete[] _str;}_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0; // 不包含最后做标识的\0};mjw::string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}mjw::string str;while (value > 0){int x = value % 10;value /= 10;str += ('0' + x);}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str;}
}int main()
{mjw::string s1;s1 = mjw::to_string(1234);return 0;
}

 

string的移动构造和移动赋值

// 移动构造
// 临时创建的对象,不能取地址,用完就要消亡
// 深拷贝的类,移动构造才有意义
string(string&& s)
{cout << "string(string&& s) -- 移动拷贝" << endl;swap(s);
}// 移动赋值
string& operator=(string&& s)
{cout << "string& operator=(string&& s) -- 移动拷贝" << endl;swap(s);return *this;
}

在vs2022

这种编译器的最新优化下,上述过程还会优化的更深入,直接优化为构造 

 

这里会想如果在这样优化的环境下,是不是移动构造/赋值就会意义,其实不然,就像下面的场景下是无法直接优化为构造的

对于右值引用的移动语义还有一些应用场景

对于大对象的传值返回,拷贝代价极大,就比如这里的杨辉三角

 

对于一些容器的插入接口也增加了右值引用的版本 

3.关于右值引用本身的属性是左值的探究

问题引入:

在引入前面所写list文件(可从前面博客中寻找)后,在list.h中实现移动构造和移动赋值后

会发现这里的打印仍然是拷贝构造,移动构造并没有触发,造成这样的原因是

这里的右值引用x本身的属性还是左值,因此接下来还是会匹配到拷贝构造的

那么这里为啥要这样规定呢,其实主要是为了解决这个问题

如果这里swap(s),s的属性是右值的话,与swap函数的参数类型是不能匹配的,况且swap函数的参数类型只能写string&,const string&和string&& 的对象是不能修改的

因此为了解决上述问题可以使用move来解决的

4.完美转发

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)
{// 模版实例化是左值引用,保持属性直接传参给Fun// 模版实例化是右值引用,右值引用属性会退化成左值,转换成右值属性再传参给FunFun(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;
}

对于这里的万能引用中Fun传参,是不能区分左右值的,都会被统一处理为左值,为了解决这个问题就可以使用完美转发

这时就会保持它的原有属性,不会发生右值退化为左值的情况

四:新的类功能 

1.移动构造和移动赋值

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}//~Person() {}
private:mjw::string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

如果这里没有显示写析构,拷贝构造,赋值的话,编译器就会自动生成移动构造和移动赋值

 

如果实现了其中的任意一个的话,就不会自动生成 

 2.强制生成默认函数的关键字default和禁止生成默认函数的关键字delete

在c++98中,当不想成员函数被拷贝时,就可以将成员函数只声明,不定义,放到private中;在c++11之后就采用delete关键字来解决

五:可变参数模板 

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
//参数包包含多个类型和参数
template <class ...Args>
void ShowList(Args... args)
{}

 1.对函数形参参数包sizeof得参数包的参数个数

2.使用递归函数方式展开参数包 

在这里如果要展开参数包的话,或许我们会首先想到这样的方法

但这样是不支持的,因为可变参数模板是在编译时解析的,而这样的方式是在运行时获取和解析的,所以不支持

这里就要使用在编译时递归推导解析参数

void Print()
{cout << endl;
}template <class T, class ...Args>
void Print(T&& x, Args&&... args)
{cout << x << " ";Print(args...);
}// 编译时递归推导解析参数
template <class ...Args>
void ShowList(Args&&... args)
{Print(args...);
}
int main()
{ShowList();ShowList(1);ShowList(1, "xxxxx");ShowList(1, "xxxxx", 2.2);return 0;
}

对于这里的原理就是,比如是有三个参数的情况下,是将有三个参数的参数包传给args,再在编译时将参数包传给Print函数,就将第一个参数传给x然后解析出来,再将其余两个参数的参数包传给Print函数的第二个参数,再递归调用自己,依次解析参数包中的内容,直至为空,此时就调用Print()函数,完成整个参数包的解析

这里在编译器实例化的时候,其实是实例化为不同的函数,然后再分别调用,如下

3.使用逗号表达式展开参数包

template <class T>
void PrintArg(T t)
{cout << t << " ";
}template <class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args),0)... };cout << endl;
}// 编译推演生成下面的函数
//void ShowList(int x, char y, std::string z)
//{
//	int arr[] = { PrintArg(x),PrintArg(y),PrintArg(z) };
//	cout << endl;
//}int main()
{//ShowList(1);//ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行
printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列
表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0),
(printarg(arg2),0), (printarg(arg3),0), etc... ),最终会创建一个元素值都为0的数组int arr[sizeof...
(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数,也就是说在构造int数组的过程中就将参数包展开了

也可以使用这样的方式

template <class ...Args>
void ShowList(Args... args)
{int arr[] = { (cout<<(args)<<" ", 0)...};cout << endl;
}//void ShowList(int x, char y, std::string z)
//{
//	int arr[] = { (cout<<(x)<<" ", 0), (cout << (y) << " ", 0), (cout << (z) << " ", 0) };
//
//	cout << endl;
//}int main()
{ShowList(1, 'A', std::string("sort"));return 0;
}

4.emplace_back接口与push_back等插入接口的区别

.template <class... Args>
void emplace_back (Args&&... args); 

这里对于普通的左值还是右值的插入,这两个区别不大

这里的可变参数模板是针对多参数的,与push_back()不同的地方为 

往下传的过程中,就会直接构造对象进行插入操作,而push_back是先构造,再拷贝/移动构造

5.在list中模拟实现emplace_back版本

 六:lambda表达式

1.实例引入

#include<algorithm>
struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价//...Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};struct Compare1
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};struct Compare2
{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(), Compare1());sort(v.begin(), v.end(), Compare2());return 0;
}

在实际的排序实例当中,一般都是对结构体按照某个成员为标准的排序,但这样的话,就需要写很多的仿函数来参与不同的排序,但当仿函数

的函数名是不明确的时候,这时还得查看源代码,于是为了清晰明了,减少仿函数,就引入了lambda表达式

2. lambda表达式语法

3.简单的lambda表达式 

实现加法

打印多行内容 

lambda表达式的一些省略

4.关于捕获列表

实现一个交换函数

此时在函数体内部是无法直接使用a,b的,而要使用的话,就要使用捕捉列表的

要想改变外面捕捉的值,就得使用引用捕捉

其余几种捕捉方式

int x = 0;
int main()
{// 只能用当前lambda局部域和捕捉的对象和全局对象int a = 0, b = 1, c = 2, d = 3;// 所有值传值捕捉auto func1 = [=]{int ret = a + b + c + d + x;return ret;};// 所有值传引用捕捉auto func2 = [&]{a++;b++;c++;d++;int ret = a + b + c + d;return ret;};// 混合捕捉auto func3 = [&a, b]{a++;// b++;int ret = a + b;return ret;};// 混合捕捉// 所有值以引用方式捕捉,d用传值捕捉auto func4 = [&, d]{a++;b++;c++;//d++;int ret = a + b + c + d;};auto func5 = [=, &d]() mutable{a++;b++;c++;d++;int ret = a + b + c + d;};return 0;
}

对于最初的排序问题,就可以使用lambda表达式添加排序方案 

// 价格升序
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool{return g1._price < g2._price;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool{return g1._price > g2._price;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool{return g1._evaluate < g2._evaluate;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool{return g1._evaluate > g2._evaluate;});	

5.函数对象与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.015;Rate r1(rate);cout << r1(10000, 2) << endl;// lambdaauto r2 = [rate](double monty, int year)->double{return monty * rate * year;};cout << r2(10000, 2) << endl;int x = 1, y = 2;auto r3 = [=](double monty, int year)->double{return monty * rate * year;};cout << r3(10000, 2) << endl;return 0;
}

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

运行代码转到底层汇编代码得

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

对于我们上层看到的是lambda表达式是一个匿名函数对象,但实际在底层是有函数名的,构成为lambda_uuid

七:包装器 

1.function包装器

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

1.function包装器的意义

ret = func(x);
// 上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能
是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下

因此提出了用包装器来解决问题,提供一种统一的方式来访问

std::function在头文件<functional> 

模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参 

2.包装实例1

#include<functional>
int f(int a, int b)
{return a + b;
}struct Functor
{
public:int operator() (int a, int b){return a + b;}
};class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};int main()
{// 包装可调用对象function<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b) {return a + b; };cout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;// 包装静态成员函数function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;// 包装非静态成员函数(注意有隐含的this指针):方式1function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;//方式2function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;return 0;
}

 实例2

 2.bind包装器

 std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可
调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。使用std::bind函数还可以实现参数顺序调整等操作

1.函数原型

 2.实例演示

#include<functional>using placeholders::_1;
using placeholders::_2;
using placeholders::_3;int Sub(int a, int b)
{return (a - b) * 10;
}int SubX(int a, int b, int c)
{return (a - b - c) * 10;
}int main()
{// bind 本质返回的一个仿函数对象// _1代表第一个实参// _2代表第二个实参// ...auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;// 调整参数顺序(不常用)auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;// 调整参数个数 (常用)auto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);cout << sub4(5) << endl;// 分别绑死第123个参数auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 1) << endl;auto sub6 = bind(SubX, _1, 100, _2);cout << sub6(5, 1) << endl;auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 1) << endl;return 0;
}

图示分析:

这里的bind包装器主要解决的是下面的这个例子 

class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{function<double(Plus, double, double)> f6 = &Plus::plusd;Plus pd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;// bind一般用于,绑死一些固定参数function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);cout << f7(1.1, 1.1) << endl;return 0;
}

其余实例:

//auto func1 = [](double rate, double monty, int year)->double {return monty * rate * year;};
auto func1 = [](double rate, double monty, int year)->double {double ret = monty;for (int i = 0; i < year; i++){ret += ret * rate;}return ret - monty;};function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);cout << func3_1_5(1000000) << endl;
cout << func5_1_5(1000000) << endl;
cout << func10_2_5(1000000) << endl;
cout << func20_3_5(1000000) << endl;

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

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

相关文章

Linux CentOS 部署Docker

1. yum 配置 &#xff08;1&#xff09;更新yum yum update -y 如果不升级更新yum 可能在后续docker部署后再更新容器会出现oci runtime error等 &#xff08;2&#xff09;安装yum工具类准备 yum install -y yum-utils device-mapper-persistent-data lvm2 &#xff08;3&…

【操作系统存储篇】Linux文件基本操作

目录 一、Linux目录 二、Linux文件的常用操作 三、Linux文件类型 一、Linux目录 Linux有很多目录&#xff0c;Linux一切皆是文件&#xff0c;包括进程、设备等。 相对路径&#xff1a;相对于当前的操作目录&#xff0c;文件位于哪个目录。 绝对路径 &#xff1a;从根目录开…

Golang | Leetcode Golang题解之第387题字符串中的第一个唯一字符

题目&#xff1a; 题解&#xff1a; type pair struct {ch bytepos int }func firstUniqChar(s string) int {n : len(s)pos : [26]int{}for i : range pos[:] {pos[i] n}q : []pair{}for i : range s {ch : s[i] - aif pos[ch] n {pos[ch] iq append(q, pair{ch, i})} e…

数仓工具—Hive语法之URL 函数

hive—语法—URL 函数 业务需求中,我们经常需要对用户的访问、用户的来源进行分析,用于支持运营和决策。例如我们经常对用户访问的页面进行统计分析,分析热门受访页面的Top10,观察大部分用户最喜欢的访问最多的页面等: 又或者我们需要分析不同搜索平台的用户来源分析,统…

jmeter同步定时器、固定定时器、统一随机定时器、常数吞吐量定时器详解

一、同步定时器 可以让多个线程同时向服务器发送请求&#xff0c;实现瞬间并发(相当于现实中同步秒杀商品)类似于集合点 例如&#xff1a;10个人约定去旅游&#xff0c;出发前提前会在某一个地方等到10个人同时都到了约定地点之后再一同排队上车 在任意接口下添加同步定时器模…

区域智慧农业解决方案

### 1. 项目概述 《区域智慧农业整体解决方案》由左岸芯慧提出&#xff0c;旨在通过数字化手段打造新型农业经营管理方式&#xff0c;实现产销一体化。 ### 2. 农业监管与需求分析 方案针对监管部门、消费者和生产服务企业的需求进行分析&#xff0c;指出农业监管包括数据收…

【SpringBoot】使用Redis

目录 0. 安装Redis 1. 导入依赖 2. 配置Redis 3. idea连接Redis 4. 使用Redis简单实现记录访问次数 1. 配置拦截器 2. 定义拦截器 3. 控制器类 0. 安装Redis 我使用的是本地Redis服务器&#xff0c;安装过程。安装完成后启动Redis服务。 1. 导入依赖 <!-- red…

负载均衡调度器--LVS

文章目录 集群和分布式集群分布式 LVS介绍LVS特点LVS工作原理LVS集群架构 LVS集群中的术语CIPVIPRSDIPRIP LVS集群的工作模式NAT模式DR模式DR模式的特点: TUN模式 LVS调度算法LVS相关软件ipvsadm 命令管理集群服务&#xff1a;增、改、删管理集群上的RS:增、改、删 创建集群 LV…

如何修复软件中的BUG

笔者上一篇博文《如何开发出一款优秀的软件》主要讲了如何开发一款优秀的软件及相应的必要条件。但对一个已上线&#xff0c;已经成型的产品&#xff0c;该如何解决存在的bug呢&#xff1f;这是本文要阐述的内容。 在这里&#xff0c;首先说一下bug的种类及bug严重程度分类&…

微信小程序认证和备案

小程序备案的流程一般包括以下步骤‌&#xff1a; 准备备案所需材料‌&#xff1a;通常需要提供‌营业执照、法人的‌身份证、两个‌手机号和一个邮箱等资料。 ‌1 ‌登录‌微信公众平台‌&#xff1a;作为第一次开发微信小程序的服务商&#xff0c;需要通过微信公众平台申请…

AAC高级音频编码技术

一、什么是AAC AAC的中文名称是高级音频编码技术&#xff0c;它是基于MPEG-2的一种全新的音频编码技术。随着时代的发展&#xff0c;目前AAC的技术升级到MPEG-4表准。AAC广泛的应用在网络传输、高清录制等领域&#xff0c;而AAC技术的出现就是为了取代之前的MP3格式。 二、为什…

酒店网站管理系统前后台完整源码

酒店网站管理系统前后台完整源码&#xff0c;架构是javasshmysql。 系统基于SSH框架&#xff0c;数据库用的mysql。系统分为前台网站部分&#xff0c;和后台管理部分。 注意&#xff0c;不是php代码&#xff0c;是java代码&#xff0c;使用者需要懂java才行。仅供懂java的人学…

如何提升网站的收录率?

要提升网站的收录率&#xff0c;其中一个特别有效的工具就是GPC爬虫池&#xff0c;这个工具通过深度研究谷歌SEO算法&#xff0c;吸引谷歌爬虫。 GPC爬虫池的基本原理是构建一个庞大的站群系统&#xff0c;并创建复杂的内链和外链结构&#xff0c;以吸引并留住谷歌蜘蛛 使用GP…

跟着李沐学ai

01 课程安排【动手学深度学习v2】-跟李沐学AI-【完结】动手学深度学习 PyTorch版-哔哩哔哩视频 (bilibili.com)https://www.bilibili.com/list/1567748478?sid358497&spm_id_from333.999.0.0&desc1&oid714717789&bvidBV1oX4y137bC 目标 介绍深度学习经典和最…

大白话说什么是“MLLM”多模态大语言模型

1. 什么是MLLM多模态大语言模型 1.1 先来思考一个问题 如果上传了一张图片&#xff0c;并向大模型提问。“图片中绿色框框中的人是谁&#xff1f;” 大模型回答&#xff1a;“那是波多野吉衣老师” 请问&#xff0c;大模型是怎么做到的&#xff1f; 我们用常规的思路来想一…

使用 MongoDB 构建 AI:Patronus 如何自动进行大语言模型评估来增强对生成式 AI 的信心

大语言模型可能不可靠&#xff0c;这几乎算不上头条新闻。对于某些用例&#xff0c;这可能会带来不便。而对于其他行业&#xff0c;尤其是受监管行业&#xff0c;后果则要严重得多。于是&#xff0c;业内首个大语言模型自动评估平台 Patronus AI 应运而生。 Patronus AI 由 Met…

SPIRNGBOOT+VUE实现浏览器播放音频流并合成音频

一、语音合成支持流式返回&#xff0c;通过WS可以实时拿到音频流&#xff0c;那么我们如何在VUE项目中实现合成功能呢。语音合成应用非常广泛&#xff0c;如商家广告合成、驾校声音合成、新闻播报、在线听书等等场景都会用到语音合成。 二、VUE下实现合成并使用浏览器播放代码…

山东大学机试试题合集

&#x1f370;&#x1f370;&#x1f370;高分篇已经涵盖了绝大多数的机试考点&#xff0c;由于临近预推免&#xff0c;各校的机试蜂拥而至&#xff0c;我们接下来先更一些各高校机试题合集&#xff0c;算是对前边学习成果的深入学习&#xff0c;也是对我们代码能力的锻炼。加油…

Faceware面部动作捕捉系统,为虚拟角色赋予真实可信的面部动画

在数字娱乐产业日新月异的今天&#xff0c;虚拟角色的真实性和可信度成为了衡量作品质量的重要标准之一。Faceware面部动作捕捉系统是这一领域中的佼佼者&#xff0c;它以先进的技术和卓越的性能&#xff0c;为虚拟角色创造了生动、自然的面部表情&#xff0c;极大地提升了观众…

RK3588 系列之3—rknn使用过程中遇到的bug

RK3588 系列之3—rknn使用过程中遇到的bug 1.librockchip_mpp.so: file format not recognized&#xff1b; treating as linker scrip2.Could not find a package configuration file provided by "OpenCV" with any of the following names参考文献 1.librockchip_…