3.1 指针的算术运算
函数find:给定一个储存任何类型数字的vector或者array,以及一个值,如果此值存在于vector或者array内,必须返回一个指针指向该值;反之则返回0,表示此值并不在vector或者array内。
任务1:将array的元素传入find(),而不指定array的类型
- 解法一:增加一个参数,用来表示array的大小
template <typename elemType>
elemType* find(const elemType *array, int size, const elemType &value)
实现:从0开始访问每个元素,依次处理至size-1个元素。
问题:传递给find()的array是以第一个元素的指针传入的,如何通过特定位置进行元素访问?
方法一:下标运算符[ ]
array[2];//返回array的第三个元素
template <typename elemType>
elemType* find(const elemType *array, int size, const elemType &value)
{
if (!array || size < 1)
return 0;
for (int ix = 0; ix < size; ++ix)
//可以对指针使用下标运算符
if (array[ix] == value)
return &array[ix];
return 0;
}
array是以第一个元素的指针传入find()中,通过下标[]运算符访问array的每个元素是通过array的起始地址加上索引值产生该元素的地址,然后该地址再被dereference以返回元素值实现的。
方法二:指针 *
*(array+2);//array+2产生的地址是array的地址加上两个整数元素的大小(4 byte)
template <typename elemType>
elemType* find(const elemType *array, int size, const elemType &value)
{
if (!array || size < 1)
return 0;
for (int ix = 0; ix < size; ++ix, ++array)//每次array+1指向下一个元素
//可以对指针使用下标运算符
if (*array == value)
return array;
return 0;
}
- 解法二:使用第二个指针来取代参数size
template <typename elemType>
elemType* find(const elemType *first, const elemType *last, const elemType &value)
{
if (!first || !last)
return 0;
//如果first不等于last,就将value拿来和first所指的元素作比较,
//如果相同就返回first,否则first+1,指向下一个元素
for (; first != last; ++first)//每次array+1指向下一个元素
//可以对指针使用下标运算符
if (*first == value)
return first;
return 0;
}
调用方式
int ia[3] = {1, 1, 2};
int *pi = find(ia, ia+3, ia[2]);
任务2:将vector的元素传入find(),而不指定vector的类型
可使用array的解法二,使用一对用来表示“起始地址/结束地址”的参数传入find()。不同之处在于vector可以是空的,因此调用前先确定vector不为空。
vector<string> svec;
//将“取第一个元素的地址”的操作包装为函数
//begin()返回0或者是vector第一个元素的地址
template <typename elemType>
inline elemType* begin(const vector<elemType> &vec)
{return vec.empty() ? 0:&vec[0];}
//end()返回0或者是vector最后一个元素的下一个地址
inline elemType* end(const vector<elemType &vec)
{return vec.empty() ? 0:&vec.end();}
//调用find()
find(begin(svec), end(svec), search_value);
如何扩展find()的功能,令它也能支持list类别。
指针的算数运算必须首先假设所有元素都储存在连续空间,因此指针的算术运算不适用于list。
解决办法:在底层指针的行为之上提供一层抽象,取代程序原本的“指针直接操作”方式。把底层指针的处理通通放在此抽象层中,让用户无须直接面对指针操作,这样可以处理标准库中的所有容器类。(???完全不同在说什么)
3.2 了解Iterator(泛型指针)
first和last都是iterator class object,可以把first和last当作指针一样,使用“和指针相同的语法”,差别在于*、!=、++运算符都是有iterator class内相关的inline函数提供的。
A. 原生指针
最普通的指针,定义: 类型 *变量名;
与之对比的是使用上有类似指针的功能 实际并不是指针。比如:迭代器
[一个类重载 *和->操作符 那么可以像指针一样使用 但是这种并不是原生的]
第一种就是 void *指针 可以指向任意的数据结构 因此可以称为"泛型"。
第二种就是指具有指针特性的泛型数据结构 如:泛型迭代器(iterator)和接下来要说的智能指针。
C++中没有自动回收内存的机制,因此出现了智能指针。一般我们将一个指针封装到一个智能指针类中,该类中有一个引用计数器。 对指针的复制等操作会使引用计数+1,delete操作会使引用计数-1。计数=0时,指针=NULL。
- 获取iterator
- begin()函数:返回一个iterator,指向第一个元素
- end()函数:返回一个iterator,指向最后一个元素的下一个位置
for(iter = svec.begin(); iter != svec.end(); ++iter)
{cout << *iter <<' ';
- 定义和使用标准容器的iterator
定义需要提供
- 迭代对象(某个容器)的类型,用来决定如何访问下一个元素
- iterator所指的元素类型,用来决定iterator提领操作的返回值
vector<string> svec;
//iter指向一个string类型的vector
//iter一开始指向svec的第一个元素
vector<string>::iterator iter = svec.begin();
::表示这个iterator是位于string vector定义内的嵌套[nested]类型。
面对const vector使用const_iterator实现遍历操作,允许读取vector的元素,但不允许写入操作。
const vector<string> cs_svec;
vector<string>::const_iterator iter = cs_svec.begin();
- 通过iterator获取元素值
采用一般指针的dereference方式
cout << "string value of element: " << *iter;
- 通过iterator调用底部元素提供的操作
采用arrow运算符->
cout << iter->size;
使用iterator取代下标运算符后的display()函数
template<typename elemType>
void display(const vector<elemType> &vec, ostream &os)
{
vector<elemType>::const_iterator iter = vec.begin();
vector<elemType>::const_iterator end_it = vec.end();
//如果vec是空的,iter=vec.end(),for循环不执行
for(;iter != vec.end();++iter)
os << *iter << ' ';
os<<endl;
}
重新实现find(),让它同时支持两种形式,一对指针或者一对指向某种容器的iterator。
template <typename IteratorType, typename elemType>
IteratorType find( IteratorType first, TteratorType last, const elemType &value)
{
for(; first != last; ++first)
if(*first == value)
return first;
return last;
}
处理array、vector、list:
const int asize = 3;
int ia[asize]={1, 1, 2};
vector<int> ivec(ia, ia+asize);
list<int> ilist(ia, ia+asize);
int *pia = find(ia, ia+asize, 1024);
if(pia != ia+asize)
//...找到了
vector<int>::iterator it;
it = find(ivec.begin(), ivec.end(), 1024);
if(it != ivec.end())
//...找到了
list<int>::iterator iter;
iter = find(ilist.begin(), ilist.end(), 1024);
if(iter != ilist.end())
//..找到了
find()使用了底部元素所属类型的equality(相等)运算符,如果底部元素所属类型没提供就GG了。解法一是传入一个函数指针,取代原本固定使用的=,解法二是使用function object。
下一个目标:将现有的find()版本演化为泛型算法。
3.3 所有容器的共同操作
- ==,!=
- =
- empty()在容器内无任何元素时返回true,否则返回false
- size()
- clear()删除所有元素
- begin()和end()
- insert()将单一元素或某个范围内的元素插入容器
- erase()将容器内的单一元素或某个范围内的元素删除
3.4 使用顺序性容器
顺序性容器用来维护一组排列有序,类型相同的元素。
- vector
- 以一块连续内存来存放元素。
- 对vector进行随机访问的效率高(顺序固定),插入或者删除的效率低(要复制每个元素)。
- 适合数列。
- list
- 以双向链接来储存内容,前向(forward)指针指向下一个(next)元素,后向(backward)指针指向上一个(preceding)元素,可以执行前进或后退操作,每个元素包含:value、back指针、front指针。
- 对list进行随机访问的效率低(每次都要遍历元素),插入或删除的效率高(设定back、front指针即可)。
- 排有顺序的表格时。
- deque(发音deck)
- 以一块连续内存来存放元素。
- 不同于vector的是deque对最前端元素和最末端元素的插入和删除操作效率更高。
- 作为底部储存元素(queue是以deque实现)。
- 定义方式
1.产生空容器
vector<int> ivec;
list<string> svec;
2.产生特定大小的容器
vector<int> ivec(10);
list<string> svec(16);
3.产生特定大小的容器并为元素赋初值
vector<string> svec(16,"unsigned");
list<int> ivec(10, -1);
4.通过一对ierator产生容器,这对ierator用来标示一组作为初值的元素的范围
string sa[3]={"how", "are", "u"};
vector<string> svec(sa, sa+3);
5.复制原有容器内的元素产生新容器
list<int> ivec1;
//...填充ivec1
list<int> ivec2(ivec1);
- 几个操作函数
push_back():在容器末尾插入一个元素
pop_back():删除容器末尾最后一个元素
list和deque还提供了push_front()和pop_front(),这些函数并不返回被删除的元素值。
back():返回容器末端元素的值。front():返回容器最前端元素的值。
#include <deque>
deque<int> a_line;
int ival;
while(cin >> ival)
{
//将ival插入a_line末端
a_line.push_back(ival)
//读取最前端的元素
int curr_value = a_line.front();
//删除最前端元素
a_line.pop_front();
}
insert()函数的四种变形
1.可将value插入position之前。会返回一个iterator,指向被插入的元素value
iterator insert(iterator position, elemType value)
2.可在position前插入值都为value的count个元素
void insert(iterator position int count, elemType value)
3.可在position前插入[first,last)所标示的各个元素
void insert(iterator1 position, iterator2 first, iterator2 last)
4.可在position前插入元素,元素初值为其所属类型的默认值
iterator insert(iterator position)
erase()函数的两种变形
1.可删除posit所指的元素
iterator erase(iterator posit)
2.可删除[first,last)范围内的元素
iterator erase(iterator first, iterator last)
list不支持iterator的偏移运算,因此不能写first+num的形式。
3.5 使用泛型算法
首先要包含对应的algorithm头文件:
#include<algorithm>
以vector为例,如果给定的值已经存在于数列之中,is_elem()返回true;否则返回false。
泛型搜索算法选用binary_search():用于有序集合的搜索,如果搜索到目标就返回true,否则返回false。
#include<algorithm>
//前置声明
extern bool grow_vec(vector<int>& ,int);
//binary_search()要求其作用对象必须经过排序,我们可以将容器先复制一份,然后对副本执行排序操作,再调用binary_search()
vector<int> temp(vec.size());
copy(vec.begin(), vec.end(), temp.begin());
sort(temp.begin, temp.end());
bool is_elem(vector<int> &vec, int elem)
{
int max_value = vec.empty() ? 0 : vec[vec.size()-1];
if(max_value<elem)
return grow_vec(vec,elem);
if(max_value==elem)
return true;
return binary_search(temp.begin(), temp.end(), elem);
}
我记得对容器的复制操作应该还可以写成
vector<int> temp(vec);
3.6 如何设计一个泛型算法
这节我看的好累...呜呜呜这知识它不进脑子啊!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
vector<int> less_than_10(const vector<int> &vec)
{
vector<int> nvec;
for(int ix = 0; ix < vec.size(); ++ix)
{
if(vec[ix] < 10)
nvec.push_back(vec[ix]);
}
return nvec;
}
//改
vector<int> less_than(const vector<int> &vec, int value);
//再改
vector<int> filter(const vector<int> &vec, int filter_value, bool(*pred)(int, int));
bool less_than(int v1, int v2)
{ return v1<v2 ? true : false; }
bool bigger_than(int v1, int v2)
{ return v1>v2 ? true : false; }
//调用方法
vector<int> old_vec;
int value;
vector<int> new_vec = filter(old_vec, value, less_than)
//filter()的定义
vector<int> filter(const vector<int> &vec, int filter_value, bool(*pred)(int, int))
{
vector<int> nvec;
for(int ix = 0; ix < vec.size(); ++ix)
{
if( pred(vec[ix], filter_value))
nvec.push_back(vec[ix]);
}
return nevc;
}
使用泛型算法find_if(),找出等于10的元素。其中泛型算法find()接受三个参数:两个iterator标示出检测范围,第三个参数是我们想要寻找的数值。
int count_occurs(const vector<int> &vec, int val)
{
vector<int>::const_iterator iter = vec.begin();
int occurs_count = 0;
while((iter = find( iter, vec.end, val)) != vec.end())
{
++occurs_count;
++iter;
}
return occurs_count;
}
- Function Object
1.定义
function object是某种对function call运算符做了重载操作的一类class的实例对象,可被当成一般函数来使用。(啥玩意儿啊)。
2.作用
function object实现了原本可能以独立函数加以定义的事物——为了效率!这样可以令call运算符成为inline,从而消除“通过函数指针来调用函数时”需付出的额外代价。
3.一组function object
算术运算(六个,plus<type>,minus<type>...)、关系运算(六个,less<type>,greater<type>...)和逻辑运算(三个,对应&&、||、!)三大类。
4.使用
首先要包含头文件
#include <functional>
以sort()为例,sort()在默认情形下会使用底部元素的类型所提供的<运算符将元素按升序排列,如果我们传入greater_than function object,元素就会以降序排列:
sort(vec.begin(), vec.end(), greater<int>());
- Function Object Adapter
function object adapter(适配器)会对function object进行修改操作。binder adapter(绑定适配器)会将function object的参数绑定至某特定值,使binary(二元)function object转化为unary(一元)function object。
bind1st会将指定值绑定至第一操作数,bind2st会将指定值绑定至第二操作数。
vector<int> filter(const vector<int> &vec, int val, less<int> <)
{
vector<int> nvec;
vector<int>::iterator iter = vec.begin();
//bind2nd(less<int>,val)会把val绑在less<int>的第二个参数上
while((iter = find_if(iter, vec.end(), bind2nd(lt, val))) != vec.end())
{
nvec.push_back(*iter);
iter++;
}
return nvec;
}
使用template消除filter()与元素类型的依赖性,使用iterator消除filter()与容器类型的依赖性。
template<typename InputIterator, typename OutputIterator, typename ElemType, typename Comp>
OutputIterator filter(InputIterator first, InputIterator last, OutputIterator at, const ElemType &val, Comp pred)
{
while((first = find_if(first, last, bind2nd(pred, val))) != last)
{
cout<<"found value: "<<*first<<endl;
*at++ = *first++;
}
return at;
}
调用filter(),测试其在array和vector上的使用。
方法一:使用insert iterator adapter
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<functional>
using namespace std;
template<typename InputIterator, typename OutputIterator, typename ElemType, typename Comp>
OutputIterator filter(InputIterator first, InputIterator last, OutputIterator at, const 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 elem_size = 8;
int ia[elem_size] = {12,8,43,0,6,21,3,7};
vector<int> ivec(ia, ia+elem_size);
//下面这个容器用来储存过滤结果
int ia2[elem_size];
vector<int> ivec2(elem_size);//elem_size个数个0
cout << "filtering integer array for values less than 10\n";
filter(ia, ia + elem_size, ia2, elem_size, less<int>());
cout << "filtering integer vector for values less than 10\n";
filter(ivec.begin(),ivec.end(),ivec2.begin(),elem_size,greater<int>());
system("pause");
return 0;
}
方法二:使用negator,对function object的真伪值取反
- not1对unary function object的值取反
- not2对binary function object的值取反
那我把
not1(bind2nd(pred, val))
的less<int>()换成greater<int>()不就行了哈哈。
3.7 使用Map
- 定义
一对数值,包括key和value,key通常是字符串。任何一个key在map内就一份,多了要用multimap。
- 实例
#include<map>
#include<string>
map<string,int> words;
//输入方式
words["happy"]=1;
string tword;
while(cin<<tword)
words[tword]++;
words[key]会取出与key对应的value,如果key不在map内,这个key会被自动加入map中,相应的value被设置为所属类型的默认值。
- 成员函数
- first,对应key
- second,对应value
map<string,int>::iterator it = words.begin();
for(; it != words.end(); ++it)
cout<<"key: "<<it->first<<" value: "<<it->second<<endl;
- 查询map内是否存在某个key
1.把key当索引用(缺点是会把key加进map里)
int count = 0;
if(!(count=words[key]))
//...key不在map里
2.把key传入map的find()函数并调用
int count = 0;
map<string,int>::iterator it = words.find("what");
if(it != words.end())
count = it->second;
3.map的count()函数会返回key在map内的个数
int count = 0;
if(words.count("what"))//存在
count=words["what"];
3.8 使用Set
- 定义
set由一群key组成,可以判断某值是否在某集合中。
- 实例
在图形遍历算法中可以用set储存每个遍历过的节点,移至下一点前可以先查询set,判断该节点是否已经遍历过。
//定义一个用来记录“排除字眼”的string类型set:
#include<set>
#include<string>
set<string> word_exclusion;
//将某个单字放入map前,先检查是否存在于word_exclusion set中
while(cin>>tword)
{
if(word_exclusion.count(tword))
//tword被排除了嘤嘤嘤,跳过这次迭代
continue;//重新对执行条件求值并开始另一次迭代
words[tword]++;
}
- 初始化
默认情形下,set元素依据其所属类型默认的从小到大次序排列。
int ia[6]={1, 5, 8, 2, 9, 1};
vector<int> vec(ia,ia+6);
set<int> iset(vec.begin(), vec.end());
iset的元素将会是{1, 5, 8}。
- 几个操作函数
1.insert():为set加入单一元素和为set加入某个范围的元素。
iset.insert(ival);
iset.insert(vec.begin(), vec.end());
2.iterator操作
set<int>::iterator it = iset.begin();
for(;it != iset.end(); ++it)
cout<<*it<<' ';
cout<<endl;
3.9 如何使用Iterator Inserter
3.6节对filter()的实现用到了赋值(assign)操作,将容器中符合要求的元素一一赋值至另一个容器。标准库提供了三个insertion adapter,这些adapter让我们可以避免使用assign运算符,但是不能用于array上。使用前添加头文件
#include<iterator>
- back_inserter()
-
以容器的push_back()函数取代assignment运算符
-
适合vector
-
传入参数为容器本身
-
inserter()
-
以容器的insert()函数取代
-
适用于vector
-
接受两个参数,一个是容器,另一个是iterator
-
front_inserter()
-
以容器的oush_front()函数取代
-
适用于list和deque
int main()
{
int ia2[elem_size];
vector<int> ivec2;//没有定义容器大小哦
cout << "filtering integer vector for values less than 10\n";
filter(ivec.begin(),ivec.end(),back_insert(ivec2),elem_size,greater<int>());
}
3.10 使用iostream Iterator
- 分类
istream_iterator和ostream_iterator,分别支持单一类型的元素读取和写入。使用前包含头文件
#include<iterator>
-
定义
//将is定义为一个“绑至标准输入设备”的istream_iterator
istream_iterator<string> is(cin);
//不指定istream对象便代表了last itertator,标示“要读取的最后一个元素的下一个位置”
istream_iterator<string> eof;
//将os定义为一个“绑至标准输出设备”ostream_iterator,输出字符串之间以空格分隔
ostream_iterator<string> os(cout, " ")
- 调用
copy(is, eof, back_inserter(text));
copy(text.begin(), text.end(), os);
从文件读入,输出到文件:
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<fstream>
#include<iterator>
using namespace std;
int main()
{
ifstream in_file("input_file.txt");
ofstream out_file("output_file.txt");
if (!in_file || !out_file)
{
cerr << "FBI warning\n";
return -1;
}
istream_iterator<string> is(in_file);
istream_iterator<string> eof;
vector<string> text;
copy(is, eof, back_inserter(text));
sort(text.begin(), text.end());
ostream_iterator<string> os(out_file, " ");
copy(text.begin(), text.end(), os);
system("pause");
return 0;
}