嗨喽大家好呀,今天阿鑫给大家带来的是c++进阶——c++11的内容,好久不见啦,下面让我们进入本节博客的内容吧!
_c++11
- 统一的列表初始化
- 右值引用
- 可变模板参数(了解,不常接触)
- lambda表达式
- function和bind包装器
1. 统一的列表初始化
1.1{}初始化
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自
定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
struct Point
{
int _x;
int _y;
};
int main()
{
int x1 = 1;
int x2{ 2 };
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
Point p{ 1, 2 };
// C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4]{ 0 };
return 0;
}
创建对象时也可以使用列表初始化方式调用构造函数初始化
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, 1, 1); // old style
// C++11支持的列表初始化,这里会调用构造函数初始化
Date d2{ 2022, 1, 2 };
Date d3 = { 2022, 1, 3 };
return 0;
}
1.2 std::initializer_list
std::initializer_list使用场景:
std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加
std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=的参数,这样就可以用大括号赋值。
namespace bit
{
template<class T>
class vector {
public:
typedef T* iterator;
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
typename initializer_list<T>::iterator lit = l.begin();
while (lit != l.end())
{
*vit++ = *lit++;
}
//for (auto e : l)
// *vit++ = e;
}
vector<T>& operator=(initializer_list<T> l) {
vector<T> tmp(l);
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_endofstorage, tmp._endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
struct Goods
{
string _name;
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str = "", double price = 0, int evaluate = 0)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
zj::vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 }};
initializer_list的原理和数组相似,都是在在栈上创建一个数组,然后将值进行拷贝
2.右值引用
匿名对象的生命周期通常局限于它们被创建和使用的上下文。然而,在特定情况下(如作为函数返回值或作为const引用的初始化器),它们的生命周期可能会被延长。但是,开发者应该始终注意避免依赖于这种延长的生命周期,因为这可能会导致难以发现的错误和未定义行为。
左值引用和右值引用的底层
从汇编角度看,左值引用和右值引用没有区别
//移动构造,临时创建的对象,用完就要消亡
string::string(string&& s)
: _str(nullptr)
,_size(0)
,_capacity(0)
{
swap(s);
std::cout << "string(string&& s) -- 移动拷贝" << std::endl;
}
场景1.传值返回
移动构造出现后加上编译器的特殊处理,相当于传值返回没有拷贝了
返回值的类型由中间生成的临时变量决定
当一个匿名对象或临时对象被引用时,会延长它的生命周期
移动赋值用来解决如下情景
string& string::operator=(string&& s)
{
cout << "string::string(string&& s)---移动赋值" << endl;
swap(s);
return *this;
}
场景2.push_back等接口中
注意点1:只有const T& x的话,只会调用深拷贝不会调用移动拷贝,所以需要两套push_back,move可以对const使用,而为什么不能依靠一个const左(const一旦沾上就不能放下),最终构造节点时,不可能将const左值传给右值。
const右值必须用const右值引用来接收
右值用右值引用或者const左值引用
注意点2:右值引用本身是左值
只有右值会调用移动拷贝,右值(语言层面上不支持修改,因为想要将右值的资源进行转移,我们可以调用移动拷贝对右值进行修改),const不支持修改。
被引用或者move后不会影响原来值的性质,右值引用本身和const引用本身都是左值,右值引用和const引用可以支持move。
万能引用—完美转发
1.当你自己没有实现移动构造函数,并且没有实现析构函数,拷贝构造,拷贝赋值构造之中任意一个(“三位一体”),编译器就会自动生成一个默认移动构造函数(对于内置类型值拷贝,对于自定义类型成员有移动构造调用移动构造,否则调用拷贝构造)
2.没有实现析构,拷贝构造需要默认的移动构造和自定义成员调用自定义移动构造的原因:对于对象来说(只要用默认的析构,值拷贝)都不需要显示实现移动构造,但为了避免对象内的自定义类型成员(比如string类型)实现了移动构造,所以需要一个默认的移动构造。
3.如果有析构就不会生成默认移动构造的原因:(1)因为如果有析构函数或者拷贝构造就说明这个自己维护了一块空间,内置类型不能只进行简单的值拷贝,所以编译器不会生成默认的移动构造,编译器认为用户想自己实现移动构造, (2)如果用户自己不写,编译器也不会生成(因为已经违反了默认移动构造的规则),编译器认为用户就是自己单纯不想调用移动构造或者不了解移动构造。
4.简而言之,自己没有资源需要管理,不代表成员变量没有成员需要管理。
3.可变模板参数(了解,不常接触)
节省了很多个模板
参数类型和个数都可变
emplace_back整体而言更高效
可以不用像之前一样传pair对象,将构造pair的参数包往下传,但是只能传一个复合对象(类似pair)
4.lambda表达式
lambda表达式实际是一种匿名函数
此处的&a与之前所认识的&a,int&a的概念有所不同,此处的&a表示的是引用。
捕捉列表的对象时成员变量存在在lambda类对象中,捕捉的本质是构造函数的初始化参数
编译器的底层没有lambda,当我们定义一个lambda表达式时,编译器吧会自动生成一个类,在该类中重载operator()。
5.function和bind包装器
5.1function包装器
function是要接收一个可调用的对象的,将接收的可调用对象作为成员变量,内部重载了operator(),进行调用时就是对接收的可调用对象的回调。
可调用对象包括:函数指针对象,仿函数对象,lambda对象
#include <functional>
#include <iostream>
class Functor {
public:
int operator()(int a, int b) const {
return a + b; // 假设这是Functor的一个简单实现
}
// 可能还需要一个转换到 std::function<int(int, int)> 的构造函数或转换操作符
// 但在这个例子中,我们不需要显式定义它,因为lambda和std::bind等可以隐式转换
};
int main() {
// f2 包装了一个 Functor 类的临时对象(或者更准确地说,是 Functor 的一个实例)
std::function<int(int, int)> f2 = Functor(); // 这里 Functor() 创建了一个临时对象
// 现在 f2 可以像函数一样被调用
std::cout << f2(2, 3) << std::endl; // 输出 5
// 关于 f3,您的原始代码有语法错误,但假设您想要的是这样的:
// 一个接受两个参数的函数,第一个参数是int,第二个参数是另一个函数(即f2的类型)
std::function<int(int, std::function<int(int, int)>)> f3 =
[](int x, std::function<int(int, int)> func) {
return func(x, x + 1); // 假设我们总是对第二个参数使用 x+1
};
// 使用 f3,其中第二个参数是 f2
std::cout << f3(4, f2) << std::endl; // 输出 9,因为 f2(4, 5) = 9
return 0;
}
注意:本质看成对函数的回调
普通成员函数的调用需要对象或者对象的指针。
普通成员函数的函数指针前要加&
对于分别传this对象和this指针的解释是,本质上不是直接将plus和plus*直接传给函数plusd的,而是通过this对象或者this指针去调用封装的函数
5.2 bind包装器
placeholders::_1表示可调用对象的第一个实参,对应sub1的第一个参数
sub1的参数代表实参
注意: _1代表的是第一个实参
_2代表的是第二个实参
bind可以用来解决function包装非静态的成员函数问题,bind本质返回的是一个仿函数对象
绑定函数调用的第一个参数来解决每次调用非静态成员函数都需要传this或者this指针的情况。
5.3function和bind的配合使用
#include<functional>
#include<iostream>
using namespace std;
int main()
{
auto func1 = [](double rate, double money, int year)->double {
double ret = money;
for (int i = 0; i < year; i++)
{
ret += ret * rate;
}
return ret-money;
};
//三年期
function<double(double)> f3_1_5 = bind(func1, 0.015, placeholders::_1, 3);
function<double(double)> f3_2_5 = bind(func1, 0.025, placeholders::_1, 3);
function<double(double)> f5_1_5 = bind(func1, 0.015, placeholders::_1, 5);
function<double(double)> f10_3_5 = bind(func1, 0.035, placeholders::_1, 10);
cout << f3_1_5(1000000) << endl;
cout << f3_2_5(1000000) << endl;
cout << f5_1_5(1000000) << endl;
cout<<f10_3_5(1000000)<<endl;
return 0;
}
function和bind相互配合可以完成类似多态的功能。