C++ STL使用说明

c/c++

浏览数:144

2019-5-20

AD:资源代下载服务

标准模板库(Standard Template Library,STL)是一系列通用化组件的集合,包括容器(container)、算法(algorithm)和迭代器(iterator).

迭代器iterator

迭代器(iterator)是一种用于遍历标准模板库容器元素的一种对象, 和指针类似可以通过迭代器访问其指向的对象,不同的是迭代器的自增运算可以遍历复杂的数据结构。

所有的STL迭代器均重载了下列运算符:

  • 解引用* 返回迭代器指向元素的引用

  • 取成员 -> 返回指向元素的某个成员

  • 自增减 i++, ++i i-- --i 移动迭代器指向下一个元素

  • 相等关系 ==, != 指向同一元素的迭代器相等

  • 赋值 = 令迭代器指向同一个元素

vector和deque的迭代器还重载了只有线性容器才可以使用的运算符:

  • 加减 + - 允许迭代器一次偏移多个元素

  • 复合赋值 +=, -=

  • 不等关系 <, >, <=, >= 判断指向元素在线性容器中的相对位置

所有支持迭代器的容器类均要实现begin()end()方法.

begin() 方法返回指向容器首元素的迭代器, end()返回指向元素末元素下一个位置的迭代器, 称为超出末端迭代器, 有时也称为哨兵.

迭代器的核心是其自增操作, 不管容器的实际数据结构多么复杂自增操作总可以遍历这个数据结构.

因为只有线性容器支持<操作, 所以迭代器只能使用!=和超出末端迭代器判断是否完成遍历.

因为同样的原因, 使用一对迭代器标识一段元素时均包含起点不包含终点, 即所谓的左闭右开区间.

void iter(vector<int> vec) {
    vector<int>::iterator i;
    for (i = vec.begin(); i != vec.end(); ++i) {
        cout << *i;
    }
}

向容器中添加或删除元素可能使迭代器失效, 使用迭代器时应特别注意.

线性容器deque, list, deque

线性容器为数据结构中的线性表,包括vector(向量),list(链表),deque(双向队列)以及线性容器适配器stack(栈),queue(队列),priority_queue(优先队列).

使用顺序容器要包含相应的头文件<vector>,<list>,<deque>

这些容器都是类模板,使用容器需要将其实例化, 只要可以进行赋值和复制的类型均可以实例化三种容器.

vector是一个可变长的一维数组,在内存中连续存储,支持快速随机访问但是添加删除元素操作通常耗费更多时间.

deque是一个双向队列,采用分页存储的方式.内存重分配效率略高于vector,但是添加删除元素同样会耗费较多时间.

list是链表,支持快速添加删除元素但是访问的效率较低.

初始化

线性容器提供了可以初始化为空的默认构造函数和用于创建副本的复制构造函数.

复制构造函数要求必须具有相同容器类型并存放相同类型元素的容器之间才可以复制.

  • vector<type>::vector() 默认构造函数

  • vector<type>::vector(vector<type>) 拷贝构造函数

  • vector<type>::vector(int n,type i) 可以将vector初始化为由n个i填充的对象

  • vector<type>::vector(int n) 可以将vector初始化为长度为n的对象,每个对象的值由对象类型的默认构造函数来指定(基本类型初始化为相应0值)

  • vector<type>::vector(iterator beg, iterator end) 由两个迭代器作为参数,将两个迭代器之间的元素复制给vector,这个迭代器可以是任何容器的迭代器类型

deque和list容器也同样支持这五种初始化方式.

访问元素

所有线性容器都可以通过front(void)和back(void)操作访问第一个和最后一个元素,但是调用空容器的front()或back()方法将产生错误

vector和deque容器支持随机访问,而list容器仅支持顺序访问.

访问vector或deque的成员可以使用下标运算符([]),下标从0开始.但是这种访问方式可能会出现下标越界的错误.

vector和deque均提供了【vector ::at(size_type)】成员函数进行访问.at进行边界检查如果越界将抛出一个out_of_range异常,如果对象存在它将返回一个引用以进行操作.

vector和deque的at()方法除了以下标作为参数外,还可以接收一个迭代器对象作为参数,访问迭代器指向的元素.

添加

三种顺序容器都定义了push_back(type)成员函数,以将参数指定对象的副本加入到容器的末端.

包括list容器在内,添加元素都是以传值的方式进行的即将对象复制后加入容器中,容器中元素的改变不影响原来的对象.

deque和list容器中定义了push_front(type)成员函数可以向容器第一个元素前添加元素,机制类似push_back,vector容器未定义该操作

容器的insert()成员函数是更通用的添加元素的方式, 提供了3种重载版本:

  • insert(p,t) 在迭代器p指向的元素前添加值为t的新元素,返回指向新元素的迭代器

  • insert(p,n,t)向迭代器p指向的元素前添加n个值为t的新元素,返回void.

  • insert(p,b,e) 向迭代器p指向的元素前添加迭代器b,e之间(左闭右开)的元素,返回void

插入时可以在不同类型容器间进行,新元素类型必须可以隐式类型转换为容器元素类型.

删除

三种容器提供了pop_back(void)方法以删除容器的最后一个元素,list和deque容器还定义了pop_front(void)函数删除第一个元素.

三种容器提供了pop_back(void)方法以删除容器的最后一个元素,list和deque容器还定义了pop_front(void)函数删除第一个元素.

三种容器的clear(void)方法可以清空容器.

erase方法提供更通用的删除操作:

  • erase(p)删除迭代器p指向的元素(p不能是哨兵),返回的下一个元素的迭代器.

  • erase(b,e)删除迭代器b,e之间的元素,返回被删除元素段后的第一个元素的迭代器.若e是哨兵则返回哨兵.

赋值

两个容器类型和元素类型相同的容器之间可以相互赋值,赋值将清空左值并将其变成右值的一个副本.

两个类型相同(容器类型和元素类型相同)容器c1,c2还可以使用c1.swap(c2)将两个容器交换.

assign方法提供更通用的操作:

  • assign(n,t)方法可以将容器重置为n个值为t的元素的容器

  • assign(b,e)则接受两个迭代器b,e作为参数并将容器元素重置为两个迭代器之间的元素(左闭右开), 支持兼容类型之间的操作.

字符串std::string

std::string不是STL提供的容器, 但其操作与STL线性容器非常相似故一并介绍.

std::string类在头文件 中声明,string类可以用来存储变长字符串.

string将字符串内容存放在动态内存中, string对象始终为16byte(32bit环境).

std::string提供了4种初始化方式:

  • string::string(void) 默认构造

  • string::string(const string&) 复制构造

  • string::string(char *) 使用c_str进行转换构造

  • string::string(int n, char ch) n个字符ch组成的字符串

string类重载了一系列关系运算符(==、!= 、> 、< 、>= 、<=).string类的大小按照ASCII字典顺序升序排列(排在前面的较小),这种排列是大小写敏感的.

这些关系运算符也可以用来比较string类对象和c风格的字符串.因为C++要求重载运算符至少有一个操作数是类对象,所以无法用于c风格字符串之间的排序.

string类也重载了+号运算符(+=运算符也被重载)用于连接string类对象或经典字符串,同样地它无法连接两个C风格的字符串.

string类重载了赋值运算符(=),使得string类之间可以互相赋值或者接受C风格字符串的值.同样因为C++对重载运算符的限制,赋值运算不能用于C风格字符串之间的拷贝.

重载的变址运算符([])允许访问每一个字符,下标从0开始.string的字符数组不是以’\0’作为结束标志的,越界访问会发生错误.string::at()方法则是更安全的选择.

string substr(int pos = 0,int n = npos) const;//返回pos开始的n个字符组成的字符串

string类同样提供了丰富的赋值, 比较,查找,替换,插入等成员函数:

赋值:

  • string &assign(const char *s);//用c类型字符串s赋值

  • string &assign(const char *s,int n);//用c字符串s开始的n个字符赋值

  • string &assign(const string &s);//把字符串s赋给当前字符串

  • string &assign(int n,char c);//用n个字符c赋值给当前字符串

  • string &assign(const string &s,int start,int n);//把字符串s中从start开始的n个字符赋给当前字符串

  • string &assign(const_iterator first,const_itertor last); //把first和last迭代器之间的部分赋给字符串

插入

  • string &insert(int p0, const char *s);

  • string &insert(int p0, const char *s, int n);

  • string &insert(int p0,const string &s);

  • string &insert(int p0,const string &s, int pos, int n); //前4个函数在p0位置插入字符串s中pos开始的前n个字符

  • string &insert(int p0, int n, char c);//此函数在p0处插入n个字符c

删除:

  • iterator erase(iterator first, iterator last);//删除[first,last)之间的所有字符,返回删除后迭代器的位置

  • iterator erase(iterator it);//删除it指向的字符,返回删除后迭代器的位置

  • string &erase(int pos = 0, int n = npos);//删除pos开始的n个字符,返回修改后的字符串

查找:

  • int find(char c, int pos = 0) const;//从pos开始查找字符c在当前字符串的位置

  • int find(const char *s,int pos = 0) const;//从pos开始查找字符串s在当前串中的位置

  • int find(const char *s, int pos, int n) const;//从pos开始查找字符串s中前n个字符在当前串中的位置

  • int find(const string &s,int pos = 0) const;//从pos开始查找字符串s在当前串中的位置 //查找成功时返回所在位置,失败返回string::npos的值

替换:

  • string &replace(int p0, int n0,const char *s, int n);//删除p0开始的n0个字符,然后在p0处插入字符串s的前n个字符

  • string &replace(int p0, int n0,const string &s);//删除从p0开始的n0个字符,然后在p0处插入串s
  • string &replace(int p0, int n0,const string &s, int pos, int n);//删除p0开始的n0个字符,然后在p0处插入串s中从pos开始的n个字符

  • string &replace(int p0, int n0,int n, char c);//删除p0开始的n0个字符,然后在p0处插入n个字符c.

比较:

  • int compare(int pos, int n,const string &s)const;//比较当前字符串从pos开始的n个字符组成的字符串与s的大小

  • int compare(int pos, int n,const string &s,int pos2,int n2)const;//比较当前字符串从pos开始的n个字符组成的字符串与s中//pos2开始的n2个字符组成的字符串的大小

string 类提供的查找函数全都返回 string::size_type 类型的值,以下标形式标记查找匹配的位置;或者返回一个名为 string::npos 的特殊值,说明查找没有匹配.string 类将 npos 定义为保证大于任何有效下标的值.

与C字符串的转换:

  • const char * string::c_str(void) 返回c_str

  • string &assign(const char *s); //用c类型字符串s赋值

与int或double类型转换

转换需要借助字符串流:

double str_to_double(string& str) {
    stringstream buf;
    double val;
    buf.clear();
    buf << str;
    buf >> val;
    return val; 
}

string double_to_str(double val) {
    stringstream buf;
    string str;
    buf.clear();
    buf << val;
    buf >> str;
    return str; 
}

关联容器map

在介绍关联容器前,先介绍一种简单的类型-pair.pair类型在头文件<utility>中定义,它是一个类模板需要两个类型参数以实例化.

每个pair对象由两个数据组成pair<type1, type2> p1(type1 a,type2 b);.头文件还定义了make_pair(type1 a, type2 b);它创建一个由a,b组成的pair,并返回其引用.

映射(map,也称为关联数组)容器在头文件<map>中定义,它的成员由键-值对组成.map中定义了3种关于其成员的数据类型:

  • key_type: 键类型,用作索引

  • mapped_type: 被关联的类型

  • value_type: pair<const key_type, mapped_type>键—值对的类型,键类型是常量

map中的元素通过key来访问它所关联的mapped_type数据,就像字典中通过词条访问它的释义.

对map的迭代器取值(*)将返回value_type型的键-值对

map容器的key是常量且不允许重复,key类型必须还要有用于比较的’<’运算符.这种比较可以设计非常复杂,但必须是严格弱排序, 即要求:

  • 一个对象与自身比较必然返回false

  • 多个对象之间的比较具有传递性,若a<b,b<c则a<c

  • 两个对象相互之间不存在”小于”关系则两者必然相等,若a<b为假且b<a为假则a=b.

初始化

map实例化需要两个类型参数为其指定key_type和mapped_type.

map容器提供了3个构造函数:

  • map<type1.type2> m1;

  • map<type1.type2> m1(map<type1.type2> );

  • map<type1.type2> m1(iterator beg, iterator end);

因为要求key不能相同,所以map容器不能按照元素数量进行初始化.

下标操作

map容器重载了下标运算符(“[]”),下标是key_type类型.当map容器中存在这个键时返回相应键值对引用,若不存在这个键则添加这个键值对并将mapped_type成员初始化为空值.这个特性非常方便但同时容易意外添加元素,必须谨慎使用.

访问

因为下标操作可能意外添加元素,map容器提供了count()和find()操作.map1.count(key_type);返回key键的个数,在map容器只有可能是0或1.可以使用:

if (map1.count(a)) {
    val=map1[a];
}

map1.find(key_type);是更为方便的访问方式,当键存在时返回该元素的迭代器,否则返回超出末端迭代器且不添加元素.

插入

  • m.insert(value_type e); e 是一个用在 m 上的 value_type 类型的值.如果键(e.first)不在 m 中, 则插入一个值为 e.second 的新元素

  • m.insert(iterator beg, iterator end); beg 和 end 是标记元素范围的迭代器,其中的元素必须为m.value_type 类型的键-值对.对于该范围内的所有元素,如果它的键在 m 中不存在, 则将该键及其关联的值插入到 m.返回 void 类型.

  • m.insert(iterator iter,value_type e);e 是一个用在 m 上的 value_type 类型的值.如果键(e.first)不在 m 中,则创建新元素,并以迭代器 iter 为起点搜索新元素存储的位置.返回一个迭代器,指向 m 中具有给定键的元素.

使用map<key_type, mapped_type>::value_type p(key_type k, mapped_type val);方式创建新的键-值对相当冗长,可以使用typedef关键字来简化类型名或者使用make_pair(key_type k, mapped_type val)

删除

map容器定义了3个erase()方法来完成删除操作:

  • m.erase(key_type k); 删除 m 中键为 k 的元素.返回 size_type 类型的值,表示删除的元素- 个数.

  • m.erase(iterator p); 从 m 中删除迭代器 p 所指向的元素.p 必须指向 m 中确实存在的元素,而且不能等于 m.end().返回 void.

  • m.erase(iterator b, iterator e); 从 m 中删除一段范围内的元素, 该范围由迭代器对 b 和 e 标记.b 和 e 必须标记 m 中的一段有效范围: 即 b 和 e 都必须指向 m中的元素或最后一个元素的下一个位置, 返回 void.

set

使用set(集合)容器必须包含 头文件,set的元素只有键而非键-值对.

与map容器一样,每个键只能添加一次且所有键均为const类型只能读取不能修改.

s1.insert (type);将一个新键加入到set容器中,返回pair类型包含一个迭代器和一个bool值表示是否插入了元素.

s1.insert(iterator beg, iterator end);插入标记区间的所有元素,返回void.

set 容器不提供下标操作符.为了通过键从 set 中获取元素,可使用 find运算.

如果只需简单地判断某个元素是否存在,同样可以使用 count 运算,返回 set 中该键对应的元素个数.

当然,对于 set 容器,count 的返回值只能是1(该元素存在)或 0(该元素不存在)..

multimap与multiset

map 和 set 容器中,一个键只能对应一个值.而 multiset 和 multimap类型则允许一个键对应多个值.

multimap 和 multiset 所支持的操作分别与 map 和 set 的操作相同,只有一个例外:multimap 不支持下标运算.不能对 multimap 对象使用下标操作,因为在这类容器中,某个键可能对应多个值.

带有一个键参数的 erase 版本将删除拥有该键的所有元素,并返回删除元素的个数. 而带有一个或一对迭代器参数的版本只删除指定的元素, 并返回 void类型.

multimap 和 multiset 容器中,如果某个键对应多个实例,则这些实例在容器中将相邻存放.按顺序存储和相邻存放保证我们可以遍历拥有同一个键的所有元素.

count 函数求出某键出现的次数, 而 find 操作则返回一个迭代器, 指向第一个拥有正在查找的键的实例,迭代遍历所有拥有该键的元素.下列三个返回迭代器的容器操作可以更方便地进行遍历,它们对map和set容器同样适用:

  • m.lower_bound(k) 返回一个迭代器,指向键不小于 k 的第一个元素.
  • m.upper_bound(k) 返回一个迭代器,指向键大于 k 的第一个元素.
  • m.equal_range(k) 返回一个迭代器的 pair 对象,它的 first 成员等价于 m.lower_bound(k).而 second 成员则等价于 m.upper_bound(k).

如果没有找到相应元素它们将返回超出末端迭代器.

algorithm

标准库算法算法是基于迭代器的一系列操作,用于在容器中完成查找排序等任务.

泛型算法独立于容器的数据结构,标准容器或与标准容器兼容的自定义容器均可使用标准库算法.

通常使用一对迭代器标记一个左闭右开区间来作为算法操作的范围.因为指针在数组中的行为与迭代器相同,所以可以利用指针作为迭代器在数组使用标准库算法.

使用标准库算法需要包含头文件<algorithms>,标准库中还定义了一系列算术算法,需包含头文件<numeric>

只读取而不修改容器元素的算法被称为只读算法.非只读算法一般有_copy版本,如

replace_copy (iterator beg, iterator end, iterator buf, type primary,type fresh)

会把改变后的容器存放到第三个参数指定的区域内.

算法不会调用容器定义的操作,只通过迭代器访问容器元素.当算法需要向容器中添加元素时需要使用迭代器的适配器–插入迭代器(inserter).

  • back_inserter()接受一个容器作为实参,使用push_back()向结尾添加元素,

  • front_inserter()接受一个容器作为实参,使用push_front向开头添加元素.

  • inserter(,)接受一个容器作为第一个实参,指向该容器元素的迭代器作为第二个实参,使用insert向第二个实参前添加元素.

  • fill (iterator beg, iterator end, type val)是最简单的写容器算法,它将标记区间内的元素全部置为val.

常用算法:

查找替换

  • find (iterator beg, iterator end, type val)
    在标记区间内查找val并返回它的迭代器.

  • find_first_of (iterator test_beg, iterator test_end, iterator pat_beg, iterator pat_end)
    在第一段标记区间内寻找首个与第二个标记区间内任意元素匹配的元素返回迭代器.

  • replace (iterator beg, iterator end, type primary, type fresh)
    在标记区间内将primary替换为fresh

排序

  • sort (iterator beg, iterator end)
    将标记区间升序排列(使用”<”判断),相同的元素可能会被交换位置

  • stable_sort (iterator beg, iterator end)
    将标记区间升序排列(使用”<”判断),相同的元素不会被交换位置

两个排序算法均有使用一个函数来判断大小的重载版本.用于检测并返回用于条件判断的值的函数称为谓词函数.

sort 的谓词函数必须接受两个与容器元素同类型的实参(通常是它们的常引用),并返回一个可用于逻辑判断的值(通常为bool值).

bool smaller(val_t a, val_t b) {
    if (a < b) {
        return true;
    }
    else {
        return false;
    }
}

void do_sort(arr_t arr) {
    sort(arr.begin(), arr.end(), smaller);
}

计数与求和

  • count (iterator beg, iterator end, type val)
    统计标记区间中值等于(==运算符判断)的元素个数.

  • count (iterator beg, iterator end, verb)
    统计标记区间中符合条件的元素个数,verb是谓词函数接收一个与容器元素同类型的实参,符合条件返回真.

  • accumulate (iterator beg, iterator end, type starter)
    以starter为初值将标记区间内的数据累加,调用”+”运算符.

  • unique (iterator beg, iterator end)
    标记区间内的元素若有重复,重复元素只保留一个.返回的迭代器指向无重复序列的下一个位置,必须使用erase等清除多余元素.

参考:

– C++ Reference

STL Containers- C++ Reference

– C++ Reference

作者:-Finley-