其中几种种序列容器类型
C++ STL中最基本以及最常用的类或容器无非就是以下几个:
对比在C语言中一般怎么使用字符串的
char* s1 = "Hello JackYu!"; //创建指针指向字符串常量,既然是常量字符串,这段字符串我们是不能修改的
//想要创建 可以修改的字符串,我们可以使用数组分配空间
char s2[20] = "Hello JackYu!";
//或者这样
char s3[] = "Hello JackYu!";
//当然我们也可以手动的动态分配内存
char* s4 = (char*)malloc(20);
gets(s4);
C++ 标准库中的string表示可变长的字符串,它在头文件string里面。
#include
using std::string;
string s1;//初始化字符串,空字符串
string s2 = s1; //拷贝初始化,深拷贝字符串
string s3 = "I am b"; //直接初始化,s3存了字符串
string s4(10, 'a'); //s4存的字符串是aaaaaaaaaa
string s5(s4); //拷贝初始化,深拷贝字符串
string s6("I am d"); //直接初始化
string s7 = string(6, 'c'); //拷贝初始化,cccccc
//
// main.cpp
// LearnEffective C++
//
// Created by 于磊 on 2018/6/24.
// Copyright © 2018 于磊. All rights reserved.
//
#include
using std::string;
using std::cout;
using std::endl;
using std::cin;
int main(int argc, const char * argv[]) {
string s1;//初始化字符串,空字符串
string s2 = s1; //拷贝初始化,深拷贝字符串
string s3 = "Hellor World"; //直接初始化,s3存了字符串
string s4(10, 'a'); //s4存的字符串是aaaaaaaaaa
string s5(s4); //拷贝初始化,深拷贝字符串
string s6("Scott"); //直接初始化
string s7 = string(6, 'c'); //拷贝初始化,cccccc
//string的各种操作
string s8 = s3 + s6;//将两个字符串合并成一个
s3 = s6;//用一个字符串来替代另一个字符串的对用元素
cin >> s1;
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
cout << s7 << endl;
cout << s8 << endl;
cout << "s7 size = " << s7.size() << endl; //字符串长度,不包括结束符
cout << (s2.empty() ? "This is empty string" : "This string is not empty") << endl;;
return 0;
}
使用cin读入字符串时,遇到空白就停止读取
" Hello World"
那么我们得到的字符串将是"Hello",前面的空白没了,后面的world也读不出来。
如果我们想把整个hello world读进来怎么办?那就这样做
cin>>s1>>s2;
hello存在s1里,world存在s2里了。
有时我们想把一个句子存下来,又不想像上面那样创建多个string来存储单词,怎么办?
那就是用getline来获取一整行内容。
string str;
getline(cin, str);
cout << str << endl;
当把string对象和字符面值及字符串面值混在一条语句中使用时,必须确保+的两侧的运算对象至少有一个是string
string s1 = s2 + ", "; //正确,此处其实包含了隐式的类型转换,string对象+字符串常量转成了string对象
string s3 = "s " + ", "; //错误,const char *中没有此算术运算符
string s4 = "hello" + ", " + s1; //错误,同上
string s5 = s1 + "hello " + ", "; //改一下顺序,s1放前头,正确了,注意理解=号右边的运算顺序
遍历以及读写string中的每一个元素
for(auto &iter:s6){
cout<
在C++里,有个新奇的东西叫做迭代器iterator,我们可以使用它来访问容器元素。
string str1("some string");
if (str1.begin() != str1.end()) {
auto it = str1.begin();
*it = toupper(*it);
}
cout << str1 << endl;
string str2("some string");
for (string::iterator it = str2.begin(); it != str2.end(); it++) {
*it = toupper(*it);
}
cout << str2 << endl;
迭代器类型也可以定义为const_iterator类型,表现形式是只能读元素不能写元素
处理string对象中的字符,在cctype头文件中定义了一部分标准库函数处理这部分工作
string还有一些很好用的算法,比如找子串
string sq("aaaaa bbb cc s");
cout << sq.find("aa", 0) << endl; //返回的是子串位置。第二个参数是查找的起始位置,如果找不到,就返回string::npos
if (sq.find("aa1", 0) == string::npos)
{
cout << "找不到该子串!" << endl;
}
vector是数组的一种类表示,它提供了自动内存管理功能,可以动态地改变vector对象的长度,并随着元素的添加和删除而增大缩小,
它提供了对元素的随机访问,在尾部添加和删除元素的时间是固定的,但在头部或中间插入和删除元素的复杂度为线性时间。除序列外,vector还是可反转容器
vector存在于头文件vector中
#include
如果vector的元素类型是int,默认初始化为0;如果vector元素类型为string,则默认初始化为空字符串。
vector v1;
vector v2;
vector v3;
vector >; //注意空格。这里相当于二维数组int a[n][n]嵌套vector;
vector v5 = { 1,2,3,4,5 }; //C11列表初始化
vector v6 = { "hi","C++","Primer","learning","hard" };
vector v7(5, -1); //初始化为-1,-1,-1,-1,-1。第一个参数是数目,第二个参数是要初始化的值
vector v8(3, "hi");
vector v9(10); //默认初始化为0
vector v10(4); //默认初始化为空字符串
向vector写入数据(注意写入位置对时间复杂度产生的影响)
for (int i = 0; i < 5; i++)
{
vec.push_back(i);
}
vector其他成员函数操作
访问和操作vector中的每个元素(注意vector是有序容器,频繁增删将导致严重的性能问题)
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << endl;
vec[i] = 100; //不建议这样写数据
cout << vec[i] << endl;
}
注意:只能对已存在的元素进行赋值或者修改操作,如果是要加入新元素,务必使用push_back。push_back的作用有两个:告诉编译器为新元素开辟空间、将新元素存入新空间里。
比如下面的代码是错误的,但是编译器不会报错,就像是数组越界。
vector vec;
vec[0] = 1; //错误!
当然我们也可以选择使用迭代器来访问元素
vector v6 = { "hi","C++","Primer","learning","hard" };
for (vector::iterator iter = v6.begin(); iter != v6.end(); iter++)
{
cout << *iter << endl;
//下面两种方法都行,对象和引用的区别
cout << (*iter).empty() << endl;
cout << iter->empty() << endl;
}
//反向迭代器
for (vector::reverse_iterator iter = v6.rbegin(); iter != v6.rend(); iter++)
{
cout << *iter << endl;
}
vector最常用的增删操作
//
// main.cpp
// LearnEffective C++
//
// Created by 于磊 on 2018/6/24.
// Copyright © 2018 于磊. All rights reserved.
//
#include
#include
#include
using namespace std;
template void showvector(vector v) {
for (typename vector::iterator it = v.begin(); it != v.end(); it++) {
cout << *it;
}
cout << endl;
}
int main(int argc, const char *argv[]) {
vector v6 = {"hi", "C++", "Primer", "learning", "hard"};
v6.resize(3); //重新调整vector容量大小
showvector(v6);
vector v5 = {1, 2, 3, 4, 5}; //列表初始化,注意使用的是花括号
cout << v5.front() << endl; //访问第一个元素
cout << v5.back() << endl; //访问最后一个元素
showvector(v5);
v5.pop_back(); //删除最后一个元素
showvector(v5);
v5.push_back(6); //加入一个元素并把它放在最后
showvector(v5);
v5.insert(v5.begin() + 1, 9); //在第二个位置插入新元素
showvector(v5);
v5.erase(v5.begin() + 3); //删除第四个元素
showvector(v5);
v5.insert(v5.begin() + 1, 7,8); //连续插入7个8
showvector(v5);
v5.clear(); //清除所有内容
showvector(v5);
return 0;
}
注意:
虽然vertor对象可以动态增长,但是也或有一点副作用:已知的一个限制就是不能再范围for循环中向vector对象添加元素。另外一个限制就是任何一种可能改变vector对象容量的操作,不如push_back,都会使该迭代器失效。另外,为容器申请内存空间的时候会为其额外的多申请一些空间。C++ Primer中专门有说明
总而言之就是:但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素!
C++中push_back和insert两个有什么区别?
顾名思义push_back把元素插入容器末尾,insert把元素插入任何你指定的位置。不过push_back速度一般比insert快。如果能用push_back尽量先用push_back。
deque模版类 double-ended queue(双端队列) ,支持随机访问,从deque对象的开始位置插入和删除元素的时间是固定的,而不像vector是线性时间。
所以多数操作是发生在序列的起始和结尾处,就应该考虑deque。为实现在deque两端执行插入和删除操作的时间为固定的这一目的,deque对象的设计比vector对象更为复杂。
因此,尽管两者都提供对元素的随机访问和在序列中部执行线性时间的插入和删除操作,但是vector容器执行这些操作时速度还要快些。
list(双向链表),除了第一个和最后一个元素外,每个元素都与前后的元素相连接,这意味着可以双向遍历链表,list和vector之间关键区别在于,list在链表中任一位置进行插入和删除的时间都是固定的(vector模版提供了除结尾处外的线性时间的插入和删除,在结尾处,它提供了固定时间的插入和删除)。因此,vector强调的是通过随机访问快速访问,而list强调的是元素的快速插入和删除。list也可以反转容器,list不支持数组表示法和随机访问。list是一个双向链表,而单链表对应的容器则是foward_list。
list的头文件
#include
遍历和访问list
//
// main.cpp
// set
//
// Created by yulei on 2018/8/10.
// Copyright © 2018 于磊. All rights reserved.
//
#include
#include
#include
using namespace std;
template void showlist(list v) {
for (typename list::iterator it = v.begin(); it != v.end(); it++) {
cout <<" "<< *it;
}
cout << endl;
}
int main(int argc, const char *argv[]) {
list lst1{1, 2, 3, 4, 5, 6, 7};
showlist(lst1);
list l2;
list lst3(10);
list lst2(5, 10); //将元素都初始化为10
showlist(lst2);
return 0;
}
值得注意的是,list容器不能调用algorithm下的sort函数进行排序,因为sort函数要求容器必须可以随机存储,而list做不到。所以,list自己做了一个自己用的排序函数,用法如下:
list lst1{1, 4, 343, 5, 666, 2, 3, 4, 5, 6, 7};
showlist(lst1);
lst1.sort();
showlist(lst1);
forward_list,它实现了单链表,在这种链表中,每个节点都只链接到下一个节点,而没有链接到前一个节点。因此forward_list只需要正向迭代器,而不需要双向迭代器。因此,不同于vector和list,forward_list是不可反转的容器。相比list,forward_list更简单,更紧凑,但功能也更少。
queue,queue模版的限制比deque更多,它不仅不允许随机访问队列元素,甚至不允许遍历队列。它把使用限制在定义队列的基本操作上,可以将元素添加到队尾,从队首删除元素,查看队首和队尾的值,检查元素数目和测试队列是否为空。
priority_queue,和queue的区别在于,最大的元素被移到队首。内部区别在于,默认的底层类是vector。可以修改用于确定哪个元素放到队首的比较方式。方法是提供一个构造函数。
7.stack,提供了典型的栈接口,stack模版的限制比vector更多。它不仅不允许随机访问栈元素,甚至不允许遍历栈,它把使用限制在定义栈的基本操作上,即可以将压入推到栈顶,从栈顶弹出元素,查看栈顶的值,检查元素数目和测试栈是否为空.于queue想死,如果要使用栈中的值,必须首先使用top()来检索这个值,然后使用pop将它中栈中删除。
其值类型与键相同,键是唯一的。这意味着集合中不会有多个相同的键。set跟vector差不多,它跟vector的唯一区别就是,set里面的元素是有序的且唯一的,只要你往set里添加元素,它就会自动排序,而且,如果你添加的元素set里面本来就存在,那么这次添加操作就不执行。
#include
//
// main.cpp
// set
//
// Created by yulei on 2018/8/10.
// Copyright © 2018 于磊. All rights reserved.
//
#include
#include
#include
using namespace std;
template void showset(set v) {
for (typename set::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
int main(int argc, const char *argv[]) {
set s1{91, 8, 341, 3, 3, 4, 3, 3, 4, 5, 66, 6, 1, 5, 5, 6, 7, 7}; //自动排序,从小到大,剔除相同项
showset(s1);
set s2{"hello", "C++", "Primer", "5th"}; //字典序排序
showset(s2);
s1.insert(9); //有这个值了,do nothing
showset(s1);
s2.insert("aaa"); //没有这个字符串,添加并且排序
showset(s2);
return 0;
}
可以有多个相同的键。
map运用了哈希表地址映射的思想,也就是key-value的思想,来实现的。内部元素按照key哈希排序。首先给出map最好用也最最常用的用法例子,就是用字符串作为key去查询操作对应的value。
注意:在查找一个不存在的key的时候,map会自动生成这个key,这里涉及到如何安全和挑选符合自己业务逻辑的关联容器格外重要
#include
//
// main.cpp
// set
//
// Created by yulei on 2018/8/10.
// Copyright © 2018 于磊. All rights reserved.
//
#include
#include
#include
如果想看看某个存不存在某个key,可以用count来判断
if (m1.count("year")) {
cout << "year is in m1!" << endl;
} else {
cout << "year do not exist!" << endl;
}
用迭代器来访问元素
for (map::iterator it = m1.begin(); it != m1.end(); it++){
cout << it->first<<" "<second << endl; //注意用法,不是用*it来访问了。first表示的是key,second存的是value.也就是在map中存储的又是另外一种数据结构(pair)
}
multimap也是可以反转的,经过排序的关联容器,但键和值的类型不同,且同一个键可能与多个值相关联。
无序关联容器是对容器概念的另一种改进。与关联容器一样,无序关联容器也将键与值关联起来,并使用键来查找值,但底层的差别在于,关联容器是基于树结构的,而无序关联容器是基于数据结构哈希表的
1.unordered_set
2.unordered_multiset
3.unordered_map
4.unordered_mutimap