【C++】Essential c++ 第三章学习笔记

Essential c++ 第三章

  这一章内容主要教你如何使用容器,以及如何脱离变量类型和容器类型来完成一个函数

1.前置知识

1.1 下标操作的实质

1.1.1 下标操作的介绍

  假设一个数组

int a[3]={1,2,3};

  a[2]是一种下标操作,代表取序列为2的数字。这种操作的实质是什么呢?

  那我们就从数组的实质开始介绍。数组实际上是内存上一段连续的空间,按照变量大小进行分割,由此产生的一种容器类型。

  如果我们尝试输出 :

cout<<a<<endl;

  实际上会输出数组a的首地址,包括数组在函数间进行传递实质上也是用这个首地址进行传递的。

  所以下标操作 a[2] 实际上是一种地址偏移运算和提领运算的组合,即
a[2] 等价于 *(a+2)

  这种地址偏移运算并不是单纯的地址数加2,而是考虑指针类型的偏移。比如假设a的首地址为1000,一些计算机中int为4位,那么执行 *(a+2) 操作之后,实际上地址指向的是1000+2x4 = 1008;

1.2 迭代器(泛型指针)

   迭代器也是一种地址的偏移运算,不过与1.1中的偏移运算不同,迭代器不是直接对内存地址的修改,而是抽象了一层。下层以内存表的形式定义好了下一个内存地址指向什么地方。迭代器进行增加后,便是读取这个内存表的下一个地址在什么地方,就决定了在哪个地方进行操作。

  迭代器与地址偏移操作相比,实际上是解决了泛型算法中,如果读取内存不连续的容器的数据的问题。比如容器list,list是储存数据时,内存是不连续的,随机存放的,各个元素直接通过前向指针和后向指针进行访问。因此,使用地址偏移操作,并不能够读取list的下一个元素,并且这种操作是不合法的。迭代器就能够通过简单的偏移操作,读取下一个元素的地址。迭代器的出现,为脱离容器类型而存在的算法的实现提供了可能。

1.3 函数对象

1.3.1 什么是函数对象

  函数对象实际上是在一个类中,将函数调用符 () 进行了重载。除了使用上非常像函数之外,更多的是,具有类的特性,因此与函数相比,多了三个优点:

  • 因为类可以实例化,因此函数对象可以当做变量进行定义
  • 因为类可以包含数据元素,因此函数对象可以增加中间储存变量,便于查看函数的状态
  • 使用函数对象在函数间进行传递,与函数指针相比,函数对象可以变成inline的,能够省去函数指针进行地址调用时候产生的额外消耗。函数对象运行效果更高。
1.3.2 函数对象的定义和使用方法

   这里定义一个打印容器元素的函数对象

template <typename inputIterator>
class myOutput
{
public:
	void operator()(inputIterator first, inputIterator last)
	{
		for (; first != last; first++)
		{
			cout << (*first) << endl;
		}
	}



};

  函数对象可以实例化再使用,也可以匿名使用,下面为两种用法的举例

定义了类myOutput,在里面重载了(),因此定义了类的实例a之后,使用运算符(),就可以当函数使用
	//1 这就是函数对象
	vector<int> ivec = { 1,2,3,8,5,6 };
	myOutput<vector<int>::iterator> a;
	a(ivec.begin(), ivec.end());
	cout << endl;

	//2 也可以通过下面这种方法,匿名调用函数对象
	myOutput<vector<int>::iterator>()(ivec.begin(), ivec.end());
	cout << endl;


1.3.3 stl函数调用函数对象的举例

  这里举例transform函数,transform函数可以对指定的两个容器进行指定的操作,并且输出到指定位置,因此transform函数具有五个参数

transform(
    t1.begin(), 
    t1.end(),
    t2.begin(),
    t3.begin(),
    multiply<int>()
    );
  • 前两个参数意义是对哪个容器进行操作,迭代器类型
  • 第三个参数意义是和谁进行操作,传入迭代器的首地址
  • 第四个参数意义是是操作完了以后存放到哪里,传入输出容器的迭代器首地址
  • 第五个参数意义是进行什么操作,是一个函数对象或者函数指针类型,用于描述两个容器之间要做什么。

  下面举例为使用两个容器进行乘法操作的例子

//定义
template<typename T>
class multiply
{
public:
	T operator()(T a, T b)
	{
		return a * b;
	}
};

//使用
vector<int> t2 = { 2,3,4,5 };
	vector<int> t3(4);
	transform(t1.begin(), t1.end(), t2.begin(),t3.begin(), multiply<int>());
	for (int i = 0; i < t3.size(); i++)
	{
		cout << t3[i] << endl;
	}

1.3.4 谓词

  返回值为bool的函数对象被称为谓词,在stl很多函数都有所应有,比如条件查找函数find_if。

  find_if函数具有三个参数

iterator find_if(iterator start,iterator end, condition);

  前面两个是要查找的数列的首末迭代器,第三个参数是一个函数对象。当数列中的元素送入函数对象,返回值为true时,返回这个元素的迭代器。

  下面举例为通过find_if 函数查找容器中n的倍数的数字。

//01 定义函数对象
template<typename num>
class multiple
{

public:
	int val;
	//因为需要指定要找谁的倍数,所以类中需要一个额外的变量
	
	//构造函数
	multiple(int val):val(val){}

	//函数对象
	bool operator()(num a)
	{
		return (a % val == 0);
	}
};

//02 使用函数对象
// 谓词-返回值为bool的函数对象,被称为谓词。
	//参数列表为一个变量时,叫做一元谓词,参数列表为两个变量时,叫做二元谓词,容器类很多函数都使用谓词作为参数传入
	//下面一个例子要介绍如果在一个数列中找到第一个4的倍数

	//02-1这个例子使用了含有构造函数的函数对象
	cout<<*(find_if(ivec.begin(), ivec.end(), multiple<int>(4)))<<endl;

	//02-2含有构造函数的函数对象的使用
	multiple<int> mul(4);//需要往构造函数里面加入一个数
	mul(8);//调用构造函数

	//02-3 匿名的
	multiple<int>(4)(8);//返回8是否为4的倍数

1.4 绑定适配器

  绑定适配器运行需要头文件

  一种绑定适配器的作用是减少函数对象的变量个数,让二元运算变成一元运算。c++中其实已经定义了很多个默认的函数对象,用来代替常用的运算符号,比如加减乘除、逻辑运算等等。而这些函数对象都是需要两个变量的,比如比较运算,而使用绑定适配器固定前一个数或者后一个数字,就变成了一元操作,让输入的变量与固定的变量进行比较,并返回bool

  • 绑定前一个数使用bind1st(val,function object);
  • 绑定后一个数使用bind2nd(function object,val);

  比如查找数列中大于5的变量,可以用find_if这么写

find_if(
    iter.begin(),
    iter.end(),
    bind2nd(greater<int>,5);
)

  另外一种绑定适配器是谓词运行结果结果取反的,not1对非二进制操作谓词取反,not2对二进制操作谓词取反,举例

 not1(bind2nd(greater<int>,5));

2.容器

  容器包含顺序容器list,vector,deque和关联容器map,set。是泛型算法操作的主要对象,用来储存各种数据的。

2.1 容器的共性操作

2.1.1 容器的定义方法

有五种定义方法

  • 产生空容器 vector a;
  • 指定大小 vector a(10);
  • 指定大小和默认值 vector a(10,0);
  • 截取某容器的一部分 vector a(ivec.begin(),ivec.end());
  • 复制 vector a(ivec);
2.1.2 插入操作

  三种插入操作-insert函数

  • iterator insert(iterator pos,typename val) 在某个地址前插入
  • void insert(iterator pos,int count,typename val) 在某个地址前插入count个数字
  • iterator insert(iterator pos,iterator start,iterator end) 在指定地址前插入一段序列

举例:

insert(it,5);

2.1.3 删除操作

  两种删除操作-erase函数

  • 删除某一个位置 iterator erase(iterator pos)
  • 删除某一组元素 iterator erase(iterator start,iterator end) 左删右不删

举例:

erase(it);

2.1.4 特殊操作
  • 在最后加入或者删除一个元素push_back()和pop_back()
  • 在前面加入或者删除一个元素push_front()/pop_front()
    备注:只有list和deque能用,list不能用
  • 读取最前或者最后的元素 front()/back()

举例:

vec.push_back(1);

2.1.5 其他操作
  • 容器判断是否相等==和!=
  • =容器赋值(assignment)
  • 判断容器是否为空empty()
  • 容器大小size()
  • 容器清空clear()
  • 容器查找 find() 返回值为符号要求的元素的迭代器

2.2 顺序容器

  顺序容器包括vector、deque和list三种。需要分别的头文件才能使用//

  vector储存地址连续,往容器最后插入元素效率高,如果往中间插入的话,需要将后面的元素全部重新复制,才能插入,效率很低。但是搜索起来非常快。适合用来保存数列

  list储存地址是随机的,因此往容器中任何一个位置插入和删除元素都很方便。但是由于存储随机,读取时候要全部遍历,非常耗时间。时候用来从档案中读取数据。

  deque储存地址连续,但是与vector不同,往容器最前端插入和删除数据效率最高。如果往容器前端插入元素,末端删除元素,选择deque是最好的。

2.3 关联容器

  需要分别的头文件才能使用
/

2.3.1 map

  map叫做字典,能够按照索引寻找数值,比如

map<string,value> imap;
string words;
cin>>words;
imap[words]++;
2.3.2 set

  set是一种关键词表,可以与map配合使用。比如用map统计一篇文章的词频,可以通过set设定不想统计的词,如果set中包含这个词,map就跳过,不加人其中。

3. 泛型算法

  泛型算法就是脱离变量类型和容器类型,来实现各自操作的算法

3.1 四种常用的泛型算法

  • 搜索无序列表中的某个元素 find()

    • iterator find(iterator start,iterator end,typename val)
  • 搜索有序列表中的某个元素 binary_search()

    • 只能用于有序集合的搜索,数列需要先按大小进行排序,排序这一过程需要由程序员进行
    • 比find更快,但是前提是你用的数列是排序过的
    • bool binary_search(iterator start,iterator end,typename val)
  • 搜索某个元素出现了几次 count()

    • int (iterator start,iterator end,typename val)
  • 搜索是否存在某个子序列 search()

    • iterator search(iterator1 start,iterator1 end,iterator2 start,iterator2 end)
    • 如果包含子序列返回第一个元素的iterator
    • 如果不包含子序列,返回end

3.2 应用举例-寻找容器中满足要求的子列


#include<iostream>
#include<vector>
#include<list>
#include<functional>
#include<algorithm>
using namespace std;

//01 方法一
vector<int> myFind1(const vector<int>& ivec, int val)
{
	vector<int> local_vec;
	vector<int>::const_iterator it = ivec.begin();
	vector<int>::const_iterator end = ivec.end();
	while ((it = (find_if(it, end, bind2nd(greater<int>(), val)))) != end)
	{
		local_vec.push_back(*it);
		it++;
	}
	return local_vec;
}

//02 方法二
vector<int> myFind2(const vector<int>& ivec, int val)
{
	//原始数列进行排序

	vector<int> local_vec(ivec);

	
	sort(local_vec.begin(), local_vec.end());


	
	vector<int>::iterator iter = find_if(local_vec.begin(), local_vec.end(), bind2nd(greater<int>(), val));

	local_vec.erase( local_vec.begin(),iter);
	return local_vec;

}
int main()
{
	vector<int> ivec1 = { 1,8,3,3,4,4,5,10,4,7 };
	//01 方法一,进行逐个的查找
	vector<int> dst_ivec1 = myFind1(ivec1, 5);
	for (size_t i=0; i < dst_ivec1.size(); i++)
	{
		cout << dst_ivec1[i] << endl;
	}
	cout << endl;


	//02 方法二,排序后删除不需要的部分
	vector<int> dst_ivec2 = myFind2(ivec1, 5);
	for (size_t i = 0; i < dst_ivec2.size(); i++)
	{
		cout << dst_ivec2[i] << endl;
	}
	return 0;
}


4. 容器设计不需要考虑容量大小的设计技巧

4.1 什么是iterator inserter

  使用iterator inserter把赋值符号取代为其他各种操作,可以不需要考虑容器的最大容量,也不需要提前设定容器大小。

  比如如果容器不初始化大小,使用它的迭代器作为输出容器会报错,但是定义了初始化大小就可能不够用,iterator inserter就是为了解决这个问题的

  需要头文件

4.2 iterator inserter有几种类型

  • back_inserter(vec)
    • 只有有push_back的容器才能使用
    • 以向容器后插入操作代替赋值符号
  • inserter(vec,vec.end())
    • 有两个参数,第一个是哪个容器,第二个是从哪开始插入
    • 以向容器指定点插入代替赋值符号
  • front_inserter(lst)
    • 只适用于list和deque
    • 以push_front操作代替赋值操作

4.3 iterator inserter使用举例

举例:

copy(src.begin(),src.end(),back_inserter(dst));

  通过这样写,dst容器进行复制时,不使用赋值符号,而改用push_back进行,这样dst就不需要提前设定大小了。

一个比较完整的例子:

  将上文中的filter函数的传入参数稍微修改了一下,输出容器使用了back_inserter,这样的话里面的赋值操作在程序运行时被改为push_back

#include<iostream>
#include<algorithm>
#include<vector>
#include<list>
#include<functional>
#include<iterator>
using namespace std;

template<typename inputIterator, typename outputIterator, typename valType, typename comp>
outputIterator filter(inputIterator first, inputIterator last, outputIterator at, valType val, comp pred)
{
	while ((first = find_if(first, last, bind2nd(pred, val))) != last)
	{
		cout << "发现了一个!" << *first << endl;
		*at++ = *first++;

	}
	return at;
}

int main()
{
	vector<int> ivec = { 1,4,5,2,6,7,8 };

	list<int> ilist = { 1,4,6,7,8,9,12,3,4 };



	//int dst_ivec[10];
	vector<int> dst_ivec;


	filter(ivec.begin(), ivec.end(), back_inserter(dst_ivec), 5, less<int>());
	cout << endl;

	filter(ilist.begin(), ilist.end(), back_inserter(dst_ivec), 5, greater<int>());




	return 0;
}



5. 来自标准输入输出和文件输入输出的迭代器操作

通过迭代器的方法读取标准输出输出和文件中的内容

  • 标准输入输出
#include<iostream>
#include<iterator>
#include<vector>
#include<algorithm>
using namespace std;
int main()

{
	vector<string> words;

	//输入迭代器
	istream_iterator<string> it(cin);
	istream_iterator<string> eof;

	//将输入迭代器内容放置到向量中
	copy(it, eof, back_inserter(words));

	//排序
	sort(words.begin(), words.end());
	
	ostream_iterator<string> os(cout, " ");

	copy(words.begin(), words.end(), os);

	return 0;
}


  • 文件输入输出
#include<iostream>
#include<iterator>
#include<vector>
#include<algorithm>
#include<fstream>
using namespace std;
int main()

{
	ifstream in_file("1.txt");
	ofstream out_file("2.txt");
	vector<string> words;

	//输入迭代器
	istream_iterator<string> it(in_file);
	istream_iterator<string> eof;

	//将输入迭代器内容放置到向量中
	copy(it, eof, back_inserter(words));

	//排序
	sort(words.begin(), words.end());

	ostream_iterator<string> os(out_file, " ");

	copy(words.begin(), words.end(), os);

	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值