Author: XFFer_
1 函数调用运算符
2 map类模版
3 function类模版
()
,如果在类中重载了函数调用运算符()
,那么就可以像使用函数一样使用该类的对象了,对象(实参)
。
system("pause");
暂停程序运行,等待任意键继续执行。
class biggerthan
{
public:
int operator()(int getvalue) const
{
if (getvalue < 0)
return 0;
return getvalue;
}
};
biggerthan bgt;
int result = bgt(200);
//int result = bgt.operator()(200);
结论:只要对象所属的类重载了()
“函数调用运算符”,这个类对象就变成可调用对象,并且可以调用多个版本的()
,只要重载()
参数列表有明显差异。
调用参数和返回值类型相同,叫做“调用形式”相同。
一种调用形式对应一个函数类型(如int(int)
)。
map
也是一个类模版/容器,但是不同于vector
,map里每一项是两个数据,一个叫“键”,一个叫“值”。
int func (int value);
#include
map<string, int (*) (int)> func_bag; //map每项有两个数据,这里第一个数据是字符串,第二个数据是int(int)函数类型的指针
func_bag.insert("src", func);
//map func_bag = {{"src", func}};
func_bag["src"](3); //func_bag[键]就是map里面的值,也就是函数的入口地址
insert({ , })
用于向map中插入对象。map也有迭代器(回忆:vector的insert功能是用迭代器(指针)向它指向的地址插入对象)
function也是类模版,要提供模版参数来表示该function类型能够表示的“对象的调用形式”。
#include
functional<int(int)> f1 = func; //统一调用形式,可以用内部重载()的类对象赋值给f1
1 类型区别基本概念
2 universal reference/万能引用/未定义引用 基本认识
3 万能引用资格的剥夺与辨认
万能引用的语境:
T&&
;auto也有万能引用的概念template <typename T>
void func(T&& src) {}
int i = 10;
func(i);
func(std::move(i));
万能引用和右值引用解释起来的区别
template <typename T>
void func(std::vector<T>&& tmp) {}
这是一个右值引用!!!不是万能引用T&&
是连续的。T可以被其他命名替换。
const
修饰词会剥夺万能引用的资格,而变成右值引用。
1 如何查看类型推断结果
2 理解模板类型推断
依赖
boost
库
boost库下载链接
需要将下载路径配置到Visual Studio属性管理器的VC++目录的包含目录中。
#include
template <typename T>
void myfunc(const T& tmprv)
{
using boost::typeindex::type_id_with_cvr;
cout << "T=" << type_id_with_cvr<T>().pretty_name() << endl;
cout << "tmprv=" << type_id_with_cvr<decltype(tmprv)>.pretty_name() << endl;
}
#include
template <typename T>
void myfunc(T&& tmprv)
{
using boost::typeindex::type_id_with_cvr;
cout << "T=" << type_id_with_cvr<T>().pretty_name() << endl;
cout << "tmprv=" << type_id_with_cvr<decltype(tmprv)>.pretty_name() << endl;
}
int i = 10;
myfunc(i); //结果是T= int &, tmprv= int &
myfunc(100); //结果是T= int, tmprv= int &&
//这里包含了引用折叠的知识,会在下一节讲到
const char*const
(第一个const代表指向的对象不能改值,第二个const代表不能改变指向的对象),当传入模板时,返回“T=const char*”“tmprv=const char*”,即可以改变指向的对象,但不可改动该对象的值1 引用折叠规则
2 转发、完美转发
3 std::forward
4 std::move和std::forward的区别
template <typename T>
void func(T&& tmprv) {} //万能引用
int i = 10;
func(i); //借用上一节的boost库判断类型
//这里返回T = int&, tmp = int&
假如我们将推断出的T = int&带入到实例化的模板中
void func(int& &&tmprv) {}
显然这并不是我们想要得到的,编译器报错。实际上,将tmprv推断的类型带入模板得到实例化函数结果是正确的
void func(int& tmprv) {}
这里int& &&
->int&
这就是引用折叠
而当传入函数一个整型右值时,T会被推断为int,tmprv被推断为int&&。
引用折叠,C++新版本,是一条规则(reference-coolapsing rules)。C++中有明确含义的引用只有两种,一种是&左值引用(类型);一种是带&&右值引用(类型)。
& &
& &&
&& &
&& &&
折叠规则: 如果任意一个引用为左值引用,那么结果就是左值引用,反之是右值引用。
模板函数把收到的参数以及这些参数相对应的类型不变的传给其他函数,这就叫 转发。
template<typename TYPE, typename SRC>
TYPE* get_info(const SRC& src)
{
TYPE* tmp;
tmp = new TYPE(src);
return tmp;
}
这里用了常量左值引用,可以接受任何类型。但是不能接受一个右值去初始化。
因为int&& i = 100
,100是一个右值,用右值引用接,但i在这里是一个左值,导致出错。
C++11提供了 完美转发:接受任意实参的函数模板;并转发给目标函数,目标函数和转发函数收到的完全相同。
template<typename TYPE, typename SRC>
TYPE* get_info(SRC&& src) //万能引用
{
TYPE* tmp;
tmp = new TYPE(std::forward<SRC>(src));
return tmp;
}
这里附上std::forward代码
template <typename _Tp>
inline
_Tp&& forward(typename remove_reference<_Tp>::type& __t)
{
return static_cast<_Tp&&>(__t);
}
template<typename TYPE, typename SRC>
TYPE* get_info(SRC&& src) //万能引用
{
TYPE* tmp;
tmp = new TYPE(std::forward<SRC>(src));
//这里src是一个左值,SRC在原始实参是左值时,这里仍转换为左值
//SRC原始实参是右值时,这里将左值src转换成右值
//故始终保持传入实参的类型
return tmp;
}
std::forward中typename remove_reference<_Tp>::type
为int类型,static_cast中_Tp&&类型根据引用折叠,推导为int& &&即int&,最后forward转发为int&类型,完美转发std::forward中typename remove_reference<_Tp>::type
为int类型,static_cast中_Tp&&类型根据引用折叠,推导为int&&即int&&,最后forward转发为int&&类型,完美转发std::forward
的两种理解:1 auto类型常规推断
2 auto类型std::initializer_list的特殊推断
auto用于变量的自动类型推断,在声明变量的时候根据变量的初始值自动选择匹配的类型,不需要显式指定类型。auto自动类型推断发生在编译期,这样不会影响程序执行期间的性能。auto和模板参数类型推断很相似。
const int i = 100;
int& im = i;
auto get = i;
auto get_m = im; //会抛弃限定符
int i = 100;
auto &&i_m = i; //万能引用传入左值int&,引用折叠
auto tp = { 30 };
auto tp2 = { 30, 40, 89 };
这种情况下= { }
初始化auto会推断类型为std::initializer_list的类型,这个类型类似于一个容器,类似于vector内部只能存同一种类型。
1 decltype含义和举例
2 decltype主要用途
decltype
:对于一个给定变量名或者表达式,能够返回该名字或表达式的类型。
const int i = 10;
auto i_ = i; //auto推断为int
decltype(i) i_ = i; //decltype推断为const int
const int& i_m = i;
auto i_m_ = i_m; //auto推断为int
decltype(i_m) i_m_ = i; //decltype推断为const int&,意味着i_m_也指向i的地址
这是在传值情况下,auto和decltype的明显区别
int i = 100;
int* p = &i;
decltype(*p) k = i; //这里*p就是一个左值,所以k的类型为int&
//如果变量名外加了一层或多层括号,编译器就会将变量当做一个表达式
decltype((i)) ikk = i; //这里ikk的类型是int&
int func() {}
decltype(func()) tmpc; //这里tmpc是int
decltype(func) tmpv; //这里tmpv是int(void)
template <typename T>
class A
{
public:
dycltype(T().begin()) iter;
};
//当传入T是const vector时,迭代器iter需要特化成const_iterator,使用dycltype解决了特化的问题
//当传入const vector时,T().begin()会返回一个const_iterator的类型用来生成一个iter对象
//C++98中就需要用偏特化(麻烦)
template <typename T>
class A
{
public:
typename T::const_iterator iter; //typename声明这是个类型,否则编译器会把::作用域操作符后的当做是一个成员函数/变量
};
后置返回类型举例:
auto func(int a, int b)->int { //语句 }
int tf(int &i)
{
...;
}
double tf(double &d)
{
...;
}
template <typename T>
FuncTmp(T &tv)->decltype(tf(tv)) //近似这样类型,不能使用前置函数类型,因为tv还未定义
{
...;
}
1 可调用对象
2 std::function(可调用对象包装器)
3 std::bind绑定器
前面我们已经将可调用对象一一提到过,这里做一个总结。可调用对象分为:
()
运算符的类(仿函数) ——本章第一节class text {
public:
using tfpoint = void (*) (int);
static void func(int v) {}
operator tfpoint() {
return func;
}
};
#include
介绍: C++11引入,能够将对象及其相关的参数绑定到一起,绑定完后能直接调用,还可以用std::function保存。
格式:std::bind(待绑定的函数对象/函数指针/成员函数指针, 参数绑定值1, 绑定值2, ...);
示例:
void func(int x, int y, int z)
{
cout << x << y << z << endl;
}
//全绑定
auto bf = std::bind(func, 10, 20, 30); //auto其实就是一个仿函数
//部分绑定
auto bf1 = std::bind(func, 20, placeholders::_1, placeholders::_2);
//这里输入的第一个参数做函数的第二个参数,输入的第二个参数做函数的第三个参数
//前面讲stl算法那节曾经再讲count_if时采用过bind
//count_if(iter, iter, [](int a)->bool { return a > 40; })
//sort前两个参数代表一个区间,第三个参数返回一个bool,用来定义规则
bool func(int a)
{
return a > 40;
}
vector<int> container = { 11, 28, 94, 1, 58 };
count_if(container.begin(), container.end(), func);
//用bind和仿函数可以这样写
count_if(container.begin(), container.end(), bind(greater<int>(), placeholders::_1, 40));
//类成员函数
class A
{
public:
void func(int i, int j)
{
i = 20; j++;
}
};
A a;
auto bj = bind(&A::func, a, placeholder::_2, placeholder::_1); //这里会调用拷贝构造函数,func内部的改动并不会对a类对象造成影响
auto bj = bind(&A::func, &a, placeholder::_2, placeholder::_1); //这里是地址传递,会对a造成影响
1 用法简介
2 捕获列表
3 lambda表达式延迟调用易出错细节分析
4 lambda表达式中的mutable
5 lambda表达式的类型及存储
6 for_each、find_if简介
lambda表达式,定义了一个匿名函数,并且可以捕获一定范围内的变量。
auto f = [](int a)->int{
return ++a;
};
格式:
[捕获列表](函数列表)->返回类型{函数体};
特点:
()
也可以省略[捕获列表]
[]
不捕获任何变量int i = 10;
auto f = [] {
return i;
}; //报错,因为不能捕获变量
[&]
捕获外部作用域中所有变量int i = 10;
auto f = [&](int i){
i = 100;
};
[=]
捕获外部作用域中所有变量,并作为副本(按值)在函数中使用,不允许赋值[this]
用于类中,用来捕获当前类中this指针,让lambda表达式和当前类对象有同样的访问权限,如果已经使用了[&]
或 [=]
,就包含了[this]
[变量名]
捕获该变量[=, &变量名]
按值捕获所有外部变量,但按引用方式捕获所指的变量。第一个位置表示默认捕获方式(隐式捕获方式),后面的位置表示显式捕获方式[&, 变量名]
按引用捕获所以外部变量,但用值捕获方式捕获所指的变量int x = 5
auto f = [=]() mutable{ //值捕获,仍可以改动
x = 6;
return x;
};
C++11中, lambda表达式的类型被称呼为“闭包(Closure Type)”。lambda表达式是一种特殊的,匿名的类类型。同样可以用std::function和std::bind保存和调用lambda表达式。
从外部[&]
[=]
[变量名]
捕获的变量都存在这个类类型对象的成员变量中,故可以调用。
语法糖:便捷写法的意思。是指基于语言现有特性,构建出一个对象,不会增加原有功能,但使用起来更简单。
int a[10];
a[3] = 10; //这就是语法糖
//实际上应该这样写
*(a + 3) = 10;
在第七章我们在算法的一节提到了这两个模板,这里我们用lambda表达式进行探讨。
vector<int> myvector = { 10, 20, 30, 40, 50 };
int sum = 0;
for_each(myvector.begin(), myvector.end(), [&sum](int vec){
sum += vec; //这样就对vector内部的所有值做了求和
});
cout << sum << endl;
auto result = find_if(myvector.begin(), myvector.end(), [](int vec){
if (vec > 30)
return true; //返回40对应的迭代器
});
if (result == myvector.end())
cout << "warning" << endl;
1 捕获列表中的&
2 静态局部变量
跳出函数作用域,仍调用函数体内部的lambda函数,对函数内局部变量做改动,导致该内存已释放形成错误。
#include //这个库用来显示系统时间
//补充:(计算语句执行时间)
//clock_t t1, t2;
//t1 = clock();
//语句;
//t2 = clock();
//cout << t2 - t1 << endl; //单位毫秒
std::vector<std::function<bool(int)>> vc;
void func()
{
srand((unsigned)time(NULL));
//用系统时间给随机数生成器一个种子,为了多次不产生相同的随机数序列
int tmpvalue = rand() % 100; //生成100以内的随机数
vc.push_back(
[&](int tv){
if (tv % tmpvalue == 0)
return true;
return false;
});
}
int main()
{
cout << vc[0](10) << endl; //这是非法调用,跳出func函数,tmpvalue不可用
}
引用捕获超出范围的情形叫做“引用悬空”。
上述代码中lambda表达式改成[=](int tv)
按值捕获就能解决悬空的问题。
捕获这个概念,只针对在创建lambda表达式的作用域内可见的非静态局部变量。
静态局部变量是不能被捕获的,但是可以在lambda中使用,另外,静态局部变量保存在静态存储去,有效期一直到程序结束,这种对static变量的使用类似于引用捕获的效果。
1 initializer_list(初始化列表)
2 省略号形参(…)
这是个标准库模板类型,能够使用的前提条件时是所有参数的类型相同,用存放非固定个数的固定类型。
initializer_list<string> myarray({"aa", "bb", "cc"});
for (auto& tmpi : myarray) //范围for
{
cout << tmpi << endl;
}
for (auto iter = myarray.begin(); iter != myarray.end(); iter++) //遍历
{
cout << (*iter).c_str << endl;
}
该类型内部存放的所有变量都是常量值,不能被改变。
拷贝、赋值一个initializer_list对象不会拷贝列表中的元素,会共享元素。
#include
这种省略形参式的可变参数函数,虽然参数数量不固定,但是函数的所有参数是存储在线性连续的栈空间中,而且带...
的可变参数函数函数必须至少要有一个普通参数用来寻址。
应用举例:
//求整个...内参数的和
double sum(int num, ...) //num里边传递进来的可变参数的数量
{
va_list valist; //创建一个va_list类型的变量
double sum = 0;
va_start(valist, num); //使valist指向起始的参数
for (int i = 0; i < num; i++)
{
//遍历参数
sum = sum + va_arg(valist, int); //参数2说明返回的类型int
}
va_end(valist); //释放va_list
return sum;
}
1 萃取概述(type traits)
2 类型萃取范例
属于泛型编程,在stl的实现源码中,这种类型使用的比较多,C++11中提供了很多类型萃取的接口。
返回值1true,0false
template <typename T>
void printTraitsInfo(const T& t)
{
cout << "----------begin----------" << endl;
cout << "我们要萃取的类型名字是:" << typeid(T).name << endl;
cout << "is_void =" << is_void<T>::value << endl; //类型是否是void
cout << "is_class =" << is_class<T>::value << endl;
cout << "is_object =" << is_object<T>::value << endl; //是否是一个对象类型
cout << "is_pod =" << is_pod<T>::value << endl; //是否是普通类
cout << "is_default_constructible =" << is_default_constructible<T>::value << endl; //是否有缺省的构造函数
cout << "is_copy_constructible =" << is_copy_constructible<T>::value << endl; //是否有拷贝构造函数
cout << "is_move_constructible =" << is_move_constructible<T>::value << endl; //是否有移动构造函数
cout << "is_destructible =" << is_destructible<T>::value << endl; //是否有拷贝构造函数
cout << "is_polymorphic =" << is_polymorphic<T>::value << endl; //是否函数虚函数
cout << "has_virtual_destructor =" << has_virtual_destructor<T>::value << endl; //是否有虚析构函数
}