大家好,又见面了,我是你们的朋友全栈君。
从c++11标准以来,c++中std定义的几种容器的效率非常高,优化的非常好,完全没有必要自己去定义类似的数据结构。了解使用它们,可以满足90%的日常编程需要。该篇文章基于c++11标准,从用户角度来介绍常用的顺序容器与并联容器(如果想从内部了解它们是怎么实现的,推荐看看《std源码剖析》这本书)。它们包括:
vector string (它不是类模板) list forward_list deque queue priority_queue stack
map multimap set multiset
unordered_map
unordered_multimap
unordered_set
unordered_multiset
力推网站: https://en.cppreference.com/w/cpp/container , 里面介绍的绝对很全的,绝对比本篇文章好太多太多。
很多容器功能是重复的,不再一一列举
// T 表示实例化类模板时使用的类型
vector<T> v1 // 默认初始化, 此时v1为空。
vector<T> v1(v2) // 执行的copy初始化,此时v1与v2的内容相同
vector<T> v1 = v2 // 与上面相同,都会执行copy构造函数
vector<T> v1(n) // 此时v1的size大小为n ,它里面的值是根据T的类型进行默认初始化的
vector<T> v1(n, a) // v1的初始化为n个值为a的元素
vector<T> v1{
a, b, c} // 列表初始化,v1内现在的元素就是a, b, c (这是c++11标准新入的)
vector<T> v1 = {
a, b, c} // 与上面相同列表初始化是什么?
对于上面的几种初始化方法,最常用的有三种,
v1.size() //v1内已经存放的元素的数目
v1.capacity() // v1现有的在存储容量(不再一次进行扩张内存空间的前提下)
v1.empty() // 判断v1是否为空
v1.max_size() // 返回vector可以存放的最大元素个数,一般这个数很大,因为vector可以不断调整容量大小。
v1.shrink_to_fit() // 该函数会把v1的capacity()的大小压缩到size()大小,即释放多余的内存空间。v1[n] // 通过下标进行访问vector中的元素的引用 (下标一定要存在 ,否则未定义,软件直接崩了)
v1.at(n) // 与上面类似,返回下标为n的元素的引用,不同的是,如果下标不存在,它会抛出out_of_range的异常。它是安全的,建议使用它。
v1.front() // 返回vector中头部的元素的引用(使用时,一定要进行非空判断)
v1.back() // 返回vector中尾部的元素 引用(使用时,一定要进行非空判断)v1.push_back(a) //在迭代器的尾部添加一个元素
v1.push_front(a) // vector不支持这个操作
v1.insert(iter, a) // 将元素a 插入到迭代器指定的位置的前面,返回新插入元素的迭代器(在c++11标准之前的版本,返回void)
v1.insert(iter, iter1, iter2) //把迭代器[iterator1, iterator2]对应的元素插入到迭代器iterator之前的位置,返回新插入的第一个元素的迭代器(在c++11标准之前的版本, 返回空)。在c++11标准中,引入了emplac_front()、 emplace()、emplace_back() 它们分别与push_front()、insert()、 push_back()相对应,用法与完成的动作作完全相同,但是实现不一样。 push_front()、insert()各push_back()是对元素使用copy操作来完成的,而emplac_front()、 emplace()和emplace_back()是对元素使用构造来完成的,后者的效率更高,避免了不必要的操作。因此,在以后更后推荐使用它们。
v1.erase(iterator) // 删除人人迭代器指定的元素,返回被删除元素之后的元素的迭代器。(效率很低,最好别用)
v1.pop_front() //vector不支持这个操作
v1.pop_back() //删除vector尾部的元素 , 返回void类型 (使用前,一定要记得非空判断)
v1.clear() //清空所有元素v1.assign({
初始化列表}) // 它相当于赋值操作,
v1.assign(n, T) // 此操作与初始化时的操作类似,用个n T类型的元素对v1进行赋值
v1.assign(iter1, iter2) // 使用迭代器[iter1, iter2]区间内的元素进行赋值(该迭代器别指向自身就可以),另外,只要迭代器指的元素类型相同即可(存放元素的容器不同,例如:可以用list容器内的值对vector容器进行assign操作,而用 "=" 绝对做不到的。
v1.swap(v2) // 交换v1与v2中的元素。 swap操作速度很快,因为它是通过改变v1与v2两个容器内的数据结构(可能是类似指针之类的与v1和v2的绑定)完成的,不会对容器内的每一个元素进行交换。 这样做,不仅速度快,并且指向原容器的迭代器、引用以及指针等仍然有效,因为原始的数据没有变。在c++ primer 中建议大家使用非成员版本的swap()函数,它在范型编程中很重要。
string与vector类似,但是string不是一种类模板,而就是一种类型,因为它专门用于存放字符的(存放的元素类型已经明确),所以没有设计为类模板。它的所有特性与vector相同,包括存储在连续的空间/快速随机访问/高效在尾部插入与删除/低效在中间插入与删除等, string的迭代器也支持算术运算。 实际上,就可以把string类型看作为vector类型, vector的所有特性都适合与string类型。当然,因为string类型比vector模板更特例化一些,因此它肯定具有一些自己特有而vector没有的特性,下面总结一下。
在陈述之前,首先说明:
正因为pos和args的样式可以随意组合,所以string的操作函数的参数是多种的,因此它的重载函数数目很多,由于对于insert(pos, args)/append(args)/erase(pos,args)/replace(pos, args)等操作。
相对于vector类型来说, string 增加一个使用字面值类型进行初始化,即:
方法1 string a("xiaoming")
方法2 string a = "xiaoming"在string中,增加了append()与 replace()函数
str.append(args) // 在尾部添加一个字符或一个字符
str.replace(pos, args) // 在尾部添加一个字符或一个字符 ,它的重载函数很多,共16个。str.substr(_pos, n) //该函数可以获得原字符串中的部分字符, 从pos开始的n个字符,当_pos超过范围时,会抛出out_of_range的异常。str.find(args) //查找args 第一次出现的位置
str.rfind(args) //查找args最后一次出现的位置
str.find_first_of(args) //搜索的是字符, 第一个是args里的字符的位置
str.find_last_of(args) // 搜索的是字符, 最后一个是args里的字符的位置
str.find_first_not_of() // 搜索的是字符,第一个不是args里的字符的位置
str.find_last_not_of() // 搜索的是字符, 最后一个不是args里的字符的位置str.length() // 该函数与str.size()函数完成一样,只是名字不同而已罢了。只所以这样搞的原因,可能开发人员感觉length更适合字串符,size更适合容器吧。to_string(val): 参数说明::其中str表示字符串, pos用于表示第一个非数值字符的下标(意思就是我给函数传入一个地址,它会对它进行赋第一个非数值字符的位置), base表数值的基数,默认为10,即10进制数。
stoi(str, pos, base) // 字符串转换为整型
stol(str, pos, base) // 转换为long
stoul(str, pos, base) // 转换为 unsigned long
stoll(str, pos, base) // 转换为 long long
stoull(str, pos, base) // 转换为unsigned long long
stof(str, pos) // 转换为float
stod(str, pos,) // 转换为double
stold(str, pos,) // 转换为long double以下内容来自:c++ primer 第五版p82, 只写出部分常用来的(字母:alpha, 数字:number或digit)
isalnum(c) // 当为字母或数字时为真
isalpha(c) // 当为字母时为真
isdigit(c) // 当为数字时真
islower(c) // 当为小写字母时为真
issupper(c) // 当为大写字母时为真
isspace(c) // 当为空格时为真
tolower(c) // 转换为小写字母, 当本身为小写字母时,原样输出
toupper(c) // 转换为大写字母, 当本身为大写字母时,原样输出与vector和string相比,list内部的实现为一个双向链表,它的元素不是存储在连续的内存空间中,而是非连续的,这就决定了它不能在常量时间内完成对元素的随机访问,只能从头到尾的遍历一遍。 因为它是用双向链表实现的,所以,它的一大特性就是它的迭代器永远不会变为无效(除非这段空间不存在了),即无论增加、删除操作,都不会破坏迭代器。
大多数对vector的操作也适合于list,由于底层实现不同,有也差异:
list与vector的差别:
forward_list的实现是使用单向链表(list为双向链表), 在操作单向链表的时候,为了对一个元素进行删除与添加,都需要访问到该元素的前趋节点,因此呢,forward_list的会有insert.after()emplase.after()/erase.after()等操作, 另外forward_list也没有size()操作,原因在于为了尽可能让forward_list与手写的单向链表的效率相同。说实话呢,forward_list操作起来有点反人类,用起来有点不方便,我个人比较买习惯使用list,但是list相对forward_list的内存空间花费更多。
以后什么时候用它的时候,再来介绍。
关联容器与顺序容器最大的区别在于关联容器没有下标,都过键值或 值本身进行索引。有序关联容器内部通过红黑树实现的,当搜索一个元素时,具有O(logn)的平均复杂度,而无序的关联容器在底层是通过散列表(哈希函数映射)实现的,当搜索一个元素时,通常O(1)的平均复杂度,最坏为O(logn), 下面介绍它们。在介绍map之前,必须先介绍pair 类型。
pair类型:
pair<int, string> sb //初始化一个默认值的pair对象sb, 它的first是默认初始化的(0,内置类型默认初始化大多数应该是未定义的啊,它这是为0), second也是采用默认初始化(空字符串)
pair<int, string> sb(1, "japan"); //很常见的初始化方法
pair<int, string> sb = (1, "japan");
pair<int, string> sb{
1,"japan"} //c++11中的列表初始化方法
pair<int, string> sb = {
1, "japan"}可以调用make_pair()模板函数,返回一个pair对象:
::value_type表示”键-值 对”类型 ::key_type表示键类型,vlue类型 ::mapped_type 表示值的类型 例如: map<int, string>, 则 map<int, string>::value_type 与pair<int, string>等价, map<int, string>::key_type与int等价, map<int, string>::mapped_type与string等价;
map同样支持使用迭代器,它会返回指向 pair类型的对象 的迭代器 map 使用[]运算符 通过key来访问对应的 value ,如果访问的key不存在,则会自动添加一个对应的pair 对象,其中它的value采用默认值。因此,当通过key来访问map时,map不能是const类型。 map 使用at()成员函数 通过key来访问对应的value, 如果访问的key不存在,则会抛出一个out_of_range的异常;
insert()或emplace()操作: 当向map中插入不存在的元素(指key值不同)时,可以插入成功,当插入一个已经存在key值的pair对象时,ma不会作任何改变。因此,当对map进行插入操作时,需要知道有没有插入成功。insert()与emplace()函数的 返回值也是一个pair类型,first为一个迭代器,指向插入时的键值对应的pair对象(可能是新插入的,也可能是已经存在的), second是一个bool类型,它表示是否插入成功(例如:当map中已经存在待插入的值时,为false) erase()操作:它有三个版本,前两个版本与顺序容器相同,使用迭代器指定一个位置或一对迭代器指定一个范围,这时返回值为一个迭代器,指向删除之后的下一个元素;第三个版本的erase()很不错,我很喜欢,它的参数为key值,删除对应key值的pair()对象, 返回值为成功删除的个数(可能为0或1,在multimap中可能为n)
6.查找操作find(key) // 查找一个特定key值的pair对象,如果找到就返回对应的迭代器,如果找不到,就返回.end()迭代器。
count(key) //统计在map容器中特征key值的pair对象的个数.(在multimap与multiset中很有用的)
equal_range(key) // 返回一个pair类型,first表示low_bound, second表示upper_bound;
lower_bound(key) //返回迭代器,对应第一个大于等于key的元素
upper_bound(key) //返回迭代器,对应第一个大于key的元素 (说明:其实,最后这四个函数,在multimap与multiset中是非常有用的) 与map容器相比,区别在于multimap允许键值重复,即一个键值可能对应多个value。所以呢,相应的操作会有一些变化,例如:multimap不可以像map中使用key 作为索引(使用operator[]和at()成员函数)进行访问元素(因为对应的value可能是多个),multimap的插入操作一定会成功等,除此之外,它们的性相同, 不多介绍。
set容器与map容器的唯一区别在于:存放的元素类型不同: map存储的是键-值对,即pair类型,而set中只存放键值。正因为如此,所以:
multiset容器相对于set容器,允许它容器内部的元素重复。没有其它区别了
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/170894.html原文链接:https://javaforall.cn