目录
1.列表初始化
2.声明
3.右值引用和移动语句
4. c++11新的类功能
5. 可变参数模板
6.lambda表达式
7.包装器
8. 后言
1. 列表初始化
1.1 {}的初始化
(1) c++98标准规定可以使用{}对数组以及结构体进行统一的列表初始化.
struct Point
{
int _x;
int _y;
};
int main()
{
int a1[] = { 1, 2, 3, 4, 5 };
int a2[5] = { 0 };
Point p = { 1, 2 };
return 0;
}
(2) c++11里面增加了对{}的使用范围, 可以对所以的内置类型和自定义类型的类型使用初始化列表, 可以加=;也可以不加.
struct Point
{
int _x;
int _y;
};
int main()
{
int x1 = 1;
int x2{ 2 };
int a1[]{ 1, 2, 3, 4, 5 };
int a2[5]{ 0 };
Point p{ 1, 2 };
return 0;
}
(3) 创建对象也可以使用列表初始化的方式调用构造函数初始化.
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);
Date d2{ 2022, 1, 2 };
Date d3 = { 2022, 1, 2 };
return 0;
}
1.2 initializer_list
(1)initializer_list的类型:
(2)initializer_list的使用场景:
一般作为构造函数的参数; 方便初始化对象, 也可以是operator=的参数, 那么就可以用{}进行赋值.
c++11里面对STL的构造函数都添加了initializer_list; 就是允许{}进行对象的初始化的一种方法.支持范围for.
//int main()
//{
// auto il = { 10, 20 ,30 };
// cout << typeid(il).name() << endl;
// return 0;
//}
//int main()
//{
// vector<int> v{ 1, 2, 3, 4 };
// list<int> lt{ 1, 2 };
// map<string, string> sict = { {"sort", "排序"}, {"inset", "插入"} };
//
// v = { 1, 2, 3 };
// return 0;
//}
int main()
{
//多参数构造类型转换: 构造+拷贝构造-> 直接构造.
Date d2 = { 2023, 11, 25 };
auto il1 = { 10, 20, 30, 40, 50 };
initializer_list<int> il2 = { 11, 22, 33 };
initializer_list<int>::iterator it2 = il2.begin();
//while (it2 != il2.end())
//{
// cout << *it2 << endl;
// it2++;
//}
//cout << endl;
for (auto e : il2)
{
cout << e << " ";
}
cout << endl;
return 0;
}
2. 声明
2.1 auto
c++98里面定义auto是一种类型局部自动存储类型, but在局部域中定义的变量就是默认是局部自动存储类型, 然而在c++11里面就是自动推断类型, 必须进行显示初始化, 以便于编译器将类型设置为初始化的类型.
2.2 decltype
思考一个小问题: 如果我要使用一个我不知道的类型, 怎么做?
使用auto吗? 那肯定是不行的, auto在初始化的时候必须给定类型才可以让编译器去推断.那么就要用到decltype. 所以decltype的作用就是将变量的类型声明为表达式指定的类型.
int main()
{
int i = 1;
double d = 2.2;
/*cout << typeid(i).name() << endl;
cout << typeid(d).name() << endl;*/
auto j = i;
auto ret = i * d;
decltype(ret) x;
vector<decltype(ret)> v;
v.push_back(1);
v.push_back(1.1);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
return 0;
}
2.3 nullptr
c++里面定义NULL为字面常量0; 但是这个字面常量0既可以表示指针常量, 还可以表示整形常量.所以c++11里面添加nullptr表示为空指针.
2.4 STL的一些变化
(1) array:
这个接口其实是对应vector, 其实也没啥用处.
(2) forward_list:
用来任意位置进行删除和插入操作.
(3) unordered_map
(4) unordered_set
上面两个序列式容器之前在哈希和红黑树里面都有出现, 这里不过多介绍了.
3. 右值引用和移动语句
3.1 左值和右值
左值:
一般是变量值或者是解引用的指针; 一般是在赋值符号的左边, 但是右值一定不会出现在赋值符号的左边. 可以获取到地址并且可以对它进行赋值, 如果是const就不行咧.
左值引用:
对左值进行引用, 给左值取别名.
int main()
{
//左值
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;
int&& rr2 = x + y;
int&& rr3 = fmin(x, y);
return 0;
}
3.2左值引用和右值引用的区别
左值引用:
(1) 左值引用只能用于引用左值, 不能用来引用右值;
(2) but const修饰的左值引用可以引用左值和右值.
右值引用:
(1) 右值引用只能引用右值, 不能引用左值;
(2)but move修饰过的左值可以被右值引用使用.
int main()
{
//左值引用
int a = 10;
int& ra1 = a;
const int& ra2 = 10;
const int& ra2 = a;
//右值引用
int&& aa1 = 10;
int a = 10;
int&& aa2 = move(a);
return 0;
}
3.3 右值引用的使用场景
(1) 先看看左值引用的应用场景, 那就是作为参数或者返回值, 但是如果出了作用域还在那么就可以使用左值引用. 因为左值引用是局部变量, 出了作用域之后不在那怎么把数据传回来捏?
(2) 右值引用和移动语义的本质: 就是将参数的右值资源直接夺取过来, 然后就不用进行深拷贝了, 这就是移动构造. 进一步提高效率.
(3) 编译器在调用构造的时候, 有移动构造就调用移动构造, 没有就调用拷贝构造.
(4) 移动赋值: 重载operator=的时候使用右值引用.
3.4 右值引用左值的场景分析
右值引用左值不能直接引用, 需要加move进行将左值转化为右值才可以, 但是左值的本质属性还是左值, 并没有改变. 如果是作为拷贝构造函数的参数, 那么就一定要注意, 如果拷贝赋值move之后, 就会将原来的数据交给新的对象, 原来的对象就被置空了.
注意:右值被右值引用之后属性变成了左值.
void push_back (value_type&& val);
int main()
{
list<bit::string> lt;
bit::string s1("1111");
// 这里调用的是拷贝构造
lt.push_back(s1);
// 下面调用都是移动构造
lt.push_back("2222");
lt.push_back(std::move(s1));
return 0;
}
3.5 完美转发
使用到模板以及右值引用. 模板里面的&&是表示万能引用, 既可以接受左值, 又可以接收右值,
可以看看下面的代码:
为啥都变成左值啦? 因为右值被右值引用之后属性变成了左值.
如果我们就是想要右值引用怎么办捏?
上完美转发, 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<typename T>
void perfectForward(T&& t)
{
//Fun(t);
//完美转发
Fun(forward<T>(t));
}
int main()
{
//右值
perfectForward(10);
//左值
int a;
perfectForward(a);
perfectForward(move(a));
//左值
const int b = 9;
perfectForward(b);
perfectForward(move(b));
return 0;
}
3.5.1 完美转发使用场景
template<class T>
struct ListNode
{
ListNode* _next = nullptr;
ListNode* _prev = nullptr;
T _data;
};
template<class T>
class List
{
typedef ListNode<T> Node;
public:
List()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
void PushBack(T&& x)
{
//Insert(_head, x);
Insert(_head, std::forward<T>(x));
}
void PushFront(T&& x)
{
//Insert(_head->_next, x);
Insert(_head->_next, std::forward<T>(x));
}
void Insert(Node* pos, T&& x)
{
Node* prev = pos->_prev;
Node* newnode = new Node;
newnode->_data = std::forward<T>(x); // 关键位置
// prev newnode pos
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
void Insert(Node* pos, const T& x)
{
Node* prev = pos->_prev;
Node* newnode = new Node;
newnode->_data = x; // 关键位置
// prev newnode pos
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
private:
Node* _head;
};
int main()
{
List<string> lt;
lt.PushBack("1111");
lt.PushFront("2222");
return 0;
}
4. c++11新的类功能
c++里面6个默认构造的类分别是:
1. 构造函数; 2. 析构函数; 3. 拷贝构造函数; 4. 拷贝赋值重载; 5. 取地址重载; 6. const 取地址重载.
现在又多了移动构造和移动赋值,:
注意:
(1) 如果你没有写移动构造函数, 并且没有写析构函数, 拷贝构造函数, 拷贝赋值重载函数的话, 那么就编译器自动生成移动构造函数.
(2)如果你没有写赋值重载函数, 并且没有写析构函数, 拷贝构造函数, 拷贝赋值重载函数那么就编译器自动生成移动赋值函数.
(3)对于内置类型进行浅拷贝, 自定义类型如果实现了移动构造就使用移动构造, 如果没有使用的话就进行深拷贝.
2.强制生成默认函数的关键字: default 进行显示的移动构造生成.
3.强制不生成默认函数的关键字: delete
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;
//强制不生成默认函数
Person(Person&& p) = delete;
private:
bit::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
5. 可变参数模板
// Args 是一个 模板参数包 , args 是一个 函数形参参数包// 声明一个参数包 Args...args ,这个参数包中可以包含 0 到任意个模板参数。template < class ... Args >void ShowList ( Args ... args ){}注意: 使用参数包是ex到家的, 还有获取参数包得值! 学的想吐血!!!获取参数值是是用到递归调用的方法.
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;
}
5.1 比较emplace和insert
emplate的接口是支持万能模板以及可变参数包的. 那和有啥区别捏? 而且emplace接口的优势在哪里捏?
支持了可变参数包, 以及emplace_back是直接构造, push_back是先构造再移动构造.
6.lambda表达式
引入: lambda底层就是仿函数, 由于当我们定义自定义类型的比较的时候, 都需要写一个类来规定比较的方法(仿函数), 使用起来非常不方便.定义就弄出来lambda表达式.
6.1lambda表达式的语法:
(1) 格式:[capture-list] (parameters) mutable -> return-type { statement }
capture-list: 捕捉列表, 捕捉上下文的变量
parameters: 参数列表; 和普通函数是一样的.
mutable: 看代码吧, 更加形象.
->returntype: 返回值类型; 和普通函数是一样的.statement:函数体 和普通函数是一样的.(2) lambda的类型是 class <lambda_a62159c664704dbd449a2ea762c73c4d>lambda + uuid;(3) lambda表达式是一个匿名函数, 如果要使用据需要auto去拿.
int main()
{
//啥都不做
[] {};
//捕捉=上下的变量,返回值类型推到为int.
int a = 3, b = 4;
[=] {return a + 3; };
//没有返回值, 捕捉&的上下文变量, 然后用到函数体里面
/*auto fun1 = [&](int c) {b = a + c; };
fun1(10);
cout << a << " " << b << endl;*/
/*auto fun2 = [=, &b](int c)->int{return b += a + c; };
cout << fun2(10) << endl;*/
int x = 10;
auto add_x = [x](int a)mutable {x *= 2; return a + x; };
cout << typeid(add_x).name() << endl;
cout << add_x(10) << endl;
}
6.2 捕捉列表
捕捉方式是传值还是传递引用都有区别:
(1) [var]: 传值捕捉
(2) [=]: 传值捕捉, 捕获子作用域里面所有变量(包括this);
(3) [&var]:表示引用传递捕捉变量var
(4) [&]:表示引用传递捕捉所有父作用域中的变量(包括this)(5) [this]:表示值传递方式捕捉当前的this指针注意:(1)捕捉列表有多个参数, 就要用 ' ,' 隔开.(2)不允许重复传递, 编译器会报错;(3)lambda表达式之间不能相互赋值;
6.3 仿函数和lambda表达式:
其实底层来看这两个东西是一样的; 仿函数只是在类里面重载operator()的对象.
7. 包装器
7.1 为啥需要包装器, 这是个啥?
(1) function是包装器也是适配器, 本质就是一个类模板.
ret = func(x);
上面这段代码可以是函数返回值, 仿函数, lambda表达式, 那你找到它到底是上面吗?
根本不知道, 而且编译器还会实例化上面出现的所有对象, 那么编译器必定效率低下.
(2) function的头文件#include<functional>
7.2 bind
bind就是一种适配器, 支持可调用对象(函数, 仿函数, lambda表达式), 生成一个新的可调用对象来适应原来的参数列表, 其中的 _n 是一种占位符, 表示可调用对象的位置.
#include <functional>
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 的第一,二个参数指定
function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
auto func2 = std::bind(Plus, 1, 2);
cout << func1(1, 2) << endl;
cout << func2() << endl;
Sub s;
绑定成员函数
function<int(int, int)> func3 = std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2);
参数调换顺序
//std::function<int(int, int)> func4 = std::bind(&Sub::sub, s,
// placeholders::_2, placeholders::_1);
//cout << func3(1, 2) << endl;
//cout << func4(1, 2) << endl;
return 0;
}
8. 后言
其中c++11里面的新增功能, 如果不使用/ 少使用很容易就忘掉, 就比如lambda, 老长的你能在很久不用记得很清楚阿? xdm多用多写! 还有给博主三联, 你绝对学的很好!!!