目录
在C++98标准中{ },仅仅应用于数组,struct, class等
- int a = 10;
- int b{10};
- int c = {10};
这三种写法都是可以的,并且STL的所有容器都支持初始化列表的方式进行初始化
vector<int> v = {1, 2, 3, 4, 5, 55, 6, 7, 6, 4};
这样使用容器就会变得更加灵活,而所谓的初始化列表本质其实是在调用构造函数
以Date类举例
-
- 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;
- };
-
- int main()
- {
- Date d1(2022, 11, 20);
- Date d2 = {2022, 11, 20};
-
-
- return 0;
- }
其实所谓的初始化列表是C++11添加的一种容器
我们使用typeid查看它的类型
- auto it = {1, 2, 3, 4, 5, 6, 0, 7, 8, 9};
- cout << typeid(it).name() << endl;
同时STL的每一个容器的构造函数都多了一项 initializer_list
原本只有单参数构造函数支持的隐式类型转换,有了初始化列表,多参数构造函数也支持了
总结:C++11以后,一切对象都可以使用初始化列表,但是建议普通对象还是使用以前的方式初始化,容器有需要可以使用初始化列表
- //常用场景
-
- unordered_map
int> m; -
- unordered_map
int>::iterator it = m.begin(); -
- auto it = m.begin();
我们正常要写迭代器,变量的类型太长,这时可以使用auto,简化代码,让编译器自动推断类型
乍一看decltype与auto十分的相似,但是它们具有本质的区别
auto是自动推导变量,推导之后,不能使用auto这个类型进行创建新的变量
而decltype是获取变量的类型,可以使用decltype创建变量
- int x = 10;
- auto y = 10.1;
-
- decltype(x) z = 11.1;
-
- cout << y << endl;
- cout << z << endl;
auto推导y一定是一个浮点数类型,而z是一个int
总结一句:左值是可以取地址的,左值不一定能够被修改,例如const修饰的变量
右值一般都是:字面量, 表达式返回值, 函数返回值,临时对象也可以被认为是右值
左值是不能直接引用右值的,但是可以在左值引用前面加上const
- template<class T>
- void Func(const T& x)
- {
-
- }
x既可以接收左值也可以接收右值
答案是不能直接引用,但是可以引用move之后的左值
- int a = 10;
- int&& ra = move(a);
move 本意为 "移动",但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。
基于 move() 函数特殊的功能,其常用于实现移动语义。
也就是说右值引用之后,右值就会变成左值,这里与后面完美转发有关
引用主要是用来减少拷贝,提高效率
左值引用常用的场景:
1、做参数:a、减少拷贝,提高效率 b、做输出型参数
2、做返回值:a、减少拷贝,提高效率 b、引用返回,可以修改返回对象
只有左值引用很难处理以下场景
to_string的返回值是一个string对象,要直接返回它代价较高,如果返回引用,就会出现野指针问题,to_string内部一定会定义一个临时string对象,它出了作用域就会销毁
如果要想提高效率,只能使用输出型参数,传一个string&给to_string,但是是不符合使用习惯的。
C++11的右值引用一个重要功能就是要解决上面的问题。
在C++11以后,将右值分为两类1、内置类型右值-纯右值 2、自定义类型右值-将亡值
为了更好的说明这个问题,我使用了之前实现的string
- namespace ww
- {
- 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)
- {
- std::swap(_str, s._str);
- std::swap(_size, s._size);
- std::swap(_capacity, s._capacity);
- }
- // 拷贝构造
- string(const string &s)
- : _str(nullptr)
- {
- cout << "string(const string& s) -- 深拷贝" << endl;
- string tmp(s._str);
- swap(tmp);
- }
- // 赋值重载
- string &operator=(const string &s)
- {
-
- cout << "string& operator=(string s) -- 深拷贝" << endl;
- string tmp(s);
- swap(tmp);
- 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;
- }
- const char *c_str() const
- {
- return _str;
- }
-
- private:
- char *_str;
- size_t _size;
- size_t _capacity;
- };
-
- string to_string(int value)
- {
- bool flag = true;
- if (value < 0)
- {
- flag = false;
- value = 0 - value;
- }
- 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()
- {
- ww::string str = ww::to_string(-2345);
- return 0;
- }
在g++关闭所有优化的条件下,调用to_string会进行两次深拷贝
一次拷贝构造是to_string里面的临时对象拷贝给返回的临时对象,另一次拷贝是临时对象拷贝给在main函数栈帧的str,调用一次to_string就要深拷贝两次,这个代价就十分的巨大了
这时右值拷贝的作用就体现了,不过它不是直接起作用,而是间接起作用,实现方式就是移动构造和移动拷贝
在STL容器中添加移动构造,移动赋值
所谓的移动构造和移动赋值 ,其实是通过右值引用,因为只有将亡值会走移动构造,移动赋值
而将亡值除了作用域很快就会销毁,我们可以使新的类的指针指向将亡值的数据,将亡值的指针指向新构造的类,然后让将亡值的析构函数来处理新构造类的原始数据,这样就延长了资源的生命周期,而没有改变对象的声明周期。这样就极大的提高了效率,如果没有移动构造移动赋值,只能走拷贝构造,赋值重载,进行深拷贝,代价极大,而移动构造和移动赋值,只是更换指针指向方向,消耗几乎可以忽略不记,例如进行红黑树的拷贝也就成了现实,红黑树如果只进行深拷贝代价十分的大,要深拷贝整棵树,而移动构造和移动赋值可以只更改树根节点指针方向
- // 移动构造
- string(string &&s)
- : _str(nullptr), _size(0), _capacity(0)
- {
- cout << "string(string&& s) -- 移动语义" << endl;
- swap(s);
- }
- // 移动赋值
- string &operator=(string &&s)
- {
- cout << "string& operator=(string&& s) -- 移动语义" << endl;
- swap(s);
- return *this;
- }
只有将亡值才会走移动构造移动赋值,不是将亡值的还会走原来的拷贝构造赋值操作符重载
STL容器,插入接口C++11以后都提供了右值版本,插入过程中,如果传递的对象是右值,那么进行资源转移,减少拷贝
- template<class T>
- void Func(T&& x)
- {
-
- }
- 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(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;
- }
- template <typename T>
- void PerfectForward(T &&t)
- {
- Fun(forward
(t));//使用完美转发,保持属性不变 - }
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
- class A final
- {
- private:
- int _a;
- };
-
- class B : public A
- {
- private:
- int _b;
- };
- class A
- {
- public:
- virtual void show()
- {
- cout << _a << endl;
- }
- private:
- int _a = 0;
- };
-
- class B : public A
- {
- public:
- virtual void show() override
- {
- cout << _b << endl;
- }
- private:
- int _b = 0;
- };
- class A
- {
- public:
- A(const A& a)
- {
- _a = a._a;
- }
-
-
- private:
- int _a = 0;
- };
这时我们使用default强制生成默认构造函数
- class A
- {
- public:
- A(const A& a)
- {
- _a = a._a;
- }
- A() = default;
-
- private:
- int _a = 0;
- };
1、如何利用delete,定义一个只能在堆上开空间的类?
很显然,只能在堆上定义的类,一定要使用new,使用new就会调用构造函数,所以我们不能够修改构造函数,那么我们就把析构函数delete就可以了,在栈上的对象,出了作用域要销毁,就要调用析构函数,无法调用析构函数,就会报错,可是删除了析构函数,要怎么回收这个类呢?
我们只要自己手动写一个类似析构函数的成员函数,然后手动调用就好了
-
- class A
- {
- public:
- ~A() = delete;
-
- A()
- {
- _a = new int[10];
- }
-
- void destory()
- {
- delete[] _a;
- operator delete(this);
- }
-
- private:
- int *_a;
- };
-
- int main()
- {
- A a;
- return 0;
- }
先来看一下直接在栈上创建对象
接下来是在堆上创建对象
-
- class A
- {
- public:
- ~A() = delete;
-
- A()
- {
- _a = new int[10];
- }
-
- void destory()
- {
- delete[] _a;
- operator delete(this);
- }
-
- private:
- int *_a;
- };
-
- int main()
- {
- A* a = new A();
- a->destory();
- return 0;
- }
注意:这里destory函数销毁自己使用的是operator delete ,不可以使用delete,因为delete会自动调用析构函数可是我们已经删除了析构函数,所以不可以使用delete,但是我们可以使用operator delete,它类似于free,不会调用析构函数
- // Args是一个模板参数包,args是一个函数形参参数包
- // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
- template <class ...Args>
- void ShowList(Args... args)
- {}
我们可以使用sizeof来获取模板参数的个数
- template<class ...Args>
- void showList(Args...args)
- {
- cout << sizeof...(args) << endl;
- }
-
- int main()
- {
- showList(1,2,3,4,5,5,5,6);
- showList();
- showList(1,2);
- return 0;
- }
- template <class T, class... Args>
- void showList(T value, Args... args)
- {
- cout << value << endl;
- showList(args...);
- }
-
- //递归终止函数
- template <class T>
- void showList(T t)
- {
- cout << t << endl;
- }
-
- int main()
- {
- showList(1, 2, 3, 4, 5, 5, 5, 6);
- // showList();
- showList(1, 2, 3);
- return 0;
- }
- template <class T>
- void PrintArg(T t)
- {
- cout << t << " ";
- }
- //展开函数
- template <class ...Args>
- void ShowList(Args... args)
- {
- int arr[] = { (PrintArg(args), 0)... };
- cout << endl;
- }
- int main()
- {
- ShowList(1);
- ShowList(1, 'A');
- ShowList(1, 'A', std::string("sort"));
- return 0;
- }
在某些场景下emplace的效率要高于insert
对于内置类型没有差别,自定义类型例如像unordered_map这样调用insert要插入键值对,或者其它类型对象的场景,效率更高
拿unordered_map举例,insert要插入一个pair,我们要么创建一个临时对象,或者make_pair,
包装器算是C++11在具体后端开发使用的较多的特性
我们可以将函数通过包装器,包装起来,然后放入到map等容器中,根据实际情况来调用
- std::function在头文件
- // 类模板原型如下
- template <class T> function; // undefined
- template <class Ret, class... Args>
- class function<Ret(Args...)>;
- 模板参数说明:
- Ret: 被调用函数的返回类型
- Args…:被调用函数的形参
- int add(int a, int b)
- {
- return a + b;
- }
-
- struct Func
- {
- int operator()(int a, int b)
- {
- return a + b;
- }
- };
-
- class Plus
- {
- public:
- static int addi(int a, int b)
- {
- return a + b;
- }
-
- double addd(double a, double b)
- {
- return a + b;
- }
- };
这里有三种不同的函数,分别演示如何包装
- function<int(int, int)> f1 = add;//没有什么特殊条件直接绑定
-
- function<int(int, int)> f2 = Func();//仿函数要绑定对象
-
- function<int(int, int)> f3 = Plus::addi;//静态成员函数指定类域
-
- function<double(Plus, double, double)> f4 = &Plus::addd;//非静态成员函数需要取地址,并且传类名
- // 原型如下:
- 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);
- class Solution {
- public:
- int evalRPN(vector
& tokens) { - stack<long long> st;
- unordered_map
long long(long long, long long)>> mp = - {
- {"+", [](long long x, long long y){ return x + y; }},
- {"-", [](long long x, long long y){ return x - y; }},
- {"*", [](long long x, long long y){ return x * y; }},
- {"/", [](long long x, long long y){ return x / y; }}
- };
- for(const auto& e : tokens)
- {
- if(mp.count(e))
- {
- long long right = st.top();
- st.pop();
- long long left = st.top();
- st.pop();
- st.push(mp[e](left, right));
- }
- else
- {
- st.push(stoll(e));
- }
- }
-
- return st.top();
- }
- };
例如:以上就是今天要讲的内容,本文仅仅简单介绍了C++11相关特性