Chapter 16 string类和标准模板库
这一章感觉难度突增啊,看得我脑壳痛
string类
内容:大致上来说就是介绍一下string类,然后说明一下它的用法
string的构造函数
Size_type是一个依赖于实现的整型。string::npos为字符串的最大长度,通常为unsigned
int 的最大值。NBTS(null-terminated string)表示以空字符结束的字符串
string(const char *s)
将string对象初始化为s指向的NBTSstring(size_type n,char c)
创建一个包含n个元素的string对象,其中每个元素都被初始化为字符cstring(const string &str)
将一个string对象初始化为string对象str(复制构造函数)string()
创建一个默认的string对象,长度为0(默认构造函数)String(const char *s,size_type n)
将string对象初始化为NBTS的前n个字符,即使超过了NBTS结尾Template <class Iter> String(Iter begin,Iter end)
将string对象初始化为区间[begin,end)内的字符,其中begin和end的行为就像指针,用于指定位置,范围包括begin在内,但不包括end。string(const string &str,size_type pos=0,size_type n=npos)
将一个string对象初始化为对象str中从位置pos开始到结尾的字符,或从位置pos开始的n个
字符string(string &&str)noexcept
这是cpp11新增的,它将一个string对象初始化为string对象str,并可能修改str(移动构造
函数) //这只后面再讨论string(initializer_list<char> il)
这是cpp11新增的,它将一个string对象初始化为初始化列表il中的字符一个示例:
#include <iostream>
#include <string>
int main()
{
using namespace std;
string one("Lottery Winner!");
cout << one << endl;
string two(20, '$');
cout << two << endl;
string three(one);
cout << three << endl;
one += "Oops!";
cout << one << endl;
two = "Sorry! That was ";
three[0] = 'P';
string four;
four = two + three;
cout << four << endl;
char alls[] = "All's well that ends well";
string five(alls, 20);
cout << five << "!\n";
string six(alls + 6, alls + 10); //6
cout << six << ", ";
string seven(&five[6], &five[10]); //6
cout << seven << "...\n";
string eight(four, 7, 16); //7
cout << eight << " in motion!" << endl;
return 0;
}
string类输入
有两种输入方式:
cin>>stuff
getline(cin,stuff);
其中,getline()有一个可选参数,用来确定输入的边界,默认为‘\n’
eg:
getline(stuff,‘:’)
使用字符串
string类能做什么?
- 比较字符串
string对全部六个关系运算符都进行了重载,你可以这样:
if(snake<dog){...}
还可以:
if(snake1.length()==snake2.size())
cout<<“...”;
其中length和size函数都是计算字符串的长度。string以多种不同的方式在字符串中搜索给定的子字符串或字符主要是通过调用find方法,下面是其中的四个版本
Size_type find(const string &str,size_type pos=0)const
从字符串的pos位置开始,查找子字符串str。如果找到,则返回该子字符串首次出现时其首字,符的索引;否则,返回string::npos
。Size_type find(const char *s,size_type pos=0)const
从字符串的pos位置开始,查找子字符串s。如果找到,则返回该子字符串首次出现时其首字符的索引;否则,返回string::npos
。Size_type find(const char *s,size_type pos=0,size_type n)
从字符串的pos位置开始,查找s的前n个字符组成的子字符串。如果找到,则返回该子字符串首次出现时其首字符的索引,否则,返回string::npos
。Size_type find(char ch,size_type pos=0)const
从字符串的pos位置开始,查找字符ch。如果找到,则返回该字符首次出现的位置;否则,返回string::npos
。
string库还提供了相关的方法:
rfind(),find_first_of(),find_last_of(),find_first_not_of()
和find_last_not_of()
,它们的特征标与find类似:
rfind
,查找子字符串或字符最后一次出现的位置
Find_first_of
,在字符串中查找参数中任何一个字符首次出现的位置。
Find_last_of
的作用不言自明。
Find_first_not_of
,在字符串中查找第一个不包含在参数中的字符。
Find_last_not_of
的作用不言自明。
string的其它功能
string的功能当然远不止这些,要想继续掌握相关内容,最好的办法是直接看string类的源码。
智能指针模板类
智能指针是行为类似于指针的类对象。智能指针有什么用呢?对于普通的指针,你使用new来分配内存时,不免要使用delete来释放内存,但有的时候就是会出一些稀奇古怪的错误,比如在释放之前触发了异常,很可能就会造成内存泄漏了。而智能指针作为一个类,它的释放不需要delete的,应该说也是需要的吧,但是它存在于析构函数中,而析构函数是不会受上述情况的影响的,所以它的优越性就很明显了。
常用的智能指针有auto_ptr、unique_ptr、shared_ptr。其中auto_ptr是cpp98提供的方案,比较老,所以较之另外两种指针,它的缺点是比较明显的。要创建智能指针对象,必须包含文件memory
智能指针模板位于名称空间std中。
e.g:
#include <iostream>
#include <string>
#include <memory>
class Report
{
private:
std::string str;
public:
Report(const std::string s):str(s)
{ std::cout << "Object created!\n"; }
~Report() { std::cout << "Object deleted!\n"; }
void comment() const { std::cout << str << "\n"; }
};
int main()
{
{
std::auto_ptr<Report> ps(new Report("using auto_ptr"));
ps->comment();
}
{
std::shared_ptr<Report> ps(new Report("using shared_ptr"));
ps->comment();
}
{
std::unique_ptr<Report> ps(new Report("using unique_ptr"));
ps->comment();
}
return 0;
}
将指针赋给智能指针只能显式转换,调用一个构造函数
e.g:
Shared_ptr<double> pshared = p_reg //not allowed
Shared_ptr<double>pshared(p_reg) //allowed
下面来讨论一下上面所说的auto_ptr的缺陷,对于一块内存,如果同时创建多个指向这块内存的指针,我们只要使用其中任一个指针调用delete即可,但是对于智能指针,就不能这样了。原因是所有的智能指针都会调用析构函数。所以当你尝试这样做时:对于auto_ptr,运行时报错。对于unique_ptr,编译时就会发出警告。但是有一种特殊情况,当其中一个指针确定不会再被使用时,这是可行的。优势很明显吧,先发现错误总比花时间去找错误好。对于shared_ptr,它允许这样做,但会增加一组表,来记录还存在的,指向这个内存块的指针的个数,当只剩一个时,这个指针就会调用析构函数了,先前的都不会调用,这样就不存在冲突了。以上就是为何要摈弃auto_ptr的原因。
标准模板库
从这里开始就有点懵了, 先尝试记一下吧,实在不行就回炉重造了。标准模板库我是怎么理解的呢,就是一群大佬,为了帮我们这些菜鸡解放生产力,就开放出一组库,使用了较为简洁的代码。我们只要掌握了这种库的用法,就可以极大的简化我们的程序了,但是还是觉得理解这些东西也不是很容易呢。
第一段摘抄如下:
STL提供了一组表示容器、迭代器、函数对象和算法的模板。容器是一个与数组类似的单元,可以储存若干个值(感觉和类挺像呢)。STL容器是同质的,即储存的值的类型相同;算法是完成特定任务(如对数组进行排序或在链表中查找特定值)的处方;迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针;函数对象是类似于函数的对象,可以是类对象或函数指针(包括函数名,因为函数名被用作指针)。STL使得能够构造各种容器(包括数组、队列和链表)和执行各种操作(包括搜索、排序和随机排列)。STL内容很多(感觉到了),所以本章会介绍一些精华部分。
- vector模板类
e.g:
#include <iostream>
#include <string>
#include <vector>
const int NUM = 5;
int main(void)
{
using std::vector;
using std::string;
using std::cin;
using std::cout;
vector<int> ratings(NUM);
vector<string> titles(NUM);
cout << "You will do exactly as told.You will enter\n"
<< NUM << "book titles and you ratings (0-10).\n";
int i;
for (i = 0; i < NUM; i++)
{
cout << "Enter title #" << i + 1 << ": ";
getline(cin, titles[i]);
cout << "Enter your rating (0-10): ";
cin >> ratings[i];
cin.get();
}
cout << "Thank you. You entered the following:\n"
<< "Rating\tBOOK\n";
for (i = 0; i < NUM; i++)
{
cout << ratings[i] << "\t" << titles[i] << endl;
}
}
- 可对矢量可执行的操作
声明一个迭代器
e.g:
Vector<double>::iterator pd;
自动类型推断
Auto 根据程序自动给出相应的类型
e.g:
Vector<double>::iterator pd;
Auto pd = scores.begin()
一些基本方法:
size()
返回容器中元素的数目
swap()
交换两个容器的内容
begin()
返回一个指向容器中第一个元素的迭代器
End()
返回一个表示超过容器尾的迭代器
Push_back()
将元素添加到矢量末尾
e.g:
Vector<double> scores;
Double temp;
while(cin>>temp && temp>=0)
Scores.push_back(temp);
Cout<<“You entered ”<<scores.size()<<“scores。\n“;
erase() 删除矢量中给定区间的元素
e.g:
Scores.erase(scores.begin(),scores.begin()+2);
insert() 作用于erase()相反,将给定区间的元素加入到某处
e.g:
Old_v.insert(old_v.begin(),new_v.begin()+1,new_v.end());
- 对矢量可执行的其他操作
For_each()
可以替代for循环
e.g:
可以将
Vector<Review>::iterator pr;
for(pr=books.begin();pr!=books.end();pr++)
ShowReview(*pr);
替换为
For_each(books.begin(),books.end(),ShowReview);
Random_shuffle()
指定一个区间,并随机排列该区间中的元素
e.g:
Random_shuffle(books.begin(),books.end());
sort()
对选中区间中的元素进行排序,有两个版本
第一种是没有函数对象作为参数的,程序使用重载的函数例如operator<()
之类的函数进行比较,所以如果程序员使用自己创建对象作为元素,而且还使用这个函数,那它要提供这个重载的函数才得行。
e.g:
Vector<int> coolstuff;
Sort(coolstuff.begin(),coolstuff.end());
第二种是有函数对象的
sort(books.begin(),books.end(),WorseThan);
- 基于范围的for循环
e.g:
Double prices[5]= {4.99,10.99,6.87,7.99,8.49};
for(double x:prices)
Cout<< x <<std::endl;
泛型编程
我的理解:就像物理中希望找到能统一四种力的定理一样,泛型编程也是这么个目的,希望能够设计出一套算法,使得它能够用于很多容器,从而简化重复设计函数的任务。
- 迭代器类型
- 输入迭代器
可用来读取容器中的信息,但不能修改值。是单向的,可以递增,不能倒退 - 输出迭代器
可以修改容器值,而不能读取。它也是单通行的。上述迭代器第二次遍历是不保证与上一次的顺序相同。 - 正向迭代器
保证顺序递增后,仍可以对前面的迭代器值解除引用(如果保存了它)。这使得多次通行算法成为可能。 - 双向迭代器
双向迭代器具有正向迭代器的所有特性,同时支持两种(前缀和后缀)递减运算符。 - 随机访问迭代器
具有双向迭代器的所有特性。添加了支持随机访问的操作(如指针增加运算)和用于对元素进行排序的关系运算符。
迭代器功能 | 输入 | 输出 | 正向 | 双向 | 随机访问 |
---|---|---|---|---|---|
解除引用读取 | 1 | 0 | 1 | 1 | 1 |
解除引用写入 | 0 | 1 | 1 | 1 | 1 |
固定和可重复排序 | 0 | 0 | 1 | 1 | 1 |
++i i++ | 1 | 1 | 1 | 1 | 1 |
–i i– | 0 | 0 | 0 | 1 | 1 |
i[n] | 0 | 0 | 0 | 0 | 1 |
i+n | 0 | 0 | 0 | 0 | 1 |
i-n | 0 | 0 | 0 | 0 | 1 |
i+=n | 0 | 0 | 0 | 0 | 1 |
i-=n | 0 | 0 | 0 | 0 | 1 |
e.g1:
#include <iostream>
#include <iterator>
#include <vector>
int main(void)
{
using namespace std;
int casts[10] = { 6,7,2,9,4,11,8,7,10,5 };
vector<int> dice(10);
copy(casts, casts + 10, dice.begin());
cout << "Let the dice be cast!\n";
ostream_iterator<int, char> out_iter(cout, " ");
copy(dice.begin(), dice.end(), out_iter);
cout << endl;
cout << "Implicit use of reverse iterator.\n";
copy(dice.rbegin(), dice.rend(), out_iter);
cout << endl;
cout << "Explicit use of reverse iterator.\n";
vector<int>::reverse_iterator ri;
for (ri = dice.rbegin(); ri != dice.rend(); ++ri)
cout << *ri << ' ';
cout << endl;
return 0;
}
e.g2:
#include <iostream>
#include <string>
#include <iterator>
#include <vector>
#include <algorithm>
void output(const std::string& s) { std::cout << s << " "; }
int main(void)
{
using namespace std;
string s1[4] = { "fine","fish","fashion","fate" };
string s2[2] = { "busy","bats" };
string s3[2] = { "silly","singers" };
vector<string> words(4);
copy(s1, s1 + 4, words.begin());
for_each(words.begin(), words.end(), output);
cout << endl;
copy(s2, s2 + 2, back_insert_iterator<vector<string>>(words));
for_each(words.begin(), words.end(), output);
cout << endl;
copy(s3, s3 + 2, insert_iterator<vector<string>>(words, words.begin()));
for_each(words.begin(), words.end(), output);
cout << endl;
return 0;
}
ps:STL对各种操作所需要的时间进行了标注,但是我觉得这还不是我应该掌握的东西,所以先略过不计。
下面介绍几种序列容器类型:
- deque
表示双端队列(DOUBLE-ENDED QUEUE)。类似于vector容器,支持随机访问。主要区别在于,从deque对象的开始位置插入和删除元素的时间是固定的,而不像vector是线性的,但为了实现这一目的,deque就要比vector更复杂,可能实现其它操作所需的时间就更多了。 - list(在list头文件中声明),表示双向链表。
除了第一个和最后一个元素外,每个元素都与前后的元素相链接,这意味着可以双向遍历链表。强调元素的快速插入和删除。
list成员函数列举
| 函数 | 说明 |
| :-: | :-: |
|void merge(list<T, Alloc>&x)
| 将链表X与调用链表合并。两个链表必须已经排序。合并后的经过排序的链表保存在调用链表中,x为空。这个函数的复杂度为线性时间。 |
|void remove(const T &val)
| 从链表中删除val的所有实例,这个函数的复杂度为线性时间
|
|void sort()
| 使用<运算符对链表进行排序;N个元素的复杂度为NlogN
|
|Void splice(iterator pos, list<T, Alloc> x)
| 将链表x的内容插入到pos前面,x将为空。这个函数的复杂度为固定时间 |
|Void unique()
| 将连续的相同元素压缩为单个元素。这个函数的复杂度为线性时间
|
merge(合并) splice(铰接)
e.g:
#include <iostream>
#include <list>
#include <iterator>
#include <algorithm>
void outint(int n) { std::cout << n << " "; }
int main(void)
{
using namespace std;
list<int> one(5, 2); //list of 5 2s
int stuff[5] = { 1,2,4,8,6 };
list<int> two;
two.insert(two.begin(), stuff, stuff + 5);
int more[6] = { 6,4,2,4,6,5 };
list<int> three(two);
three.insert(three.end(), more, more + 6);
cout << "List one: ";
for_each(one.begin(), one.end(), outint);
cout << endl << "List two: ";
for_each(two.begin(), two.end(), outint);
cout << endl << "List three: ";
for_each(three.begin(), three.end(), outint);
three.remove(2);
cout << endl << "List three minus 2s: ";
for_each(three.begin(), three.end(), outint);
three.splice(three.begin(), one);
cout << endl << "List three after splice: ";
for_each(three.begin(), three.end(), outint);
cout << endl << "List one: ";
for_each(one.begin(), one.end(), outint);
three.unique();
cout << endl << "List three after unique: ";
for_each(three.begin(), three.end(), outint);
three.sort();
three.unique();
cout << endl << "List three after sort&unique: ";
for_each(three.begin(), three.end(), outint);
two.sort();
three.merge(two);
cout << endl << "Sorted two merged into three: ";
for_each(three.begin(), three.end(), outint);
cout << endl;
return 0;
}
- forward_list
单向链表,只需正向迭代器,不可反转。更简单,更紧凑,但功能更少。 - queue
queue模板类(在头文件queue中声明)是一个适配器类。不仅不允许随机访问队列元素,甚至不允许遍历队列。
queue的操作
| 方法 | 说明 |
| - | - |
|Bool empty() const
| 如果队列为空,则返回true;否则返回false |
|Size_type() const
| 返回队列中元素的数目 |
|T& front()
| 返回指向队首的元素的引用 |
|T& back()
| 返回指向队尾元素的引用 |
|void push(const T& x)
| 在队尾插入x |
|void pop()
| 删除队首元素 | - priority_queue(在queue头文件中声明)
是另一个适配器类。支持的操作与queue相同,主要区别在于,最大的元素被移到队首。内部区别在于,默认的底
层类是vector。可以修改用于确定哪个元素放到队首的比较方式,方法是提供一个可选的构造。
函数参数:
Priority_queue<int> pg2(greater<int>);
- stack(在头文件stack中)
把使用限制在定义栈的基本操作上
| 方法 | 说明 |
| - | - |
|Bool empty() const
| 如果栈为空,则返回true;否则返回false |
|size_type size() const
| 返回栈中的元素数目 |
|T &top()
| 返回指向栈顶元素的引用 |
|void push(const T&x)
| 在栈顶元素插入x |
|void pop()
| 删除栈顶元素 | - array(头文件array定义)
并非STL容器,因为其长度是固定的。
有关关联容器:
STL提供四种关联容器:set、multiset、map和multimap
前两种是在头文件set中定义的,而后两种是在头文件map中定义的。最简单的关联容器是set其键和值的类型相同,键是唯一的。multiset类似于set,只是可能有多个值的键相同。在map中,值和键的类型不同,键是唯一的,每个键只对应一个值。multimap与map相似,只是一个键可以与多个值相关联。
set&&multiset:
有关set的一个示例:
#include <iostream>
#include <string>
#include <set>
#include <algorithm>
#include <iterator>
int main()
{
using namespace std;
const int N = 6;
string s1[N] = { "buffoon","thinkers","for","heavy","can","for" };
string s2[N] = { "metal","any","food","elegant","deliver","for" };
set<string> A(s1, s1 + N);
set<string> B(s2, s2 + N);
ostream_iterator<string, char> out(cout, " ");
cout << "Set A: ";
copy(A.begin(), A.end(), out);
cout << endl;
cout << "Set B: ";
copy(B.begin(), B.end(), out);
cout << endl;
cout << "Union of A and B:\n";
set_union(A.begin(), A.end(), B.begin(), B.end(), out);
cout << endl;
cout << "Intersection of A and B:\n";
set_intersection(A.begin(), A.end(), B.begin(), B.end(), out);
分区 C^LM^LMPrimer Plus 的第 35 页set_intersection(A.begin(), A.end(), B.begin(), B.end(), out);
cout << endl;
cout << "Difference of A and B:\n";
set_difference(A.begin(), A.end(), B.begin(), B.end(), out);
cout << endl;
set<string> C;
cout << "Set C:\n";
set_union(A.begin(), A.end(), B.begin(), B.end(),
insert_iterator<set<string>>(C, C.begin());
copy(C.begin(), C.end(), out);
cout << endl;
string s3("grungy");
C.insert(s3);
cout << "Set C after insertion:\n";
copy(C.begin(), C.end(), out);
cout << endl;
cout << "Showing a range:\n";
copy(C.lower_bound("ghost"), C.upper_bound("spook"), out);
cout << endl;
return 0;
}
- map&&multimap
count() 接受键为参数,返回具有该键的元素数目
Equal_range用键作为参数,且返回两个迭代器,它们表示的区间与该键匹配。
e.g:
#include <iostream>
#include <string>
#include <map>
#include <algorithm>
typedef int KeyType;
typedef std::pair<const KeyType, std::string> Pair;
typedef std::multimap<KeyType, std::string> MapCode;
int main(void)
{
using namespace std;
MapCode codes;
codes.insert(Pair(415, "San Francisco"));
codes.insert(Pair(510, "Oakland"));
codes.insert(Pair(718, "Brooklyn"));
codes.insert(Pair(718, "Staten Island"));
codes.insert(Pair(415, "San Rafael"));
codes.insert(Pair(510, "Berkley"));
cout << "Number of cities with area code 415: "
<< codes.count(415) << endl;
cout << "Number of cities with area code 718: "
<< codes.count(718) << endl;
cout << "Number of cities with area code 510: "
<< codes.count(510) << endl;
cout << "Area Code City\n";
MapCode::iterator it;
for (it = codes.begin(); it != codes.end(); ++it)
cout << "
" << (*it).first << "
"
<< (*it).second << endl;
pair<MapCode::iterator, MapCode::iterator>range
= codes.equal_range(718);
cout << "Cities with area code 718:\n";
for (it = range.first; it != range.second; ++it)
cout << (*it).second << endl;
return 0;
}
无序关联容器
关联容器是基于树结构的,而无序关联容器是基于数据结构哈希表的。那么等我学完了数据结构再说吧。
函数对象
很多STL算法都使用函数对象–也叫函数符。它包括函数名,指向函数的指针,重载了()运算符的对象。
STL定义的函数符的概念
- 生成器(generator)是不用参数就可以调用的函数符
- 一元函数(unary function)是用一个参数就可以调用的函数符
- 二元函数(binary function)是用两个参数就可以调用的函数符
- 返回bool值的一元函数是谓词(predicate)
- 返回bool值的二元函数是二元谓词(binary predicate)
有关预定义的函数运算符
STL提供了一系列模板函数对象,这样在进行一些基础操作使就不必重复定义函数了(它们处
于头文件functional中)
e.g
#include<functional>
...
Plus<double> add;
Double y=add(2.3,3.4);
transform(gr8.begin(),gr8.end(),m8.begin(),out,plus<double>());
运算符 | 相应的函数符 |
---|---|
+ | plus |
- | minus |
* | multiplies |
/ | divides |
% | modulus |
- | negate |
== | equal_to |
!= | not_equal_to |
> | greater |
< | less |
>= | greater_equal |
<= | less_equal |
&& | logical_and |
! | logical_not |
自适应函数符和函数适配器
感觉就是可以通过相应的规则使不可能变为可能吧。比如说,STL使用binder 1st和binder2nd类自动完成这一过程。
e.g
bind1st(multiplies<double>(),2.5)
transform(gr8.begin(),gr8.end(),out,
bind1st(multiplies<double>(),2.5));
算法
STL将算法库分成4组
- 非修改式序列操作
- 修改式序列操作
- 排序和相关操作
- 通用数字计算
STL和string类
string虽然不是STL的组成部分,但是它设计是也考虑到了STL的,所以它提供了一些成员以
便使用STL。其中next_permutation()算法将区间内容转换为下一钟排列方式
e.g:
#include <iostream>
#include <string>
#include <algorithm>
int main()
{
using namespace std;
string letters;
cout << "Enter the letter grouping (quit to quit): ";
while (cin >> letters && letters != "quit")
{
cout << "Permutations of " << letters << endl;
sort(letters.begin(), letters.end());
cout << letters << endl;
while (next_permutation(letters.begin(), letters.end()))
cout << letters << endl;
cout << "Enter next squence (quit to quit): ";
分区 C^LM^LMPrimer Plus 的第 39 页cout << "Enter next squence (quit to quit): ";
}
cout << "Done.\n";
return 0;
}
模板initializer_list
它可使用初始化列表语将STL容器初始化为一系列值