C++ —— C++11新增语法

目录

一,列表初始化

1.1 这是什么?

1.2 initializer_list

1.3 在容器的运用 

1.4 STL中的变化

二,右值引用和左值引用

2.1 是什么?

2.2 这两个东西有啥关系?

2.3 有啥用? 

三,*移动构造和移动赋值

 3.1 什么是移动构造?

3.2 移动赋值

 3.3 STL中的变化

3.4 完美转发

​编辑

​编辑 3.5 类的新功能

3.6 小练习

四,可变参数模板

4.1 是什么?

4.2 怎么用

4.2.1 打印参数个数

4.2.2 递归获取参数

 4.2.3 另一种递归

 4.2.4 奇怪的玩法

五,lambda表达式

5.1 是什么?

5.2 lamdba使用场景

5.3 其他捕捉方法

六,function包装器

6.2 包装器使用

6.3 逆波兰OJ题

6.4 bind

七,补充

8.1 auto和decltype

8.2 两个被批评的容器

8.3 emplace系列接口

一,列表初始化

1.1 这是什么?

C++98中,标准允许使用花括号进行统一的列表初始化的设定,下面两种初始化是一样的

int x1 = 1;
int x2 = { 2 };

 而在C++11中,扩大了花括号的初始化列表的使用范围,使其可以用于所有内置类型和用户自定义的类型,其中 "=" 也可以省略了

int x3{ 2 };
int x4(1); //调用int的构造
int* pa = new int[4] {0};//C++11中列表初始化也可以适用于new表达式中

 C++11后自定义类型也支持列表初始化,本质是调用自定义类型的构造函数

class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout << "调用Date的构造函数";cout << "Date(int year, int month, int day)" << endl;}private:int _year;int _month;int _day;
};void main1()
{Date d1(2022, 11, 22);Date d2 = { 2022,11,22 }; //(构造+拷贝,优化成直接构造)   //如果不想让自定义类型这样搞,在构造函数前面加个explicit表示该构造函数不能隐式转换Date d3{ 2022,11,22 };//C++11
}

1.2 initializer_list

STL可以支持不同数量参数初始化,因为花括号括起来的常量数组,C++把它识别成一个类型initializer_list

文档介绍这个是一个类模板,用来访问C++初始化列表中的的值,且此类型的对象由编译器根据初始化列表声明自动构造,如下演示代码:

void main(
{//STL可以支持不同数量参数初始化,因为花括号括起来的常量数组,C++把它识别成一个类型                    initializer_listauto i1 = { 1,2,3,4,5,6 };auto i2 = { 1,2,5,6 };cout << typeid(i1).name() << endl;cout << typeid(i2).name() << endl;initializer_list<int>::iterator it1 = i1.begin();initializer_list<int>::iterator it2 = i2.begin();cout << it1 <<endl;
{

1.3 在容器的运用 

void main()
{Date d1(2022, 11, 22);Date d2 = { 2022,11,22 };Date d3{ 2022,11,22 };//C++11cout << "---------------------" << endl;vector<int> v1 = { 1,2,3,4,5,6 }, v2{ 1,2,3,4,5,6 }; //容器都可以用列表初始化list<int> lt1 = { 1,2,3,4,5,6 }, lt2{ 1,2,3,4,5,6 };vector<Date> v3 = {d1, d2};                            //用对象初始化vectorvector<Date> v4 = { Date(2024,1,29), Date(2024,1,30)}; //用匿名对象初始化vector<Date>v5 = { {2024,1,1}, {2023,11,11} };         //隐式类型转换string s1 = "11111"; //单参数的构造函数支持隐式类型的转换//支持initializer_list的构造map<string, string> dict = { {"sort","排序"},{"insert","插入"}};pair<string, string> kv = { "Date","日期" };//赋值重载initializer_list<pair<const string,string>> kvil = { { "left", "左边" }, { "left", "左边" } };dict = kvil;
}

1.4 STL中的变化

C++11后所有STL容器都会增加一个支持类似list(initializer_list<value_type> il)这样的构造函数,下面是vector的新构造函数模拟实现

vector(initializer_list<T> il)
{/*initializer_list<T>::iterator it = il.begin();while (it != li.end()){push_back(*it);++it;}*/for(auto& e : il){push_back(e);
}

总结:C++11以后一切对象都可以用列表初始化,但是我们建议普通对象还是用以前的方式初始化,容器如果有需求,可以用列表初始化

二,右值引用和左值引用

2.1 是什么?

先回顾下什么是左值 --> 可以获取它的地址的表达式,const修饰的不能赋值,其它大部分都可以给它赋值,如下代码:

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;double x = 1.1, y = 2.2;

 所以右值就是 --> 不能取地址的表达式,并且不能出现在赋值符号左边,如下代码:

10;          //cout<<&10<endl;  自变量(无法打印地址)
x + y;       //cout<<&(x+y)<<endl;  表达式返回的临时变量(也无法打印地址)
fmin(x, y);  //函数返回值//下面就是几个右值引用的例子
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x,y);

2.2 这两个东西有啥关系?

①首先左值引用不能直接给右值取别名

//double& r1 = x + y;
//左值不能引用右值,因为这是权限的放大了,x + y传值时是生成一个临时对象,这个临时对象具有常性,所以要加const
const double& r1 = x + y;//const左值可以引用右值

②右值引用不能给左值取别名

//int&& rr5 = b;不能
int&& rr5 = move(b);//右值引用可以给move以后的左值取别名

③const左值引用可以引用左值也可以引用右值

void Func(int& x) { cout << "左值引用" << endl; }
void Func(const int& x) { cout << "const 左值引用" << endl; }void Func(int&& x) { cout << "右值引用" << endl; }
void Func(const int&& x) { cout << "const 右值引用" << endl; }void main()
{//const左值引用可以引用右值,但缺点是无法知道我引用的是左值还是右值//所以右值引用的第一个意义就是在函数传参的时候进行更好的参数匹配int a = 0;int b = 1;Func(a);Func(a + b);
}

 

2.3 有啥用? 

引用的核心价值 --> 减少拷贝

左值引用能解决的问题:

1,做参数 -- ①减少拷贝,提高效率  ②做输出型参数

2,做返回值 -- ①减少拷贝,提高效率  ②可以修改返回对象,比如map的operator[],可以修改插入

左值引用不能解决的问题: 

1,传值返回时返回局部对象时,局部对象出了函数栈帧作用域时会被销毁,这时候引用对应的值就没了,引用失效,出现类似野指针的野引用

2,容器插入接口的对象拷贝问题,C++以前,是void push_back(const T& x); 不论T是自定义还是内置类型,都会生茶一个对象x,然后再拷贝赋值然后再销毁x,这样做消耗太大

 所以C++11以后所有的容器的插入接口都重载了个类似void push_back(T&& x); 的右值引用接口,使自定义类型走右值引用的移动拷贝,实现资源转移不拷贝,提高效率

所以C++11最大的亮点之一就是右值引用和接下来要讲的移动语义,这两个东西加起来可以大大提高拷贝赋值的效率

三,*移动构造和移动赋值

为了方便演示构造和赋值打印,我们先简单模拟实现string

namespace bit
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){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);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;//string tmp(s._str);//swap(s);_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// 移动构造string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移动构造(资源转移)" << endl;swap(s);}// 拷贝赋值string& operator=(const string& s){cout << "string& operator=(const string& s) -- 拷贝赋值(深拷贝)" << endl;string tmp(s);swap(tmp);return *this;}// 移动赋值 -- 延长了对象内的资源的生命周期  s = 将亡值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动赋值(资源移动)" << endl;swap(s);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];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){push_back(ch);return *this;}string operator+(char ch){string tmp(*this);tmp += ch;return tmp;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity;};
}

 3.1 什么是移动构造?

bit::string To_String(int value)
{bool flag = true;if (value < 0){flag = false;value = 0 - value;}bit::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; 
}

 如上图左图,在bit::string To_String(int value)函数中可以看到,这里只能使用传值返回,所以会有两次拷贝构造(现在的编译器会把连续构造两次的情况优化为只构造一次)

string(const string& s):_str(nullptr)
{cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;//string tmp(s._str);//swap(s);_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}
// 移动构造
string(string&& s):_str(nullptr), _size(0), _capacity(0)
{cout << "string(string&& s) -- 移动构造(资源转移)" << endl;swap(s);
}bit::string to_string(int value)
{bit::string str;//...return str;
}
int main()
{bit::string ret = bit::to_string(-1234);return 0;
}

 如上代码,to_string的返回值是一个右值,用这个右值构造ret,如果没有移动构造,编译器就会匹配调用拷贝构造,因为const左值引用可以引用右值,这里就是一个深拷贝

如果同时有拷贝构造和移动构造,那么编译器会选择最匹配的参数调用,这里就是一个移动构造。而这个更好的参数匹配我们前面讲左值和右值引用的关系的第三点提过

3.2 移动赋值

string& operator=(string&& s)
{cout << "string& operator=(string&& s) -- 移动语义" << endl;swap(s);return *this;
}void main()
{bit::string s1;s1 = to_string1(1234); //C++98中,先生成对象s1,然后to_string返回时再生成一个str对象返回,然后拷贝给临时对象,然后再拷贝给s1,消耗太大 --> 两次深拷贝//C++11后,直接生成s1,然后to_string返回时,将返回时临时生成的str识别成将亡值,把资源给临时对象,然后通过string& operator=(string&& s)再移动给s --> 只是单纯的资源转移,无任何拷贝,大大提高效率//bit::string("hello world");   //析构函数添加打印~string的话,这一条语句会打印~string//const bit::string& ref1 = bit::string("hello world"); //不打印~string,// const引用延长传参时临时对象生命周期,是为了解决void push_back(const string& s); // 当我们v.push_back(string("1111"));时,string("1111")的生命周期只有这一行,这里用引用的话一旦生命周期过了,就没了,所以这种场景下必须延长匿名对象生命周期//bit::string s2;//const bit::string& ref2 = bit::to_string(1234); //如果to_string返回值为const引用,出了作用域后调用析构函数销毁了,这时候再去访问就造成了类似野指针的野引用//如果to_string是传值返回,就可以用const引用接收了,因为传值返回时返回的就是str的拷贝
}

 3.3 STL中的变化

由于移动语义在提高效率上的飞跃,C++11后所有容器的拷贝和赋值都增加了右值引用的移动语义重载

void main()
{std::string s1("hello world");std::string s2(s1);//拷贝构造//std::string s3(s1+s2);std::string s3 = s1 + s2;//C++98就是拷贝构造,C++11已经更新,这里现在是移动构造std::string s4 = move(s1);//s1被move后就成为了一个右值,就被弄走了,所以调试时走完这一行,监视窗口的s1没了
}

并且,所有可插入容器的插入接口也都重载了右值引用版本,如下代码:

void main()
{vector<bit::string> v;bit::string s1("hello");v.push_back(s1);cout << "--------------------------" << endl;v.push_back(bit::string("world"));//v.push_back("world");//平时喜欢这样写cout << "==========================" << endl;list<bit::string> lt;//bit::string s1("hello");lt.push_back(s1); //左值lt.push_back(move(s1)); //右值cout << "--------------------------" << endl;//lt.push_back(bit::string("world")); //匿名对象是右值,也是一次移动构造lt.push_back("world"); //但是我们喜欢这样写,上面那个不推荐
}

3.4 完美转发

//万能引用/引用折叠 -- t能引用左值,也能引用右值,如果没有完美转发,会被全部搞成左值
template<typename T>
void PerfectForward(T&& t)
{//完美转发 -- 保持t引用对象属性//Func(std::forward<T>(t));Func(t);//没有完美转发时,main()里面就全部搞为左值引用
}
//模板中的&&不代表右值引用,代表万能引用,它既能接收左值又能接收右值
//但是模板的万能引用只是提供了能够同时接收左值引用和右值引用的能力
//但是引用类型的唯一作用就是限制了接收的类型,后续使用的时候会全部退化成左值
//所以我们希望能够在传递过程中保持它的左值或右值的属性,就需要用到完美转发 --> std::forward<T>()
#include"list.h"
void main()
{//如果没有完美转发,则下面全部搞成了左值引用PerfectForward(10);           // 右值cout << "------------------------" << endl;int a;PerfectForward(a);            // 左值PerfectForward(std::move(a)); // 右值cout << "------------------------" << endl;const int b = 8;PerfectForward(b);		      // const 左值PerfectForward(std::move(b)); // const 右值cout << "------------------------" << endl;bit::list<bit::string> lt;bit::string s1("hello");lt.push_back(s1);cout << "------------------------" << endl;//如果是确定类型就是走拷贝,模板就可以走右值引用lt.push_back("world");
}

演示模拟转发,需要在list_node类节点里面添加右值引用版本的构造

namespace bit
{template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){}list_node(T&& x)//这里也提供一个右值引用:_data(std::forward<T>(x))//完美转发, _next(nullptr), _prev(nullptr){}};// typedef __list_iterator<T, T&, T*>             iterator;// typedef __list_iterator<T, const T&, const T*> const_iterator;// 像指针一样的对象template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> iterator;//typedef bidirectional_iterator_tag iterator_category;typedef T value_type;typedef Ptr pointer;typedef Ref reference;typedef ptrdiff_t difference_type;Node* _node;// 休息到17:02继续__list_iterator(Node* node):_node(node){}bool operator!=(const iterator& it) const{return _node != it._node;}bool operator==(const iterator& it) const{return _node == it._node;}// *it  it.operator*()// const T& operator*()// T& operator*()Ref operator*(){return _node->_data;}//T* operator->() Ptr operator->(){return &(operator*());}// ++ititerator& operator++(){_node = _node->_next;return *this;}// it++iterator operator++(int){iterator tmp(*this);_node = _node->_next;return tmp;}// --ititerator& operator--(){_node = _node->_prev;return *this;}// it--iterator operator--(int){iterator tmp(*this);_node = _node->_prev;return tmp;}};template<class T>class list{typedef list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;//typedef __reverse_iterator<iterator, T&, T*> reverse_iterator;//typedef __reverse_iterator<const_iterator, const T&, const T*> //const_reverse_iterator;const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}/*reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}*/void empty_init(){// 创建并初始化哨兵位头结点_head = new Node;_head->_next = _head;_head->_prev = _head;}template <class InputIterator>list(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);++first;}}list(){empty_init();}void swap(list<T>& x)//void swap(list& x){std::swap(_head, x._head);}// lt2(lt1)list(const list<T>& lt){empty_init();list<T> tmp(lt.begin(), lt.end());swap(tmp);}// lt1 = lt3list<T>& operator=(list<T> lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void push_back(const T& x){//Node* tail = _head->_prev;//Node* newnode = new Node(x);_head          tail  newnode//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _head;//_head->_prev = newnode;insert(end(), x);}void push_back(T&& x)//右值引用 + 完美转发,不加完美转发就会被折叠成左值引用{insert(end(), std::forward<T>(x));}void push_front(const T& x){insert(begin(), x);}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}iterator insert(iterator pos, T&& x)//右值引用 + 完美转发,不加完美转发就会被折叠成左值引用{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(std::forward<T>(x));// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);}private:Node* _head;};
}

 3.5 类的新功能

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

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

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

class Person
{
public:Person(const char* name = "", int age = 0):_name(name),_age(age){}一个构造函数可以复用其他构造函数//Person(const char* name)//	:Person(name,18) //委托构造//{}/*Person(const Person& p):_name(p._name) ,_age(p._age){}*//*Person& operator=(const Person& p){if(this != &p){_name = p._name;_age = p._age;}return *this;}*/// 如果不想让Person对象拷贝或被拷贝//Person(const Person& p) = delete; //delete表示禁止生成//强制生成移动构造和移动赋值
//	Person(Person&& p) = default;
//	Person& operator=(Person&& p) = default; //default表示强转生成
//
//	~Person()
//	{
//		cout << "~Person()" << endl;
//	}private:bit::string _name; //自定义类型int _age;          //内置类型
};void main()
{Person s1("张三",18);Person s2 = s1;              //拷贝构造Person s3 = std::move(s1);   //移动构造(没有构造,也可以调用构造)cout << endl << endl;Person s4;s4 = std::move(s2);  //调用bit::string的移动拷贝
}

3.6 小练习

题目 -- 要求用delete关键字实现一个类,且只能在堆上创建对象

class HeapOnly//指类只能在堆上
{
public:HeapOnly(){_str = new char[10];}~HeapOnly() = delete;void Destroy(){delete[] _str;operator delete(this);}
private:char* _str;
};void main15()
{//HeapOnly hp1;//static HeapOnly hp2;//无法在堆以外的区域生成对象HeapOnly* ptr = new HeapOnly;//delete ptr;析构被限制,调不了ptr->Destroy();             //把类里的构造函数new的10个char释放//operator delete(ptr);     //把生成的ptr对象释放掉
}

四,可变参数模板

4.1 是什么?

C++11的新特性可变参数模板可以让我们创建接收可变参数的函数模板和类模板,相比C++98,以前的类模板和函数模板只能含固定数量的模板参数。但是这块使用起来比较抽象,容易出bug,所以对于这一块本文章只点到为止,不进行深入学习

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

 首先Args是一个模板参数包,args是一个函数形参参数包。

我们把带省略号的参数称为“参数包”,里面包含了大于0个模板参数。我们无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是参数包的一个重要特征,也是最大的难点,因为解析参数包过程比较复杂,一旦代码变多就容易出bug并且不好排查,下面将介绍可变参数模板的解析与使用

4.2 怎么用

4.2.1 打印参数个数

template<class ...Args>
void ShowList1(Args ...args)//(Args ...args)是函数参数包
{cout << sizeof ...(args) << endl;//打印参数包内参数的个数
}
void main()
{string str("hello");ShowList1();ShowList1(1);ShowList1(1,'A');ShowList1(1,'A',str);
}

4.2.2 递归获取参数

void ShowList2()//应对0个参数的参数包时
{cout << endl;
}
//Args... args代表N个参数包(N >= 0)
template<class T, class ...Args>
void ShowList2(const T& val, Args ...args)
{cout << "ShowList(" << val << ", 包里还剩" << sizeof...(args) << "个参数)" << endl;ShowList2(args...); //利用类似递归的方式来解析出参数包
}
void main()
{string str("hello");ShowList2(1, 'A', str);ShowList2(1, 'A', str, 2, 3, 5.555);
}

 4.2.3 另一种递归

void _ShowList3()
{cout << endl;
}
template <class T, class ...Args>
void _ShowList3(const T& val, Args... args)
{cout << val << " ";cout << __FUNCTION__ << "(" << sizeof...(args)+1 << ")" << endl;_ShowList3(args...);
}
template <class ...Args>
void ShowList3(Args... args)
{_ShowList3(args...);
}void main()
{string str("hello");ShowList3(1, 'A', str);ShowList3(1, 'A', str, 2, 3, 5.555);
}

 4.2.4 奇怪的玩法

template<class T>
int PrintArg(T t)
{cout << t << " "; //所有的参数可以在这里获取到return 0;
}
template<class ...Args>
void ShowList4(Args ...args)
{int arr[] = { PrintArg(args)... }; //这里利用了编译器自动初始化,这里自动初始化这个数组,编译器编译的时候对这个数组进行大小确认再开空间,就去解析后面这个内容//就把参数包第一个值传给PrintArg的T,然后又因为后面是...所以参数包有几个值就要生成几个PrintArg表达式,然后生成几个表达式前面的那个数组就开多大//然后PrintArg就拿到了所有参数包的值,然后main函数调用后就开始打印cout << endl;
}//编译器编译推演生成了一下代码
//void ShowList(char a1, char a2, std::string a3)
//{
//	int arr[] = { PrintArg(a1),PrintArg(a2),PrintArg(a3) };
//	cout << endl;
//}void main()
{ShowList4(1, 'A', string("sort"));ShowList4(1, 2, 3);
}

五,lambda表达式

5.1 是什么?

在C/C++中可以像函数那样使用的对象/类型有:1,函数指针(C)  2,仿函数/函数对象(C++)  3,lamdba(C++)

lamdba表达式又叫匿名函数。具体语法如下:

lamdba表达式语法:[capture-list](parameters)mutable -> return_type { statrment }捕捉列表      参数列表            返回值类型         函数体实现

 ①捕捉列表(capture-list):该部分出现在lamdba最开始,编译器根据[]来判断接下来的代码是否为lamdba函数,捕捉列表能够捕捉上下文的变量供lamdba函数使用

②参数列表(parameyers):与普通函数的参数列表一致,如果不需要传参数,次()可以省略

③mutable:默认情况下,lamdba函数总是一个const函数,mutable可以取消其常量性。(一般用不到,省略)

④返回值类型(retuen_type):用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。但是在返回值类型明确情况下,仍可省略,编译器会自动堆返回类型进行推导

⑤函数体(statement):与普通函数体一样,可以使用参数列表和捕捉列表的变量

下面是lamdba的简单演示:

void main()
{//最简单的lamdba表达式,无意义[] {};//两个数相加的lambdaauto add1 = [](int a, int b)-> int {return a + b; };//本质来说是一个对象//cout << [](int x, int y)->int {return x + y; }(1, 2) << endl;cout << add1(1, 2) << endl;//使对象能像普通函数一样调用// 省略返回值auto add2 = [](int a, int b){return a + b; };cout << add2(1, 2) << endl;//交换变量的lambdaint x = 0, y = 1;                 auto swap1 = [](int& x1, int& x2) //返回值为void,一般省略,而且参数列表和函数参数一样,交换值要用引用,不然交换的仅仅是形参{int tmp = x1; x1 = x2; x2 = tmp; };swap1(x, y);                              cout << x << ":" << y << endl;//捕捉列表//不传参数来交换x y的lambda -- 捕捉列表//默认捕捉过来的变量不能被修改auto swap2 = [x, y]()mutable//()mutable使捕捉过来的参数可修改{int tmp = x;x = y;y = tmp;};swap2();//此处不传参 -- 但是mutable仅仅让形参修改,不修改实参,下面打印后会发现没有交换cout << x << ":" << y << endl;//要捕捉只能传引用捕捉auto swap3 = [&x, &y]{int tmp = x;x = y;y = tmp;};swap3();//此处不传参cout << x << ":" << y << endl;
}

5.2 lamdba使用场景

假设一个场景:我们自定义一个商品类型,然后这个商品有名字,价格,评价三种属性,然后我们想堆商品进行排序,如下代码:

struct Goods
{string _name;  // 名字double _price; // 价格int _evaluate; // 评价//...Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};//struct ComparePriceLess
struct Compare1
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};//struct ComparePriceGreater
struct Compare2
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};void main24()
{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());*///sort(v.begin(), v.end(), Compare1());//sort(v.begin(), v.end(), Compare2());//sort的时候需要传一个比较的对象过去,所以需要传一个函数指针或者仿函数过去,而这个函数指针或仿函数又需要定义实现在全局// 而且在比较大的项目中,有很多人,你用你的名字,我用我的,最后就导致我看不懂你的,你看不懂我的,容易混乱//所以万一遇到命名或者作用域不一样的问题,就很难解决,所以lamdba就可以很好地解决这个问题//lamdba是一个局部的匿名函数对象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; });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; });
}

5.3 其他捕捉方法

void main()
{int a = 1, b = 2, c = 3, d = 4, e = 5;auto f1 = [=]() {  //[]里面加=,全部传值捕捉,=改成&就是全部引用捕捉cout << a << b << c << d << e << endl; };//但是不打印,因为f1仅仅只是定义,需要调用才会打印f1();//混合捕捉auto f2 = [=, &a]() //表示除a是引用捕捉以外全部传值捕捉{a++;cout << a << b << c << d << e << endl;a--;};f2();static int x = 0;if (a){	//a使用传值捕捉,其他全部用引用捕捉auto f3 = [&, a]() mutable{a++;b++;c++;d++;e++;x++;//可以捕捉位于静态区的变量cout << a << b << c << d << e << endl;};f3();}//捕捉列表本质是在传参,其底层原理和仿函数很相同,类似于范围for和迭代器的关系
}

5.4 函数对象与lamdba表达式

函数对象,又称仿函数,可以像函数一样使用对象,是因为在类中重载了operator()运算符的类对象。现在我们观察下列代码和反汇编指令:

class Rate
{
public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){cout << "调用对象的仿函数" << endl;return money * _rate * year;	}private:double _rate;
};class lambda_xxxx{};
void main() //lambda不能相互赋值
{// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);//调用汇编看,先call构造一个仿函数的对象,再去call调用operator()//编译器在编译时会把lambda表达式搞成一个空类,所以lambda表达式的大小是一个字节cout << sizeof(lambda_xxxx{}) << endl;// 仿函数lambda_uuid -- uuid是随机生成的不重复唯一标识符,当有很多个lanbda时,汇编代码就通过uuid获取不同的lambda (uuid是算法算出来的一种唯一字符串)// lambda -> lambda_uuid -- 所以对我们来说是匿名的,对编译器来说是有名的,也导致lambda不能相互赋值auto r2 = [=](double monty, int year)->double{return monty*rate*year; }; //landba调用时也区call调用一个构造和对象调用是一样的r2(10000, 2);auto r3 = [=](double monty, int year)->double{return monty*rate*year; };r3(10000, 2);
}

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

(另外就是代码注释里提到的lamdba_uuid是随机数,截图中我用的是VS2022的反汇编,结果是lamdba在该函数作用域中的顺序编号,这其实是因为编译器的不同,对lamdba的处理方式不同,下面是VS2013调用反汇编的情况:)

六,function包装器

6.1 是什么?

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

#include<functional>template<class T> functiontemplate <class Ret, class... Args>
classs function<Ret(Args...)>其中Ret表示被调用函数的返回类型,Args...表示被调用函数的形参可变参数包

 如下列代码:

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 Functor3
{double operator()(double d){return d / 3;}
};void main()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor3(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
}

可以看到,useF打印count的地址时打印了三个不同的值,代表着useF函数模板被实例化了三份。所以一旦类型过多,函数指针,函数对象,lamdba表达式,这些都是可调用的类型,类型过多就会导致模板的效率低下,所以我们需要用到包装器,这个东东本质是对可调用对象进行再封装适配

如下代码和现象:

void main()
{//函数指针function<double(double)> f1 = f;cout << useF(f1, 11.11) << endl;// 函数对象function<double(double)> f2 = Functor3();cout << useF(f2, 11.11) << endl;// lamber表达式对象function<double(double)> f3 = [](double d)->double{ return d / 4; };cout << useF(f3, 11.11) << endl;
}

 

用了包装器之后打印的地址就是一样的了 

6.2 包装器使用

int _f(int a, int b)
{cout << "int f(int a, int b)" << endl;return a + b;
}
struct Functor
{
public:int operator()(int a, int b){cout << "int operator()(int a, int b)" << endl;return a + b;}
};void main()
{int(*pf1)(int, int) = _f; //map<string, > 上面两个东东调用起来是一样的都是函数,但是类型完全不同,所以无法用map同时声明两个function<int(int, int)> f1 = _f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b) {cout << "[](int a, int b) { return a + b;}" << endl;return a + b;};cout << f1(1, 2) << endl;cout << f2(10, 20) << endl;cout << f3(100, 200) << endl;cout << "---------------" << endl;map<string, function<int(int, int)>> opFuncMap;opFuncMap["函数指针"] = f1;opFuncMap["函数对象"] = Functor();opFuncMap["lamdba"] = [](int a, int b) {cout << "[](int a, int b) { return a + b;}" << endl;return a + b;};cout << opFuncMap["函数指针"](1, 2) << endl;cout << opFuncMap["函数对象"](1, 2) << endl;cout << opFuncMap["lamdba"](1, 2) << endl;}

class Plus1
{
public:Plus1(int rate = 2):_rate(rate){}static int plusi(int a, int b) { return a + b; }double plusd(double a, double b) { return a + b; }
private:int _rate = 2;
};void main()
{function<int(int, int)> f1 = Plus1::plusi;//包装类内部静态函数function<double(Plus1, double, double)> f2 = &Plus1::plusd;//包装内部非静态函数要加&,而且类内部函数还有个this指针,所以要传三个参数cout << f1(1, 2) << endl;cout << f2(Plus1(), 20, 20) << endl;cout << f2(Plus1(), 1.1, 2.2) << endl;Plus1 pl(3);cout << f2(pl, 20, 20) << endl;//为什么不能下面这样用?我传个指针为啥不行?//function<double(Plus1, double, double)> f2 = &Plus1::plusd;//cout << f2(&Plus1(), 20, 20) << endl;//Plus1 pl(3);//cout << f2(&pl, 20, 20) << endl;//用指针是可以的,但是后面用的时候只能像这样传指针了不能传匿名对象了,左值可以取地址,右值不能取地址//如果传的是对象就用对象去调用,如果传的是指针就用指针去调用//成员函数不能直接调用,需要用对象去调用
}

 

6.3 逆波兰OJ题

//逆波兰表达式
class Solution 
{
public:int evalRPN(vector<string>& tokens){stack<long long> st;  //返回值    参数包map<string, function<long long(long long, long long)>> opFuncMap =   //这里这样写就不用再用which case语句了,简便很多{//列表初始化,pair初始化{"+",[](long long a,long long b) {return a + b; }},{"-",[](long long a,long long b) {return a - b; }},{"*",[](long long a,long long b) {return a * b; }},{"/",[](long long a,long long b) {return a / b; }},};for (auto& str : tokens){if (opFuncMap.count(str)) //题目传给我们的只有数字和运算字符,如果str是字符,那么表示这个字符必定在包装器里面,直接出栈,然后opFuncMap是一个map,通过str找到对应关系,然后通过lambda得出值{long long right = st.top();st.pop();long long left = st.top();st.pop();st.push(opFuncMap[str](left, right)); //把上一个运算符算出的结果再入栈}else//操作数,直接入栈{st.push(stoll(str)); //stoll字符串转数字函数}}return st.top();}
};

6.4 bind

 std::bind定义在头文件中,也是一个函数模板,像一个函数包装器,接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。简单来说我们可以用它把一个原本接收N个参数的函数fn,沟通过绑定一些参数,返回一个接收M个参数的新函数,bind还可以实现参数顺序调整等操作

template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

6.4.1 bind调整参数顺序:

//调整顺序
// _1,_2是占位符,代表绑定函数对象的形参,定义在placeholders中
// _1,_2分别代表第一个第二个形参...
using namespace placeholders;
void Print(int a, int b)
{cout << "void Print(int a, int b)" << endl;cout << a << " ";cout << b << endl;
}
void main()
{Print(10, 20);auto RPrint = bind(Print, _2, _1);// 是类似占位符,_1是第一个参数,_2就是第二个参数,把第二个参数放到第一个位置去。绑定完后bind会返回一个对象//function<void(int, int)> RPrint = bind(Print, _2, _1);RPrint(10, 20);
}

 

6.4.2 bind调整参数个数 

//调整参数个数
class Sub
{
public:Sub(int rate = 0):_rate(rate){}int sub(int a, int b){return a - b;}
private:int _rate;
};void main()
{function<int(Sub, int, int)> fSub = &Sub::sub;fSub(Sub(), 10, 20);//这里要传对象是因为只能通过对象去调用sub//上面两个有点麻烦,因为我们还要传个对象过去,还有两个参数 --> 麻烦//包装的时候,可以绑死某个参数,减少调用的时候传多余的参数,最好和function一起用//function<int(Sub, int, int)> funcSub = &Sub::sub;//这条语句就可以变成下面这个function<int(int, int)> funcSub1 = bind(&Sub::sub, Sub(), _1, _2);cout << funcSub1(10, 20) << endl;//这里的sub我们定义在一个类中,这里调用的时候传参就要传四个,但是我们前面用了绑定了之后,就只需要传对应_1和_2的参数了//上面那个是绑死前面要传的对象参数,如果我们想绑死中间那个参数呢function<int(Sub, int)> funcSub2 = bind(&Sub::sub, _1, 100, _2);cout << funcSub2(10, 20) << endl;map<string, function<int(int, int)>> opFuncMap ={{"-",bind(&Sub::sub,Sub(),_1,_2)}};cout << opFuncMap["-"](1, 2) << endl;cout << endl;}

 

七,补充

7.1 auto和decltype

在C++98中auto只是一个存储类型的说明符,表示该变量是自动存储类型,但是局部域中定义的局部变量默认就是自动存储,所以auto就没什么用了。C++11中放弃了auto以前的用法,将其用于实现自动类型判断,让编译器将定义对象的类型设置为初始化值的类型

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

decltype关键字的作用是将变量的类型声明为表达式指定的类型 

void main2()
{int x = 10;//typeid不能定义对象,它只是拿到字符串的类型,而且拿到这个类型后也不能直接初始化对象,只能去打印//typeid(x).name() y = 20;//decltype可以定义一个和括号内类型一样的,但是和auto不一样哦decltype(x) y1 = 20.22;auto y2 = 20.22;cout << y1 << endl;//打印20cout << y2 << endl;//打印20.22//vector存储类型跟x*y表达式返回值类型一致//decltype推导的表达式类型,然后用这个类型实例化模板参数或者定义对象vector<decltype(x* y1)> n;
}

7.2 两个被批评的容器

C++11新增了一些容器,其中最有用的两个闪光点容器就是unordered_map和unordered_set,这两个容器我们前面已经详细讲过。

但是也有两个容器是被批评的,也就是几乎没啥用的:array和forward_list

void main()
{const size_t N = 100;int a1[N];//C语言数组越界检查的缺点:1,越界读根本检查不出来  2,越界写可能会抽查出来a1[N];//a1[N] = 1;//a1[N + 5] = 1;//C++11新增的array容器,就把上面两个问题全解决了array<int, N> a2;//a2[N];a2[N] = 1;a2[N + 5] = 1;//但实际上,array用得非常少,一方面大家用c数组用习惯了,其次用array不如用vector + resize去替代c数组
}

7.3 emplace系列接口

这个系列接口没啥好说的,直接看下面两段代码

void main()
{std::list<bit::string> mylist;bit::string s1("1111");mylist.push_back(s1);mylist.emplace_back(s1); //都打印深拷贝cout <<"----------"<< endl;bit::string s2("2222");mylist.push_back(move(s2));mylist.emplace_back(move(s2)); //都打印移动拷贝cout <<"----------"<< endl;//上面的没有区别,下面的就有区别了,和上面两个比起来只有一个移动构造mylist.push_back("3333");   // 构造匿名对象 + 移动构造mylist.emplace_back("3333");// 直接构造vector<int> v1;v1.push_back(1);v1.emplace_back(2);//std::vector::emplace_back//template<class... Args>//void emplace_back(Args&&... args);vector<pair<std::string, int>> v2;v2.push_back(make_pair("sort", 1));//pair的一次构造,再对象的拷贝构造+移动构造//std::vector::push_back//void push_back (const value_type& val);//void push_back (value_type&& val);v2.emplace_back(make_pair("sort",1));v2.emplace_back("sort",1);//结论:深拷贝 emplace_back和push_back效率差不多相同
}

class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date(const Date& d)" << endl;}Date& operator=(const Date& d){cout << "Date& operator=(const Date& d))" << endl;return *this;}Date(Date&& d){cout << "Date(Date&& d)";}Date& operator=(Date&& d){cout << "Date& operator=(Date&& d)" << endl;return *this;}private:int _year;int _month;int _day;
};
void main()
{//浅拷贝的类//没区别std::list<Date> list2;Date d1(2023, 5, 28);list2.push_back(d1);list2.emplace_back(d1);cout <<"----------"<< endl;Date d2(2023, 5, 28);list2.push_back(move(d1));list2.emplace_back(move(d2));cout << endl;// 有区别cout << "----------" << endl;list2.push_back(Date(2023, 5, 28));list2.push_back({ 2023, 5, 28 }); //多参数隐式类型转换cout << endl;cout <<"----------"<< endl;list2.emplace_back(Date(2023, 5, 28)); // 构造+移动构造list2.emplace_back(2023, 5, 28);       // 直接构造,直接传参数,通过可变参数包一直往下传
}

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

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

相关文章

基于DCT(离散余弦变换)的图像水印算法,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

牛角工具箱源码 轻松打造个性化在线工具箱,附带系统搭建教程

这是一款在线工具箱程序&#xff0c;您可以通过安装扩展增强她的功能 通过插件模板的功能&#xff0c;您也可以把她当做网页导航来使用~ &#x1f38a; 环境要求 PHP > 7.2.5 MySQL > 5.7 fileinfo扩展 使用Redis缓存需安装Redis扩展 去除禁用函数proc_open、putenv、s…

C语言-写一个简单的Web服务器(四)

经过以上几次的构建&#xff0c;我们基本上已经构建出来了一个简易的Web服务器&#xff0c;接下来将使用查询从文本中查询我们的问题。 查询结果 在这里我设置了一个page全局参数用来记录是哪个页面&#xff0c;避免和登录页面进行冲突重复查询&#xff08;大家可以自行优化&am…

YOLOv9改进策略 :blcok优化 | 极简的神经网络VanillaBlock 实现涨点 |华为诺亚 VanillaNet

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a; VanillaNet&#xff0c;是一种设计优雅的神经网络架构&#xff0c; 通过避免高深度、shortcuts和自注意力等复杂操作&#xff0c;VanillaNet 简洁明了但功能强大。 &#x1f4a1;&#x1f4a1;&#x1f4a1;引…

Fastgpt 无法启动或启动后无法正常使用的讨论(启动失败、用户未注册等问题这里)

FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排&#xff0c;从而实现复杂的问答场景&#xff01; FastGPT是非常实用并且相当厉害的个人知识库AI项目&#xff0c;项目是非常…

Python:文档注释、类型标注和注释宏# type:

目录 1、增加文档注释2、增加类型标注3、增加注释宏 看一段简单的代码 def add(x, y):return x y如下代码调用函数&#xff0c;可以正常执行 print(add(1, 2)) # 3 print(add(1, 2)) # 121、增加文档注释 def add(x, y):"""sum x and y:param x: int:param y…

DVWA-CSRF通关教程-完结

DVWA-CSRF通关教程-完结 文章目录 DVWA-CSRF通关教程-完结Low页面使用源码分析漏洞利用 Medium源码分析漏洞利用 High源码分析漏洞利用 impossible源码分析 Low 页面使用 当前页面上&#xff0c;是一个修改admin密码的页面&#xff0c;只需要输入新密码和重复新密码&#xff…

github项目名称变更sourcetree如何同步

github项目名称变更sourcetree如何同步 方法1:删除本地仓库 重新从URL克隆 方法2:修改远程地址链接 1.打开项目所在文件夹的终端 2.删除本地关联的这个远程仓库origin git remote rm origin 3.关联修改名字后的远程仓库地址 git remote add origin <新的远程仓库地址&…

详解TCP的三次握手和四次挥手

文章目录 1. TCP报文的头部结构2. 三次握手的原理与过程三次握手连接建立过程解析 3. 四次挥手的原理与过程四次挥手连接关闭过程的解析 4. 常见面试题 深入理解TCP连接&#xff1a;三次握手和四次挥手 在网络通信中&#xff0c;TCP&#xff08;传输控制协议&#xff09;扮演着…

Linux-进程控制

&#x1f30e;进程控制【上】 文章目录&#xff1a; 进程控制 为什么要有地址空间和页表 程序的内存       程序申请内存使用问题 写时拷贝与缺页中断 父子进程代码共享       为什么需要写时拷贝       页表的权限位       缺页中断 退出码和错误码…

【ENSP】交换机和交换机之间实现静态路由

1.概念 三层交换机只能在Vlanif逻辑口配置iP地址 2.实现方法 交换机允许对应vlan通行&#xff0c;配置vlanif的ip地址&#xff0c;做静态路由 3.静态路由配置方法 ip route-static 目的网段 子网掩码 下一跳设备 LSW1三层交换机配置 u t m sys vlan batch 10 20 …

HarmonyOS 应用开发之UIAbility组件生命周期

概述 当用户打开、切换和返回到对应应用时&#xff0c;应用中的UIAbility实例会在其生命周期的不同状态之间转换。UIAbility类提供了一系列回调&#xff0c;通过这些回调可以知道当前UIAbility实例的某个状态发生改变&#xff0c;会经过UIAbility实例的创建和销毁&#xff0c;…

LeetCode_1.两数之和

一、题目描述 二、方法 1.方法1&#xff08;暴力枚举法&#xff09; 利用两个for循环&#xff0c;对数组进行逐一的遍历&#xff0c;直到找到两个数的和为目标值时返回这两个数的下标。以下为c实现的完整代码。 # include<iostream> using namespace std; #include<…

【Linux】nmcli命令详解(文末送书)

目录 一、概述 二、常用参数使用 2.1 nmcli networking 1.显示NM是否接管网络 2.查看网络连接状态 3.开/关网络连接 2.2 general ​编辑 1.显示系统网络状态 2.显示主机名 3.更改主机名 2.3 nmcli connection ​编辑1.显示所有网络连接 2.显示某个网卡的详细信息…

Linux文件IO(2):使用标准IO进行文件的打开、关闭、读写、流定位等相关操作

目录 前言 文件的打开和关闭的概念 文件的打开 文件的打开函数 文件打开的模式 文件的关闭 文件的关闭函数 注意事项 字符的输入&#xff08;读单个字符&#xff09; 字符输入的函数 注意事项 字符的输出&#xff08;写单个字符&#xff09; 字符输出的函数 注意…

探索海外应用加速的作用与优势

随着互联网的快速发展&#xff0c;海外应用加速作为一种提高网络连接速度和性能的技术手段越来越受到关注。那么&#xff0c;海外应用加速究竟有什么作用呢&#xff1f;以下是这种加速技术的主要作用&#xff1a; 降低延迟&#xff1a; 海外应用加速在降低数据传输延迟方面发挥…

项目模块—实现抑郁测评(小程序)

script <script setup> import { ref } from "vue";//控制轮播图页码 let current ref(0);//答题逻辑 const add (value) > {if (current.value < 9) {current.value current.value 1;} else {uni.switchTab({url: "/pages/my/my",});} }…

双端队列deque和vector以及list的优缺点比较

参考:https://blog.csdn.net/TWRenHao/article/details/123483085 一、vector vector具体用法详情点这里 优点&#xff1a; 支持随机访问 CPU高速环缓存命中率很高 缺点&#xff1a; 空间不够&#xff0c;便需要增容。而增容代价很大&#xff0c;还存在一定的空间浪费。 头部…

redis在docker安装并启动流程

1、启动server docker run -d -p 6379:6379 --name redis01 redis:7.2.4以上命令&#xff0c;每次启动新的Redis容器&#xff0c;数据会丢失。 我们需要挂载数据文件&#xff0c;在宿主机上面&#xff0c;这样就可以持久化数据. 2、挂载数据文件&#xff08;可根据需求选择…

Git相关命令(一)

一、简介 Git 是一个开源的分布式版本控制系统。 当然&#xff0c; git 不会傻傻的把你的每一个版本完整的存储下来&#xff0c;他仅仅会存储每次修改的位置和内容&#xff08;可持久化&#xff09;&#xff0c;每一次 commit 可以理解为产生一个版本&#xff0c;接下来的版本…