C++(内存高级话题、未归类知识点)

文章目录

内存高级话题

new、delete的进一步认识

new

1)new类对象时加不加括号的问题

A *pa = new A;
A *pa = new A();

当类中有成员变量时,带括号的初始化会把一些和成员变量有关的内存清0,但不是整个对象的内存全部清0;
当类中有构造函数时,两种的结果一样;

int *p = new int;//初值随机
int *p = new int();//初值0
int *p = new int(100);//初值100

2)new

A *pa = new A();

new调用了operator new函数,malloc分配内存,并调用了A的构造函数(有的话 );delete与之相反。

new内存分配、operator new 和operator new[]

new内存分配
void func()
{
	char *ppoint = new char[10];
	memset(ppoint,0,10);
	delete[]ppoint;
}

new和delete内存分配工作是很复杂的;
在分配的4个字节周围也花了字节数去记录分配的字节数等,多分配的内存造成了严重的浪费;

重载operator new 和operator delete操作符

用代码替换operator new操作符

class A 
{
public:
	static void *operator new(size_t size);//静态成员函数,类内定义
	static void operator delete(void *phead);
}
void *A::operator new(size_t size)//在类外编写
{
	A ppoint = (A*)malloc(size);
	return ppoint;
}
void A::operator delete(void *phead)
{
	free(phead);
}
void func()
{
	A *pa = new A();
	delete pa;
}
重载类中的operator new[] 和operator delete[]操作符

就是在new和delete后面加[]

class A 
{
public:
	static void *operator new[](size_t size);//静态成员函数,类内定义
	static void operator delete[](void *phead);
}
void *A::operator new[](size_t size)//在类外编写
{
	A ppoint = (A*)malloc(size);
	return ppoint;
}
void A::operator delete[](void *phead)
{
	free(phead);
}
void func()
{
	A *pa = new A[3]();//三个类对象的数组
	delete[] pa;
}

构造和析构会被调用三次,但是operator只有一次;
专门有四个字节用于记录数据的大小,然后会根据这个记录调用构造函数;

内存池:提高运行效率、减少内存浪费

内存池概念

malloc:频繁分配内存,会造成浪费;
内存池:减少malloc的调用次数,以减少浪费
实现原理:用malloc先分配一大块内存,当使用时一点一点进行分配;当大内存要用完时,申请一大块;

针对一个类的内存池实现代码
class A
{
public:
	static void *operator new(size_t size);
	static void delete operator(void *phead);
	static int m_iCout;
	static int m_iMallocCout;
private:
	A *next;//指向下一块内存的4指针
	static A*m_FreePosi;//指向一块可以分配出去的内存的首地址
	static int m_sTrunCout;//一次分配多少倍的该类内存
};
int A::m_iCout = 0;
int A::m_iMallocCout = 0
A *A::m_FreePosi = nullptr;
int A::m_sTrunkCout = 5;

void *A ::operator new (size_t size)
{
	//核心代码
	A *tmplink;
	if(m_FreePosi == nullptr)//如果为空就分配内存块
	{
		size_t realsize = m_sTrunkCout;//申请这么多的内存
		m_FreePosi = reinterpret_cast<A*>(new char[realsize]);//传统new
		tmplink = m_FreePosi;//将内存连在一起
		//把分配出来的一大块(五小块)链起来,供后续使用
		for (;tmplink != &m_FreePosi[m_sTrunkCout - 1]; ++ tmplink)//追踪其他四块
		{
			tmplink->next = tmplink + 1;//指向下一个内存块的首地址
		}
		temlink->next = nullptr;//最后一个指向空
		++m_iMaalocCount;
	}
	tmplink = m_FreePosi;//空闲的内存首地址
	m_FreePosi = m_FreePosi->next;
	++m_iCout;
	return tmplink
}
void A::operator delete(void *phead)
{
	(static_cast<A*>(phead))->next = m_FreePosi;
	m_FreeePosi = static_cast<A*>(phead);//指向释放的地方
}

主要作用是减少内存浪费,在效率上提升不多;
malloc每次的地址都不相邻

嵌入式指针、内存池改进

嵌入式指针

embedded pointer一般应用在内存池代码中;
工作原理:借用类A所占用的前4个字节,用来链住这些空闲的内存块;
但是一旦被分配出去,这4个字节就不再需要,可以被正常使用;

超过四个字节的类就可以使用嵌入式指针,因为指针的字节长度就是4;

class TestEP
{
public:
	int m_i;
	int m_j;
public:
	struct obj//嵌入式
	{
		struct obj *next;//指向obj对象的的嵌入式指针
	};
private:
	
}
void func()
{
	TestEP mytest;
	cout << sizeof(mytest)<< endl;//8
	TestEP::obj *ptemp;
	ptemp = (TestEP::obj)&mytest;//把mytest对象首地址给了指针ptemp
	cout << sizeof(ptemp->next)<< endl;
	cout << sizeof(TestEP::obj)<< endl;
	ptemp->next = nullptr;
}
内存池改进

单独给内存池写个类,并使用嵌入式指针

重载全局new、delete,定位new和重载

重载全局operator new、operator delete
void *operator new(size_t size);
{
	return malloc(size);
}
void *operator new[](size_t size);
{
	return malloc(size);
}
void operator delete(void *phead)
{
	free(phead);
}
void operator delete[](void *phead)
{
	free(phead);
}
class A
{
public:
	A()
	{
	}
	~A()
	{
	}
}
void func()
{
	int *pint = new int(12);
	delete pint;

	char *parr = new char [10];
	delete[] parr;

	A *p = new A();
	delete *p;

	A *pa = new A[3]();
	delete[] *p;
}

定位new(placement new)

功能:在已经分配的原始内存中初始化一个对象;
a)已经分配,定位new并不分配内存,需要将这个定位new要使用的内存分配出来;
b)初始化一个对象,就要调用它的构造函数;
所以定位new就是在一个预先分配好的内存地址中构造一个对象;
格式:
new (地址) 类类型()

不会调用malloc因为内存已经分配好了;

多版本operator new重载

只要参数不同,可以重载多次,但是第一个参数size_t不变,表示new对象的sizeof值;

STL标准模板库

STL总述

C++标准库:c++ standard library
标准模板库:standard template library 是c++标准库中的重要组成部分;
泛型编程:使用模板为主要的编程手段;

可以认为标准模板库就是用泛型编程编码方式写的一套标准库;
《c++标准库》《STL源码剖析》;
《算法导论》;

所有用到的东西都在std命名空间里;
标准库里和stl(标准模板库)相关头文件的有几百个;

组成部分:
a)容器:vector\list\mao
b)迭代器:用于遍历或者访问容器中的元素;
c)算法:函数,用来实现一些功能;search\sort\copy
d)分配器(内存分配器)
e)其他:适配器、仿函数。

容器

容器分类

vector,list,map是用于保存数据的
1)顺序容器,放进去在哪就排在哪;
2)关联容器(红黑树):元素是 键值 对,适合查找。能控制插入内容,不能控制位置。
3)无序容器(哈希表):位置不重要,重要的是是否在里面;

容器说明

1)array是个顺序容器(两边无开口),也是数组,大小固定不能再变;
对象的地址是连续的,但是里面的字符串的地址不一定;

void func()
{
	//包含5个元素的数组
	array <string ,5> mystring = {"","",""};
	cout<< mystring.size()<< endl;//元素个数
	cout<<sizeof(string)<<endl;//字符串长度
	for(size_t i - 0;i<mystring,size(); ++i)
	{
		const char *p = mystring[i].c_str();
	}
}

2)vector顺序容器(末尾开口)
适合使用末尾操作push_back;
查找速度慢;

class A
{
public:
	int m_i;
	A(int tmpv):m_i(tmpv)
	{
	}
	~A()
	{
	}
}
void func()
{
	vector <A> myveca;//容器里放A对象
	for(int i = 0;i<5;++i)
	{
		myveca.push_back(A(i));//插入5个临时对象
	}
}

第一次会执行一个构造、一个拷贝构造、一个析构,空间为1;
第二次会执行一个构造、一个拷贝构造、一个析构,空间为2;
第三次会执行一个构造、两个拷贝构造、两个析构,空间为3;
第四次会执行一个构造、三个拷贝构造、三个析构,空间为4;
第五次会执行一个构造、四个拷贝构造、四个析构,空间为6;//多留一个

为了保证内存空间的连续,每次增加元素,都会重新分配内存,然后将原来的拷贝过去,然后将原来的析构掉;
空间数量一定不会小于元素的数量;capacity()>=size()

int count = 0;
for(auto pos = myveca.begin();pos != myveca.end();++pos)//迭代器
{
	icount++;
	if(icount == 2)
	{
		myveca.erase(pos);//把中间的删掉
		break;//退出
	}
	
}

删掉一个元素,发现剩下的元素的地址依次往前挪一个,不需要析构和拷贝构造;

int count = 0;
for(auto pos = myveca.begin();pos != myveca.end();++pos)//迭代器
{
	icount++;
	if(icount == 2)
	{
		myveca.insert(pos,A[10]);
		break;//退出
	}
	
}

插入一个元素,导致后面的会发生析构和拷贝构造;
所以往中间插入效率较低;

vector <A> myveca;
cout<<myveca.capacity()<<endl;//0
cout<<myveca.size()<<endl;//0
myveca.reserve(10)<< endl;//事先预留10个
cout<<myveca.capacity()<<endl;//10
cout<<myveca.size()<<endl;//0

用reserve可以设置预留空间,这样就不需要再重新分配内存;

容器说明

deque和stack

顺序容器
deque:双端队列double_ended queue(双端开口),相当于动态数组,头尾部效率高,中间效率低;

class A
{
public:
	int m_i;
	A(int tmpv):m_i(tmpv)
	{
	}
	~A()
	{
	}
}
void func()
{
	deque <A> mydeque;//容器里放A对象
	for(int i = 0;i<5;++i)
	{
		mydeque.push_front(A(i));//插入5个临时对象
	}
	for(int i = 0;i<5;++i)
	{
		mydeque.push_back(A(i));//插入5个临时对象
	}
	for(int i = 0;i<mydeque.size();++i)
	{
		cout<<mydeque[i].m_i<<endl;
	}
}

deque是一个分段数组,每一段的内存时连续的,和其他段是不连续的;

stack(堆栈/栈)后进先出,和vector的区别是vector支持中间的插入删除,stack只支持栈顶放入和栈顶取出元素;
deque包含着stack的功能;

queue队列

一端入队,一端出队,先进先出,deque也包含着queue;

list链表

双向链表,不需要内存连在一起,根据指针进行连接,插入和删除元素快,查找效率不高;


vector和list的区别:
a)vector类似于数组,内存空间连续,而list双向链表,内存空间不连续;
b)vector从中间或者开头插入元素效率比较低,但是list插入元素效率非常高;
c)vector当内存不够时,会重新找一块内存,对原来的内存对象析构,在新的内存中重新构建对象
d)vector能够高效的随机存取(内存连续),而list只能通过指针挨着寻找


forward_list

c++11新增:单向链表
节省内存

void func()
{
	forward_list<A> myforlist;
	myforlist.push_front(A(1));
}
set/map

关联容器:内部实现的数据结构多为红黑树;
保存数据时不需要指定数据位置,会自动安排位置
map每个元素有两项,是个键值对(key/value),一般通过key找value;
通过key查找效率很高;
不允许key相同;
(有点像字典);

#include<map>
void func()
{
	map<int,string> mymap;
	mymap.insert(std::make_pair(1,"a"));
	mymap.insert(pair<int,string>(2,"b"));//两种插入方式

	auto iter = mymap.find(3);
	if(iter != mymap.end())//找到了
	{
		//
	}
}
set

不存在键值对,每个元素就是value;
元素不能重复;
(有点像集合);

关联容器总结:
a)插入式,要找位置稍微慢点
b)查找很快;

unordered_set、unordered_multiset、unordered_map

无序容器,采用哈希表的数据结构;
每个 篮子会挂0-n个元素;
增加篮子数量的目的是为了防止篮子后面挂的元素太多,从而影响查找速度;
自动指定元素位置,不需要使用者干预;

void func()
{
	unordered_set<int> myset;
	cout<<myset.bunket_count()<<endl;//8
	for (int i = 0; i <8; ++i)
	{
		myset.insert(i)//插入8个
	}
	cout<<myset.bunket_count()<<endl;//8
	myset.insert(8);//插入第9个
	cout<<myset.bunket_count()<<endl;//64
	
}

在元素较多时,篮子会自动增加;
容器有find尽量使用容器的find函数;

分配器

分配器概述

和容器紧密关联,一起使用;
内存分配器,扮演内存池的角色,通过大量减少对malloc()的调用,来节省内存,甚至还与一定的分配效率的提高;

void func()
{
	list<int,std::allocator<int>>mylist;
}
void func()
{
	list<int> mylist;//双向链表
	mylist.push_back(10);
	for(auto iter = mylist.begin();iter !=mylist.end(); ++iter )
	{
		cout<<*iter<<endl;
		int *p = &(*iter);
	}
	return;
	
}

经过测试,allocator这个内存分配器没有采用内存池的工作机制;

分配器的使用

allocator分配器是个模板,一般很少会直接用到;

其他分配器

迭代器

迭代器概念

迭代器是一个可遍历STL容器全部或者部分元素的对象(行为类似于指针的对象);
迭代器用来表现容器中的某一个位置,迭代器紧密依赖于容器,由容器提供;
iter指向容器中的元素,*iter代表元素内容;

void func()
{
	vector<int> iv = {100,200,300};//定义一个容器
	for (vector<int>::iterator iter = iv.begin(); iter != iv.end(); ++iter)
	{
		cout << *iter <<endl;
	}
	return;
}
迭代器的分类

分类依据:移动特性和迭代器上能够做的操作;
迭代器,根据其跳跃能力,每个分类是一个struct定义:
a)输出型迭代器:output iterator 向前写入
struct output_iterator_tag;
b)输入型迭代器:intput iterator 向前读取一次
struct intput_iterator_tag;
c)前向迭代器:forward iterator 向前读取
struct forward_iterator_tag;
d)双向迭代器:bidirectional iterator 向前和向后读取(list set map multiset multimap)
struct bidirectional_iterator_tag;
d)随机访问迭代器:random-access iterator 以随机访问方式读取 (array vector deque string)
struct random_access_iterator_tag;
迭代器之间有继承关系;

大多数容器里有一个::iterator迭代器类型,但也有没有的:stack、queue

迭代器能力:
a)输出型迭代器
一步一步往前走,并能够通过这个种类的迭代器来改写迭代器数据:
*iter
++iter
iter++
b)输入性迭代器
一次一个向前的方向读取元素,按照顺序一个一个返回元素值
*iter
iter++
++iter

==
!=
c)前向迭代器
继承自输入性迭代器
*iter
iter++
++iter

==
!=
d)双向迭代器
在前向迭代器基础上增加了反向迭代
–iter
iter–
c)随机访问迭代器
在双向迭代器基础上又增加了随机访问能力,也就增减某个偏移量,能够计算距离
iter[n]
iter+=n
iter-=n
n+iter
n-iter
< > <= >=

算法(函数)概述、内部处理

算法概述

理解为函数模板;
比如查找、排序等等;
算法的前两个形参一般是迭代器类型,用来表示容器中的元素的区间;

void func()
{
	list<int>mylist = {100,200,300}
	list<int>::iterator iterbg = mylist.begin();//第一个元素
	list<int>::iterator itercd = mylist.end();//最后一个元素的下一个位置
}

算法(iterbg, itercd)是一个前闭后开的区间 [begin(), end() )
前闭后开:
a)只要迭代器等于后面这个开区间,则迭代结束;
b)当两个形参一样,则是一个空区间;

算法是搭配迭代器使用的全局函数,算法与迭代器有关而与容器无关;
算法这种泛型编程方式,增加灵活性,但是缺失了直观性;某些数据结构(迭代器)和容器的兼容性不好;

算法内部的处理

算法会根据传递进来的迭代器来分析出迭代器种类,不同的迭代器算法会有不同的处理,要编写不同的代码来处理。这也就是stl内部要对迭代器进行分类的原因。

for_each
#include <algorithm>
void myfunc(int i)//参数类型时容器中元素的元素类型
{
	cout << i << endl;
}
void func()
{
	vector<int> = {10,20,30};
	for_each(myvector.begin(),myvector.end(),myfunc);
}

myfun是可调用对象,for_each不断迭代两个迭代器之间的元素,然后拿元素作为实参来调用myfunc;

算法区分迭代器种类,种类决定这算法的效率;

find
#include <algorithm>
void func()
{
	vector<int> = {10,20,30};
	vector<int>::iterator finditer = find(myvector.begin(),myvector.end()+3,400);
	if (finditer != myvector.end())
	{
		//找到了
	}
}

find在两个迭代器中不断寻找等于400的元素,finditer等于end()就找到了,否则就没找到;
当有成员函数和全局函数同时存在时,优先使用自己的成员函数,没有的话在考虑这些算法;

find_if

第三个参数不是元素,而是一个可调用对象

auto result = find_if(myvector.begin(), myvector.end(),[](int val)//lambdaa表达式是可调用对象
{
	if(val > 15)
		return true;//停止遍历
	return false;
}
if (result = myvecotr.end())
{
	cout <<"没找到"<<endl;
}
sort

对范围内的进行排序(从小到大);

void func()
{
	vector<int> myvector= {10,20,30};
	sort(myvector.begin(),myvector.end()+3);
	for(auto iter = myvector.begin(); iter != myvector.end(); ++iter)
	{
		cout <<*iter<<endl;
	}
}

如果要从大到小,可以自定义比较函数,返回值是布尔型;

void myfunc(int i, int j)//自定义函数做可调用对象
{
	return i>j;
}
void func()
{
	vector<int> myvector= {10,20,30};
	sort(myvector.begin(),myvector.end()+3,myfunc);//加一个参数
	
	for(auto iter = myvector.begin(); iter != myvector.end(); ++iter)//输出
	{
		cout <<*iter<<endl;
	}
}

list不支持sort算法(使用迭代器的算法),但是有自己的函数:

list<int> mylist = {10,20,30};
mylist.sort(myfunc);

for(auto iter = myvector.begin(); iter != myvector.end(); ++iter)//输出
{
	cout <<*iter<<endl;
}

排序一般只适合于顺序容器,其他容器有自己的固定位置算法;

函数对象

在 STL中一般和算法配合使用(比如上例中的myfunc);
这些函数对象主要用于服务于算法;
格式:函数名(参数列表);
标准库中也提供了一些可以使用的函数对象,使用之前要包含头文件functional;
函数对象分类:
算数运算类;
关系运算类;
逻辑运算类;
位运算类;
使用格式:
函数名<类型>()
括号代表产生系统产生的临时对象;

范例

自定义的

//A是个布尔型类对象
vector<int> myvector= {10,20,30};
A mya;
sort(myvector.begin(),myvector.end()+3,mya);
for(auto iter = myvector.begin(); iter != myvector.end(); ++iter)//输出
{
	cout <<*iter<<endl;
}

系统的

vector<int> myvector= {10,20,30};
sort(myvector.begin(),myvector.end()+3,greater<int>());//注意格式
for(auto iter = myvector.begin(); iter != myvector.end(); ++iter)//输出
{
	cout <<*iter<<endl;
}

适配器

基本概念

把一个既有的东西进行适当的改造,就构成了一个适配器;

容器适配器

类模板;
stack(单进单出)和queue(一边进一遍出)是deque(两边进出)的缩水版;

算法适配器
绑定器bind
void func()
{
	vector <int> myvector = {10,20,30};
	//统计出现次数
	int cishu = count(myvector.begin(),myvector.end(),80)
	cout<<cishu<<endl;
}
class A
{
public:
	bool operator()(int i)
	{
		return 40 < i;
	}
}
void func()
{
	vector <int> myvector = {10,20,30};
	//统计出现次数
	A mya;
	int cishu = count(myvector.begin(),myvector.end(),mya);//算法
	cout<<cishu<<endl;

	//bind(less<int>(),40,placeholder::_1)
	//auto = bf= bind(less<int>(),40,placeholder::_1);
	cishu =count_if(myvector.begin(),myvector.end(),bind(
	less<int>,//临时对象
	40,//第一个参数,被绑定的参数,对比前面greater的功能
	placeholders::_1//占位符,遍历迭代器里的元素
	)
	);
}

bind 函数适配器中的绑定器,应用绑定器后某些参数可以被绑住;
less<>()是个函数对象,这里是个临时对象;
count_if是个算法

迭代器适配器

reverse_vector反向迭代器;

void func()
{
	vector<int> iv ={10,20,30};
	for(vector<int>::reverse_iterator riter= iv.rbegin();riter != iv.rend(); ++riter)
	{
		cout <<*riter << endl;
	}
}

未归类知识点

函数调用运算符、function类模板

()就是“函数调用运算符”;
如果在类中重载了函数调用运算符(),就可以像使用函数一样来使用该类的对象了;对象(实参);

class biggerthanzero
{
public:
	int operator() (int value) const//返回0以上的数
	{
		if (value<0) return 0;
		return value;
		
	}
}
//
int i =200;
biggerthanzero obj;//定义对象,注意这里是没有括号的,如果有括号就变成了定义并初始化对象,调用了类的构造函数
int result = obj(i);//调用该对象

使用:
a)定义对象
b)使用该对象,()中加实参列表

结论:只要这个对象重载了“函数调用运算符”,那么这个类对象就变成可调用的;
这个类重载了(),就读了个新名字“函数对象”,这些对象的行为像函数一样。

不同调用对象的相同调用形式

调用参数和返回值相同,就叫“调用形式相同”。

标准库function类型介绍

function类模板;
要提供模板参数来表示该function类型能够表示的“对象的调用形式”;

#include <functional>
function <int(int)> f1 = echovalue;//函数指针
function <int(int)> f2 = obj;//类对象
function <int(int)> f3 = biggerthanaero();//用类名生成对象

//
f1(5);
f2(6);
f3(7);

//
map<string ,function <int(int)> >myoper = {
	{"ev",echovalue}};
//
muoper["ev"](12);//就是调用echovalue函数

万能引用universal reference

类型区别基本属性
万能引用 ——T&&

万能引用需要的语境:
a)使用函数模板;
b)发生了模板类型推断并且函数形参的样子是 T&&;
此时T&&就是万能引用;
右值引用和万能引用的区别:
a)哟之引用智能传递右值,否则报错;
b)万能引用做形参,可以传递左值,也能传递右值;传递进去什么值就是什么引用;

万能引用剥夺与辨认

const会剥夺万能引用,剥夺后会变成右值引用;
将设置类模板,在成员函数中使用T&&,成员函数不进行类型推断,不是万能引用;
在模板对应的函数使用T&&才是万能引用;

理解模板类型推断

查看类型推断结果

推断(推导)auto ;
使用boost库;

#include <boost/type_index.hpp>
template <typename T>
void myfunc(T & tmprv)
{
	using boost::typenindex::type_id_with_cvr;
	cout<<type_id_with_crv<T>().pretty_name()<<endl;
}

理解模板类型推断
#include <boost/type_index.hpp>
template <typename T>
void myfunc(const T & tmprv)
{
	using boost::typenindex::type_id_with_cvr;
	cout<<type_id_with_crv<T>().pretty_name()<<endl;
}
//main
myfunc(100);//T被推断为int类型,tmprv会被推断为const int &类型

T的类型不仅和和调用的实参有关,还和tmprv的类型有关;

指针或引用类型

是指针或引用类型,但不是万能引用

#include <boost/type_index.hpp>
template <typename T>
void myfunc(T & tmprv)
{
	using boost::typenindex::type_id_with_cvr;
	cout<<type_id_with_crv<T>().pretty_name()<<endl;//显示T类型
	cout<<type_id_with_crv<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}
//main
int i = 18;
const int j = i;
const int &k = i;

myfunc(i);//T类型int,temprv类型int &
myfunc(j);//T类型int,temprv类型const int &
myfunc(k);//T类型const int,tmprv类型const int &

结论:
a)若实参是引用类型,引用类型会被忽略,T不会变成引用类型;
b)当向引用类型的形参temprv传入const类型实参时,形参就会成为const &;
实参的const属性会成为类型模板参数T的类型推导的组成部分,所以不用担心在func中能够修改原来有const属性的实参;

#include <boost/type_index.hpp>
template <typename T>
void myfunc(const T & tmprv)//常量引用
{
	using boost::typenindex::type_id_with_cvr;
	cout<<type_id_with_crv<T>().pretty_name()<<endl;//显示T类型
	cout<<type_id_with_crv<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}
//main
int i = 18;
const int j = i;
const int &k = i;

myfunc(i);//T类型int,temprv类型const int & //temprv多了const
myfunc(j);//T类型int,temprv类型const int & //T少了const
myfunc(k);//T类型int,tmprv类型const int & //T少了const

a)若实参是引用类型,则引用类型会被忽略,T不会被推导为引用类型;
b)对于有const属性的实参,在T中推导后,T中的const属性没有了,因为模板函数的形参出现了const

#include <boost/type_index.hpp>
template <typename T>
void myfunc(T * tmprv)//指针
{
	using boost::typenindex::type_id_with_cvr;
	cout<<type_id_with_crv<T>().pretty_name()<<endl;//显示T类型
	cout<<type_id_with_crv<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}
//main
int i = 18;
const *pi = &i;

myfunc(&i);//T类型int,temprv类型int *
myfunc(pi);//T类型const int,temprv类型const int *

如果tmprv中没有const。则实参中的const会被带到T中。如果temprv有const,则T类型中不会有const

万能引用

根据传入的左值和右值,表现的类型也不同

#include <boost/type_index.hpp>
template <typename T>
void myfunc(T && tmprv)//万能引用
{
	using boost::typenindex::type_id_with_cvr;
	cout<<type_id_with_crv<T>().pretty_name()<<endl;//显示T类型
	cout<<type_id_with_crv<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}
//main
int i = 18;
const int j = i;
const int &k = i;

myfunc(i);//i是左值,T类型int &,temprv类型int & 
myfunc(j);//j是左值,T类型const int &,temprv类型const int & 
myfunc(k);//k是左值,T类型const int &,tmprv类型const int & 
myfunc(100);//100是右值,T类型int,tmprv类型int &&
传值方式

常规的传值方式

#include <boost/type_index.hpp>
template <typename T>
void myfunc(T tmprv)//常规传值
{
	using boost::typenindex::type_id_with_cvr;
	cout<<type_id_with_crv<T>().pretty_name()<<endl;//显示T类型
	cout<<type_id_with_crv<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}
//main
int i = 18;
const int j = i;
const int &k = i;

myfunc(i);//T类型int,temprv类型 int & 
myfunc(j);//T类型int,temprv类型 int & 
myfunc(k);//T类型int,tmprv类型 int & 

const属性没传递,因为对方是新副本

#include <boost/type_index.hpp>
template <typename T>
void myfunc(T tmprv)//常规传值
{
	using boost::typenindex::type_id_with_cvr;
	cout<<type_id_with_crv<T>().pretty_name()<<endl;//显示T类型
	cout<<type_id_with_crv<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}
//main
char mystr[] = "abc";
const char *const point = mystr;//第一个const表示point指向的目标的内容不能改变。第二个const表示ponit指向一个内容后不能再指向其他内容
myfunc(point);//T类型const char *,tmprv类型const char *,第二个const没了第一个cosnt保留

传递的是const char *或者cosnt char []则会被保留

数组做实参

数组名字代表数组首地址;

值传递情况下:

const char mystr[] = "abc";
myfunc(mystr);// T类型const char *,tmprv类型const char *

推断结果类似上面;

引用传递情况:

const char mystr[] = "abc";
myfunc(mystr);//T类型const char [3],tmprv 类型const char (&)[3]

T被推导成了数组类型,tmprv代表该数组的一个引用

函数名做实参

函数名相当于首地址,可以赋给一个函数指针;

void testFunc(){}
void myfunc(T tmprv)//值传递
{
	...
}

//mian
mufunc(testFunc);// T类型void(*)(void),tmprv类型void(*)void 函数指针

函数指针

void testFunc(){}
void myfunc(T &tmprv)//值传递
{
	...
}

//mian
mufunc(testFunc);// T类型void(*)void,tmprv类型void(&)void 函数引用
总结

a)推断中,引用类型实参的引用类型没用;
b)万能引用,实参为左值右值,结果不同;
c)按值传递的实参,传递给形参时const属性不起作用,若传递过去指针在另当别论;
d)数组或函数类型在类型推断中被看做是指针,除非函数模板的形参是个引用;

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

引用折叠规则

根据上面

int i = 18;//i是左值,i类型时int
myfinc(i);//i是左值,T = int &,tmprv = int &
myfunc(100);//100是右值, T = int, tmprv = int &&

将T和tmprv分别带回原函数后得到int &&& temprv = int & tmprv的结果;
这是引用折叠,是一条规则,引用塌陷;

void myfunc(int& &&temprv)第一组左值,第二组右值
折叠规则:如果任一个引用为左值引用,那么结果就为左值引用,否则就是右值引用;

引用的引用是非法的,即 &空格&,智能

转发、完美转发

把收到的参数和类型转发给其他函数——转发

//模板函数,把收到的参数和对应的类型不变的转发给其他函数
template<typename F,typename T1, typename T2>
void myFuncTemp(F f, T1 t1, T2 t2)//通过此函数转发 
{
	f(t1,t2);
}
void myfunc(int v1, int v2)
{
	++v2;
	cout<<v1+v2<<endl;
}
//
//int i = 50;
//myfunc(41, i);
myFuncTemp(myfunc,20,j);//将20和j转发给函数myfunc

修改myfuncTemp模板函数,让这个模板函数的参数能够保持给定实参的左值性(以及const);
使用万能引用: T&&实参的所有信息都传回到万能引用中去,从而让编译器推导出来函数模板最终的形态类型;

//模板函数,把收到的参数和对应的类型不变的转发给其他函数
template<typename F,typename T1, typename T2>
void myFuncTemp(F f, T1 && t1, T2 && t2)//通过此函数转发 
{
	f(t1,t2);
}
void myfunc(int v1, int v2)
{
	++v2;
	cout<<v1+v2<<endl;
}
//
//int i = 50;
//myfunc(41, i);
myFuncTemp(myfunc,20,j);//将20和j转发给函数myfunc

另外要注意实参和形参的左值右值,不能将左值绑给右值;

使用完美转发,可以接受任意类型实参的函数模板,并将其转发到目标函数,并且使接受到的完全相同:

std::forward

新函数,为转发而存在,要么返回左值,要么返回右值;
发挥作用的条件:调用模板函数,模板函数参数是万能引用类型,模板函数负责转发;
std::forward按参数原来的类型进行转发;

f(std::forward<T1>(t1),std::forward<T2>(T2));

a)实参如果是左值,到了形参中变成左值,到了函数处还是左值;
b)实参如果是右值,到了形参变成左值,会被强行转换成右值给函数;(核心功能:左值转右值)

void printInfo(int &t)//
{
	//
}
void printInfo(int &&t)//
{
	//
}
template <typename T>
void TestF(T && t)//万能引用
{
	printInfo(t);
	printInfo(std::forward<T>(t));
	printInfo(std::move(t));//左值转右值
}

mian

//mian
TestF(1);
printInfo(t);//1是右值,但是t本身是左值,调用第一个重载函数
printInfo(std::forward<T>(t));//1是右值,但是t本身是左值,但是被转换成了右值,调用第二个重载函数
printInfo(std::move(t));//左值转右值,调用第二个重载函数
TestF(i);
printInfo(t);//i是左值,t也是左值,调用第一个重载函数
printInfo(std::forward<T>(t));//i是左值,t也是左值,不用转换调用第一个重载函数
printInfo(std::move(t));//左值转右值,调用第二个重载函数

实参是左值还是右值这个信息会被保存到万能引用里面的T类型中去的;

forward和move之间的区别:
forward是强制把左值转成右值,但如果就是左值则不处理;需要万能引用进行配合才能万能转发;
move无条件强制类型转换;

auto类型推断、应用场合

std::forward补充

类似move的用法,但是要使用<>明确要转换的类型

int ix = 12;
int &&def = std::move(ix);//将左值ix强行转换成右值后可以作为右值赋给右值引用
int &&def = std::forward<int>(ix);//也可以,<int>代表要转成右值,<int &>代表要转成左值
auto常规类型转换

auto用于变量的自动类型推断:在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型,而不需要显式指定类型;
特点:
1)auto自动类型推断发生在编译器,多以不会影响执行期间的性能;
2)auto定义变量必须立即初始化,这样编译器才能推断它的实际类型;
3)使用灵活,可以和指针、引用和const一起使用;

auto和函数模板推断非常类似,auto推断出来是一个具体类型;

#include <boost/type_index.hpp>
template <typename T>
void myfunc(T& tmprv)//常规传值
{
	using boost::typenindex::type_id_with_cvr;
	cout<<type_id_with_crv<T>().pretty_name()<<endl;//显示T类型
	cout<<type_id_with_crv<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}
//mian
int i = 18;
const int j = i;
const int &k = i;
myfunc(i);//T = int ,tmprv = int &
myfunc(j);//T = const int,tmprv = const int &
myfunc(k);//T = const int ,tmprv =  const int &

auto
a)传值方式


传值方式的auto会抛弃引用类型和const等限定符;


auto x = 27;//x = int ,auto = int自动推断
cosnt auto x2 = x;

b)指针或者引用类型但不是万能引用


不会抛弃const等限定符,但是会抛弃引用


const auto &xy = x;//xy = const int &, auto = int
auto xy2 = xy;//xy2 = int, auto = int 

传值方式:引用类型会被抛弃,cosnt属性被抛弃,对方看成新副本

auto &xy3 = xy;//xy3 = const int & , auto = const int

引用会被丢弃,但const属性会被保留

auto y = new auto (100);// y = int *, auto = int *这里是指针

auto 可以用于new操作符

const auto *xp = &x; // xp = const int * ,auto = int 
auto *xp2 = &x;//xp2= int *, auto = int
auto xp3 = &x;//xp3 = int *,auto = int *

xp3没有声明为指针,但是也推导成了指针类型;

c)万能引用类型

auto && wnyy1 = x;//auto = int &, wnyy1 = int & 

x是左值,结果和函数模板类型推断的结果一样;同样出现了引用折叠

auto && wnyy2 = x2;//auto = const int &, wnyy2 = const int &

x2带const属性

auto && wnyy3 = 100;//auto = int , wnyy3 = int &&

100是右值,wnyy3右值引用

auto针对数组和函数的判断
const char mystr[] = "abc";
auto mystr = mystr;//const char *
auto &mystr2 = mystr;//const char (&)[14]

int a[2] ={1,2};
auto  aauto = a;//aauto = int *,auto - int *

a是首地址,aauto应该是指针类型

void myfunc3(double,int){}

//
auto tempf = myfunc3();//auto = void(*)(double,int)函数指针
auto &temp2 = myfunc3();//auto = void(&)(double,int)引用
auto类型std::initializer_list的特殊判断
auto x = 10;//x = int
auto x{40};//x = int
auto x = {30};

x = std::initializer_list类型;
新类型,表示某种特定的值的数组;
这种推断只适用于auto,不适合模板类型,其他差不多;

auto不适合的场合

a)不能用于函数传参,如void func(auto i)
b)普通成员变量不可以(static const可以,但是必须类内初始化)

auto使用场合举例
std::map<string,int> mymap;
mymap.insert({"abc",20});
...

std::map<string,int>::iterator iter;
for(iter = mymap.begin(); iter !- mymap.end(); ++iter)
{
	cout <<iter->first<<iter->second<<endl;
}

用auto后

std::map<string,int> mymap;
mymap.insert({"abc",20});
...

for(auto iter = mymap.begin(); iter !- mymap.end(); ++iter)
{
	cout <<iter->first<<iter->second<<endl;
}

auto作为自动推断迭代器类型,可以简化代码;

decltype

含义和举例

用于推导类型:对于一个给定的变量名或者表达式,能推断出对应的类型;
又想推断又想不进行初始化化;
1)发生在编译阶段;
2)并不会推断表达式的值;

const int i = 0;
const int &iy = i;
auto j1 = i;//j1 = int:传值方式推断中引用和cosnt都会被抛弃
decltype(i) j2 = 15;//j2 = const int :decltype中变量的const属性会被返回
decltype(iy) j3 = j2;//j3 = const int & :decltype中变量的引用const属性都会被返回
decltype括号是变量
class CT
{}
//
decltype(CT::i) a;//直接引用成员变量,a = int
CT tmpct;
decltype(tmpct) tmpct2;//tmpct2 = CT
decltype (tmpct2,i) mv = 5;//mv = int
int x = 1, y = 2;
auto &&z = x;//万能引用,auto = int &, x = int &
decltype(z) && h = y;//引用折叠,折叠成了左值, int & &&h = y;
decltype括号是非变量

会返回表达式的结果对应的类型

decltype(8) kkk = 5;//kkk = int
int i = 0;
int *pi = &i;
int &yi = i;
decltype(iy +1) j;//j = int
decltype (pi) k;//k = int*
*pi = 4;
decltype(*pi) k2 = i;//k2 = int &,

pi是指针pi所指向的对象,而且能够给这个对象赋值,所以pi是个左值;如果表达式结果能够作为左值,那么decltype返回的就是个引用,类似int &这样的结构;

decltype ((i)) iy3 = i;

如果在变量名外额外加一层括号,那么就会被当做一个表达式,所以最后会被当成左值,变成 int &;decltype((变量))的结果肯定是引用;

函数的情况:

不用调用函数但是可以返回函数返回类型

int testf(){
retuen 14;
}
decltype(testf()) tmpv = 14;//tmpv的类型就是函数返回的类型

decltype(testf) tmpv2;//tmp2 = int(void) 这个有返回类型,有参数类型,代表一种可调用对象

标准库function类模板

function <decltype(testf)> ftmp = testf;

声明一个函数类型,用来代表一个可调用对象;所代表的的是 int(void )

主要用途
应付可变类型

解决模板偏特化的问题(如果容器是const那么其迭代器也必须定义成cosnt的才行)
使用decltype可以自动生成和容器属性相同的迭代器,不用手动再设置

//typename T::iterator iter
decltype(T().begin()) iter;

T().begin()没有生成临时对象但是却能够返回一个和容器类型相同的迭代器类型;

class A
{
int func() cosnt{}
}

//
A().func();//生成了一个临时对象,但是由于没接又析构了

(const A()).func();//和上面一样

decltype(A().func()) aaa;//aaa = int 有生成临时对象的结果但是没有这个过程(没有构造没有析构),并且保留又func的属性cosnt int

通过变量表达式抽取变量类型
vector<int> ac;
ac.push_back(1);
ac,push_back(2);
vector<int>::size_type mysize = ac.size();
cout << mysize <<endl;//查看ac的元素个数

decltype<(ac)::size_type mysize2 = mysize;//返回ac类型
cout << musize2 <<endl;
typedef decltype(sizeof(0)) size_t;
//typedef unsigned int size_t;
size_t abc;
abc = 1;
auto结合decltype构成返回类型后置语法
auto func(int a, int b ) -> int(...)
auto add(int i, int k) ->decltype(i + k)
{
	return i + k; 
}
auto &tf(int &i)
{
	return i;
}
double tf(double &d)
{
	return d;
}
template <typedef T>
auto FuncTemp(T &tv) -> decltype(tf(tv))
{
	return tf(tv);
}

//
int i = 19;
FuncTmp(i);//调用第二个,19

double d =28.1f;
FuncTmp(d);//调用第三个,28.1f 

auto在这里不是自动推断类型的含义,只是返回类型后置语法的组成部分

decltype(auto)

c++14
用于函数返回类型;

template <typename T>
T & mydouble(T & vi)
{
	v1 *= 2;
	return v1;
}
//
int a= 100;
mydouble(a) = 20;//返回int &
a;//20

不能换成 auto mudouble(T &v1)因为auto会抛弃引用类型
可以使用decltype(auto):

decltype(auto) mydouble(T &v1)

auto为要推导的类型,decltype带边推断的过程;

int x = 1;
const int &y = 1;
auto a = y;
decltype(auto) a2 = y;//a3 = cosnt int &

auto 丢掉的const,decltype能捡回来

int i = 10;
decltype((i)) iy3 = 10;//多括号时变量变引用

decltype(auto) tf1()
{
	int i = 1;
	return i;
}
decltype(auto) tf2()
{
	int i = 1;
	return (i);//这里会返回引用,但是i会随着函数消失,导致出错
}

可调用对象、std::function、std::bind

可调用对象
函数指针
int myfunc(){}
void (*pmf)(int)  = &myfunc;//定义一个函数指针pmf
pmf(15);//使用函数指针调用函数
有operator()成员函数的类对象(仿函数)

仿函数:类似函数,通过在类中重载()运算符实现,又称为能行使函数功能的类;

class TC
{
	void operator()(int tv){}
}

//
TC tc;
tc(20);

调用的是()操作符,也是个可调用对象,等价于tc.operator()(20);

可被转换为函数指针的类对象
class TC2
{
using tfpoint = void(*)(int);
static void mysfunc(int tv)
{

}
operator tfpoint(){return mysfunc;}
}
//
TC2 tc2;
tc2(50);

先调用tfpoint,在调用operator返回成员函数指针,等价于tc2.operator TC2::tfpoint()(50)

类成员函数指针
TC tc;
void (TC::*myfpoint)(int) = %TC::pfunc;//类成员函数指针变量myfpoint定义并初始化
(tc.*myfpointpt)(68);//也是一个可调用对象

总结

a)看成对象
b)对其使用()运算符

std::function(可调用对象包装器)

不能装类成员函数指针,其他可调用对象能装;
std::function类模板特点:就是能够通过给指定模板参数,它就能够用统一的方式来处理函数;

绑定普通函数
std::function<void(int)> f1 = myfunc;//
f1(100);//等价于myfunc(100)
绑定类静态成员函数
static int stcfunc(int tv)
{
	
}
//
std::function<int(int)> fs2 = TC::stcfunc;
fs2(100);
绑定仿函数
TC tc3;
std::function<void(int)> f2 = tc3;
tc3(100);
std::bind绑定器

能够将对象和相关参数绑在一起,绑定完之后可以直接调用;
std::bind(待绑定的函数对象/函数指针/成员函数指针,参数绑定值,)

void myfunc(int x, int y, int z){}

//
auto bf1 = std::bind(myfunc,10,20,30);//auto不关系bind返回的类型,其实bind返回的是一个仿函数对象
bf1();//执行myfunc函数,三个参数已经被绑定

auto bf2 = std::bind(myfunc,placeholders::_1,placeholders::_2,30);
bf2(10,20);//表示myfunc的第三个参数为30.第一个第二个参数分别由调用bf2的第一个第二个参数决定

std::bind(myfunc,placeholders::_1,placeholders::_2,30)(10,20);//直接调用
void myfunc2(int &x, int &y)
{
	x++;
	y++
}

//
int a = 2;
int b = 3;
auto bf2 = std::bind(myfunc,a,placeholder_1);//auto不关系bind返回的类型,其实bind返回的是一个仿函数对象
bf2(b);

结果 a= 2, b= 4;这说明bind是传递的,所以a才没有变,没有bind的b使用引用传递会变化;

class CT
{
void myfunpt(int x, int y)
{
	m_a = x;
}
}

//
CT ct;
auto bf5 = std::bind(&CT::myfunc,  ct,  std::placeholder::_1,std::placeholders::_2);
bf5(10,15);//m_a仍然为0

第一个参数是类成员 函数,第二个参数是类对象(调用就必须写),第三个第四个是函数参数;
上行第二个参数ct,会导致调用拷贝构造函数时生成了CT临时对象,作为bind的返回值,后续的myfunc调用修改的是临时对象的m_a值,而不是真实对象ct的m_a值;

CT ct;
auto bf5 = std::bind(&CT::myfunc,  &ct,  std::placeholder::_1,std::placeholders::_2);
bf5(10,15);//m_a为10

如果ct加一个&,就不生成临时的CT对象了,后续myfunc调用修改的时ct对象的m_a值;

和function一起使用:

std::function(void(int,int)) bfc = std::bind(&CT::myfunc,  ct,  std::placeholder::_1,std::placeholders::_2)
bf5(10,15);//m_a为10
CT ct;
std::function<int &()> bf7 = ats::bind(&CT::m_a,ct);
bf7() = 60;

把成员变量当函数一样绑定,绑定的结果就在std::function(void(int &(void)))里保存,可调用对象:

lambda表达式,for_each,find_if

lambda

也是一种可调用对象;
定义了一种匿名函数,并且可以捕获到一定范围内的变量;

auto f = [](int a) -> int{return a+ 1};//一定加分号
cout << f(1) <<endl;

特点:
a)匿名函数,可调用的代码单元,未命名的内联函数;
b)有一个返回类型,一个参数列表,一个函数体
c)与函数不同的是,可以在函数内部定义,这个是常规函数做不到的;

格式:
[捕获列表](参数列表)->返回类型 (函数体)
返回类型有时也可省略;
没有参数时参数列表也能省略;
这种格式auto f1= {return 1};在rn 语法上也是正确的
[]和{}不能省略;
可以不返回任何类型,此时就是void;
末尾分号不能省略;

捕获列表

通过捕获列表捕获一定范围内的变量;
a)[]不捕获任何变量,但是静态函数例外;

int i = 9;
auto f1 = []{
	return i;//报错
}

b)[&]捕获外部作用域中所有变量,并作为引用在函数体中使用

int i = 9;
auto f1 = [&]{
	return i;
}

c)[=]捕获外部作用域中所有变量,并作为副本在函数中使用,可以使用但是不能赋值;

d)[this]一般用于类中,捕获当前类中this指针,让lambda表达式有和当前类成员函数相同的访问权限;
(无论是[=][&][this]都能打到访问类成员的目的;)

e)如果是多个变量,则在[]中 彼此之间用,分隔;
[&变量名]:按引用捕获变量名代表的变量,同时不捕获其他变量(捕获什么写什么);

f)[=,&变量名]按值捕获所有外部变量,但按引用捕获&中所指的变量,这个=必须写在开头;

g)[&,变量名]按引用来捕获所有外部变量,但按值来捕获变量名所代表的的变量;

lambda表达式延迟调用易出错细节分析
int x = 5;
auto f = [=]
{
	return x;
}
x= 10
cout << x<<endl;

认为应该是10但输出是5,因为[=]是按值捕获的;
解决办法:[&]引用绑定

lambda表达式中的mutable
int x= 5;
auto f = [=]()mutable//此时()不能被省略
{
	x = 6;
	return x;
}

加了mutable则变得可以修改了;

lambda表达式的类型及储存

闭包类型:函数中的函数;
f本质上是表达式创建时运行时期的对象;
可以认为是带有operator()的类类型对象;
std::function和std::bind可以用来保存和调用lambda表达式;

std::function<int(int)> fc2 = std::bind(
	[](int tv){
		return tv;
	},
	16
);
cout<< fc2(15)<<endl;//输出16,因为16已经被绑到lambda表达式里了

bind第一个参数是函数指针,第二个参数开始是真正的函数参数
不捕获任何变量的lambda表达式,可以转换成一个普通的函数指针;

using functype = int(*)(int);//定义一个函数指针类型
functype fp = [](int tc)(return tv);
cout <<fp(17) <<endl;//和调用函数一样
语法糖

一种便捷写法
基于语言现有特性构建出的方便的东西,没有增加功能

int a[5];
a[0]=1;//便捷写法
*(a+1)=3;
lambda表达式再演示和优点
for_each
#include <algorithm>

vector<int> myvector = {10,20,30};
for_each(myvector.begin(),myvector.end(),myfunc);

将迭代器区间的元素分别传入函数;

#include <algorithm>

vector<int> myvector = {10,20,30};
int isum = 0;
for_each(
	myvector.begin(),myvector.end(),[&isum](int val){ 
		isum += val;
		cout << val <<endl;
	}
)

for_each能把返回值当做第一个参数参入lambda表达式

find_if

查找由第三个参数也是函数对象(lambda表达式)决定的内容;

vector<int> myvector = {10,20};
auto result = find_if(myvector.begin(),myvector.end(),[&isum](int val){ 
		cout << val <<endl;
		if(val >15)
			return false;//当大于15时则停止遍历
		return false;
		}
		);
if(result == myvector.end())
{
	cout << "没找到"<<endl;
}

只要返回false,那么就不停进行遍历;返回true停止遍历;

lambda表达式捕获模式的陷阱

捕获列表中的&

捕获外部作用域中所有变量;
这样会导致表达式包含绑定到局部变量的引用;

void myfunc()
{
	srand((unsigned)time(NULL));
	int tempvalue = rand() % 6;//产生0-5之间的数
	gv.push_back(
		[&](int tv){
			if(tv %tempvalue == 0)//如果tv是tempvalue的倍数
			{
				return true;
			}
		}
	)
}

//
myfunc();
cout<< gv[0](10)<<endl;

在函数执行完之后,函数中的局部变量已经被回收了,如果再调用lambda表达式会用到已经回收的局部变量导致出错;
引用捕获方式超出范围的情形也叫做“引用悬空”,采用按值捕获的防止可以解决此问题;

形参列表中使用auto
成员变量的捕获问题
class AT
{
int m_tmpvalue = 7;
void addItme()
{
	gv.push_back(
		[=](int tv){
			if(tv %tempvalue == 0)//如果tv是tempvalue的倍数
			{
				return true;
			}
		}
	)
}
//
AT *pat = new AT();
pat -> addItem();
cout<<gv[0](10)<<endl;

lambda表达式执行正确与否,取决于pat对象是否存在;
捕获,值针对于在创建lambda表达式的作用域内可见的非静态局部变量;
但tempvalue并不是静态局部变量(成员变量),成员变量是不能被捕获到的;
[=]捕获到的是this指针值;即 this -> tempvalue;
所以还要依赖于对象pat,当对象被删除则this指针也不在了;

class AT
{
int m_tmpvalue = 7;
void addItme()
{
	auto tempvalueCopy = m_tempvalue;
	gv.push_back(
		[tempvalueCopy](auto tv){
			if(tv %tempvalueCopy == 0)//如果tv是tempvalue的倍数
			{
				return true;
			}
		}
	)
}

将成员变量拷贝一份副本将此副本传入lambda表达式中,这样就不依赖于对象了;

广义lambda捕获

c++14;

class AT
{
int m_tmpvalue = 7;
void addItme()
{
	//auto tempvalueCopy = m_tempvalue;
	gv.push_back(
		[abc = m_tempvalue](auto tv){
			if(tv %abc == 0)//如果tv是tempvalue的倍数
			{
				return true;
			}
		}
	)
}
静态局部变量

捕获不包括静态局部变量(不需要捕获),但是可以在lambda表达式中使用,持续时间一直到程序结束;

void myfunc()
{
	srand((unsigned)time(NULL));
	static int tempvalue = rand() % 6;//产生0-5之间的数
	gv.push_back(
		[&](int tv){
			if(tv %tempvalue == 0)//如果tv是tempvalue的倍数
			{
				return true;
			}
		}
	)
}

对于静态变量的使用类似引用捕获的效果;

可变参数函数、初始化列表、省略号形参

可变参数

能接受非固定参数个数的函数时可变参数函数;
使用initializer_list标准库类型,前提条件是所有实参类型相同;

initializer_list

如果类型相同,个数未知,就可以使用initializer_list类型形参来接收;
把initializer_list理解成某种类型的数组;这个类模板里指定的类型模板参数就是数组里数据的类型;

initializer_list<int> array;//数组
initializer_list<int> array2 = {10,20,30};//数组
//里面的元素是常量
void printvalue(initializer_list<int> tmpstr)
{
	for (auto beg = tmpstr.begin();beg != tmpstr.end() ;++beg)//迭代器
	{
		cout <<(*beg).c_str()<<endl;//打印元素
	}
	cout << tmpstr.size()<<endl;//元素个数
}
//
printvalue({"aa","bb","cc"});//要传递序列,必须放到花括号里传递

{}是一种比较通用的初始化方式;

拷贝和赋值一个initializer_list对象,不会拷贝列表中的元素。原来对象和拷贝或者赋值出来的对象共享表中的元素。

initializer_list <string> myarray3 = {"aa","bb","cc"};
initializer_list <string> myarray4(myarray3);
initializer_list <string> myarray5;
myarray5 = myarray4;

这三个对象的地址不同,但是都指向的内容是相同的;不会拷贝但是会共享;

初始化列表做构造函数参数:

class CT
{
public:
	CT(const initializer_list <int> &tmpvalue )
	{
	}
}

//
CT ct1 = {10,20,30,40};//隐式类型转换
CT CT1{10,20,30,40};

如果要禁止的话,就加一个explicit禁止隐式类型转换;
不使用等号可以直接赋值,无需转换;

省略号形参(…)

无法正确处理类对象,能处理简单的int , char *;
虽然参数数量不固定,但是函数的所有参数是储存在线性连续的栈空间的;
而且带省略号的可变参数函数必须至少要有一个普通参数,就可以通过这个普通参数来寻址后续的所有可变参数的类型以及值;
使用头文件 #include “stdarg.h”

萃取技术概念

概述

泛型编程,在stl的实现源码中,这种类型萃取技术用的比较多;
https://en.cppreference.com/w/cpp/types查看类型萃取接口;
萃取信息:
1.类型种类;
2.复合类型种类;
3.类型属性
4.支持的操作
等等

范例

通过萃取接口中的value值为true,就能萃取出很多有用信息;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值