C++模板库笔记

C++标准模板库笔记(C++ Primer plus)

1.除序列外,vector还是可反转容器(reversible container)概念的模型。

它有两个类方法:rbegin()和rend(),前者返回一个指向反转序列的第

一个元素的迭代器,后者返回反转序列的超尾迭代器。这些迭代

器都是reverse_iterator,对这样的迭代器执行递增操作,将导致它

反向遍历可反转容器。

如果dice是一个vector容器,而Show(int)是显示一个整数的函数,

则:

for_each(dice.begin(),dice.end(),Show); //正向显示dice的内容

for_each(dice.rbegin(),dice.rend(),Show);   //反向显示dice的内容

vector模板类是最简单的序列模型,除非其他类型的特殊有点能够更好地满足程序的要求,否则应该默认使用这种类型。

2.deque模板类(在deque头文件中声明)表示双端队列(double-ended queue),通常被简称为deque。

在STL中,其实现类似于vector容器,支持随机访问。其主要区别

在于,从deque对象的开始位置插入和删除元素的时间是固定的,

而不像vector中那样是线性时间的。

虽然vector和deque都提供对元素的随机访问和在序列中部执行线

性时间的插入和删除操作,但vector容器执行这些操作时速度要快些。

3.list模板类(在list头文件中声明)表示双向链表。

区别:list在链表中任一位置进行插入和删除的时间都是固定的,

因此,vector强调的是通过随机访问进行快速访问,而list强调的

是元素的快速插入和删除。

与vector相似,list也是可反转容器。不同的是,list不支持数组表

示法和随机访问,从容器中插入或删除元素之后,链表迭代器指

向元素将不变。(只是修改连接信息,指向某个元素的迭代器仍

然指向该元素。)

list 模板包含了链表专用的成员函数:
[图片上传失败...(image-ab7c24-1567059183323)]insert()和splice()之间的主要区别在于:insert()将原始区间的副本插入到目标地址,而splice()则将原始区间移到目标地址。移动后,被移动的容器将置空(splice()方法还有其他原型,用于移动单个元素和元素区间)。原始容器的迭代器将重新定位到新容器,迭代器仍然指向相同的元素。

1.void splice ( iterator position, list& x );

2.void splice ( iterator position, list& x, iterator it );

3.void splice ( iterator position, list& x, iterator first, iterator last );

position 是要操作的list对象的迭代器

1:会在position后把list&x所有的元素到剪接到要操作的list对象
2:只会把it的值剪接到要操作的list对象中
3:把first 到 last 剪接到要操作的list对象中

unique()只能将相邻的相同值压缩为单个值,但使用sort()后,再

使用unique()时,每个值将只占一个位置。

对于 非成员sort()函数,它需要随机访问迭代器,因为快速插入的

代价是放弃随机访问功能,所以不能将非成员函数sort()用于链表。

因此,list容器中包括了一个只能在类中使用的成员版本。

4.C++11新增了容器类forward_list,它实现了单链表。在这种链表

中,每个节点都只链接到下一个节点。因此,forward_list只需要

正向迭代器,是不可反转容器。相比于list,forward_list更简单,更

紧凑,但功能也更少。

5.queue模板类是一个适配器类

ostream_iterator模板也是一个适配器,让输出流能够使用迭代器

接口。queue模板让底层类(默认为deque)展示典型的队列接口。

queue模板的限制比deque更多,它不仅不允许随机访问队列元

素,甚至不允许遍历队列。

6.priority_queue模板类(在queue头文件中声明)是另一个适配

器类,它支持的操作与queue相同。两者的主要区别在于:在

priority_queue中,比重最大的元素被移到队首,内部区别在于,

默认的底层类是vector。
它可以修改用于确定哪个元素被放到队首的比较方式

7.stack 与queue相似,stack也是一个适配器类,它给底层类(默

认是vector)提供了典型的栈接口

stack模板的限制比vector更多,它不仅不允许随机访问栈元素,

甚至不允许遍历栈。

8.array 模板类array在头文件array中定义,它不是STL容器,因为

其长度是固定的。像push_back(),insert()这种调整容器大小的操

作,array都没有,但是它有operator和at()。可将很多标准STL算

法用于array对象,如copy()和for_each()。

9.关联容器(associative container) 将值与键关联在一起,并用

键来查找值。

关联容器的优点在于,它提供了对元素的快速访问。关联容器允

许插入新元素,但不能指定元素的插入位置。原因是关联容器通

常有用于确定数据放置位置的算法,以便能快速检索信息。

关联容器通常是使用某种树实现的。像链表一样,树的节点使得

添加或删除数据项比较简单。但相对于链表,树的查找速度更

快。

STL提供了四种关联容器:set, multiset, map, 和multimap
(set和map头文件)

最简单的关联容器是set,其值和键的类型相同,键是唯一的。

multiset类似于set,只是可能有多个值的键相同。

map中 值与键的类型不同,键是唯一的。但是multimap中一个键

可以对应多个值。

10.无序关联容器 与关联容器一样,也将值与键关联起来,并使用

键来查找值。但底层的差别在于,关联容器是基于树结构的,而

无序关联容器是基于哈希表的,目的是提高元素的添加和删除速

度以及提高查找算法的效率。

11.函数对象: 很多STL算法都使用函数对象——也叫函数符

(functor)。函数符是可以以函数方式与()结合使用的任意对象。这

包括函数名,指向函数的指针和重载了()运算符的类对象。

12.对于所有内置的算术运算符,关系运算符和逻辑运算符,STL

都提供了等价的函数符。

2
13. STL算法有两个主要的通用部分。首先,它们都使用模板来提

供泛型;其次它们都使用迭代器来提供访问容器中数据的通用表

示。

STL将算法库分成4种:

  • 非修改式序列操作(find(),for_each()...)
  • 修改式序列操作(transform(),random_shuffle(),copy()...)
  • 排序和相关操作(sort(),其它集合操作等等)
  • 通用数字运算(比如将区间的内容累积,计算两个容器的内部乘积)
    前3组在头文件algorithm中,第4组在numeric中。

有些算法有两个版本:就地版本和复制版本。STL的约定是,复

制版本的名称将以_copy结尾。复制版本将接受一个额外的输出迭

代器参数,该参数指定结果的放置位置。如:void replace_copy(...)

有些函数有这样的版本,即根据将函数应用于容器元素得到的结果

来执行操作。这些版本的名称通常以_if结尾。如:void replace_if(...)

14.string类虽然不是STL的组成部分,但设计它时考虑到了STL,

因此可以使用STL接口。

next_permutation()算法将区间内容转换为下一种排列方式。对于

字符串,排列按照字母递增的顺序进行。如果成功则返回true,

如果区间已经处于最后的序列中,则返回false。比如:

string letters;
...
sort(letters.begin(),letters.end());
while(next_permutation(letters.begin(),letters.end())
cout<

这个将输出字符串的所有排列。

15.有时可以选择使用STL方法STL函数。通常 方法 是更好的选

择。首先,它更适合于特定的容器;其次,作为成员函数,它可

以使用模板类的内存管理工具,从而在需要时调整容器的长度。

而函数,它不是成员,并不能调整容器的长度。(不过可以让函

数的返回值是指向新的超尾值的迭代器,来提醒容器长度改变了)

16.编写一个程序,让用户输入单词。希望最后得到一个按输入顺

序排列的单词列表、一个按字母顺序排列的单词列表(忽略大小写)

,并记录每个单词被输入的次数。

//set会自动对元素进行排序,并且不会有相同元素出现。
#include
#include
#include
#include
#include
#include
#include
using namespace std;

char toLower(char ch) { return tolower(ch); }
string& ToLower(string& st);
void display(const string& s);

int main()
{
    vectorwords;
    cout << "Enter words (enter quit to quit):" << endl;
    string input;
    while (cin >> input && input != "quit") {
        words.push_back(input);
    }
    cout << "You entered the following words:" << endl;
    for_each(words.begin(), words.end(), display);
    cout << endl;
    
    setwordset;
    transform(words.begin(), words.end(), 
        insert_iterator>(wordset, wordset.begin())
        , ToLower);
    cout << endl << "Alphabetic list of words:" << endl;
    for_each(wordset.begin(), wordset.end(), display);
    cout << endl;

    mapwordmap;
    set::iterator si;
    for (si = wordset.begin(); si != wordset.end(); ++si)
        wordmap[*si] = count(words.begin(), words.end(), *si);

    cout << endl << "Word frequency:" << endl;
    for (si = wordset.begin(); si != wordset.end(); ++si)
        cout << *si << ": " << wordmap[*si] << endl;
    return 0;
}

string& ToLower(string& st)
{
    transform(st.begin(), st.end(), st.begin(), toLower);
    return st;
}

void display(const string& s)
{
    cout << s << " ";
}

C++提供三个数组模板:vector、valarray和array。
vector模板类支持面向容器的操作,
valarray是面向数值计算的,不是STL的一部分,
array是为替代内置数组而设计的。
若将两个数组各个元素相加,并将和存储到新的的对象中:
vector< double > ved1(10),ved2(10),ved3(10);
array vod1,vod2,vod3;
valarray< double >vad1(10),vad2(10),vad3(10);

对于vector类:
transform(ved1.begin(),ved1.end(),ved2.begin(),ved3.begin(),plus< double >());
对于array类:
transform(vod1.begin(),vod1.end(),vod2.begin(),vod3.begin(),plus< double >());
对于valarray类:
vad3=vad1+vad2; //因为valarray类重载了所有算术运算符。

显然,执行多步运算时,valarray接口的简单性更加明显。

17.initializer_list
使用initializer_list对象,必须包含头文件initializer_list。这个模板

类包含成员函数begin()和end(),可以使用这两个函数来访问列表

元素。它还包含成员函数size(),返回元素数。

可按值或者按引用传递initializer_list对象。这种对象本身很小,通

常是两个指针(一个指向开头,一个指向末尾的下一个元素),

也可能是一个指针和一个表示元素数的整数,所以传递方式对性

能的影响不大。

initializer_list的迭代器类型为const。

initializer_list类的初衷是将一系列值传递给构造函数或其他函数。

18.STL是一个容器类模板,迭代器模板,函数对象模板和算法模

板的集合,它们的设计都是基于泛型编程原则。算法通过使用模

板,从而独立于所存储的对象的类型;通过使用迭代器接口,从

而独立于容器的类型。

19.智能指针
智能指针模板(auto_ptr,unique_ptr和shared_ptr)(头文件

memory)都定义了类似指针的对象,可以将new获得(直接或间

接)的地址赋给这种对象。当智能指针过期时,其析构函数将使

用delete来释放内存。

所有智能指针类都有一个explicit构造函数,该构造函数将指针作

为参数。

三种智能指针都应该避免的一点:

double a=1.0;
shared_ptr pa(&a);  //NO!
//因为这样会导致delete运算符用于非堆内存。

为了避免删除同一个对象2次,可以用下面这些方法:

  • 重载赋值运算符,使之执行深复制
  • 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针拥有它,然后让赋值操作转让所有权。(这种方法的缺点是当指针丧失所有权后,若我们继续用这个指针去访问对象,会引发异常,因为丧失所有权后的智能指针是空指针。)
  • 引用计数(reference counting),跟踪引用特定对象的智能指针数。 比如赋值时,计数加1,指针过期时,计数减1,当最后一个指针过期时,调用delete。

unique_ptr和auto_ptr都是用建立所有权的办法,但是unique_ptr比auto_ptr更严谨,不容易引发异常。假如程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值(比如返回值是非引用变量),编译器不会报错,但是如果源unique_ptr将存在一段时间,编译器会报错。

20.相比于auto_ptr,unique_ptr还有另一个优点:
因为delete和new配对,delete[]和new[]配对。而模板auto_ptr使用delete而不是delete[],因此只能与new一起使用,而不能与new[]一起使用。但unique_ptr可以(shared_ptr也不可以):
std::unique_ptrpda(new double(5))

21.注意下面show函数里面的参数


3

你可能感兴趣的:(C++模板库笔记)