赞
踩
C++98中允许花括号对数组和结构体进行初始化:
struct A {
int _a;
int _b;
};
int main()
{
int arr[] = { 1,2,3,4 };
A add = { 1,2 };
return 0;
}
1.支持内置类型和用户自定义类型使用初始化列表。
2.自定义类型的列表初始化是依赖构造函数初始化。
3.总结:C++11 一切皆可列表初始化!
class Date { public: Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) {} Date(Date& t) { _year = t._year; _month = t._month; _day = t._day; } private: int _year; int _month; int _day; }; int main() { //1.内置类型的列表初始化: int a = { 1 }; int b{1}; double c = { 1.1 }; double d{1.1}; //2.自定义类型的页表初始化: vector<int> arr_1 = { 1,2,3,4,5,6 }; vector<int> arr_2{ 1,2,3,4,5,6 }; //C++11的列表初始化适合于new表达式! int* n = new int[5] {1, 2, 3, 4, 5}; //自定义类型的{}初始化 Date d1 = { 2023,10,21 }; Date* d2 = new Date(2023, 10, 21); return 0; }
1.initializer_list类型在C++11中通过列表初始化!
2.列表中的数据类型确定initializer_list的具体存贮数据类型。
int main()
{
//1.创造对象+map插入数据
pair<int, string> s_1(1, "one");
pair<int, string> s_2(2, "two");
pair<int, string> s_3(3, "three");
map<int, string> s1 = { s_1,s_2,s_3 };
//2.列表初始化:
map<int, string> s2 = { {1,"one"},{2, "two"},{3, "three"}};
return 0;
}
1.首先map中是一个pair类型的数据
2.给map进行列表初始化,通过pair类型的数据进行列表初始化。
3.pair支持列表初始化,给pair进行列表初始化,给map进行列表初始化。
1.vector的构造函数C++11支持列表初始化的原理?
2.initializer_list支持模板的一个类,根据列表中的的内容确定这个对象的具体的类型。
3.用initializer_list对象去重载构造函数。
4.本质用initializer_list对象作为构造函数参数传递数据的一个过程。
想要去支持列表初始化?
1.实现列表初始化的构造函数。
2.可以实现一个mystack/myqueue继承stack/queue
3.给自己的类新增列表初始化的构造函数。
#include <iostream> #include <stack> #include <vector> #include <initializer_list> template <typename T> class MyStack : public std::stack<T, std::vector<T>> { public: MyStack(std::initializer_list<T> initList) { for (const auto& elem : initList) { this->push(elem); } } }; int main() { // 使用自定义构造函数和初始化器列表初始化MyStack MyStack<int> myStack = {1, 2, 3, 4, 5}; // 输出stack中的元素,应该是5 4 3 2 1(因为stack是LIFO) while (!myStack.empty()) { std::cout << myStack.top() << ' '; myStack.pop(); } std::cout << std::endl; return 0; }
1.变量类型的自动推导,解决类型不好写的一个情况。
2.迭代器,lambda表达式,map<> , set<>类型可以auto自动推导。
int main()
{
map<int, string> s1 = { {1,"one"},{2,"two"},{3,"three"}};
//1.自己去写类型:
map<int, string>::iterator it_1 = s1.begin();
//2.自动类型推导:
auto it_2 = s1.begin();
return 0;
}
1.auto组为返回值类型是一个非常不好的语法。
2.函数只有一个还可以去找函数看类型。
3.如果使用多个函数并且使用auto返回那么我们可能找不到具体的类型。
4.可以使用typeid(变量名).name()打印变量类型吗?
5.自定义类型的打印返回是非常复杂的。
1.declitype(表达式) 将变量的类型声明为表达式类型。
2.auto返回的一个函数获取类型auto识别。
3.声明一个对应类型的对象就可以使用decltype
auto add(int a, int b)
{
int sum = a+b;
return sum;
}
int main()
{
auto n_1 = add(10,20);
//decltype(变量名称/表达式)作为类型去使用!
decltype(n_1) n_2;
n_2 = n_1;
return 0;
}
int main() { //1.底层const:修饰对象本身 int n = 10; const int* p = &n; decltype(p) p2 = p; *p2 = 10; //2.顶层const:修饰指向内容 int m = 10; int* const p1 = &m; decltype(p1) p3 = p1; *p3 = 10; return 0; }
1.在C++中NULL被定义为字面量0,这样就可能会带来一些问题,因为0既能表示指针常量,又能表示整形常量。所以考虑到安全性,C++11中新增加了nullptr,用来表示空指针。
2.使用了条件编译,既可以表示整形,又可以用来表示指针类型。
1.支持迭代器就支持范围for
2.范围for依赖了auto自动类型识别。
3.引用可以修改容器中的内容。
1.C语言中使用数组如果不在外面计算出数组的长度那么让数组传参数。
2.在函数中就等于丢失了数组的长度。
3.C++11的array类似于数组但是没有这样的问题。
1.array的出现主要解决了函数传参过程中类型的降低。
2.sizeof(数组名)代表这个数组的大小,一传参数就退化为指针了。
3.array解决了这个问题!
1.forward_list支持头插可以支持尾插但是需要去找尾。
2.stl中就没有去实现单链表的尾插。
3.forwardlist有一些自己的使用场景。
unordered_set和unordered_map底层实现
1.引用就是取别名!
2.语法上左值引用和右值引用都是去取别名。
3.本质都是指针:引用是通过指针实现的,左值引用的是当前左值的地址,右值引用是存贮右值拷贝到栈上的一个临时空间的地址。
1.左值是一个表示数据的表达式:变量名 + 解引用指针 + 函数引用返回值。
2.左值可以出现在赋值符号的左边,不可以出现在赋值符号的右边。
3.左值的地址是可以被取地址的,右值是不可以被取地址的。
4.左值引用就是对左值的引用,给左值取取别名。
5.左值在一般情况下可以对它去赋值。
1.左值引用解决了那些问题呢?
2.解决了传参拷贝的问题不需要多去拷贝数据。
3.解决部分返回对象的拷贝问题,(出函数作用域,返回对象还在的,可以使用左值引用返回)。
C++98中的左值引用没有解决什么问题呢?
1.如果返回的对象是一个局部变量,出了函数的作用域生命周期就到了,只能进行传值返回,就存在拷贝,一些对象拷贝的消耗非常大怎么办?
2.比如返回一个二维数组,存在拷贝问题怎么办?
1.C++11中新增了右值引用,解决上面产生的问题。
2.右值和左值一样也是一个表示数据的表达式,(字面量,表达式,函数返回值)。
3.右值可以出现在赋值符号的右边,不可以出现在赋值符号的左边。
4.右值是不可以被取地址的。
5.右值引用就是对右值的引用对右值取别名。
C++11对右值的解释:
1.纯右值(内置类型的右值)比如:20 30
2.将亡值(自定义类型的右值)比如:匿名对象,传值返回的函数。
1.C++98提供的优化,直接使用函数中的s1去拷贝构造main中的s1.
2.C++11中新增了移动构造和移动赋值!
void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } //1-2:移动构造 string(string&& str) { cout << "string(string&& str) 移动构造" << endl; swap(str); } //1-3:移动赋值: string& operator=(string&& str) { cout << "string& operator=(string&& str) 移动赋值" << endl; swap(str); return *this; }
sfpy::string fun(const char* str)
{
sfpy::string s1(str);
cout << "******************" << endl;
return s1;
}
int main()
{
sfpy::string s1 = fun("12345");
cout << "1234" << endl;
return 0;
}
//移动构造+移动赋值: list(list<T>&& tmp) { swap(tmp); } list& operator=(list<T>&& tmp) { swap(tmp); return *this; } sfpy::list<int> fun() { sfpy::list<int> l1 = { 1,2,3,4,5 }; cout << &l1 << endl; return l1; } int main() { sfpy::list<int> L1 = fun(); cout << &L1 << endl; return 0; }
移动构造+移动赋值:
拷贝构造+拷贝赋值
1.我们在对右值进行右值引用然后为了右值引用后的对象可以被处理–>右值被右值引用后属性是一个左值!
2.产生了一个问题:连续使用右值引用会导致属性发生变化,导致右指属性丢失?
std::forward完美转发在传参的过程中保留对象原有的属性!
1.左值引用不可以给右值取别名,但是const左值引用可以。
2.右值引用不可以给左值取别名,但是可以给move(左值)可以。
// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。 // 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力, // 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值, // 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用完美转发 template<typename T> void PerfectForward(T&& t) { //使用完美转发保持原有的类型! Fun(std::forward<T>(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; }
默认成员函数:构造+析构 拷贝构造+拷贝赋值构造 取地址重载+const取地址重载 -------> 移动构造+移动赋值 , 一共有8个默认成员函数!
1.如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用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: sfpy::string _name; int _age; }; int main() { Person s1; Person s2 = s1; Person s3 = std::move(s1); return 0; }
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person { public: Person(const char* name = "", int age = 0) :_name(name) , _age(age) {} Person(const Person& p) = delete; private: sfpy::string _name; int _age; }; int main() { Person s1; Person s2 = s1; Person s3 = std::move(s1); return 0; }
一个类我不想让他去拷贝,C++98可以private声明。
C++11中可以提供delete关键字阻止默认函数的生成。
1.我们最早接触到的使用参数链表的形式是printf的格式化打印。
2.本质上是使用数组去保存参数列表中的数据。
3.调用数组中的数据进行格式化的打印。
验证是否可以使用像printf数组的方式去获取数据!
1.使用递归的方式去展开参数包。
2.函数模板自动对类型进行匹配。
3.加一个模板参数T的方式可以提取参数包中的每一个数据。
4.重载一个递归结束的版本,方便递归结束!
template<class T> void showlist(const T& value) { cout << value << ' '; } template<class T, class... Args> void showlist(const T& value , Args... args) { cout << value << ' '; showlist(args...); } int main() { showlist(1,2,3); return 0; }
1.观察emplace系列有两个地方值到注意。
2.模板的可变参数包。
3.支持万能引用。
//模拟实现list的emplace_back
template<class ...Args> void emplace_back(Args&&... arg) { emplace(end(), std::forward<Args>(arg)...); } template<class ...Args> iterator emplace(iterator pos,Args&&... args) { Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode = new Node(std::forward<Args>(args)...); // prev newnode cur prev->_next = newnode; newnode->_prev = prev; newnode->_next = cur; cur->_prev = newnode; //return iterator(newnode); return newnode; } template<class... Args> ListNode(Args&&... args) :_next(nullptr) , _prev(nullptr) , _data(std::forward<Args>(args)...) {}
class Date { public: Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) {} Date(Date& t) { _year = t._year; _month = t._month; _day = t._day; } template<class... Args> Date(Args&&... args) {} private: int _year; int _month; int _day; }; int main() { sfpy::list<Date> l1; l1.emplace_back(2023, 10, 21); return 0; }
1.emplace系列:
2深拷贝的类对象,减少一次移动构造。
3.浅拷贝的类对象,减少一次拷贝构造。
3.对于深拷贝的类对象其实减少一次移动构造优化不是特别明显!
过程总结:
1.emplace_back去传参数包–>参数是万能引用的模板参数包。
2.emplace_back去调用emplace()使用完美转发这个模板参数包。
3.emplace进行节点的创建去调用节点的构造函数,使用完美转发这个模板参数包。
4.内置类型直接构造,自定义类型调用它的构造。
5.注意:需要声明一个模板参数包的一个构造要不然会导致类型不匹配的问题!
//1.函数指针:c语言常用 int cmp_1(const void* p1, const void* p2) { return *((int*)p1) - *((int*)p2); } int main() { //1.函数指针+排序: int arr[] = { 5,6,2,3,7,9,2,1,0 }; int n = sizeof(arr) / sizeof(arr[0]); qsort(arr, n ,sizeof(arr[0]), cmp_1); for (auto& e : arr) { cout << e << " "; } cout << endl; return 0; }
//2.仿函数:C++ struct comper { bool operator()(int& t1, int& t2) { return t1 > t2; } }; int main() { //2.仿函数排序+lambda表达式: vector<int> arr_2 = { 5,6,2,3,7,9,2,1,0 }; sort(arr_2.begin(),arr_2.end(), comper()); for (auto& e : arr_2) { cout << e << " "; } cout << endl; return 0; }
int main()
{
//3.lambda表达式:
vector<int> arr_3 = { 5,6,2,3,7,9,2,1,0 };
sort(arr_3.begin(), arr_3.end(), [](int t1, int t2) {return t1 > t2; });
for (auto& e : arr_3)
{
cout << e << " ";
}
cout << endl;
return 0;
}
书写格式:
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
1 lambda表达式各部分说明
[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变lambda函数使用。
2.参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
3.returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
//排序使用:
struct Goods { string _name;// 名字 double _price;// 价格 int _evaluate;// 评价 Goods(const char* str, double price, int evaluate) :_name(str) , _price(price) , _evaluate(evaluate) {} }; int main() { vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } }; //1.使用lambda表达式显而易见如何进行比较! sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) { return g1._price > g2._price; }); }
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
//捕捉数据默认是const的所以不可以进行修改因为const常变量。
//在()后加mutable关键字可以取消常量性:
//1.a 和 b 的值捕捉。
//2.a 和 b 的引用捕捉:
//3.除了a是引用捕捉,其他的变量都是值拷贝捕捉。
//4.除了a是值捕捉其他变量都是引用捕捉。
注意:
a. 父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
使用declitype进行类型推断?
struct Goods { string _name;// 名字 double _price;// 价格 int _evaluate;// 评价 Goods(const char* str, double price, int evaluate) :_name(str) , _price(price) , _evaluate(evaluate) {} friend ostream& operator<<(ostream& cout, Goods& Date); }; ostream& operator<<(ostream& cout, const Goods& Date) { cout << "名字:" << Date._name << " - "; cout << "价格:" << Date._price << " - "; cout << "评价:" << Date._evaluate << endl; return cout; } int main() { vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } }; //1.lambda表达式: auto com = [](const Goods& g1, const Goods& g2) {return g1._price > g2._price;}; //2.priority_queue比较特性:decltype(com)推导对象类型非常有意义! priority_queue<Goods,vector<Goods>,decltype(com)> p1(v.begin(),v.end(),com); //3.结果的打印: while (!p1.empty()) { std::cout << p1.top() << endl; p1.pop(); } }
总结:
1.可以在sort比较函数中去直接使用lambda表达式的对象。
2.在priority_queue这样的类型中考虑使用decltype进行类型推导。
3.并且lambda没有默认的构造函数所以只有唯一的对象可以重复使用。
4.lambda表达式优化了仿函数的操作,本质还是仿函数。
1.lambda表达式可以认为一个对象这个对象的类型是编译器自动生成。
2.底层是这个类型的类的operator()的重载在使用的时候。
3.底层通过uuid算法==(vs2022使用了不同的命名风格)==去对类型进行命名。
int main()
{
auto fun1 = []() {cout << "hello word" << endl; };
auto fun2 = []() {cout << "hello word" << endl; };
fun1();
fun2();
return 0;
}
1.function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
2.为什么需要包装器?保证模板的高效性。
3.不同的类型:函数指针,仿函数,lambda都是一个类型这些类型都可以通过模板的方式进行调用,但是通过模板调用就需要不同的模板参数。
4.可以通过包装器把不同的类型进行保证可以更好的适配模板。
//不去使用包装器:
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; }
//观察结果表示实例化了三份代码!
//使用包装器:
1.Ret被调用的返回值类型。
2.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 Functor { double operator()(double d) { return d / 3; } }; int main() { // 函数名 function<double(double)> f1 = f; std::cout << useF(f1, 11.11) << endl; // 函数对象 function<double(double)> f2 = Functor(); std::cout << useF(f2, 11.11) << endl; // lamber表达式 function<double(double)> f3 = [](double d)->double { return d / 4;}; std::cout << useF(f3, 11.11) << endl; return 0; }
class Solution { public: int evalRPN(vector<string>& tokens) { map<string,function<int(int,int)>> op = { {"+",[](int i , int j){return i+j;}}, {"-",[](int i , int j){return i-j;}}, {"*",[](int i , int j){return i*j;}}, {"/",[](int i , int j){return i/j;}}, }; stack<int> s1; for(auto& tmp:tokens) { //1.字符栈: if(op.find(tmp) != op.end()) { int right = s1.top(); s1.pop(); int left = s1.top(); s1.pop(); s1.push(op[tmp](left,right)); } //2.数值栈: else { s1.push(stoi(tmp)); } } return s1.top(); } };
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
调用bind的一般形式:auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
void callable(int a, int b) { cout << a - b << endl; } int main() { //1.需要一个参数顺序:在placeholders命名空间中: auto fun1 = bind(callable, placeholders::_1, placeholders::_2); auto fun2 = bind(callable, placeholders::_2, placeholders::_1); fun1(10,20); fun2(10,20); return 0; }
void callable(int a, int b , int c)
{
cout << a + b + c << endl;
}
int main()
{
//1.需要一个参数顺序:在placeholders命名空间中:
//2.存在一个默认绑定的概念,可以控制传入函数参数的个数:
auto fun2 = std::bind(callable, 20,placeholders::_1, placeholders::_2);
fun2(30,20);
return 0;
}
1.fun()函数传多少参数就在bind的时候使用,几个placeholders::_1 。
2.注意顺序匹配的问题!
3.记住:placeholders::_1 placeholders::_2 placeholders::_3 应该对应的是原来函数的参数顺序。
4.我们正常传参对应传递应该是,bind里面的placeholders::_3的顺序!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。