3 STL中的容器
3.1 string容器
C风格字符串(以空格符结尾的字符数组)太过复杂,不适合大程序开发,所以C++标准库定义了一种string类。
C++的字符串与C语言的字符串比较:
- C语言:char*是一个指针。
- C++:
- string是一个类,内部封装了char*,用来管理这个容器。
- string类中封装了很多的功能函数,例如:find、copy、delete、replace、insert等。
- 不用考虑内存释放和越界的问题。
string管理char*所分配的内存。每一个string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。
3.1.1 string容器的构造
// string构造
void test1(){
// 无参构造,创建一个空字符串
string str1; // 等于string str = string();
//通过一个string去构造另一个string
string str2 = string("hello world");
//通过一个char(字符)数组去构造一个string
const char* array = "hello world";
string str3 = string(array);
// 通过指定数量的指定字符,构造一个字符串
string str4 = string(5,'A'); // 内容是:AAAAA
};
3.1.2 string的赋值
// string赋值
void test2(){
string str1;
// 通过等号赋值,等号已经被string进行了运算符重载
// 通过字符串赋值
str1 = "hello world";
// 通过字符数组赋值
const char* arr = "abc";
str1 = arr;
// 通过字符赋值
str1 = 'a';
// assign函数。都是&引用,即可以链式操作。
str1.assign("hello world");
str1.assign(arr);
str1.assign(5,'A');
// string& assign(const char* s, int n); 把字符串s的前n个字符赋值给string
str1.assign("hello world",5);
cout << str1 << endl;
// string& assign(const char* s, int start, int n); 将s从start开始n个字符赋值给字符串
str1.assign("hello world",3,4); // llo w
}
3.1.3 string存取字符操作
// string的存取字符操作
void test3(){
// 通过下标访问
// char& operator[](int n); 通过[]访问
// char& at(int n); 通过at访问
string str1 = "hello world";
cout << str1[2] << endl;
cout << str1.at(4) << endl;
str1[2] = 'a';
str1.at(4) = 'a';
cout << str1 << endl;
// 使用了&引用(取别名)
// 使用字符引用返回值,存储字符串中指定下标位字符的引用。
char& c = str1[4];
// 修改引用的值,这里引用的是字符串中的字符数组中的指定下标位的元素,所以c改变,数组中也会对应改变。
c = '!';
cout << str1 << endl;
// 注意:
// 一旦字符串中的字符数组内存重新分配了,使用之前的引用再进行空间访问,可能会出现问题。
// 重写分配的情况:c++中,字符串长度小于16位时,空间开辟在栈上,超过16位,重新分配内存。
// c_str: 将C++风格的字符串转换成C风格的字符串(返回的是C++的string类中维护的那个字符数组的指针)
cout << (int*)str1.c_str() << endl; // 原来地址
str1 = "12345678901234"; // 重新赋值,长度未超过16位,不会重新分配内存,地址不发生改变
cout << c << endl; // 输出引用
str1 = "12345678901234567"; // 重新赋值,长度超过16位,会重新分配内存,地址发生改变
cout << (int*)str1.c_str() << endl; // 新地址
// cout << c << endl; //地址改变,引用的c地址已经被释放了
}
3.1.4 string拼接
// string拼接
void test4(){
string str = "hello";
// +
string str1 = str + "world";
// += 后面跟需要加上的字符或字符串或者字符数组
str += "world";
// append函数
// string& append(const char* s);
str.append("world");
// string& append(const char* s, int n); 把字符串前n个字符拼接到当前字符串结尾
str.append("world",5);
// string& append(const string& str); 把字符串拼接到当前字符串结尾,同 +=
// string& append(const string& str, int start, int n); 把字符串从start开始n个字符拼接到当前字符串结尾
str.append("world",3,4);
// string& append(int n, char c); 把n个字符c拼接到当前字符串结尾
str.append(5,'A');
cout << str << endl;
}
3.1.5 string查找和替换
// string查找和替换
void test5(){
string str = "hello world! hello1 world1!";
// 查找 find:第一次出现的位置,返回首字符的位置下标
// int find(const string& str, int pos); 从pos位置开始查找字符串str第一次出现的位置,pos默认是0
// int find(const char* s, int pos); 从pos位置开始查找字符串s第一次出现的位置
// int find(const char* s, int pos, int n); 从pos位置开始查找字符串s前n个字符第一次出现的位置
// int find(char c, int pos); 从pos位置开始查找字符c第一次出现的位置
cout << str.find("world") << endl;
cout << str.find("world1", 10) << endl;
cout << str.find("world1", 5, 5) << endl;
cout << str.find('1') << endl;
// 如果找不到,返回-1
cout << str.find("hello2") << endl; //这个却并不是-1
int res = str.find("hello2"); // 现在是-1
cout << res << endl;
// 查找rfind :最后一次出现的位置,从后往前找
// int rfind(const string& str, int pos) const; // 查找字符串str最后一次出现的位置,pos默认最大值
// int rfind(const char* s, int pos, int n) const; // 查找字符串s的前n个字符最后一次出现的位置
// int rfind(const char c, int pos) const; // 查找字符c最后一次出现的位置
cout << str.rfind("world") << endl;
cout << str.rfind("world1", 10) << endl; // 从后往前找,从pos往前找,即从10->0的位置找
// 替换
// string& replace(int start, int n, const string& str); 从start位置开始,删除n个字符,并用字符串str替换
// string& replace(int start, int n, const char* s); 从start位置开始,删除n个字符,并用字符串s替换
cout << str.replace(0,5,"no") << endl; // 返回的是这个string对象本身,即str也会发生改变
cout << str << endl;
}
3.1.6 string的比较
// string比较
void test6(){
// 字符串大小比较规则:比的是字典顺序(更加深入来说,就是字符在字符集中的映射的数字)
// 依次比较字符串中的每一位字符,如果字符相同,继续比较后面的一位字符,直到某一位的比较可以确定大小关系,返回大小关系
// 仍然可以使用> < >= <= == !=来比较,但是有一定局限性。
// 局限性:比较结果是bool类型,无法充足的表示每一种比较结果(大于,小于,等于)。
// 因此string提供了compare函数,返回一个int类型:
// 0:相等;
// 1:str1大于str2;
// -1:str1小于str2
string str1 = "abe";
string str2 = "abc";
cout << str1.compare(str2) << endl;
}
3.1.7 string的子串获取
// string子串的获取
void test7(){
// pos:起始下标,从pos开始获取子串,默认0,不要越界,越界报错。
// n:长度,获取多少个字符,可以越界,将剩余的字符串全部获取。
// string substr(int pos, int n) const; // 返回从pos开始的n个字符组成的新字符串
string str = "hello world";
cout << str.substr(0,5) << endl;
cout << str.substr(6) << endl;
cout << str.substr(6,20) << endl;
cout << str << endl; // 不会对原字符串产生影响
}
3.1.8 string的插入和删除
// string的插入和删除
void test8(){
// string& insert(int pos, const char* s); // 在pos位置插入字符串s
// string& insert(int pos, const string& str); // 在pos位置插入字符串str
// string& insert(int pos, int n, char c); // 在pos位置插入n个字符c
// string& erase(int pos, int n = npos); // 删除从pos开始的n个字符,默认所有npos
string str = "hello world";
str.insert(0,"123");
cout << str << endl;
str.insert(1,"123",2); // 在第1个位置开始,插入字符串的前两个字符
cout << str << endl;
str.insert(1,"123456",3,2); // 在第1个位置,插入字符串的从第3个字符开始的2个字符
cout << str << endl;
// 删除
str.erase(0,5);
cout << str << endl;
}
3.2 vector容器
array是静态空间,一旦配置了就不可以更改,要更改则需要配置新空间,然后进行赋值。而vector容器是一个动态数组,可以动态的增加和删除元素,内部机制会自动扩充空间以容纳新元素。使用时需要引入#include "vector"
3.2.1 vector容器的遍历
// vector容器的遍历
void test1() {
// 1. 构造一个vector对象,通过vector的无参构造,构造一个空的vector容器
vector<int> v;
// 2. 添加元素
v.push_back(10);
v.push_back(20);
v.push_back(30);
// 3. 遍历vector容器,使用迭代器
// 迭代器:使用普通指针,依次指向vector中的每一个元素
// begin():获取到的是vector容器中的首元素的地址
// end():获取到的是vector容器中的最后一位元素的下一位指针
// vector<int>::iterator it = v.begin();
// cout << "vector容器的遍历:" << *it << endl;
// it++; // 指针后移,注意:越界输出会报错
// cout << "vector容器的遍历:" << *it << endl;
for (vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout << "vector容器的遍历:" << *it << endl;
};
for (vector<int>::iterator it = v.begin(); it != v.end(); it++){
if (*it == 20){
*it = 200;
};
cout << "vector遍历时修改值:" << *it << endl;
};
// 迭代器遍历时缩写
// 依次将v容器中的每一个元素,给ele进行赋值
// 注意:是赋值,所以改变ele的值,不会影响v容器中的元素,如果要修改v容器中的元素,要引用&
for (int ele:v){
cout << ele << endl;
}
// 遍历、缩写、引用、修改值
for (int &ele:v){
if(ele == 10){
ele = 100; // 这样修改的值才会影响v容器中的元素
}
}
// 倒序遍历
for (vector<int>::iterator it = v.end();it != v.begin();){
it--;
cout << "vector容器的遍历倒序:" << *it << endl;
}
}
3.2.2 vector容器的构造函数
// vector容器的构造函数
void test2(){
// 1. 通过vector的无参构造,构造一个空的vector容器
vector<int> v1;
// 2. vector(n, ele) 使用n个ele填充容器
vector<int> v2(5, 100);
printVector(v2);
// 3. vector(const vector& v), 拷贝构造函数
// 4. vector(v.begin(), v.end()) 左闭右开
vector<int> v3(v2.begin(), v2.end()); // 将v2容器中从begin->end中的元素拷贝到v3容器中
printVector(v3);// 注意:end()指向的是最后一个元素的下一个位置,元素从0开始
vector<int> v4(v2.begin(), v2.begin()+4); // 将v2容器中前4个元素拷贝到v4容器中
printVector(v4);
int array[] = {1, 2, 3, 4, 5};
vector<int> v5(array, array+3);
// array是0位置的地址,array+3是3位置的地址,由于end()指向的是最后一个元素的下一个元素,所以v5容器中实际有的是0 1 2这三个位置的元素
// 也可以说成是将array数组的前3个元素拷贝到v5容器中,上面的同理
printVector(v5);
// 注意:指针加1,指的是指针向后偏移一个位置,不是单纯的地址的值加1,减同理。
}
3.2.3 vector容器的赋值操作
// vector的常用赋值操作
void test3(){
// assign(beg, end); 将[beg, end)区间中的数据拷贝赋值给本身
// assign(n, ele); 将n个ele拷贝赋值给本身
// vector& operator = (const vector & vec); // 重载的等号赋值运算符
// swap(vec); 交换两个vector容器的元素
int array[] = {1, 2, 3, 4, 5};
// vector构建
vector<int> v1;
v1.assign(array, array+3); // array是数组的首元素的地址,array+3是数组的第4个元素的地址,左闭右开
printVector(v1);
vector<int> v2;
v2.assign(3,10);
printVector(v2);
vector<int> v3;
v3 = v2; // 使用重载后的等号运算符=
printVector(v3);
cout << "交换前:" << endl;
printVector(v1);
printVector(v2);
v1.swap(v2);
cout << "交换后:" << endl;
printVector(v1);
printVector(v2);
}
3.2.4 vector的大小操作
//vector 的大小操作
void test4(){
vector<int> v(10,5);
// 返回容器中元素个数
cout << "容器中元素个数:" << v.size() << endl;
// 判断容器是否为空
cout << "容器是否为空:" << v.empty() << endl;
// 返回容器容量
cout << "容器容量:" << v.capacity() << endl;
// 重新指定长度,不是指定容器的容量,而是容器中元素的个数,如果容器长度不够,会进行扩容
v.resize(20); // 如果新长度大于原来长度,则用默认值填充,也可以指定填充值,如v.resize(20, 100);
cout << "resize,新的长度>原来长度" << endl;
cout << "容器中元素个数:" << v.size() << endl;
cout << "容器容量:" << v.capacity() << endl;
// 如果新长度小于原来长度,则多余的元素被删除;
v.resize(4); // 如果新长度大于原来长度,则用默认值填充,也可以指定填充值,如v.resize(20, 100);
cout << "resize,新的长度<原来长度" << endl;
cout << "容器中元素个数:" << v.size() << endl;
cout << "容器容量:" << v.capacity() << endl;
// 运行发现,容器中元素是4个,容量是20,怎么让它一致呢?
// 创建一个新的空容器,将v容器中的元素拷贝到新容器中,然后交换两个容器,v指向新容器,匿名函数指向旧容器,运行完会自动回收空间
// 拷贝构造新容器时,新容器的容量是跟元素数量一致的,所以容量=元素个数
vector<int>(v).swap(v); // swap收缩空间
cout << "容器中元素个数:" << v.size() << endl;
cout << "容器容量:" << v.capacity() << endl;
}
3.2.5 vector的数据存取
// vector的存取
void test5(){
// at(int idx); // 返回索引idx所指的数据,如果idx大于容器中实际元素个数,则抛出异常
// operator[]; 返回索引idx所指的数据,如果idx大于容器中实际元素个数,则抛出异常
// front(); 返回容器中第一个数据元素,即0号位元素
// back();返回容器中最后一个数据元素
int array[] = {1, 2, 3, 4, 5,6,7,8,9,10};
vector<int> v(array,array+sizeof(array)/sizeof(array[0]));
int& ele = v.at(2); // 引用
cout << v.at(2) << endl;
ele = 100; // 修改会对v容器中的元素进行修改
cout << v.at(2) << endl;
printVector(v);
int& ele2 = v[2];
cout << v[2] << endl;
ele2 = 200;
printVector(v);
cout << v.front() << endl;
cout << v.back() << endl;
}
3.2.6 vector的插入和删除
// vector的插入和删除
void test6(){
int array[] = {1, 2, 3, 4, 5,6,7,8,9,10};
vector<int> v(array,array+sizeof(array)/sizeof(array[0]));
// insert(const_iterator pos, int count, ele); // 迭代器指向位置pos插入count个元素ele
// push_back(ele); // 在容器尾部插入元素ele
// pop_back(); // 删除容器中最后一个元素
// erase(const_iterator start, const_iterator end); // 删除迭代器[start, end)区间的元素
// erase(const_iterator pos); // 删除迭代器指向的元素
// clear(); // 删除容器中所有元素
v.insert(v.begin()+3,2, 100); // 在容器的3位置处插入2个100,变成了1, 2, 3, 100, 100, 4, 5……
printVector(v);
v.push_back(100); // 在容器尾部插入元素100
printVector(v);
v.pop_back(); // 删除容器中最后一个元素
printVector(v);
v.erase(v.begin()+3, v.begin()+5); // 删除容器中3和4位置的元素,不包括5,先闭后开
printVector(v);
v.erase(v.begin()+3); // 删除容器中3位置的元素
printVector(v);
v.clear(); // 删除容器中所有元素
// printVector();
}
3.3 deque容器
depeque容器是一种双端队列,允许在容器的开头和结尾进行插入和删除操作。操作与vector容器类似,不同的是,deque容器允许在容器的开头和结尾进行插入和删除操作。同样的,使用deque容器时,需要引入#include "deque"
void printDeque(deque<int>& deq){
// for (auto it = deq.begin(); it != deq.end(); it++) {
// cout << *it << " ";
// };
for (int& ele: deq) {
cout << ele << " ";
};
cout << endl;
}
int main(){
int array[] = {1,2,3,4,5,6,7,8,9,10};
// 构造
deque<int> deq1 = deque<int>(array, array+10);
printDeque(deq1);
deque<int> deq2(5,2);
printDeque(deq2);
// 赋值
deq2.assign(array, array+3);
printDeque(deq2);
deq2.swap(deq1);
printDeque(deq2);
printDeque(deq1);
// 大小,deque没有capacity,它没有预先分配容量
cout << deq1.size() << endl;
cout << deq1.empty() << endl;
deq1.resize(8);
printDeque(deq1);
deq1.resize(10, 9);
printDeque(deq1);
//
deq1.push_back(999);
deq1.push_front(888);
printDeque(deq1);
deq1.pop_back();
deq1.pop_front();
printDeque(deq1);
// 访问
int& ele = deq1.at(4); // 访问指定位置元素,下标
cout << ele << endl;
ele = 666;
cout << deq1[4];
printDeque(deq1);
return 0;
}
3.4 stack容器(栈)
stack容器是一种先进后出的结构,栈。stack容器允许新增元素,移除元素,取得栈顶元素。入栈push,出栈pop。
#include "iostream"
#include "stack"
using namespace std;
int main(){
// 构造函数:
// 1.无参构造 2.拷贝构造
stack<int> s1;
// 压栈/入栈
s1.push(10); // 最先入栈的是栈底
s1.push(20);
s1.push(30); // 最后入栈的是栈顶
// 获取栈顶的元素
int& ele = s1.top();
cout << ele << endl;
ele = 100;
cout << s1.top() << endl;
// 出栈
s1.pop(); // 栈顶元素出栈,pop()返回值为void
cout << s1.top() << endl;
// 大小
cout << s1.size() << endl;
cout << s1.empty() << endl;
cout << "------------------输出所有元素---------------------------" << endl;
while (not s1.empty()) {
cout << s1.top() << ", ";
s1.pop();
}
return 0;
}
3.5 queue容器(队列)
queue容器是一种先进先出结构,队列。queue容器允许新增元素,移除元素,取得队列头元素。
#include "iostream"
#include "queue"
using namespace std;
int main(){
queue<int> q;
q.push(10);
q.push(20);
q.push(30);
// 获取队头元素
cout << "队头元素:" << q.front() << endl;
// 获取队尾元素
cout << "队尾元素:" << q.back() << endl;
// 从队列中移出元素(队头元素)
q.pop();
cout << q.front() << endl;
// 大小
cout << "队列大小:" << q.size() << endl;
cout << "队列是否为空:" << q.empty() << endl;
return 0;
}
3.6 list容器
list容器是一个双向链表,由多个节点组成,节点内容分为数据域和指针域。
list提供的迭代器是双向迭代器Bidirectional Iterator,可以进行前移和后移。
插入和删除操作不会对原有的list迭代器有影响,list元素的删除也只有被删除的那个元素的迭代器失效,其它迭代器不受影响。
#include "iostream"
#include "list"
using namespace std;
void printList(list<int> &l){
for(list<int>::iterator it = l.begin(); it != l.end(); it++){ // list<int>::iterator可以换为auto
cout << *it << ", ";
}
cout << endl;
// for (int& i: l){
// cout << i << ", ";
// }
// cout << endl;
}
int main(){
// 构造函数:
int arr[] = {1,2,3,4,5};
list<int> l1(arr, arr+5);
// 元素的插入和删除:
l1.push_back(6); // 尾部插入
l1.push_front(0); // 头部插入
printList(l1);
l1.pop_back(); // 移除尾部元素
l1.pop_front(); // 移除头部元素
printList(l1);
list<int>::iterator it = l1.begin();
l1.insert(it, 100); // it只能++或者++,由于地址不是连续了,+n不允许
// 如果想在指定位置插入元素,需要先将指针it移到指定位置,通过++和--移动。
printList(l1);
l1.insert(it, 3, 200); // 在it位置插入2个200
printList(l1); // 由于已经在it的位置插入了元素,所以it已经不是第一个元素(第0位元素)了,而是第1位元素
list<int>::iterator it2 = l1.begin();
l1.insert(++it2, arr+3, arr+5); // 在it2位置插入arr[3]、arr[4],左闭右开
printList(l1);
// 删除
auto start = l1.begin();
// l1.erase(++start); // 删除it3位置的元素
// 删除[3,6)
for(int i = 0;i < 3;i++){
start++;
}
auto end = l1.begin();
for(int i = 0;i < 6;i++){
end++;
}
l1.erase(start, end);
printList(l1);
// l1.clear(); // 清空list
l1.remove(200); // 删除所有值为200的元素
printList(l1);
// 大小
cout << l1.size() << endl;
cout << l1.empty() << endl;
// l1.resize(13);
// 数据存取
cout << l1.front() << endl; // 获取第一个元素
cout << l1.back() << endl; // 获取最后一个元素
// 只是获取,不是弹出后返回,不影响l1
// 获取其余位置元素:迭代器,自增即可
auto it3 = l1.begin();
for(int i = 0;i < 3;i++){
it3++;
}
cout << *it3 << endl;
// 反转
l1.reverse();
printList(l1);
// 排序
l1.sort();
printList(l1);
return 0;
}
3.7 set/multiset容器
set不允许有重复元素,所有元素都会根据元素的值自动排序。set的迭代器是const_iterator。
set特性:当对容器中的元素进行插入和删除操作时,操作之前的所有迭代器,在操作完成之后依然有效,但不包括被删除的那个元素。
multiset的特性以及用法跟set一致,但是multiset允许有重复值。
set和multiset的底层实现都是红黑树,即平衡二叉树的一种。
平衡二叉树:左子树和右子树的高度差不超过1的二叉搜索树,遍历使用中序遍历(左、中、右,即从小到大)。
#include "iostream"
#include "set" // set和multiset都是这个包
using namespace std;
void printSet(set<int> &s){
// set的迭代器是一个只读迭代器,只能读取,不能修改
for (auto it = s.begin(); it != s.end(); it++) {
cout << *it << " ";
}
cout << endl;
// for(int ele : s){ // 简化的遍历,不能int& ele进行引用,因为set的元素是const的,只读,不能修改
// cout << ele << " ";
// }
// cout << endl;
}
int main(){
// 构造函数
set<int> s;
multiplies<int> ms;
// 插入元素
s.insert(1);
s.insert(4);
s.insert(2);
s.insert(3);
s.insert(3);
printSet(s); // 结果中自动排序和去重了
// 删除元素,使用迭代器删除
auto it = s.begin();
s.erase(it); // 删除当前迭代器指向的元素,并返回下一个元素的指针
printSet(s);
// s.erase(s.begin(), s.end()); 区间删除
// s.clear(); // 清空set
// 查找元素:set容器没有下标
set<int>::iterator target = s.find(3); // 如果存在,返回指向元素的迭代器(指针),否则返回end()
// set<int>::iterator可以换成auto
cout << s.count(3) << endl; // 查找元素的数量,set去重了,所以结果是1或0。multiset不去重,结果大于等于0
auto res1 = s.lower_bound(3); // 查找第一个大于等于3的元素的迭代器
auto res2 = s.upper_bound(3); // 查找第一个大于3的元素的迭代器
// multiset与set用法一致,但可以插入重复元素
return 0;
}
3.8 map/multimap容器
pair<key, value>:将两个数据整合到一起,成为一个整体,这两个数据类型可以不同,一个是key,一个是value。
void test_pair(){
// 构建pair
pair<int, int> p1(1, 2);
cout << p1.first << " " << p1.second << endl;
pair<string, int> p2 = make_pair("hello", 1);
cout << p2.first << " " << p2.second << endl;
}
- map中存储的元素是一个个的pair,pair中的第一个元素是key,第二个元素是value。
- map中存储的所有键值对,会按照key进行排序。
- map中键不能重复,但值可以重复。(multimap的key可以重复)
- 可以通过迭代器修改map中pair的值,但是不能修改键。
#include "map" // 同样的需要引入map
void printMap(map<string, int> &m){
for (map<string, int>::iterator it = m.begin(); it != m.end(); it++) {
cout << it->first << " " << it->second << endl;
};
cout << endl;
}
int main(){
map<string, int> m;
// 插入
m.insert(make_pair("math", 98));
m.insert(pair<string, int>("chinese", 99));
m.insert(map<string, int>::value_type ("english", 89));
m["history"] = 90;
printMap(m);
m["history"] = 100; // 若不存在,添加值,已经存在就修改值
printMap(m);
// 删除
// m.erase(m.begin()); // 删除第一位
// m.erase("english"); // 根据键删除键值对pair
// printMap(m);
// 查找
map<string ,int>::iterator it = m.find("math"); // 查找指定键的键值对的迭代器
auto it2 = m.find("math");
cout << m.count("english") << endl; // 查找有多少个指定键的键值对
// 大小操作
cout << m.size() << endl;
cout << m.empty() << endl;
m.clear();
// multimap允许键重复
return 0;
}