C++入门到精通——第八章 未归类知识点

八、未归类知识点

Author: XFFer_

01 函数调用运算符、function类模版

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模版

map 也是一个类模版/容器,但是不同于vector,map里每一项是两个数据,一个叫“”,一个叫“”。

int func (int value);
#include <map>
map<string, int (*) (int)> func_bag;	//map每项有两个数据,这里第一个数据是字符串,第二个数据是int(int)函数类型的指针
func_bag.insert("src", func);
//map<string, function<int (int)> func_bag = {{"src", func}};

func_bag["src"](3);	//func_bag[键]就是map里面的值,也就是函数的入口地址

insert({ , }) 用于向map中插入对象。map也有迭代器(回忆:vector的insert功能是用迭代器(指针)向它指向的地址插入对象)

标准库function类型介绍

function也是类模版,要提供模版参数来表示该function类型能够表示的“对象的调用形式”。

#include <functional>
functional<int(int)> f1 = func;	//统一调用形式,可以用内部重载()的类对象赋值给f1

02 万能引用universal reference

1 类型区别基本概念
2 universal reference/万能引用/未定义引用 基本认识
3 万能引用资格的剥夺与辨认

universal reference/万能引用/未定义引用 基本认识

万能引用的语境:

  • 必须是函数模板
  • 必须是发生了模板类型推断并且函数模板形参样子是 T&&;auto也有万能引用的概念
template <typename T>
void func(T&& src)	{}

int i = 10; 
func(i);
func(std::move(i));

万能引用和右值引用解释起来的区别

  • 右值引用需要传递右值,否则编译器报错(左值、右值的概念在第2章第12节曾经讲过)
  • 万能引用做函数形参时,我们可以传递给它左值,也可以传递给它右值
    • 传递左值时,这个万能引用就变成了左值引用
    • 传递右值时,这个万能引用就变成了右值引用
template <typename T>
void func(std::vector<T>&& tmp)	{}

这是一个右值引用!!!不是万能引用T&&是连续的。T可以被其他命名替换。

const修饰词会剥夺万能引用的资格,而变成右值引用。

03 理解模板类型推断、查看类型推断结果

1 如何查看类型推断结果
2 理解模板类型推断

如何查看类型推断结果

依赖boost

boost库下载链接
需要将下载路径配置到Visual Studio属性管理器的VC++目录的包含目录中。

用boost库返回类型推断结果
#include <boost/type_index.hpp>

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 <boost/type_index.hpp>

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*”,即可以改变指向的对象,但不可改动该对象的值

04 引用折叠,转发、完美转发,forward

1 引用折叠规则
2 转发、完美转发
3 std::forward
4 std::move和std::forward的区别

hook

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;
}
  • 当get_info传入int左值类型,SRC推导为int&,传递给std::forward的模板参数就是int&类型,std::forward中typename remove_reference<_Tp>::type为int类型,static_cast中_Tp&&类型根据引用折叠,推导为int& &&即int&,最后forward转发为int&类型,完美转发
  • 当get_info传入int右值类型,SRC推导为int,src推导为int&&,传递给std::forward的模板参数就是int类型,std::forward中typename remove_reference<_Tp>::type为int类型,static_cast中_Tp&&类型根据引用折叠,推导为int&&即int&&,最后forward转发为int&&类型,完美转发
std::forward的两种理解:
  • 实参如果原来是左值,到了形参中还是左值,forward按照原先的类型处理,std::forward之后还是左值
  • 实参如果原来是右值,到了形参中成为了左值,forward按照原先的类型处理,std::forward之后还是右值

05 理解auto类型推断,auto应用场合

1 auto类型常规推断
2 auto类型std::initializer_list的特殊推断

auto类型常规推断

auto用于变量的自动类型推断,在声明变量的时候根据变量的初始值自动选择匹配的类型,不需要显式指定类型。auto自动类型推断发生在编译期,这样不会影响程序执行期间的性能。auto和模板参数类型推断很相似。

  • 传值方式(非指针,非引用) :“传值方式的auto会抛弃引用,const等限定符”
const int i = 100;
int& im = i;
auto get = i;
auto get_m = im;	//会抛弃限定符
  • 指针或者引用类型但是不是万能引用:“不会抛弃const,但会抛弃引用”
  • 万能引用
int i = 100;
auto &&i_m = i;	//万能引用传入左值int&,引用折叠
auto类型std::initializer_list的特殊推断
auto tp = { 30 };
auto tp2 = { 30, 40, 89 };

这种情况下= { }初始化auto会推断类型为std::initializer_list的类型,这个类型类似于一个容器,类似于vector内部只能存同一种类型。

06 详解decltype含义,decltype主要用途

1 decltype含义和举例
2 decltype主要用途

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的明显区别

  • 括号内是表达式:会返回表达式结果的类型
    • 当表达式结果能够作为左值,那么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)

decltype主要用途

应付可变类型
template <typename T>
class A
{
public:
	dycltype(T().begin()) iter;
};
//当传入T是const vector<int>时,迭代器iter需要特化成const_iterator,使用dycltype解决了特化的问题
//当传入const vector<int>时,T().begin()会返回一个const_iterator的类型用来生成一个iter对象

//C++98中就需要用偏特化(麻烦)
template <typename T>
class A
{
public:
	typename T::const_iterator iter;	//typename声明这是个类型,否则编译器会把::作用域操作符后的当做是一个成员函数/变量
};
auto结合decltype构成函数后置返回类型

后置返回类型举例:

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还未定义
{
	...;	 
}

07 可调用对象、std::function、std::bind(重点)

1 可调用对象
2 std::function(可调用对象包装器)
3 std::bind绑定器

简述可调用对象

前面我们已经将可调用对象一一提到过,这里做一个总结。可调用对象分为:

class text {
public:
	using tfpoint = void (*) (int);
	static void func(int v)	{}
	operator tfpoint() { 
		return func;
	} 
};

std::bind绑定器

#include <functional>

介绍: 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造成影响

08 lambda表达式,for_each,find_if简介

1 用法简介
2 捕获列表
3 lambda表达式延迟调用易出错细节分析
4 lambda表达式中的mutable
5 lambda表达式的类型及存储
6 for_each、find_if简介

用法简介

lambda表达式,定义了一个匿名函数,并且可以捕获一定范围内的变量。

auto f = [](int a)->int{
	return ++a;
};

格式
[捕获列表](函数列表)->返回类型{函数体};

特点

  • 是个匿名函数,也可以理解为“可调用的代码单元”,也可以理解成“未命名的内联函数
  • 它也有一个返回类型,一个参数列表,一个函数题
  • 与函数不同的是,lambda表达式可以在函数内部定义
  • 必须是一个返回类型后置语法
  • lambda返回值明显时,返回类型可以省略
  • 没有参数时,参数列表可以省略,()也可以省略

捕获列表

[捕获列表]

  • [] 不捕获任何变量
int i = 10;
auto f = [] {
	return i;
	};	//报错,因为不能捕获变量
  • [&] 捕获外部作用域中所有变量
int i = 10;
auto f = [&](int i){
	i = 100;
	};
  • [=] 捕获外部作用域中所有变量,并作为副本(按值)在函数中使用,不允许赋值
  • [this] 用于类中,用来捕获当前类中this指针,让lambda表达式和当前类对象有同样的访问权限,如果已经使用了[&][=],就包含了[this]
  • [变量名] 捕获该变量
  • [=, &变量名] 按值捕获所有外部变量,但按引用方式捕获所指的变量。第一个位置表示默认捕获方式(隐式捕获方式),后面的位置表示显式捕获方式
  • [&, 变量名] 按引用捕获所以外部变量,但用值捕获方式捕获所指的变量

lambda表达式中的mutable

int x = 5
auto f = [=]() mutable{	//值捕获,仍可以改动
	x = 6;
	return x;
};

lambda表达式的类型及存储

C++11中, lambda表达式的类型被称呼为“闭包(Closure Type)”。lambda表达式是一种特殊的,匿名的类类型。同样可以用std::function和std::bind保存和调用lambda表达式。

从外部[&] [=] [变量名]捕获的变量都存在这个类类型对象的成员变量中,故可以调用。

语法糖概念

语法糖:便捷写法的意思。是指基于语言现有特性,构建出一个对象,不会增加原有功能,但使用起来更简单。

int a[10];
a[3] = 10;	//这就是语法糖

//实际上应该这样写
*(a + 3) = 10;

for_each、find_if

第七章我们在算法的一节提到了这两个模板,这里我们用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;

09 lambda表达式捕获模式的陷阱分析和展示

1 捕获列表中的&
2 静态局部变量

捕获列表中的&

跳出函数作用域,仍调用函数体内部的lambda函数,对函数内局部变量做改动,导致该内存已释放形成错误。

#include <ctime>	//这个库用来显示系统时间

//补充:(计算语句执行时间)
//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变量的使用类似于引用捕获的效果。

10 可变参数函数、initializer_list、省略号形参

1 initializer_list(初始化列表)
2 省略号形参(…)

initializer_list

这是个标准库模板类型,能够使用的前提条件时是所有参数的类型相同,用存放非固定个数的固定类型。

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 <stdarg.h>

这种省略形参式的可变参数函数,虽然参数数量不固定,但是函数的所有参数是存储在线性连续的栈空间中,而且带...的可变参数函数函数必须至少要有一个普通参数用来寻址。

应用举例:

//求整个...内参数的和
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;
}

11 萃取(traits)技术概念、范例等

1 萃取概述(type traits)
2 类型萃取范例

萃取概述(type traits)

属于泛型编程,在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;	//是否有虚析构函数
}

END!!!

Thank you for your watch!!!

Wish you can have a legendary programming life!!!

发布了27 篇原创文章 · 获赞 60 · 访问量 6579

猜你喜欢

转载自blog.csdn.net/qq_44455588/article/details/104474038