benxintuzi

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

[1] string基础

[1.1] string 的构

 1 #include <iostream>
 2 #include <string>
 3 
 4 int main()
 5 {
 6     using namespace std;
 7 
 8     cout << "1 --- string(const char* s):将string对象初始化为s指向的C风格字符串" << endl;
 9     string one("benxintuzi_1");
10     cout << "one = " << one << endl;    // 重载了 <<
11 
12     cout << "2 --- string(size_type n, char c):以字符c初始化包含n个字符的string对象" << endl;
13     string two(20, '$');
14     cout << "two = " << two << endl;
15 
16     cout << "3 --- string(const string& str):复制构造函数初始化" << endl;
17     string three(one);
18     cout << "three = " << three << endl;
19 
20     cout << "4 --- string():默认构造函数" << endl;
21     string four;
22     four += one;            // 重载了 +=
23     four = two + three;     // 重载了 +
24     cout << "four = " << four << endl;
25 
26     cout << "5 --- string(const char* s, size_type n):用s指向的C风格字符串的前n个字符初始化string对象" << endl;
27     char s[] = "benxintuzi";
28     string five(s, 5);
29     cout << "five = " << five << endl;
30 
31     cout << "6 --- string(Iter begin, Iter end):用[begin, end)区间内的字符初始化string对象" << endl;
32     string six(s + 2, s + 5);
33     cout << "six = " << six << endl;
34 
35     cout << "7 --- string(const string& str, string size_type pos = 0, size_type n = npos):将string对象初始化为str中从pos开始的n个字符" << endl;
36     string seven(four, 5, 2);
37     cout << "seven = " << seven << endl;
38 
39     /*
40 cout << "8 --- string(string&& str) noexcept:将string对象初始化为str,并可能修改str,移动构造函数[C++11新特性]" << endl;
41 
42     cout << "9 --- string(initializer_list<char>il:将string对象初始化为初始化列表il中的字符[C++11新特性]" << endl;
43     string nine = {'t', 'u', 'z', 'i'}; // or string nine{'t', 'u', 'z', 'i'};
44     cout << "nine = " << nine << endl;
45     */
46 }
View Code

 

[1.2] string 的输入

对于 C 风格字符串,有3种输入方式

char info[100];

cin >> info;             // 从流中读一个单词存放到info中

cin.getline(info, 100);  // 从流中读入一行存放到info,删除流中的\n

cin.get(info, 100);      // 从流中读入一行存放到info,保留流中的\n

对于 string 对象,有2种输入方式

string stuff;

cin >> stuff;            // 从流中读取一个单词

getline(cin, stuff);     // 从流中读取一行,删除流中的\n

getline可以指定用于分割单词的分隔符,将其放到第三个参数中,如:

cin.getline(info, 100, ‘:’);      // C 风格字符串使用这种形式

getline(stuff, ‘:’);                 // string 类使用这种形式

 

string与传统 C 风格字符串的比较:

使用string类输入时,不需要具体指定输入的字符个数,其可以自动匹配字符串大小,使用C风格字符串必须显示指定,因为C风格字符串使用的是istream类的方法,而string版本使用的是独立的函数,因此形式上有所区别,这种区别在其他运算符上也有体现,如>>操作符:

C 风格字符串使用:cin.operator>>(fname),而string风格为:operator(cin, fname);

说明:

string版本的getline()在如下三种情况下结束读取:

1 到达文件末尾:此时输入流中的eofbit置位;

2 遇到分割符:默认为\n,此时,删除流中的\n;

3 读取的字符个数达到最大值[min(string::npos, 空闲内存)],此时将输入流的failbit置位。

 

在输入流中有一个统计系统,用于跟踪流的状态:

1 检测到文件末尾,设置eofbit寄存器;

2 检测到错误,设置failbit寄存器;

3 检测到无法识别的故障,设置badbit寄存器;

4 检测到一切顺利,设置goodbit寄存器。

 

string 类对全部 6 个关系运算符进行了重载,对于每个关系运算符,又进行了3种重载,分别是:

string 对象与 string 对象;

string 对象 C 风格字符串;

C 风格字符串与 string 对象。

 

[2] 智能指针(smart pointer)

智能指针是行为类似于指针,但却是类。其可以帮助管理动态分配的内存,有三种可选:分别为auto_ptr\unique_ptr\shared_ptr。auto_ptr是C++ 98提供的,C++ 11已经抛弃了(虽然已经被抛弃,但是仍然被大量使用)。

小知识:

为何新标准要放弃auto_ptr?理由如下:

假设如下赋值:

auto_ptr<string> ps1(new string(“benxintuzi”));

auto_ptr<string> ps2;

ps2 = ps1;

如果ps2和ps1是普通指针,那么他们将同时指向一块堆内存,那么auto_ptr类型的指针若同时指向一块堆内存,那么就会调用两次delete,这个太可怕了,因此,auto_ptr采用的策略是如果发生赋值,那么就会令ps1为0,同样unique_ptr也是采用这种策略,但是更加严格。

如下,当ps2 = ps1,即ps1置空时,如果发生调用*ps1,那么相当于调用了一个空指针指向的对象,在auto_ptr情况下,可以编译通过,在运行时会出错;但在unique_ptr情况下,不会让你通过编译的。

结论就是unique_ptr比auto_ptr更安全一些。

 

shared_ptr对于每个堆对象,其维护一个指向同一对象的引用计数,只有当引用计数为0时才调用delete,因此可以很好地支持智能指针赋值操作。

要创建智能指针,必须包含头文件memory,然后使用模板语法实例化所需类型的指针,例如auto_ptr包含如下构造函数:

template<class X> class auto_ptr

{

public:

  // throw()意味着不引发异常【C++ 11也将throw()抛弃了】

    explicit auto_ptr(X* p = 0) throw(); 

    ...

}

因此,智能指针的使用非常简单,只需要用特定类型的指针初始化即可,如:

auto_ptr<double> pd(new double);

说明:

智能指针都放在名称空间std中(注意shared_ptr和unique_ptr都是C++ 11新增的,旧时的编译器可能不支持)。

 1 #include <iostream>
 2 #include <string>
 3 #include <memory>
 4 
 5 class Report
 6 {
 7 public:
 8     Report(const std::string s) : str(s)
 9     {
10         std::cout << "Object created!" << std::endl;
11     }
12     ~Report()
13     {
14         std::cout << "Object deleted!" << std::endl;
15     }
16     void comment() const
17     {
18         std::cout << "str = " << str << std::endl;
19     }
20 
21 private:
22     std::string str;
23 };
24 
25 int main()
26 {
27     std::auto_ptr<Report> pa(new Report("using auto_ptr"));
28     pa->comment();
29 
30     std::shared_ptr<Report> ps(new Report("using shared_ptr"));
31     ps->comment();
32 
33     std::unique_ptr<Report> pu(new Report("using unique_ptr"));
34     pu->comment();
35 
36     return 0;
37 }
38 
39 /** output */
40 Object created!
41 str = using auto_ptr
42 Object created!
43 str = using shared_ptr
44 Object created!
45 str = using unique_ptr
46 Object deleted!
47 Object deleted!
48 Object deleted!

注意:

绝对不要将非堆内存指针赋予智能指针,否则虽然可以通过编译,但是情况并非总是乐观的,这就相当于delete了非堆内存,容易引发难以发现的问题。

 

关于如何选择合适的智能指针,如下给出参考建议:

如果需要多个指向同一堆对象的有效指针,那么选择使用shared_ptr这种情况包括:

1 有一个指针数组,并且使用一些辅助指针标识特殊元素,如标识最值元素;

2 两个对象中都包含指向第三个对象的指针;

3 STL容器中的指针等,很多STL算法都支持复制和赋值操作。

如果程序不需要多个有效指针同时指向同一堆对象,那么使用unique_ptr。如果需要将unique_ptr作为右值时,可将其赋值给shared_ptr;在满足unique_ptr条件时,也可使用auto_ptr,但unique_ptr似乎是更好的选择(如果编译器不提供unique_ptr,可以使用boost库提供的scoped_ptr,其功能与unique_ptr类似)。

 

[3] 迭代器

[3.1] 迭代器基础

迭代器就是广义的指针,可对其进行递增操作和解引用操作等的对象。每个容器类都定义了一个与之相关的迭代器,该迭代器是一个名为iterator的typedef定义,作用域为整个类。模板使算法独立于数据类型,而迭代器使算法独立于容器类型。

迭代器主要用于遍历容器中的元素,其应该具有如下基本功能:

1 支持解引用操作

2 赋值操作

3 比较操作,如 ==、!=

4 自增操作

具备以上能力就差不多了。其实STL按照迭代器功能强弱定义了多种级别5种)的迭代器,说明如下:

迭代器类型

说明

输入迭代器

【读容器】可来读取容器中的元素,但可能不会修改容器中的元素。输入迭代器必须可以访问容器中的所有元素,因此,支持++操作。

输出迭代器

【写容器】程序可以修改容器中的元素值,但不能读取容器中的元素。

正向迭代器

【读或者写容器】单向读写。

双向迭代器

【双向读或者写容器】双向读写。支持自减运算符。

随机访问迭代器

【双向读或者写容器】提供了附加的“跳步”访问能力。

迭代器支持的操作含义:

表达式

说明

a + n、n + a

指向a所指元素后的第n个元素

a - n

指向a所指元素前的第n个元素

r += n、r -= n

r = r + n、r = r – n

a[n]

*(a + n)

b - a

a ~ b区间的元素个数

a < b、a > b、a >= b、a <= b

逻辑判断

STL迭代器功能汇总:

总结:

迭代器支持的通常操作为解引用读或者写;所有类型的迭代器都支持自增操作;自减操作只有双向迭代器和随机访问迭代器支持;随机访问迭代器为“跳步”访问提供了可能。因此迭代器能力大小可以表示如下:

随机访问迭代器 > 双向迭代器 > 正向迭代器 > 输入迭代器 == 输出迭代器

 

[3.2] 迭代器进阶

迭代器是广义的指针,而指针满足所有迭代器的要求。因此STL算法可以使用指针来对基于指针的非STL容器进行操作。例如,可将STL算法用于数组:

如果要将一个double Receipts[100]进行排序,可以使用STL的算法sort,但传入的确是指针,如:

sort(Receipts, Receipts + 100);

STL提供了一些预定义的迭代器:copy()、ostream_iterator、istream_iterator。

假设有如下定义:

int casts[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

vector<int> dice(10);

copy(casts, casts + 10, dice.begin()); // 将[casts, casts + 10)范围内的数据复制到dice.begin()开始的目标空间中

 

输出流迭代器:

假设现在需要将数据复制到显示器上,那么需要一个表示输出流的迭代器,STL为我们提供了ostream_iterator模板。该迭代器是一个适配器,可将所有的接口转换为STL使用的接口。我们使用时必须包含头文件iterator,并且做出如下声明来创建该迭代器:

#include <iterator>

...

ostream_iterator<int, char> out_iter(cout, “ ”);

说明:

int: 表示发送到输出流中的数据类型;

char: 表示输出流使用的字符类型;

构造函数中,cout: 表示要使用的输出流;“ ”:表示所发送数据的分隔符;

可以这样使用迭代器:

*out_iter++ = 15; // 等价于cout << 15 << “ ”;

这条语句表明将”15 ”赋予指针指向的位置,然后指针向前移动一个单位。

 

实现如上的copy动作为:

copy(dice.begin(), dice.end(), out_iter)即可将dice容器的整个区间复制到显示器中显示。

当然也可以创建匿名的迭代器,如下:

copy(dice.begin(), dice.end(), ostream_iterator<int, char>(cout, “ ”));

 

输入流迭代器:

对于的输入流迭代器为istream_iter模板:

copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(), ostream_iterator<int, char>(cout, “ ”));

说明:

int:要读取的数据类型;

char:输入流使用的数据类型;

构造函数中,第一个参数cin表示读取由cin管理的输入流;省略构造函数意味着输入失败,因此上述代码从输入流中读取int型数据,直到文件末尾、类型不匹配或者出现其他输入故障为止才输出到屏幕上。

 1 #include <iostream>
 2 #include <iterator>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(), 
 8          ostream_iterator<int, char>(cout, " "));
 9     return 0;
10 }
11 
12 /** output */
13 1 2 3 4 5
14 1 2 3 4 5 __ 

除了istream_iterator和ostream_iterator外,头文件iterator中还提供了一些专用的迭代器,如:reverse_iterator、back_insert_iterator、front_insert_iterator和insert_iterator。

reverse_iterator执行递增时,指针实际上发生递减操作。例如:vector类中的rbegin()和rend()分别返回指向最后一个元素下一位置的指针、指向第一个元素的指针。

强调:

虽然rbegin()和end()返回的指针虽然指向同一个位置,但是类型不同,前者类型是reverse_iterator,后者类型是iterator。

同样要注意的是:对于反向迭代的解引用的具体实现实则是先递减,再解引用。否则会出错。试想一下,如果一个rp是rbegin()返回的,直接解引用将导致操作一个空指针。

 1 #include <iostream>
 2 #include <iterator>
 3 #include <vector>
 4 using namespace std;
 5 
 6 /*
 7 int main()
 8 {
 9     copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(),
10          ostream_iterator<int, char>(cout, " "));
11     return 0;
12 }
13 */
14 
15 int main()
16 {
17     int casts[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
18     vector<int> dice(10);
19     copy(casts, casts + 5, dice.begin());          // 将casts的前5个数据复制到dice中
20 
21     ostream_iterator<int, char> out_iter(cout, " "); // 创建输出流迭代器
22     copy(dice.begin(), dice.end(), out_iter);     // 将dice中的全部元素复制到屏幕上输出
23 
24     cout << endl;
25 
26     /* 隐式使用reverse_iterator */
27     copy(dice.rbegin(), dice.rend(), out_iter);  // 将dice中的全部元素反向输出到屏幕上
28 
29     cout << endl;
30 
31     /* 显式使用reverse_iterator */
32     vector<int>::reverse_iterator ri;
33     for(ri = dice.rbegin(); ri != dice.rend(); ++ri)
34         cout << *ri << " : ";
35     cout << endl;
36 
37     return 0;
38 }
39 
40 /** output */
41 1 2 3 4 5 0 0 0 0 0
42 0 0 0 0 0 5 4 3 2 1
43 0 : 0 : 0 : 0 : 0 : 5 : 4 : 3 : 2 : 1 :

在形式上,STL中的很多算法都和copy函数类似,如下为三种插入迭代器操作:

back_insert_iterator将元素插入容器尾部;

front_insert_iterator将元素插入容器头部;

insert_iterator将元素插入指定位置。

插入操作相比于copy操作而言,可以自动增加空间,而copy确是静态分配好的空间,即使不够了也不会向操作系统去申请,默认情况下会截断后边的数据,只复制容量允许的数据。使用插入迭代器时,使用容器类型作为模板参数,使用容器变量作为构造参数,如下所述:

back_insert_iterator<vector<int>> back_iter(dice);

对于insert_iterator,还需要一个指定插入位置的构造参数:

insert_iterator<vector<int>> insert_iter(dice, dice.begin());

 

[4]容器类

容器用于存储对象,其要求所存储的对象必须具有相同的类型,而且该类型必须是可复制构造和可赋值的。一般基本类型和类类型都满足要求(当然除非你把这两种函数声明为私有或保护类型的)。C++ 11中又添加了可复制插入和可移动插入要求

容器类型是用于创建具体容器的模板。之前共有11个容器类型:deque/list/queue/priority_queue/stack/vector/map/multimap/set/multiset/biset,C++ 11新增了5个:forward_list/unordered_map/unordered_multimap/unordered_set/unordered_multiset。

 

何为序列?

序列保证了元素将按特定的顺序排列,不会在两次迭代之间发生变化。序列还要求其元素严格按线性顺序排列,即存在第一个/第二个/.../等。比如数组和链表都是序列,但分支结构就不是序列。

由于序列中的元素具有特定的顺序,因此可以执行像插入元素到特定位置、删除特定区间等操作。如下容器都为序列容器

vector

vector是数组的一种类的表示,它提供了自动管理内存的功能,可以动态改变vector的长度,增大或减小。vector是可以反转的,主要是通过rbegin()和rend()实现的,并且其返回的迭代器类型都是reverse_iterator。

deque

双端队列。其实现类似于vector,支持随机访问。主要区别在于可以从deque中开头和结尾处插入和删除元素,而且其时间复杂度固定。

list

双向链表。与vector类似,list也可以反转。主要区别是:list不支持随机访问。除此之外,list模板类还包括了链表专用的成员函数,如下所示:

void merge(list<T, Alloc>& x): 将链表x与调用链表合并,并且已然有序。合并后的链表保存在调用链表中,x为空。

void remove(const T& val): 从链表中删除val的所有实例。

void sort(): 使用<运算符对链表进行排序,时间复杂度为nlgn

void splice(iterator pos, list<T, Alloc>x): 将链表x的内容插入到pos前边,插入后x为空。

void unique(): 将链表中连续的相同元素压缩为单个元素。

说明:

insert()和splice()之间的主要区别在于:insert是插入原始区间的副本到目标地址,而splice是将原始区间转移到目标地址。

C++ 11新增了容器类forward_list,实现了单链表,与之关联的迭代器是正向迭代器而非双向迭代器。

queue

queue模板类是一个适配器类,如前所述,ostream_iterator模板也是一个适配器,可以让输出流使用迭代器接口,同样,queue模板类让底层类(默认为deque)展示出队列接口。

queue模板的限制比deque更多。他不仅不允许随机访问,而且不允许遍历操作。其把使用限制在队列基本操作上,如入队、出队、取队首、判队空等,具体如下:

bool empty() const: 如果队列为空则返回true;否则返回false

size_type size() const: 返回队列中元素的数目。

T& front(): 返回指向队首元素的引用。

T& back(): 返回指向队尾元素的引用。

void push(const T& x): 在队尾插入x

void pop(): 删除队首元素。

说明:

priority_queue与queue的主要区别在于:最大元素被移到队首。其内部实现为vector,可以指定内部元素排序规则。

stack

适配器类,使用vector实现,支持操作如下:

bool empty() const: 如果栈为空则返回true;否则返回false

size_type size() const: 返回栈中元素的数目。

T& top(): 返回指向栈顶元素的引用。

void push(const T& x): 在栈顶插入x

void pop(): 删除栈顶元素。

array(C++ 11 新增)

模板类array并非STL容器,因为其长度是固定的。因此不能使用动态调整容器大小的函数如push_back()和insert()等,但是其定义了很有用的成员函数,如operator[]()和at(),正如之前所述,许多标准的STL算法也可用于array对象,如:copy()和for_each()。

 

操作示例:

 1 #include <iostream>
 2 #include <list>
 3 #include <iterator>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 void PrintList(int n){ cout << n << " "; }
 8 
 9 int main()
10 {
11     list<int> one(5, 2); // 5个2
12     cout << "list one: ";
13     for_each(one.begin(), one.end(), PrintList);
14     cout << endl;
15 
16     list<int> two;
17     int stuff[3] = {2, 5, 9};
18     two.insert(two.begin(), stuff, stuff + 3);
19     cout << "list two: ";
20     for_each(two.begin(), two.end(), PrintList);
21     cout << endl;
22 
23     list<int> three(two);
24     three.insert(three.end(), one.begin(), one.end());
25     cout << "list three: ";
26     for_each(three.begin(), three.end(), PrintList);
27     cout << endl;
28 
29     cout << "merge one and three: ";
30     three.splice(three.begin(), one);
31     for_each(three.begin(), three.end(), PrintList);
32     cout << endl;
33 
34     cout << "delete the duplicate elements: ";
35     three.unique();
36     for_each(three.begin(), three.end(), PrintList);
37     cout << endl;
38 
39     cout << "merge with another: four = ";
40     list<int> four(3, 8);
41     four.merge(three);
42     for_each(four.begin(), four.end(), PrintList);
43     cout << endl;
44 
45     cout << "remove the value 8 in four: ";
46     four.remove(8);
47     for_each(four.begin(), four.end(), PrintList);
48     cout << endl;
49 
50     return 0;
51 }
52 
53 /** output */
54 list one: 2 2 2 2 2
55 list two: 2 5 9
56 list three: 2 5 9 2 2 2 2 2
57 merge one and three: 2 2 2 2 2 2 5 9 2 2 2 2 2
58 delete the duplicate elements: 2 5 9 2
59 merge with another: four = 2 5 8 8 8 9 2
60 remove the value 8 in four: 2 5 9 2

 

 

 

 

posted on 2015-09-09 17:34  benxintuzi  阅读(2425)  评论(1编辑  收藏  举报