目录
inilither_list
就是一个{};auto il = { 1,2,3,4,5 };这种就被识别成inilither_list,但如果对象是一个多参数的,就会被识别为构造
声明
声明告诉编译器变量的类型和名称,但不分配内存,声明的目的是使编译器知道某个变量或函数的存在,以便在其他文件中 引用 它。例如,使用 extern 关键字可以声明变量,而不定义它。
decltype
typeid().name()只是返回字符串,不能作为变量的类型名,也不能实例化模板参数;auto更不行实例化模板参数,连东西都传不了,没有对象来源;C++11引入decltype
double d = 1.1231000;
vector<decltype(d * c)> v;
v.push_back(1);
v.push_back(1.1);
for (auto e : v)
cout << e << " ";
cout的结果是1 1.1;奇怪,难道push_back进去的是两个不同的类型,当然不是,都是double,少遇到:cout打印double会省略0,这很合理
补充:
- 定义指的是:定义不仅声明了变量的类型和名称,还分配了内存空间,定义可以包含初始化,也可以不包含int y;int z = 10;这两个都是定义,也是声明
nullptr
//在c++下NULL被定义为常量0
#ifndef NULL
//用于检查某个宏是否 未定义
#ifdef __cplusplus//C++环境下,NULL被定义为常量0
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
移动语义
namespace nineone
{
class string
{
public:
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& str)
{
::swap(_str, str._str);
::swap(_size, str._size);
::swap(_capacity, str._capacity);
}
string(const string& str)
{
cout << "拷贝构造" << endl;
string tmp(str._str);
swap(tmp);
}
string(string&& str)
{
cout << "移动构造" << endl;
swap(str);
}
string& operator=(const string& str)
{
cout << "赋值重载" << endl;
string tmp(str._str);
swap(tmp);
return *this;
}
string& operator=(string&& str)
{
cout << "移动赋值" << endl;
swap(str);
return *this;
}
~string()
{
delete[]_str;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 4;
};
}
移动语义
- C++11新加入的两个成员函数:移动构造,移动赋值;如果没有显式定义,且没写拷贝构造,拷贝赋值和析构的条件下,编译器会尝试生成默认的移动构造函数和移动赋值运算符,这时候如果这个变量是自定义类型,会去调用它的移动构造,如果没有移动构造,那么编译器会退而求其次,调它的拷贝构造
右值
- 是一种表达式;如果是一个式子,一定是在等号右边;临时对象,字面量,函数返回值,表达式的返回值,这是C++11中成为实现移动语义和完美转发的关键。
目的
- 减少深拷贝,优化资源管理和提高程序性能
右值引用的使用
- const int a = 0;
- const int&& b = move(a);
- 不会方法权限
误区
- 右值可以修改,且必须能够修改
- 和左值一样不会占用新的存储空间
list push_back
template<class T>
struct node
{
T _data;
node<T>* _next;
node<T>* _prev;
node(const T& data = T())
:_data(data)
, _next(nullptr)
, _prev(nullptr)
{}
node(T&& data)
:_data(move(data))
, _next(nullptr)
, _prev(nullptr)
{}
};
void push_back(T&& data)
{
insert(end(), move(data));
}
iterator insert(iterator pos, T&& data)
{
node* cur = pos._node;
node* prev = cur->_prev;
node* newnode = new node(move(data));
prev->_next = newnode;
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
_size++;
return iterator(newnode);
}
//调用
nineone::string func()
{
nineone::string str("111");
return str;
}
void Test2()
{
const int a = 0;
const int&& b = move(a);
nineone::list<nineone::string> lt;
lt.push_back(func());
lt.push_back("222");
lt.push_back("333");
}
步骤
- 原本str是一个左值
- return str是移动构造给临时对象
- push_back的data参数是右值引用了临时对象
- 移动构造node,node里被转移的是data
注意
- str和临时对象不是同一个地址,临时对象是构造(移动构造)自然有自己的空间(栈)
- 在函数右值引用传参的时候data其实已经是一个左值了,右值引用的目的操作是swap到新的地方,比如说data是一个string类型目的是为了拿到_str,_size,_capacity,因为可以通过这三个来做到资源的重复利用,这里的资源就是_str
- 因为在函数参数列表中,任何标识符都是左值
- 移动语义核心在于右值引用,由常性可读的临时对象,变成可读可写的普通对象,那不就可以修改了吗,从语法上来说确实是放大了权限;但是使用上并没有,目的是为了构造对象,最终是通过swap的交换来构造,问:那么这不就是修改吗?答:我觉得这里的修改指的是新的对象的资源里利用
- 我觉得这里用move好于完美转发,因为data只是一个对象,不存在像函数模板一样存在找不对的情况
万能引用
void func(string& x) { cout << "1" << endl; }
void func(const string& x) { cout << "2" << endl; }
void func(string&& x) { cout << "3" << endl; }
void func(const string&& x) { cout << "4" << endl; }
template<class T>
void PerfectForward(T&& t)
{
func(forward<T>(t));
}
int main()
{
string str = "111";
const string c_str = "111";
PerfectForward(str);
PerfectForward(c_str);
PerfectForward(move(str));
PerfectForward(move(c_str));
}
- 通过右值引用实现完美转发
- 可以根据传递给它的模板参数来确定是否需要将传入的参数转换为右值引用
参数包
模板
template <class ...Args>
void ShowList(Args... args){}
- 模板参数包:Args... 表示一组类型,是在模板参数列表中定义的
- 函数参数包:args... 表示一组对象或值,是在函数参数列表中定义的。
- 扩展参数包:... 是用于展开参数包的操作符
递归解析模板参数
void _ShowList()
{
cout << endl;
}
template <class T, class ...Args>
void _ShowList(const T& t, Args... args)
{
cout << t << " ";
_ShowList(args...);
}
template <class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl;
// 不能这么解析参数包
//for (size_t i = 0; i < sizeof...(args); i++)
// cout << args[i] << ' ';
//cout << endl;
_ShowList(args...);
}
注意
- 无参的重载一定要写,因为会递归到没有可以传入的类型T,这时候就会报没有可调用的重载函数
- args...这是传入一个参数包,会去找最匹配的重载的函数
- const T& t可以改写成T&& t用万能引用来传参
数组解析参数包
template <class T>
int PrintArgs(const T& t)
{
cout << t << ' ';
return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { PrintArgs(args)... };
cout << endl;
}
- const T& t可以改写成T&& t用万能引用来传参
- 会被展开成int arr[] = { PrintArgs(arg1), PrintArgs(arg2), PrintArgs(arg3), PrintArgs(arg4) };
- 在C++11及更高版本中,数组初始化列表允许使用参数包展开来初始化数组。
数组和逗号表达式解析参数包
template <class T>
void PrintArgs(T&& t)
{
cout << t << ' ';
}
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArgs(args),0)... };
cout << endl;
}
- 逗号运算符允许在一个表达式中执行多个操作,并返回最后一个操作的结果。
- 展开成int arr[] = { (PrintArgs(arg1), 0), (PrintArgs(arg2), 0), ..., (PrintArgs(argN), 0) };
- template <class T, class ...Args>
int PrintArgs(const T& t, Args... args)这样传也可以;这样的换参数包就是0个 - 本质还是8借助数组初始化的特性
lambda
auto lambda_year_greater = [](const Date& d1, const Date& d2)mutable->bool
{
return d1._year < d2._year;
};
auto lambda_year_less = [](const Date& d1, const Date& d2)mutable->bool
{
return d1._year > d2._year;
};
lambda_year_greater = lambda_year_less;
- [ ]捕获
- ( )参数列表
- mutable lumbda调用的运算符()重载函数默认是const,而mutable可以去掉const属性,使得被捕获的父类变量可以被改变
- ->类型 {}里写了return,可以不写
- {} 函数体
- lumbda的底层和仿函数一样
[]
- =以传值的方式捕获所有父类作用域的变量,包括this
- &以传引用的方式捕获所有父类作用域的变量,包括this
- this 以传值的方式捕获所有当前类的成员变量
- 不能重复:有了=就不能再有传值的了;&的同理
注意
- lumbda里没有重载= ;lambda 表达式,默认的赋值运算符并不会自动生成,这是由于 lambda 表达式背后所代表的匿名类(由编译器自动生成的、没有显式名称的类)的特性决定的。它们背后的匿名类可能包含捕获的变量,这些捕获的变量可能会影响默认成员函数的生成
- lambda 表达式在编译时被转换为一个匿名类,包含捕获的变量和逻辑
- Lambda:这是一个通用术语,通常指的是 lambda 表达式及其生成的对象。
- Lambda 表达式:这是用于定义匿名函数的语法结构,如
[capture](parameters) -> return_type { body }
。 - Lambda 函数:由 lambda 表达式定义的匿名函数,即 lambda 表达式的执行体。()的调用
- Lambda 类:由编译器为实现 lambda 表达式生成的匿名类,包含捕获的变量和
operator()
成员函数。
包装器
function
int fun_Add(int a, int b)
{
return a + b;
}
struct class_Add
{
int operator()(int a, int b)
{
return a + b;
}
};
int main()
{
//auto lambda = [](int a, int b) {return a + b; };
map<string, function<int(int, int)>> mp
{
{"func_Sub", fun_Add},
{"class_Sub", class_Add() },
{"lambda", [](int a, int b) {return a + b; } }
};
cout << mp["func_Sub"](1, 1) << endl;
cout << mp["class_Sub"](1, 2) << endl;
cout << mp["lambda"](1, 3) << endl;
}
- 用于包装可调用对象:函数指针,仿函数对象,lambda对象,lambda表达式
- 前向声明:template <class T> function;
- 部分特化声明:template <class Ret, class... Args> class function<Ret(Args...)>;
bind
struct sub
{
static int funci(int i, int j)
{
return i + j;
}
double funcd(double i, double j)
{
return i + j;
}
};
int main()
{
function<int(int, int)> f1 = sub::funci;
function<double(sub*, double, double)> f2 = &sub::funcd;
sub sb;
f2(&sb, 1.1, 1.1);
//f2(&sub(), 1.1, 1.1); 右值不能取地址
function<double(sub, double, double)> f3 = &sub::funcd;
function<double(double, double)> f4 = bind(&sub::funcd, sub(), placeholders::_1, placeholders::_2);
}
函数
- 普通函数:是指在全局作用域或命名空间中定义的函数。它们可以在程序的任何地方被调用,只要它们的声明在调用之前出现。
- 类中的静态成员函数:这些函数属于类本身,而不是类的某个具体对象。它们只能访问类的静态成员,不能访问类的非静态成员。
- 全局作用域中的静态函数:使其仅在定义它的文件中可见。这在多文件项目中用于限制函数的可见性。
template <class Fn, class... Args> /* unspecified */ bind (Fn&& fn, Args&&... args); namespace placeholders { extern /* unspecified */ _1; extern /* unspecified */ _2; extern /* unspecified */ _3; // ... }
- bind和function是C++11标准库中两个重要的组件
- bind是一个函数模板,比如上述的f4 调用新的可调用对象
- function是一个类模板,用于存储和调用任何可调用对象
注意:
- function<double(double, double)> f4 = bind(&sub::funcd, placeholders::_1, placeholders::_2, sub()); // 错误
- 绑定的对象实例(
this
指针或对象实例)必须紧跟在成员函数指针之后 - 对成员函数的包装的调用必须用对象地址或者对象来调用★★★