个人主页:仍有未知等待探索-CSDN博客
专题分栏:C++
目录
一、统一列表初始化
二、变量类型推导
1、auto
2、decltype
3、typeid
三、左值/右值
1、左值引用/右值引用(粗)
2、右值
3、右值引用(细)
1.移动构造、移动赋值
2.对于默认生成移动构造、移动赋值的条件
四、lambda表达式
五、新的类功能
六、模板的可变参数
1、计算形参对象包的数据个数?
2、打印出形参对象包里面的数据内容?
3、容器中emplace系列
七、包装器
1、function --- 类模板
2、bind --- 函数模板
一、统一列表初始化
一切可以用列表初始化。
注意:列表初始化和初始化列表是两个概念。
列表初始化是一个动作,而初始化列表是一种数据结构。
1、int arr[] = {1, 2, 3, 4, 5};
2、int x = {2}; // 正常的定义是: int x = 2; 但是为了统一列表初始化,上述也能支持
3、单参数的隐式类型转化
4、多参数的隐式类型转换 const A& aa1 = {2, 2};
5、vector<int> v = {1, 2, 3, 4, 5, 6};
6、构造:vector<int> v({1,2,3})
x自定义类型 = y类型 -> 隐式类型转换 x(y e)需要x支持y类型为参数的构造函数
二、变量类型推导
1、auto
自动推导变量类型。
注意:
1、auto修饰的变量必须初始化。
2、auto类型不能用于函数参数。
3、auto类型不能用于定义数组。
4、auto无法推导模板参数。
5、auto不可以用于非静态成员变量。
推导类型规则:
// 当auto没有声明引用或者指针类型,auto类型推导则不带原变量的const和引用属性 // 输出 // 10 // 11 const int a = 10; auto c = a; c++; cout << a << std::endl << c; // 当auto类型声明了引用类型或者指针类型,auto类型推导带原变量的const和引用属性 const int a = 10; auto& c = a; c++; // 这句报错 cout << a << std::endl << c;
2、decltype
将变量的类型声明为表达式指定的类型。
和auto的区别:
这个推导出来的类型可以定义变量。
list<int>::iterator it;
decltype(it) it2;
3、typeid
用于获取一个表达式或类型的运行时类型信息。
list<int>::iterator it;
cout << typeid(it).name() << endl;
三、左值/右值
左值和右值不仅仅是一个值,也可能是有返回值的表达式。
左值和右值的根本区别:
1、能不能被取地址。
2、左值能被取地址。
3、右值不能被取地址。(匿名对象,字面常量,临时对象)
1、左值引用/右值引用(粗)
引用的意义:提高效率,减少拷贝。
左值引用:给左值起别名。
右值引用:给右值起别名。
注意:
- 左值引用不能给右值起别名,但是const左值引用可以。
- 右值引用不能给左值起别名,但是可以给move之后的左值起别名。
2、右值
右值包括:匿名对象,字面常量,临时对象。
右值分为两种:
1、纯右值。(内置类型)
2、将亡值。(自定义类型)
class A {}; // 纯右值 10、'e'、3.14 // 将亡值 A()
3、右值引用(细)
注意:
右值引用本身是左值。
只有右值引用本身处理成左值,才能实现移动构造和移动复制,转移资源。
1.移动构造、移动赋值
移动构造和移动赋值的本质就是转移资源。
- 右值引用的属性如果是右值,那么移动构造和移动复制,要转移资源的语法逻辑是矛盾的,右值是不能被改变的(右值带有const属性)。
- 右值引用本身是左值。只有右值引用本身处理成左值,才能实现移动构造和移动复制,转移资源。
深拷贝的类才有转移资源的移动系列函数。
说白了转移资源就是把将亡值的数据直接和类内成员变量进行交换。
#include <iostream>
#include <string>
using namespace std;
class A
{
private:
string _a;
public:
// 强制生成A()
A() = default;
// 类A的拷贝构造 --- 深拷贝
A(const string& a)
:_a(a)
{}
// 类A的赋值重载
A& operator=(const string& a)
{
_a = a;
return *this;
}
// 移动构造
A(string&& a)
{
swap(_a, a);
}
// 移动赋值
A& operator=(string&& a)
{
swap(_a, a);
}
};
int main()
{
// 拷贝构造、赋值
A a;
string b = "abcd";
a = b;
A c(b);
// 移动构造、移动赋值
A d("11111");
A e = string("11111");
return 0;
}
2.对于默认生成移动构造、移动赋值的条件
- 如果你没有实现移动构造函数,且没有实现析构函数、拷贝构造,拷贝复制重载中的任意一个,那么编译器会默认生成一个默认的移动构造。对于内置类型成员会执行逐成员按字节拷贝;对于自定义类型成员,如果该自定义类型实现了移动构造函数就执行,刚没有实现就调用拷贝构造。
- 如果你没有实现移动赋值重载,且没有实现析构函数、拷贝构造,拷贝复制重载中的任意一个,那么编译器会默认生成一个默认的移动赋值重载。对于内置类型成员会执行逐成员按字节拷贝;对于自定义类型成员,如果该自定义类型实现了移动赋值重载就执行,刚没有实现就调用拷贝赋值。
- 如果提供了移动构造函数或者移动赋值,编译器则不会自动提供拷贝构造和拷贝赋值。
四、lambda表达式
匿名函数对象。
#include <iostream>
using namespace std;
int main()
{
int c = 2, d = 1;
// 捕捉列表,捕捉变量
auto add = [c, d](int a, int b)->int {return a + b + c + d; };
cout << add(1, 2) << endl;
cout << "-------------------------------------------------------" << endl;
// 默认的捕捉列表里面的变量具有const性,且是传值
// 这么写会报错:表达式必须是可修改的左值
auto swap = [c, d]()->void
{
int t = c;
d = c;
c = t;
};
cout << "-------------------------------------------------------" << endl;
// 这样就没有什么问题,因为mutable取消了const性
// 但是c、d仍无法交换
auto swap = [c, d]()mutable->void
{
int t = c;
c = d;
d = t;
};
cout << c << " " << d << endl;
swap();
cout << c << " " << d << endl;
cout << "-------------------------------------------------------" << endl;
// 这么写就能正常的交换
auto swap = [&c, &d]()mutable->void
{
int t = c;
c = d;
d = t;
};
cout << c << " " << d << endl;
swap();
cout << c << " " << d << endl;
return 0;
}
五、新的类功能
- 强制自动生成默认函数 --- default
class A { private: int _a; public: A() = default; // 强制生成无参构造函数 A(int a) :_a(a) {} };
禁止生成函数 --- delete
class A { private: int _a; public: A() = default; // 强制生成无参构造函数 A(int a) :_a(a) {} A& operator=(int a) = delete; // 禁止生成默认函数 };
六、模板的可变参数
1、计算形参对象包的数据个数?
sizeof...(args)
2、打印出形参对象包里面的数据内容?
模板的可变参数应该是在编译的时候进行解析的,而不是在运行时进行解析。
如下是错误示例:
template <class ...Args> void print(Args... args) { // 错误做法读取args // error C3520: “args”: 必须在此上下文中扩展参数包 // 不支持、不匹配 // 参数包是在编译时逻辑 // 代码是运行时逻辑 for (int i = 0; i < sizeof...(args); i++) { cout << args[i] << " "; } }
如下是正确示范:
#include <iostream> using namespace std; // 用于判断参数包是否已经读完 // 不能用数据个数来判断是否已经结束,因为sizeof是运行的时候才进行替换 // 而这个打印参数包内容是在编译时就已经展开完毕 void cpp_print() { cout << endl; } // 打印主体 // 第一个参数用一个模板进行接收,从而获取到参数包内容 template <class T, class ...Args> void cpp_print(T val, Args... args) { cout << val << endl; cpp_print(args...); } // 外壳函数 template <class ...Args> void print(Args... args) { // 调用主体函数,传参的时候要传参数包 // 虽然调用的函数模板参数有两个,但是可以传一个参数包,自动把参数包第一个参数匹配到val中,剩下的还传到args参数包中 cpp_print(args...); } int main() { print(1, "22", 3.333); return 0; }
3、容器中emplace系列
容器中emplace系列的参数都是传的参数包。
所以当你要传入多个参数的时候,建议使用emplace系列函数。
list<pair<string, int>> it; it.push_back({ "xxxxxx" , 1}); it.emplace_back("bbbbbbb", 2);
七、包装器
包装可调用对象。
下列的头文件都在<functional>。
1、function --- 类模板
// 包装类包装类内成员函数
#include <iostream>
#include <functional>
using namespace std;
class Plus
{
public:
static int func1(int a, int b)
{
return a + b;
}
double func2(double a, double b)
{
return a + b;
}
};
int main()
{
// 包装静态的成员函数
function<int(int, int)> f1 = &Plus::func1;
cout << f1(1, 2) << endl;
// 包装飞机静态的成员函数
// 第一种方式
function<double(Plus*, double, double)> f2 = &Plus::func2;
Plus p2;
cout << f2(&p2, 1.1, 2.2) << endl;
// 第二种方式
function<double(Plus, double, double)> f3 = &Plus::func2;
cout << f3(Plus(), 1.1, 2.2) << endl;
return 0;
}
2、bind --- 函数模板
包装可调用对象和参数包。
#include <iostream> #include <functional> using namespace std; class Sub { public: int sub(int a, int b) { return a - b; } }; int main() { auto f1 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3); cout << f1(Sub(), 10, 5) << endl; // bind可以调整可调用对象的参数个数和顺序 auto f2 = bind(&Sub::sub, placeholders::_1, placeholders::_3, placeholders::_2); cout << f2(Sub(), 10, 5) << endl; // 调整对调用对象的参数个数 auto f4 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2); cout << f4(10, 5) << endl; Sub sub; auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2); cout << f5(10, 5) << endl; return 0; }
谢谢大家!!!