《EssentialC++》笔记1—泛型编程风格

  本文主要供笔者复习使用,所以很多地方写的很简略,还请见谅。



  STL主要由两种组件构成,一是容器,包括vector、list、set、map等类.
  另一组组件是用以操作这些容器的所谓泛型算法 包括find() sort() replace() merge()等.
  vector和list是所谓的顺序容器 我们主要在顺序容器上进行所谓的迭代操作.
  map和set是关联容器。关联容器可以让我们快速查找到容器中的元素值
  map是一组key/value组合,key是关键字,用于查找,

  所谓set,其中仅含有key。我们对它进行查找操作。

  如果想建立一个索引表,我们需要减检查需要插入的key是否在set中,如果不在则插入。

  这些算法之所以称为泛型,是因为它们和它们想要操作的元素的类型无关。

  泛型算法系使用的是function template技术。

  我们想实现一个find函数,让他对任何类型的vector和arr都能生效

//利用指针和模板来实现这一要求
template <typename T>
T* find(const T* first, const T* last, const T& value)
{
	assert(first && last);
	while (first < last)
	{
		if (*first == value)
			return first;
		first++;
	}
	return 0;
}

  为了给vector也适配,需要vector中first和last的地址,我们封装成一个函数。

template <typename T>
inline T* begin(const vector<T> &vec)
{
	return vec.empty() ? 0 : &vec[0];
}
template <typename T>
inline T* end(const vector<T>& vec)
{
	return vec.empty() ? 0 : &vec[vec.size()];
}

  我们还想让这个find函数能够处理list类,但是不幸的是list类中的元素不是连续存储的,所以它的语法显著的和上面的find函数不同。

  但是C++提供了新的东西,在底层指针的行为之上提供一层抽象,取代程序原本的“指针直接操作”方式。我们把底层指针的处理通通放在此抽象层中,让用户无需直接面对指针操作。

3.1 了解泛型指针(Iterator)

  标准库提供了iterator class,这层抽象能够使的如果first和last都是iterator class object,iterator的百度翻译是迭代器,可以说是非常形象了,可以这样写:

while (first != last)
{
    cout << *first << ' ';
    ++first;
}

  这就好像把first和last当做指针一样。
  唯一的差别在于其运算符*、!=、++都是由iterator class内相关的操作符重载函数提供的。
  对于list iterator来说,其递增函数会沿着list的指针前进到下一个元素,对vector iterator而言,其递增函数前进至下一个元素的方式,是将目前的地址加上一个元素的大小。

  如何取得iterator呢,每个标准容器都有一个begin()函数和一个end()函数,范围一个iterator指针,指向第一个元素和最后一个元素的下一个位置。

  如何定义iterator?

vector<string> svec;
vector<string>::iterator iter = svec.begin();

  改进find函数:

template <typename IteratorType, typename elemtype>
IteratorType find1(IteratorType first, IteratorType last,
 const elemtype& value)
{
	while (first != last)
	{
		if (*first == value)
			return first;
		++first;
	}
	return last;
}
int main()
{
	const int size = 8;
	int a[size] = { 1, 1, 2, 3, 5, 8, 13, 21 };
	vector<int> ivec(a, a + size);
	list<int> ilist(a, a + size);
	int* p = find1(a, a + size, 1024);
	if (p == a + size)
	{
		cout << "find fail\n";
	}
	vector<int>::iterator it;
	it = find1(ivec.begin(), ivec.end(), 1024);
	if (it == ivec.end())
	{
		cout << "vector find fail\n";
	}
	list<int>::iterator iter;
	iter = find1(ilist.begin(), ilist.end(), 1024);
	if (iter == ilist.end())
	{
		cout << "list find fail\n";
	}
	return 0;
}

3.2 所有容器的共同操作

以下为所有容器类(以及string类)的共同操作

  • ==和!=运算符,返回true或false
  • =运算符,将某个容器复制给另一个容器
  • empty()容器内无元素时返回true 否则返回false
  • size()返回容器内目前元素的个数
  • clear()删除所有元素。
  • begin()返回一个iterator,指向指向容器的第一个元素。
  • end()返回一个iterator,指向容器最后一个元素的下一个位置。
  • insert()将单一或某个范围内的元素插入容器内。
  • erase()将容器内的单一元素或某个范围内的元素删除。
  • insert()和erase()的行为视容器本身是顺序性容器还是关联容器而有所不同。

3.3 使用顺序性容器

  vector list deque

  • vector是一个动态数组

  • list是一个带头双向循环链表

  • deque双向队列

  deque对于最前端插入和最后端插入元素和删除元素效率较高。

  要使用顺序容器,首先要引头文件

#include <vector>
#include <list>
#include <deque>

定义顺序容器的方式

  1. 产生空的容器:

    list<string> slist;
    vector<int> ivec;
    
  2. 产生特定大小的容器。每个元素都以其默认值为初值

    list<int> ilist(32);
    vector<double> dvec(24);
    
  3. 产生特定大小的容器,并为每个元素都指定初值。

    vector<int> ivec(32,-1);
    list<string> slist(24,"unassigned");
    
  4. 通过一堆iterator产生容器。这对iterator一般用来标示一整组作为初值的元素的范围。

    int a[9] = { 1, 1, 2, 3, 5, 8, 13, 21 };
    vector<int> ivec(a, a + 8);
    
  5. 根据某个已存在的容器产生新容器。

    list<string> slist;
    list<string> slist2(slist);
    

顺序容器的插入和删除

  • push_back()
  • push_front()
  • pop_back()
  • pop_front()
  • front()
  • back()
    作用就不说了,在数据结构中都有提到。

  insert的四种用法(配合find使用)

  • iterator insert(iterator pos, elemtype val);
    //将val插入到pos位置之前 返回指向这个元素的iterator
    
  • void insert(iterator pos, int count, elemtype value);
    //在pos位置之前插入count个元素 这些元素的值都和value相同
    
  • void insert(iterator1 pos, iterator2 first, iterator2 last);
    //在pos位置之前插入[first, last)所标示的各个元素
    
  • iterator insert(iterator pos);
    //在pos位置之前插入元素,元素的初值为其所属类型的默认值
    

  erase的两种用法

  • iterator erase(iterator pos);
    //删除pos所指的元素
    
  • iterator erase(iterator first, iterator last);
    //删除[first, last)范围内的元素。
    

    返回的iterator指向被删除的最后一个元素的下一个元素的位置。

    list不支持iterator的偏移运算,也就是说不能这样写

    slist.erase(it1, it1 + num);
    

3.4 使用泛型算法

#include <algorithm>

四种泛型搜索算法:

  1. find()用于搜索无序集合里是否存在某值,搜索范围由iterator[first , last)标出。如果找到目标,find()会返回一个iterator指向该值,否则返回一个iterator指向last。
  2. binary_search()二分查找,搜索范围同上,如果找到目标返回一个true 找不到则返回false
  3. count()返回数值相符的元素数目。
  4. search()对比某个容器中是否存在某个子序列,如果存在则返回一个iterator指针指向子序列起始处;否则返回一个iterator指针指向容器末尾。

  函数对象实现了我们本来可能要以独立函数加以定义的事务,主要是为了效率。

  • 六个算术运算符:

    plus<type> minus<type> negate<type> 
    multiplies<type> divides<type> modules<type>
    
  • 六个关系运算符:

    less<type> less_equal<type> greater<type> 
    greater_equal<type> equal_to<type> not_equal_to<type>
    
  • 三个逻辑运算符

    logical_and<type> logical_or<type> logical_not<type>
    

使用它们要引头文件

#include <functional>

函数对象适配器

  函数对象适配器会对函数对象进行修改操作,binder adaper绑定适配器会将function object的参数绑定至某特定值,使二个操作数的函数对象转化为一个操作数的函数对象。

  标准库提供了两个binder adapter,bind1st会将指定值绑定至第一操作数,bind2nd则将指定值绑定至第二操作数。

  我们的需求是将用户的每个给定值与val进行比较,所以我们直接把less<int>与val绑定。

vector<int> filter(const vector<int>& vec, int val, 
less<int>& lt)
{
	vector<int> nvec;
	vector<int>::const_iterator iter = vec.begin();
	while ((iter = find_if(iter, vec.end(), bind2nd(lt, val))) 
	!= vec.end())
	{
		nvec.push_back(*iter);
		iter++;
	}
	return nvec;
}

  接下来消除filter()与vector的元素类型,以及filter()与vector容器类型的依赖关系,来让filter()更加泛型化。

template <typename InputIterator, typename OutputIterator,
	typename ElemType, typename Comp>
	OutputIterator filter(InputIterator first, 
	InputIterator last, OutputIterator at, 
	ElemType val, Comp pred)
{
	while ((first = find_if(first, last, bind2nd(pred, val)))
	!= last)
	{
		cout << "found value :" << *first << endl;
		*at++ = *first++;
	}
	return at;
}

int main()
{
	const int size = 8;
	int arr[size] = { 12,8,43,0,6,21,3,7 };
	vector<int> vec(arr, arr + size);
	int ret1[size];
	vector<int> ret2(size);
	cout << "test array :\n";
	filter(arr, arr + size, ret1, 8, less<int>());
	cout << "vector test :\n";
	list<int> l(arr, arr + size);
	list<int> ret3(size);
	filter(vec.begin(), vec.end(), ret2.begin(), 
	8, greater<int>());
	cout << "list test :\n";
	filter(l.begin(), l.end(), ret3.begin(), 
	8, less_equal<int>());
}

  取反适配器negator,它会对函数对象的真伪值取反。
  not1可以把一元操作符函数对象的真伪取反,not2可以把二元操作符的函数对象的真伪值取反。如果我们要找出大于等于10的元素,可以在while处这样修改:

while ((first = find_if(first, last, 
not1(bind2nd(pred, val)))!= last)

  练习:写一个泛型算法,给一个线性容器,返回一个线性容器,这个容器里有所有不小于给定值val的值。

template <typename OutputIterator, typename InputIterator, 
typename ElemType, typename cmp>
OutputIterator sub(InputIterator first, InputIterator last, 
OutputIterator ret, ElemType val, cmp Cmp)
{
	sort(first, last);
	if ( find_if(first, last, 
	not1(bind2nd(Cmp, val))) != last)
	{
		InputIterator iter = find_if(first, last, 
		not1(bind2nd(Cmp, val)));
		while (first != iter)
		{
			*ret++ = *first++;
		}
	}
	return ret;
}

int main()
{
	int arr[10] = { 5,6,8,9,4,3,2,1,10,7 };
	vector<int> l(arr, arr + 10);
	vector<int> ret(10);
	sub(l.begin(), l.end(), ret.begin(), 5, less<int>());
	return 0;
}

3.5 使用map

  map被定义为一对数值,其中key扮演索引的角色,通常是个字符串,另一个数值是value

#include <map>
map<string, int> words;
//输入key/value最简单的方式
words["hello"] = 1;
words[tword]会取出tword对应的value 
如果tword不在map内,它便会因此放到map中,并获得默认值0

  map对象有一个名为first的member,对应于key,有另一个名为second的member,对应于value。

  map的查找使用find函数,如果key已经在其中,会返回一个iterator,指向key/value形成的一个pair,反之返回end()。

  map的查找还可以使用count(),会返回某特定项在map内的个数。

  任何一个key值在map中最多只会有一份,如果需要存储多份相同的key值,就必须使用multimap。

3.6 使用Set

  Set由一群key组成。如果我们想知道某值是否存在于某个集合内,就可以使用set。

  • 创建set

    set<ElemType> s1(iterator i1, iterator i2);
    set<ElemType> s2(ElemType val);
    
  • 插入set

    s1.insert(ival);
    s2.insert(iterator1, iterator2);
    

3.7 使用Iterator Inserter

  直接使用泛型指针迭代可能容器空间不够会出问题,为此,STL提供了三个所谓的插入适配器,这些适配器让我们可以避免使用指针的运算符造成空间不够的情况。

  • back_inserter()
    

    会以容器的push_back()函数取代运算符。对vector容器来说,这是比较合适的inserter,传入back_insert()的参数就是容器本身。

  • inserter()
    

    会以容器的insert()函数取代运算符,inserter()函数接受两个参数,一个是容器,另一个是容器,另一个是一个iterator,指向容器内的插入操作的起点。

  • front_inserter()
    

    会以容器的push_front()函数取代运算符,这个只适用于list和deque

要使用这些适配器,要引头文件

#include <iterator>
//下面是使用插入适配器泛型指针的实例
int main()
{
	const int size = 8;
	int arr[size] = { 12,8,43,0,6,21,3,7 };
	vector<int> vec(arr, arr + size);
	int ret1[size];
	vector<int> ret2(size);
	cout << "test array :\n";
	filter(arr, arr + size, ret1, 8, less<int>());
	cout << "vector test :\n";
	list<int> l(arr, arr + size);
	list<int> ret3(size);
	filter(vec.begin(), vec.end(),
	 back_inserter(ret2), 8, greater<int>());
	cout << "list test :\n";
	filter(l.begin(), l.end(),
	 front_inserter(ret3), 8, less_equal<int>());
}

3.8 使用iostream Iterator

  C++提供了可以指向输入输出流的泛型指针 它的简单用法如下:
  思想和前面的iterator的使用一样,需要一个first和一个last来标记输入的范围。

istream_iterator<string> is(cin);
//将is定义为一个绑定至标准输入设备的istream_iterator
istream_iterator<string> eof;
//不为它指定istream对象 意味着它代表end_of_file
vector<string> test;
//把标准输入的东西先放到test里
copy(is, eof, back_inserter(test));
ostream_iterator<string> os(cout, " ");
//将标准输出绑定到os上 并且输出的间隔符是第二个参数" "

  从记事本读入字符串,然后写到另一个记事本里:

ofstream out_file("output_file.txt", ios_base::app);
//第二个参数表示不丢弃原有内容的基础上追加内容。
int main()
{
	ifstream in_file("input_file.txt");
	ofstream out_file("output_file.txt");
	if (!in_file || !out_file)
	{
		cerr << "! 打开文件失败!\n";
		return -1;
	}
	istream_iterator<string> is(in_file);
	istream_iterator<string> eof;
	vector<string> test;
	copy(is, eof, back_inserter(test));
	sort(test.begin(), test.end());
	ostream_iterator<string> os(out_file, "\n");
	copy(test.begin(), test.end(), os);
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值