c++ primer plus chapter16 string类和标准模板库

内容摘要:

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类是在头文件<string>中支持的,要注意区分,string类是一个显式实例化模板类,如下:

typedef basic_string<char> 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 <class Iter>
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_list<char>il)

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:需要包含头文件<memory>,使用名称空间std,并且不能再使用new和delete了

泛型编程:旨在编写独立于数据类型的代码,关注的是算法

迭代器:对于数组中查找元素和在链表中查找元素,是两个不同的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<int> dice(10);
copy(casts, casts + 10, dice.begin());

前两个迭代器参数表示复制的范围,最后一个迭代器参数表示将第一个元素复制到什么位置。前两个参数是输入迭代器,最后一个是输出迭代器。此函数将覆盖原有容器中的数据,同时目标容器必须足够大。

STL预定义迭代器:

1.ostream_iterator:是一个模板,此模板是一个输出迭代器的模型,在头文件<iterator>中提供,使用如下:

ostream_interator<int, char>out_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<int, char>(cout, " "));

2.istream_iterator:和第一个类似,它使istream输入可以用作迭代器接口,是一个输入迭代器概念模型,可以用两个istream_iterator对象定义copy()的输入范围:

copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(), dice.begin());

也使用两个模板参数,第一个是从输入流中读取的数据类型,第二个是输入流的字符类型,使用构造函数参数cin意味着使用cin管理的输入流,省略构造函数参数表示输入失败,这段代码的意思就是,从输入流中读取,直到文件结尾、类型不匹配或者出现其他输入故障为止。

3.reverse_interator:是反向迭代器,对其递增会导致它被递减。一个反向迭代器的应用如下:

ostream_iterator<int, char>out_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:

是模板类,在头文件<vector>中提供,是数组的一种类表示,提供了自动内存管理功能,可以动态改变vector对象的长度。vector提供了对元素的随机访问。在尾部添加和删除是O(1)时间,在头部或者中间插入和删除是O(N)时间,因为涉及搬移。vector除了是序列还是可反转容器,有rbegin()和rend()用于反向遍历。

2.deque:

是模板类,在头文件<deque>中提供,表示双端队列(double-ended queue)。和vector类似,支持随机访问,区别是在deque对象的头插入和删除的时间也是O(1)的。若多数操作发生在序列的开始和结尾处,应该使用deque,为了实现额外的功能deque设计比vector复杂,导致相同的操作,比如在序列中部插入数据,都是线性时间但是vector更快。

3.list

是模板类,在头文件<list>中提供,表示双向链表,和vector以及deque的区别是,在中间进行插入和删除的时间是固定的。vector强调的是快速访问,list强调的是元素的快速插入和删除。list是可反转容器。和vector不同,插入或者删除之后,迭代器指向的元素不变,因为只是改变了指针的链接性。list的一些常用成员函数:

void merge<list<T, Alloc> &x)        //合并两个排序链表,保存在调用链表中,x清零
void remove(const T & val)           //删除所有值为val的元素
void sort()                          //使用 < 运算符排序,O(NlogN)时间
void splice(iterator pos, list<T, Alloc>x)    //将链表x内容插入到pos前面,x清零
void unique()                        //将连续的相同元素压缩为单个元素,O(N)时间

4.forward_list:

c++11提供,单链表,是正向迭代器,不可反转

5.queue:

是模板类,在头文件<queue>中提供,是一个适配器类,提供的操作如下:

bool empty() const        //若队列为空,返回true
size_type size() const    //返回队列中元素数目
T & front()               //返回指向队首元素的引用
T & back()                //返回指向队尾元素的引用
void push(const T &x)     //在队尾插入x
void pop()                //删除队首元素

6.priority_queue:

是模板类,在头文件<queue>中声明,是适配器类,支持的操作和queue相同。两者之间主要区别是,在priority_queue中,最大的元素被移动到队首,底层是vector而不是deque,支持用户自定义哪个元素放到队首的比较方式,如下:

priority_queue<int> pq1;
priority_queue<int> pq2(greater<int>);

greater<>()函数是一个预定义的函数对象,在函数对象中描述

7.stack:

在头文件<stack>中提供,是适配器类,给底层类(默认是vector)提供了典型的栈接口。不允许随机访问栈,也不允许遍历栈,把使用限制在栈的基本操作,如下:

bool empty() const
size_type size() const
T & top()
void push(const T & x)
void pop()

8.array:

c++11提供,在头文件<array>中定义,不是STL容器,因为长度是固定的,没有用于调整容器大小的操作如push_back和insert,有operator[]()和at(),可以将很多STL标准算法用于array,如copy()和for_each()

关联容器:对容器概念的一个改进,将值和键关联,通过键找值,通常使用某种树实现的。STL提供了4中关联容器,set、multiset、map、multimap,前两个在头文件<set>中提供,后两个在头文件<map>中提供。set是最简单的关联容器,值类型和键相同。set中键是唯一的,multiset中可能有多个值相同的键。map中键和值类型可以不同,multimap可以允许多个值相同的键。一个使用set的例子:

const int N = 6;
string s1[N] = {"ab", "ab", "dc", "cc", "ee", "ff"};
set<string> A(s1, s1 + N);
ostream_iterator<string, char>out(cout, " ");
copy(A.begin(), A.end(), out);

相同的元素"ab"只剩一个,并且集合被排序。集合可以合并,如下:

set_union(A.begin(), A.end(), B.begin(), B.end(), 
        ostream_iterator<string, char>out(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<set<string> >(C, C.begin()));

其他集合函数:

set_intersection()        //求交集,参数和set_union()相同
set_difference()          //求差,参数和set_union()相同
lower_bound()             //将键作为参数,返回一个迭代器,指向第一个不小于键的成员
upper_bound()             //返回第一个大于键的成员

一个multimap使用示例:

multimap<int, string> codes;
pair<const int, string>item(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<typename InputIterator, typename Function>
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<double> gr8(arr1, arr1 + LIM);
ostream_iterator<double, char>out(cout, " ");
transform(gr8.begin(), gr8.end(), out, sqrt);

表示将每个元素求平方后输出

plus<>类:在头文件<functional>中提供,使用如下:

#include <functional>
plus<double>add;
double y = add(2.2, 3.4);

使得将函数对象作为参数很方便:

transform(gr8.begin(), gr8.end(), m8.begin(), out, plus<double>());

对于所有内置运算符,STL都提供了等价的函数符

自适应:是一个概念,有5种:

1.自适应生成器

2.自适应一元函数

3.自适应二元函数

4.自适应谓词

5.自适应二元谓词

算法组:STL将算法分成四组:

1.非修改式序列操作:对区间中的每个元素进行操作,这些操作不修改容器的内容,比如find()和for_each()

2.修改式序列操作:transform(),copy()等

3.排序和相关操作:sort()

4.通用数字运算:将区间内的内容累积、计算两个容器的内部乘积等,通常是数组的操作特性,vector最有可能使用这些操作

前3组在头文件algorithm中描述,第4组在头文件<numeric>中提供

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值