内容摘要:
1.标准c++ string类
2.模板auto_ptr,unique_ptr,shared_ptr
3.标准模板库STL
4.容器类
5.迭代器
6.函数对象functor
7.STL算法
8.模板intializer_list
C语言提供的字符串相关函数在string.h和cstring中,而c++的string类是在头文件
typedef basic_string string;
string类有9个构造函数,其中2个是c++11提供的,如下:
string(const char *)
string(size_type n, char c)
string(const string * str)
string()
string(const char *, size_type n)
template
string(Iter begin, Iter end)
string(const string &, size_type pos = 0, size_type n = npos)
string(string &&str) noexcept //c++11,移动构造函数,可能修改str
string(initializer_listil)
string类最大长度:即允许的最长字符串的长度,在string:npos中定义,一般是unsigned long最大值
string类重载的赋值运算符:
+=:将一个字符串附加到另一个字符串后面
=:将一个字符串赋值给另一个
<<:用来打印
[]:访问字符串中的各个字符
string类对所有6个关系运算符都做了重载,每个重载都有3个版本,string和string,string和C字符串,C字符串和string
string类提供了在一个字符串中搜索给定子字符串或者字符的find方法,如下:
/* 从某个位置pos开始,查找特定子串str,若找到,返回首次出现的首字符索引,否则
返回string::npos */
size_type find(const string & str, size_type pos = 0) const
/* 和上个函数类似,查找的是C字符串 */
size_type find(const char * s, size_type pos = 0) const
/* 从字符串pos位置开始,查找s前n个字符组成的子字符串,返回值同上 */
size_type find(const char * s, size_type pos = 0, size_type n)
/* 和上面类似,查找某个字符 */
size_type find(char ch, size_type pos = 0) const
string类还提供了其他方法,如下:
rfind //查找字符或者字符串最后一次出现的位置
find_first_off //查找参数中任意一个字符首次出现的位置
find_last_off //和上面类似,查找最后一次出现的位置
find_first_not_off //查找第一个不包含在参数中的位置
find_last_not_off //和上面类似,查找最后一次出现的位置
智能指针模板类:为了解决用指针申请内存忘记释放的问题,比如异常栈解退,没有在catch语句中释放内存。只要在指针销毁的时候调用析构就好了,但是指针只是指针,所以把指针设计成对象,就是智能指针类的设计思想。c++98提供了auto_ptr,c++11提供了unique_ptr和shared_ptr。
使用auto_ptr:需要包含头文件
泛型编程:旨在编写独立于数据类型的代码,关注的是算法
迭代器:对于数组中查找元素和在链表中查找元素,是两个不同的find函数,一个使用数组索引,一个使用next指针,但是广义上来说是相同的,将值依次和容器中的值进行比较,直到找到匹配的为止。泛型编程旨在使用同一个find函数处理数组、链表或者其他容器类型,即函数不仅独立于容器中存储的数据类型,还独立于容器本身的数据结构。模板提供了容器中数据类型的通用表示,还需要遍历容器中的值的通用表示,这就是迭代器。
要实现find函数,迭代器应该具有的特征:
1.应该可以解引用操作,如p是迭代器,那么应该对*p进行定义
2.应该能够将一个迭代器的值赋给另一个,即p和q都是迭代器,应该对p = q进行定义
3.应该能够将一个迭代器和另一个进行比较,即p和q都是迭代器,应该对p == q和p != q进行定义
4.应该能够用迭代器遍历容器中的所有元素,通过为迭代器定义++p和p++实现
指针就能满足迭代器的需求,在数组中查找按照如下方式:
typedef double * iterator;
iterator find_ar(iterator begin, iterator end, const double & val)
{
iterator ar;
for (ar = begin; ar != end; ar++)
if (*ar = val)
return ar;
return end;
}
对于链表中查找,可以定义一个迭代器类,如下:
struct Node
{
double item;
Node * p_next;
};
class iterator
{
private:
Node * pt;
public:
iterator() : pt(0) {}
iterator(Node * pn) : pt(pn) {}
double operator*() {return pt->item;}
iterator & operator++()
{
pt = pt->p_next;
return *this;
}
iterator operator++(int)
{
iterator tmp = *this;
pt = pt->p_next;
return tmp;
}
};
为了区分++的前缀版本和后缀版本,c++将operator++()作为前缀版本,operator++(int)作为后缀版本。通过重载运算符之后,查找数组的find_ar也可以用于查找链表。唯一的区别是,find_ar使用超尾迭代器,找不到元素返回超尾,查找链表找不到返回空指针。可以要求链表最后一个节点后面还有一个额外的超尾元素。对迭代器的要求,变成了对容器类的要求,容器类要适应迭代器的实现。
STL遵循这个法则,每个容器类定义了相应的迭代器类型。对于其中某个类,迭代器可能是指针,而对另一个类,迭代器是对象;每个容器类都有超尾标记;每个容器类都有begin()和end()方法,分别返回指向第一个位置的和指向超尾的迭代器。使用迭代器的时候,不用知道它是怎么实现的,用就行了。
STL定义了5种迭代器:输入迭代器、输出迭代器、正向迭代器、双向迭代器、随机访问迭代器
1.输入迭代器:
输入是从程序的角度来说的,来自容器的信息被视为输入,输入迭代器用来读取容器中的信息。对输入迭代器解引用将使程序能够读取容器中的值,但是不一定能修改。
输入迭代器能够访问容器中的所有值,基于输入迭代器的任何算法都应该是单同行(single-pass)的,不依赖于上一次遍历时的迭代器的值,也不依赖于本次遍历中前面的迭代器的值。
输入迭代器只能递增,不能倒退。
2.输出迭代器:
信息从程序输出到容器,解引用让程序能修改容器值但是不能读取。比如cout,能修改发送到显示器的字符流,但是不能读取显示器内容。
3.正向迭代器:
只使用++来遍历容器,总是按相同的顺序遍历一系列值,将其递增后,仍然可以对前面的值解引用,允许多次通行算法
正向迭代器支持读写
4.双向迭代器:
具有正向迭代器的所有特性,同时支持两种递减运算符
5.随机访问迭代器:
有些算法,比如标准排序、二分查找,要求直接跳到容器中任何一个元素,叫做随机访问。具有双向迭代器的所有特性,同时支持了随机访问操作和用于元素排序的关系运算符。
概念、改进和模型:
STL算法可以使用任何满足其要求的迭代器实现,比如类或者指针,概念指的是一些列需求;概念可以有类似继承的关系,例如,双向迭代器继承了正向迭代器的功能,但是这和类继承不同,正向迭代器可以是一个类,双向迭代器是一个指针,这显然不是类继承,改进用来描述这种概念的继承关系;概念的具体实现叫做模型,比如指针是一个随机访问迭代器模型,也是一个正向迭代器模型,因为满足概念的所有要求。
copy():是一种STL算法,将数据从一个容器复制到另一个,是以迭代器实现的,如下:
int casts[10] = {6, 7, 2, 9, 4, 11, 8, 7, 10, 5};
vector dice(10);
copy(casts, casts + 10, dice.begin());
前两个迭代器参数表示复制的范围,最后一个迭代器参数表示将第一个元素复制到什么位置。前两个参数是输入迭代器,最后一个是输出迭代器。此函数将覆盖原有容器中的数据,同时目标容器必须足够大。
STL预定义迭代器:
1.ostream_iterator:是一个模板,此模板是一个输出迭代器的模型,在头文件
ostream_interatorout_iter(cout, "");
out_iter现在是一个接口,可以使用cout显示信息。第一个模板参数是要发送给输出流的数据类型,第二个模板参数是输出流使用的字符类型(还可以是wchar_t)。构造函数第一个参数是要使用的输出流(还可以是fout),最后一个字符参数是发送给输出流的每个数据项后显示的分割符。可以这样使用迭代器:
*out_iter++ = 15;
对于常规指针,意思是将15赋值给当前指针指向的内存然后指针递增,对于迭代器,意思是将15和空格发送给cout管理的输出流,并为下一个操作做好了准备。可以将copy用于迭代器,如下:
copy(dice.begin(), dice.end(), out_iter);
这意味着将dice容器的整个区间复制到输出流中,即显示容器的内容。也可以不创建命名的迭代器,直接构建一个匿名的迭代器,可以这样使用适配器:
copy(dice.begin(), dice.end(), ostream_iterator(cout, " "));
2.istream_iterator:和第一个类似,它使istream输入可以用作迭代器接口,是一个输入迭代器概念模型,可以用两个istream_iterator对象定义copy()的输入范围:
copy(istream_iterator(cin), istream_iterator(), dice.begin());
也使用两个模板参数,第一个是从输入流中读取的数据类型,第二个是输入流的字符类型,使用构造函数参数cin意味着使用cin管理的输入流,省略构造函数参数表示输入失败,这段代码的意思就是,从输入流中读取,直到文件结尾、类型不匹配或者出现其他输入故障为止。
3.reverse_interator:是反向迭代器,对其递增会导致它被递减。一个反向迭代器的应用如下:
ostream_iteratorout_iter(cout, " ");
copy(dice.rbegin(), dice.rend(), out_iter);
rbegin()和rend()就是方向迭代器,前者指向超尾,后者指向第一个元素,通过先递减再解引用解决超尾不应该被处理的问题。使用这两个STL函数比显式声明反向迭代器好,防止出错。
copy函数的问题是会覆盖原有容器数据,并且在预先不知道长度的时候,无法处理,插入迭代器解决了这个问题,将复制替换为插入,不会覆盖已有元素,并且使用自动内存分配保证能够容纳新的信息。三种插入迭代器:
1.back_insert_iterator:将元素插入容器尾部,只允许在尾部快速插入的容器,如vector
2.front_insert_iterator:将元素插入容器前端,只允许在起始位置做时间固定插入的容器,如queue
3.insert_iterator:将元素插入构造函数制定的位置前面,没有限制
容器类型:deque,list,queue,priority_queue,stack,vector,map,multimap,set,multiset,bitset
c++11新增容器类型:forward_list,unorder_map,unordered_multimap,unorder_set,unordered_multiset
复杂度:
1.编译时间,操作在编译期执行,执行时间为0
2.固定时间,发生在运行阶段,但独立于对象中的元素数目
3.线性时间,执行时间和元素数目成正比
复杂度要求是STL特征,虽然实现细节可以隐藏,性能规格应该公开。
序列:保证元素按特定顺序排列,不会在两次迭代之间发生变化。序列是一种改进,有7中STL都是序列,如下:
1.vector:
是模板类,在头文件
2.deque:
是模板类,在头文件
3.list
是模板类,在头文件中提供,表示双向链表,和vector以及deque的区别是,在中间进行插入和删除的时间是固定的。vector强调的是快速访问,list强调的是元素的快速插入和删除。list是可反转容器。和vector不同,插入或者删除之后,迭代器指向的元素不变,因为只是改变了指针的链接性。list的一些常用成员函数:
void merge &x) //合并两个排序链表,保存在调用链表中,x清零
void remove(const T & val) //删除所有值为val的元素
void sort() //使用 < 运算符排序,O(NlogN)时间
void splice(iterator pos, listx) //将链表x内容插入到pos前面,x清零
void unique() //将连续的相同元素压缩为单个元素,O(N)时间
4.forward_list:
c++11提供,单链表,是正向迭代器,不可反转
5.queue:
是模板类,在头文件
bool empty() const //若队列为空,返回true
size_type size() const //返回队列中元素数目
T & front() //返回指向队首元素的引用
T & back() //返回指向队尾元素的引用
void push(const T &x) //在队尾插入x
void pop() //删除队首元素
6.priority_queue:
是模板类,在头文件
priority_queue pq1;
priority_queue pq2(greater);
greater<>()函数是一个预定义的函数对象,在函数对象中描述
7.stack:
在头文件
bool empty() const
size_type size() const
T & top()
void push(const T & x)
void pop()
8.array:
c++11提供,在头文件
关联容器:对容器概念的一个改进,将值和键关联,通过键找值,通常使用某种树实现的。STL提供了4中关联容器,set、multiset、map、multimap,前两个在头文件
const int N = 6;
string s1[N] = {"ab", "ab", "dc", "cc", "ee", "ff"};
set A(s1, s1 + N);
ostream_iteratorout(cout, " ");
copy(A.begin(), A.end(), out);
相同的元素"ab"只剩一个,并且集合被排序。集合可以合并,如下:
set_union(A.begin(), A.end(), B.begin(), B.end(),
ostream_iteratorout(cout, " ");
有5个迭代器参数,前两个确定一个迭代器,接下来两个确定另一个迭代器,最后一个是输出迭代器。假设要将结果放在第三个集合中,显然的一个选择是使用C.begin(),但是不好用,原因是:
1.关联集合将键看作常量,C.begin()返回的是常量迭代器,而不是输出迭代器
2.set_union()和copy()类似,将覆盖容器中已有数据,C是空的,不满足这个要求。
可以使用insert_iterator解决这个问题,如下:
set_union(A.begin(), A.end(), B.begin(), B.end(),
insert_iterator >(C, C.begin()));
其他集合函数:
set_intersection() //求交集,参数和set_union()相同
set_difference() //求差,参数和set_union()相同
lower_bound() //将键作为参数,返回一个迭代器,指向第一个不小于键的成员
upper_bound() //返回第一个大于键的成员
一个multimap使用示例:
multimap codes;
pairitem(213, "Los Angeles");
codes.insert(item);
cout << item.first << endl;
cout << item.second << endl;
无序关联容器:c++11提供,是容器概念的改进,和关联容器的区别是,底层不使用树,而是哈希,目的是提高插入和删除速度。有4种无序关联容器:unordered_set,unordered_multiset,unordered_map,unordered_multimap
函数对象:是可以以函数方式与()结合使用的任意对象,包括函数名、指向函数的指针、重载了()运算符的类对象,即定义了operator()()的类,如下:
class Linear
{
private:
double slope;
double y0;
public:
Linear(double s1_ = 1, double y_ = 0) : slope(s1_), y0(y_) {}
double operator()(double x){return y0 + slope * x;}
};
Linear f1;
Linear f2(2.5, 10.0);
double y1 = f1(12.5);
double y2 = f2(0.4);
关于for_each函数的第三个参数:使用如下:
for_each(books.begin(), books.end(), ShowReview);
不能把第三个参数声明为函数指针,因为函数指针指定了参数类型,容器可以包含任何类型,预先不知道使用哪种参数类型,因此通过使用模板解决这个问题,原型看起来如下:
template
Function foreach(InputIterator, InputIterator, Function f);
函数符:
1.生成器generator是不用参数就可以调用的函数符
2.一元函数unary function是用一个参数可以调用的函数符
3.二元函数binary function使用两个参数可以调用的函数符
提供给for_each的函数是一元函数,因为每次操作一个容器对象
上面3个概念有改进:
1.返回bool值的一元函数是谓词predicate
2.返回bool值的二元函数是二元谓词binary predicate
一些STL函数需要谓词或者二元谓词参数,如下:
bool WorseThan(const Review & r1, const Review & r2);
sort(books.begin(), books.end(), WorseThan);
预定义函数符:STL提供,用来执行诸如相加、比较等操作,transform函数使用如下:
const int LIM = 5;
double arr1[LIM] = {36, 39, 42, 45, 48);
vector gr8(arr1, arr1 + LIM);
ostream_iteratorout(cout, " ");
transform(gr8.begin(), gr8.end(), out, sqrt);
表示将每个元素求平方后输出
plus<>类:在头文件
#include
plusadd;
double y = add(2.2, 3.4);
使得将函数对象作为参数很方便:
transform(gr8.begin(), gr8.end(), m8.begin(), out, plus());
对于所有内置运算符,STL都提供了等价的函数符
自适应:是一个概念,有5种:
1.自适应生成器
2.自适应一元函数
3.自适应二元函数
4.自适应谓词
5.自适应二元谓词
算法组:STL将算法分成四组:
1.非修改式序列操作:对区间中的每个元素进行操作,这些操作不修改容器的内容,比如find()和for_each()
2.修改式序列操作:transform(),copy()等
3.排序和相关操作:sort()
4.通用数字运算:将区间内的内容累积、计算两个容器的内部乘积等,通常是数组的操作特性,vector最有可能使用这些操作
前3组在头文件algorithm中描述,第4组在头文件