C++语法(24) 哈希应用_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/130714567?spm=1001.2014.3001.5501
目录
1.{}初始化
1.应用
1.普通变量
2.数组和指针
3.自定义类型对象
4.stl中的应用
2.std::initializer_list
1.介绍
2.调用
3.解释
2.声明
1.auto
2.decltype
3.左值和右值
1.介绍
2.左值引用与右值引用比较
1.总结
2.作用
3.右值引用的更高阶理解
1.本质
2.万能引用
4.增加的默认成员函数
1.说明
2.强制生成默认函数的关键字default
3.禁止生成默认函数的关键字delete
1.C++98
2.C++11
5.lambda表达式
1.介绍格式
2.捕获元素格式说明
3.本质
6.线程库
7.可变参数模板
1.求参数包中有几个参数
2.递归式打印args
3.展开函数打印
4.应用
8.包装器---类型模板
类型模板
应用
9.bind---函数模板
1.应用
设计的思路大概就是为了使得所有的变量都能兼容花括号
1.普通变量
int x1 = 1; int x2 = { 1 }; int x3{ 1 };
2.数组和指针
int array1[] = { 1, 2, 3, 4, 5 }; int array2[]{ 1, 2, 3, 4, 5 }; int* p3 = new int[10]; int* p4 = new int[10]{1, 2, 3, 4};
3.自定义类型对象
struct Point { int _x; int _y; }; Point* p5 = new Point[2]{{ 1, 1 }, { 2, 2 }};
4.stl中的应用
int main() { vector
v1 = { 1, 2, 3, 4 }; vector v2{ 1, 2, 3, 4,6,7,7 }; list lt = { 1, 2 }; map dict = { { "字符串", "string" }, { "排序", "sort" } }; return 0; } stl中的构造可以自定义数量
2.std::initializer_list
1.介绍
2.调用
auto il = { 1, 2, 3, 4, 5 }; cout << typeid(il).name() << endl;
3.解释
之所以stl中的构造可以自定义数量,得益于initializer_list
其实赋值=左边的花括号是initializer_list类型,其实生成list是list类型中的构造函数被调用,随后生成随意大小的list。其实就是将initializer_list花括号的数一个一个push到list中。
例如:自己写的vector中调整支持initializer_list的构造
vector(initializer_list
il) : _start(nullptr) , _finish(nullptr) , _endofstorge(nullptr) { auto it = il.begin(); while (it != il.end()) { push_back(*it); it++; } } void test2() { vector a{1,2,3,4,5,6,7,8}; for (auto e : a) { cout << e << " "; } }
c++11提供了多种简化声明的方式,尤其是在使用模板时
1.auto
可自动推导变量的类型,以往的案例与模拟均有实现过,不过多讨论
int main() { int i = 10; auto p = &i; auto pf = strcpy; cout << typeid(p).name() << endl; cout << typeid(pf).name() << endl; map
dict = { {"sort", "排序"}, {"insert", "插入"} }; auto it = dict.begin(); return 0; }
2.decltype
关键字decltype将变量的类型声明为表达式指定的类型。推导类型,有些时候可以防止精度丢失。
int main() { const int x = 1; double y = 2.2; decltype(x * y) ret; // ret的类型是double decltype(&x) p; // p的类型是int* cout << typeid(ret).name() << endl; cout << typeid(p).name() << endl; return 0; }
1.介绍
左值:左值是一个表示数据的表达式。变量,指针,引用这些可以获取它的地址+可以对它赋
值左值引用:就是给左值取别名,&
int main() { // 以下的p、b、c、*p都是左值 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; return 0; }
右值:左值是一个表示数据的表达式,不能取地址的值是右值。非左值就是右值,需明确的是左值的位置可以在右边,也就是左值可以赋值给别人,但是右值不能在左边
右值引用:就是给右值取别名,&&
int main() { double x = 1.1, y = 2.2; // 以下几个都是常见的右值 10; x + y; fmin(x, y); // 以下几个都是对右值的右值引用 int&& rr1 = 10; double&& rr2 = x + y; double&& rr3 = fmin(x, y); // 这里编译会报错:error C2106: “=”: 左操作数必须为左值 10 = 1; x + y = 1; fmin(x, y) = 1; return 0; }
2.左值引用与右值引用比较
1.总结
左值引用总结:
1. 左值引用只能引用左值,不能引用右值。
2. 但是const左值引用既可引用左值,也可引用右值int main() { // 左值引用只能引用左值,不能引用右值。 int a = 10; int& ra1 = a; // ra为a的别名 //int& ra2 = 10; // 编译失败,因为10是右值 // const左值引用既可引用左值,也可引用右值。 const int& ra3 = 10; const int& ra4 = a; return 0; }
右值引用总结:
1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以move以后的左值int main() { // 右值引用只能右值,不能引用左值。 int&& r1 = 10; // error C2440: “初始化”: 无法从“int”转换为“int &&” // message : 无法将左值绑定到右值引用 int a = 10; int&& r2 = a; // 右值引用可以引用move以后的左值 int&& r3 = std::move(a); return 0; }
2.作用
左值引用的作用:减少拷贝(函数传参/函数传返回值)
针对函数传参,彻底解决了减少拷贝,因为普通的左值引用和const左值引用引用左值和右值;但是函数传返回值减少拷贝并没有彻底解决。因为返回值可能是临时变量,那么左值引用就不能返回。
函数传参,彻底解决了减少拷贝
//左值引用彻底解决函数传参 template
void func1(const T& x) { } template const T& func2(const T& x) { // ... return x; } // 函数传返回值,并没有彻底解决减少拷贝,以前都是传入输出型参数
//这样写是错误的,因为ret出作用域就失效了。左值引用不可以解决函数传返回值问题 template
T& func3(const T& x) { T ret; // ... return ret; } //以前的解决 template void func3(const T& x,T& ret) { // ... } 移动构造
那么右值引用就可以解决输出型参数的问题
借助移动构造将将亡值或者纯右值减少拷贝
string(string&& s) { cout << "string(const string& s) -- 移动拷贝" << endl; swap(s); } string s1("111111"); string s2 = to_string(-1234);
特别的,其中的s1会变为空字符串,原因是移动构造直接将s1看为右值,将字符串内容全部swap,使得s1的内容变为s2的空内容
string s1("111111"); string s2(move(s1));
移动赋值
// 移动赋值 string& operator=(string&& s) { cout << "string& operator=(string s) -- 移动赋值" << endl; swap(s); return *this; }
右值引用和左值引用的减少拷贝原理不一样:
左值引用:起别名,直接起作用
右值引用:间接起作用,通过移动构造和移动赋值将右值(将亡值或者纯右值)进行资源转移
右值引用的价值
1.就是补齐这个最后一块短板,传值返回的拷贝问题
2.对于插入一些插入右值数据,也可以减少拷贝
此外,stl中也兼容了c++11中的移动构造和移动赋值
3.右值引用的更高阶理解
1.本质
右值本身是不被允许做上面一系列的操作的,但是其实设计的时候就是将右值存入到一个指定的空间进行存储,使得其右值引用拥有了左值的属性来完成上面的移动构造的操作。
那么我们能知道右值引用其实本质是左值,那么它就不能用来当右值使用。
2.万能引用
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
void PerfectForward(T&& 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; } 我们会发现,该模板下,无论左值右值均能生成。下面是分析
1.但是,我们执行该段代码时会发现,所有的函数打印都是左值。这就是因为上面所说。在右值被右值引用后,这个右值引用其实是是左值,因为它可以改变了嘛(设计的时候就是将右值存入到一个指定的空间进行存储)。所以在PerfectForward函数中的t是左值,反应到Fun函数中打印的一律都是左值。
2.这里看不出什么大问题,但是如果T的类型表示的数据结构内存消耗很大,那么我们在mian函数调用右值传到PerfectForward确实起到了节省拷贝的功能,但是到了PerfectForward函数层,t已经不是右值了,那不就跟没有使用过右值一样吗,还是没起到节省拷贝嘛,如果函数的层数很大,那么就更没什么用了。为了解决这个问题,我们需要引入新的功能
3.完美转发
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
void PerfectForward(T&& t) { Fun(std::forward (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; } 执行时,会发现对应的左右值都会调用我们想要调用的函数。
1.说明
1.C++11中又多了两个默认函数,就是上面说过的移动构造函数和移动赋值函数
2.如果没有自己实现移动构造函数,并且都没有实现析构函数 、拷贝构造、拷贝赋值重载。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
3.如果没有自己实现移动赋值重载函数,并且都没有实现析构函数 、拷贝构造、拷贝赋值重载,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
4.如果提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
2.强制生成默认函数的关键字default
由于前面讲了,如果析构函数 、拷贝构造、拷贝赋值重载任意一个写了,那就不会有默认的移动构造函数和赋值重载函数。但是关键字default可以强制生成默认函数。
class Person { public: Person(const char* name = "", int age = 0) :_name(name) , _age(age) {} Person(const Person& p) :_name(p._name) ,_age(p._age) {} Person(Person&& p) = default; private: bit::string _name; int _age; }; int main() { Person s1; Person s2 = s1; Person s3 = std::move(s1); return 0; }
只要向上面的操作一样,针对某个默认函数,我们只需要写出它的申明,在函数的后面加上"=default"就可以生成默认函数了
3.禁止生成默认函数的关键字delete
想要限制某些默认函数的生成,有哪些方法呢?
1.C++981.是在该函数设置成private,并且只需要写出其声明的形式即可,这样只要其他人想要调用就会报错。
2.不过,在class中,存在public的函数会调用到默认构造(比如在函数函数中使用了一个临时的对象)那么此时只能说我们只完成了一半的禁止
2.C++11
只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person { public: Person(const char* name = "", int age = 0) :_name(name) , _age(age) {} Person(const Person& p) = delete; private: bit::string _name; int _age; };
1.介绍格式
lambda表达式书写格式:[捕捉元素] (参数)函数名 ->返回值类型{ 代码段}
[捕捉元素] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用,不可省略。
(传入参数):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
函数名:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
->返回值类型:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{代码段}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,不可省略。
2.捕获元素格式说明[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
3.本质
其实lambda也是一种对象函数,在底层调用的就是仿函数。其类型是一个随机生成的类型。
void print1(int& x) { for (; x < 100; ++x) { cout <<"thread1:" << x << endl; } } void print2(int& y) { for (; y < 100; ++y) { cout <<"thread2:"<< y << endl; } } int main() { int i = 0; thread t1(print1, ref(i)); thread t2(print2, ref(i)); t1.join(); t2.join(); cout << "xxxxxxxxxxxxxxxxxxxxxxxxxxx" << endl; return 0; }
1.Args是一个模板参数包,args是一个函数形参参数包
2.声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数
1.求参数包中有几个参数cout << sizeof...(args) << endl;
template
void ShowList(Args... args) { // 参数包中有几个参数 cout << sizeof...(args) << endl; } int main() { ShowList(1); ShowList(1, 1.1); ShowList(1, 1.1, string("xxxxxx")); ShowList(); return 0; } 2.递归式打印args
void ShowList() { cout << endl; } // args参数包可以接收0-N个参数 template
void ShowList(T val, Args... args) { cout < 通过递归逐层剥离出数据进行打印
3.展开函数打印
template
int PrintArg(T t) { cout << t << " "; return 0; } //展开函数 template void ShowList(Args... args) { int arr[] = { PrintArg(args)... }; cout << endl; } int main() { ShowList(1); ShowList(1, 1.1); ShowList(1, 1.1, string("xxxxxx")); return 0; } 4.应用
stl的容器在C++11中都追加了可变参数模板即emplace_back
包装器是一种可调用对象
类型模板
//类模板原型如下 template
function; // undefined template class function ; 模板参数说明: //Ret: 被调用函数的返回类型 //Args…:被调用函数的形参 函数指针,仿函数,函数对象,lambda的使用会让人觉得凌乱,那么用包装器封装后
#include
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() { // 函数名(函数指针) std::function func1 = f; cout << func1(1, 2) << endl; // 函数对象 std::function func2 = Functor(); cout << func2(1, 2) << endl; // lamber表达式 std::function func3 = [](const int a, const int b) {return a + b; }; cout << func3(1, 2) << endl; // 类的成员函数 std::function func4 = &Plus::plusi; //静态成员函数 cout << func4(1, 2) << endl; std::function func5 = Plus::plusd; //类成员函数 cout << func5(Plus(), 1.1, 2.2) << endl; return 0; } 注意:
1.成员函数的包装需要先找到类域,还需要在前面加上&修饰
2.类型在主观上看都基本一致,不过调用还是原来的函数,包装器是对各种类型调用的统一
3.此时的对象函数似乎没有达到统一,我们需要对函数进行再包装,在原来的基础上套一次lambda即可
//原本 std::function
func5 = Plus::plusd; //类成员函数 cout << func5(Plus(), 1.1, 2.2) << endl; //改版 Plus plus //生成一个类型 std::function func5 = [&plus](double x,double y)->double{return plus,plusd(x,y);}; //类成员函数 cout << func5(1.1, 2.2) << endl; 应用
力扣:150. 逆波兰表达式求值
class Solution { public: int evalRPN(vector
& tokens) { stack st; map > opFunMap= { {"+",[](int x,int y)->int{return x+y;}}, {"-",[](int x,int y)->int{return x-y;}}, {"*",[](int x,int y)->int{return x*y;}}, {"/",[](int x,int y)->int{return x/y;}}, }; for(auto str:tokens) { if(opFunMap.count(str)==1) { int right = st.top(); st.pop(); int left = st.top(); st.pop(); st.push(opFunMap[str](left,right)); } else st.push(stoi(str)); } return st.top(); } }; 如果不使用function,我们只能用条件判断语句一个一个的判断调用。如果遇到的方法有很多,那么这样的行为是不够高效的,固然我们使用function统一调用函数使得简化了代码,而且会很灵活,我们需要增加函数就增加,不会影响到其他的函数
模板:
// 原型如下: template
/* unspecified */ bind (Fn&& fn, Args&&... args); // with return type (2) template /* unspecified */ bind (Fn&& fn, Args&&... args); int Plus(int a, int b) { return a + b; } class Sub { public: int sub(int a, int b) { return a - b; } }; int main() { //表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定 std::function
func1 = std::bind(Plus, placeholders::_1,placeholders::_2); //auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2); //func2的类型为 function 与func1类型一样 //表示绑定函数 plus 的第一,二为: 1, 2 auto func2 = std::bind(Plus, 1, 2); cout << func1(1, 2) << endl; cout << func2() << endl; Sub s; // 绑定成员函数 std::function func3 = std::bind(&Sub::sub, s,placeholders::_1, placeholders::_2); // 参数调换顺序 std::function func4 = std::bind(&Sub::sub, s,placeholders::_2, placeholders::_1); cout << func3(1, 2) << endl; cout << func4(1, 2) << endl; return 0; } 多个参数,我们可以通过bind修改参数的顺序,如func3和func4的使用