C++学习笔记——数据结构
记录c++学习中的常用数据结构容器,以及它们的常用方式。
如有错误,欢迎指出帮助修正。
0. begin()、front()、end()、back()、rbegin()、rend()
对这些函数功能的一些理解:
begin():用法类似头部指针的类,可以通过*访问值,类似指针加法进行偏移【常用遍历方式】
front():返回第一个元素的值
end():不可使用,作为结束标记符号存在的位置
back():返回最后一个元素的值
rbegin():反向遍历的头部指针
rend():反向遍历的结束标记
1. a[5] 数组(长度固定,连续存储)
C风格的数组,也可以使用多个[]创建多维数组。
长度固定。
成员在内存中以栈的形式连续存储,可以通过指针偏移直接访问成员。
缺点:在作为单个参数传递到函数中时,只能传递头部指针,而不能同时传递长度,如果不将长度作为额外参数传入的话,将会无法判断是否越界。
#include <iostream>
using namespace std;
int main ()
{
// 创建一个int类型的数组,长度为10
int a[5];
// 为每一个成员赋值
for (int i = 0; i < 5; i++) {
a[i] = i;
}
// 遍历数组并输出
for (int i = 0; i < 5; i++) {
cout << a[i] << endl;
}
return 0;
}
运行得到:
0
1
2
3
4
2. string 字符串
字符串分两种:
一种是C语言风格的以char[]形式定义,以’\0’为结尾的字符数组(这种字符串在c++中也可以通过头部【即数组名】直接在cout中输出完整内容),通过#include <cstring>
调用库函数。
此处指另一种:c++中的字符串类,通过#include <string>
调用库函数。
注:反转字符串需要额外调用库#include <algorithm>
输入一行字符串【不包括换行符】
#include <iostream>
#include <string>
// 调用反转函数reserve需要的库
#include <algorithm>
using namespace std;
int main ()
{
// 创建一个字符串
string s1 = "123";
cout << "字符串s1为:" << s1 << endl;
string s2 = "abc";
// 创建一个未定义的字符串
string s3;
// 获取字符串长度可以使用size或者length
// c++中的size()和length()没有区别
int length;
length = s1.size();
cout << "字符串s1的长度为:" << length << endl;
cout << "字符串s1的长度为:" << s1.length() << endl;
// 字符串可以直接复制覆盖别的字符串
s2 = s1;
cout << "被覆盖后的s2为:" << s2 << endl;
// 字符串可以用加法拼接
s3 = s1 + s2;
cout << "s1与s2拼接:" << s3 << endl;
// 反转字符串
reverse(s3.begin(), s3.end());
cout << "反转s3:" << s3 << endl;
// 在字符串的末尾添加【字符串】
// 注:此处输入只能是双引号的字符串,单引号的char会报错
// 后续类似的注意点都用同样的括号重点标注出来
s1.append("4");
cout << "在s1的末尾添加4:" << s1 << endl;
// 在指定位置插入【字符串】
s1.insert(0, "0");
cout << "在s1的第0位添加0:" << s1 << endl;
// 在指定位置插入多个相同【字符】
s1.insert(1, 2, '9');
cout << "在s1的第1位添加2个9:" << s1 << endl;
// 将字符串s1从下标1开始的2个字符放到字符串s2的下标0位置
s2.insert(0, s1, 1, 2);
cout << "将字符串s1从下标1开始的2个字符放到字符串s2的下标0位置:" << s2 << endl;
// 查找子串位置
int pos = s1.find("991");
cout << "查找s1中的子串991的第一个开始位置:" << pos << endl;
// 从头部开始查找子串
int first = s1.find_first_of("9");
// 从尾部开始查找子串
int last = s1.find_last_of("9");
cout << "查找s1中的第一个9与最后一个9的位置:" << first << " " << last << endl;
// 替换子串
// 从s1的下标2开始的2个字符替换为8
// 注:是把2个字符的字符串整体替换为字符串"8",而不是替换成2个8
s1.replace(2, 2, "8");
cout << "从s1的下标2开始的2个字符替换为8:" << s1 << endl;
// 切片出一个新的子串
// 从s1的下标3开始的2个字符被复制出来作为一个新的字符串end()
string s4 = s1.substr (3, 2);
cout << "从s1的下标3开始的2个字符被复制出来作为一个新的字符串:" << s4 << endl;
return 0;
}
运行得到:
字符串s1为:123
字符串s1的长度为:3
字符串s1的长度为:3
被覆盖后的s2为:123
s1与s2拼接:123123
反转s3:321321
在s1的末尾添加4:1234
在s1的第0位添加0:01234
在s1的第1位添加2个9:0991234
将字符串s1从下标1开始的2个字符放到字符串s2的下标0位置:99123
查找s1中的子串991的第一个开始位置:1
查找s1中的第一个9与最后一个9的位置:1 2
从s1的下标2开始的2个字符替换为8:098234
从s1的下标3开始的2个字符被复制出来作为一个新的字符串:23
3. array 数组(长度固定,连续存储)
长度固定。
成员在内存中以栈的形式连续存储。
在作为单个参数传递到函数中时,能够同时传递长度,可以判断是否越界。
#include <iostream>
#include <array>
using namespace std;
int main ()
{
// 初始化一个数组并赋值、
// 可以不给所有位置赋值
array<int, 6> a1 = {1, 2, 3, 4};
// 用指针遍历数组并输出所有元素
// 没有被赋值过的元素输出是0
cout << "a1中的元素包括:";
for (auto p = a1.begin(); p != a1.end(); p++) {
cout << *p << " ";
}
cout << endl;
// 获取数组的固定尺寸
cout << "a1的内存大小为:" << sizeof(a1) << endl;
// 未被赋值的成员依然占据了大小,与最大尺寸保持一致
cout << "a1的当前尺寸为:" << a1.size() << endl;
cout << "a1的最大尺寸为:" << a1.max_size() << endl;
cout << "a1是否为空:" << a1.empty() << endl;
// 访问元素
cout << "a1的下标1的元素:" << a1.at(1) << endl;
cout << "a1的头部元素:" << a1.front() << endl;
cout << "a1的尾部元素:" << a1.back() << endl;
// 全部初始化为固定值
array<int, 6> a2;
a2.fill(10);
cout << "a2中的元素包括:";
for (auto p = a2.begin(); p != a2.end(); p++) {
cout << *p << " ";
}
cout << endl;
// 交换数组元素
a1.swap(a2);
cout << "交换后,a1中的元素包括:";
for (auto p = a1.begin(); p != a1.end(); p++) {
cout << *p << " ";
}
cout << endl;
cout << "交换后,a2中的元素包括:";
for (auto p = a2.begin(); p != a2.end(); p++) {
cout << *p << " ";
}
cout << endl;
return 0;
}
运行得到:
a1中的元素包括:1 2 3 4 0 0
a1的内存大小为:24
a1的当前尺寸为:6
a1的最大尺寸为:6
a1是否为空:0
a1的下标1的元素:2
a1的头部元素:1
a1的尾部元素:0
a2中的元素包括:10 10 10 10 10 10
交换后,a1中的元素包括:10 10 10 10 10 10
交换后,a2中的元素包括:1 2 3 4 0 0
4. vector 向量(长度可变,连续存储)
长度可变。
成员在内存中连续存储。
可以判断是否越界。
在各种算法中被广泛使用,出场率非常高的数据类型。
#include <iostream>
#include <vector>
using namespace std;
// 为了方便起见,写一个打印向量的函数
void print_vec(vector<int> v) {
for (auto p = v.begin(); p != v.end(); p++) {
cout << *p << " ";
}
cout << endl;
}
// 参考:https://blog.csdn.net/wkq0825/article/details/82255984
int main ()
{
// vector初始化
// 定义长度但不进行初始化
vector<int> v1(10);
// 定义长度且初始化为1
vector<int> v2(10, 1);
// 赋值一个原有向量到新向量
vector<int> v3(v2);
// 将v2下标0到下标2的元素给v4
vector<int> v4(v2.begin(),v2.begin()+3);
// 将数组作为向量初值
int a1[7]={1,2,3,4,5,9,8};
vector<int> v5(a1, a1+7);
// 赋值
v1 = {1, 2, 3, 4, 5, 6};
// 将v2设置为长度6,每个元素初始化为3
v2.assign(6, 3);
cout << "将v2设置为长度6,每个元素初始化为3:";
print_vec(v2);
// 用指针遍历并输出所有元素
// 与数组array不同,向量vector未被赋值的成员不会被输出
cout << "v1中的元素包括:";
// 自定义的打印函数
print_vec(v1);
cout << endl;
// 获取向量的固定尺寸
cout << "v1的内存大小为:" << sizeof(v1) << endl;
// 与数组array不同,向量vector未被赋值的成员没有占据size大小
cout << "v1的当前尺寸为:" << v1.size() << endl;
cout << "v1的最大尺寸为:" << v1.max_size() << endl;
cout << "v1是否为空:" << v1.empty() << endl;
cout << endl;
// 调整内存大小
cout << "v2在内存中总共可以容纳的元素个数:" << v2.capacity() << endl;
// 将v2的现有元素个数调至10个,多则删,少则补,其值随机
v2.resize(10);
cout << "将v2的现有元素个数调至10个,多则删,少则补,其值未初始化:";
print_vec(v2);
// 将v2的现有元素个数调至20个,多则删,少则补,其值为4
v2.resize(20, 4);
cout << "将v2的现有元素个数调至20个,多则删,少则补,其值为4:";
print_vec(v2);
// 手动给vec扩容内存空间,用于准备输入大量数据
// 不会影响到size()返回的成员数量
v2.reserve(30);
cout << "扩容后,v2在内存中总共可以容纳的元素个数:" << v2.capacity() << endl;
cout << endl;
// 访问成员
cout << "v1的头部元素为:" << v1.front() << endl;
cout << "v1的尾部元素为:" << v1.back() << endl;
cout << "v1的下标2的元素为:" << v1.at(2) << endl;
cout << endl;
// 在向量末尾插入元素
v1.push_back(26);
cout << "v1在末尾插入元素:";
print_vec(v1);
// 在向量末尾删除元素
v1.pop_back();
cout << "v1在末尾删除元素:";
print_vec(v1);
// 添加元素
// 在v1的下标1处插入5
v1.insert(v1.begin()+1,5);
cout << "在v1的下标1处插入5:";
print_vec(v1);
// 在v1的下标1处插入3个值为4的数
v1.insert(v1.begin()+1, 3, 4);
cout << "在v1的下标1处插入3个值为4的数:";
print_vec(v1);
// 在v1的下标1处插入v2的第3个元素到第5个元素
cout << "v2中的元素:";
print_vec(v2);
v1.insert(v1.begin()+1,v2.begin()+3,v2.begin()+6);
cout << "在v1的下标1处插入v2的第3个元素到第5个元素:";
print_vec(v1);
// 删除元素
// 删除v1中下标1开始的2个元素
v1.erase(v1.begin()+1, v1.begin()+3);
cout << "删除v1中下标1开始的2个元素:";
print_vec(v1);
cout << endl;
// 交换元素
v1.swap(v2);
cout << "交换后,v1为:";
print_vec(v1);
cout << "交换后,v2为:";
print_vec(v2);
// 清空元素
v1.clear();
cout << "清空后,v1为:";
print_vec(v1);
return 0;
}
运行得到:
将v2设置为长度6,每个元素初始化为3:3 3 3 3 3 3
v1中的元素包括:1 2 3 4 5 6
v1的内存大小为:24
v1的当前尺寸为:6
v1的最大尺寸为:4611686018427387903
v1是否为空:0
v2在内存中总共可以容纳的元素个数:10
将v2的现有元素个数调至10个,多则删,少则补,其值未初始化:3 3 3 3 3 3 0 0 0 0
将v2的现有元素个数调至20个,多则删,少则补,其值为4:3 3 3 3 3 3 0 0 0 0 4 4 4 4 4 4 4 4 4 4
扩容后,v2在内存中总共可以容纳的元素个数:30
v1的头部元素为:1
v1的尾部元素为:6
v1的下标2的元素为:3
v1在末尾插入元素:1 2 3 4 5 6 26
v1在末尾删除元素:1 2 3 4 5 6
在v1的下标1处插入5:1 5 2 3 4 5 6
在v1的下标1处插入3个值为4的数:1 4 4 4 5 2 3 4 5 6
v2中的元素:3 3 3 3 3 3 0 0 0 0 4 4 4 4 4 4 4 4 4 4
在v1的下标1处插入v2的第3个元素到第5个元素:1 3 3 3 4 4 4 5 2 3 4 5 6
删除v1中下标1开始的2个元素:1 3 4 4 4 5 2 3 4 5 6
交换后,v1为:3 3 3 3 3 3 0 0 0 0 4 4 4 4 4 4 4 4 4 4
交换后,v2为:1 3 4 4 4 5 2 3 4 5 6
清空后,v1为:
需要导入#include<algorithm>
库的进阶用法:
#include <iostream>
#include <vector>
// 需要算法头文件
#include<algorithm>
using namespace std;
// 为了方便起见,写一个打印向量的函数
void print_vec(vector<int> v) {
for (auto p = v.begin(); p != v.end(); p++) {
cout << *p << " ";
}
cout << endl;
}
// 参考:https://blog.csdn.net/wkq0825/article/details/82255984
int main ()
{
// vector初始化
vector<int> v1(10);
v1 = {1, 2, 3, 4, 5, 6};
// 定义长度且初始化为1
vector<int> v2(3, 9);
cout << "v1:";
print_vec(v1);
// 排序
sort(v1.begin(), v1.end());
cout << "v1排序之后:";
print_vec(v1);
// 反转
reverse(v1.begin(), v1.end());
cout << "v1反转之后:";
print_vec(v1);
// 复制
cout << "v2:";
print_vec(v2);
// 把v2的所有元素复制覆盖到v1的索引3往后的数据
copy(v2.begin(), v2.end(), v1.begin()+1);
cout << "把v2的所有元素复制覆盖到v1的索引3往后的数据:";
print_vec(v1);
// 查找
vector<int>::iterator index_p = find(v1.begin(), v1.end(), 9);
// 返回的实际上是一个指针,指向值的位置
if (index_p != v1.end()) {
cout << "v1中9的位置的指针的值:" << *index_p << endl;
// 将指针转换为索引值
auto index = distance(v1.begin(), index_p);
cout << "v1中9的位置的索引:" << index << endl;
}
else {
cout << "v1中找不到9的位置" << endl;
}
return 0;
}
运行得到:
v1:1 2 3 4 5 6
v1排序之后:1 2 3 4 5 6
v1反转之后:6 5 4 3 2 1
v2:9 9 9
把v2的所有元素复制覆盖到v1的索引3往后的数据:6 9 9 9 2 1
v1中9的位置的指针的值:9
v1中9的位置的索引:1
5. list 列表(长度可变,不连续存储)
长度可变。
成员在内存中以链表形式不连续存储,类似双向链表。
优点:能够快速添加与删除任何位置的元素。
缺点:不适合随机访问,整体操作较慢。
#include <iostream>
#include <list>
// 需要算法头文件
#include<algorithm>
using namespace std;
// 从前往后打印一个list
void print_list(list<int> list) {
for (auto p = list.begin(); p != list.end(); p++) {
cout << *p << " ";
}
cout << endl;
}
// 从后往前打印一个list
void rprint_list(list<int> list) {
for (auto p = list.rbegin(); p != list.rend(); p++) {
cout << *p << " ";
}
cout << endl;
}
// 条件筛选函数
bool is_small_than_5(const int& value) {
return value < 5;
}
int main ()
{
// 创建一个list
list<int> list1;
// 声明一个迭代器
list<int>::iterator iter;
// 从尾部插入元素
list1.push_back(4);
list1.push_back(8);
list1.push_back(3);
list1.push_back(1);
// 正向遍历list1
print_list(list1) ;
// 反向遍历list1
rprint_list(list1) ;
// 从头部插入元素
list1.push_front(9);
cout << "在前方插入9:";
print_list(list1) ;
cout << endl;
// 对列表赋值
list<int> list2;
// 赋值8个1
list2.assign(8,1);
cout << "赋值8个1:";
print_list(list2) ;
// 在列表中插入数据
list2.insert(++list2.begin(),3,9);
cout << "在列表中插入数据3个9:";
print_list(list2) ;
// 向指定索引插入数据需要让迭代器自增抵达
auto iter1 = begin(list2);
// 在索引4的位置
advance(iter1, 4);
list2.insert(iter1, 3, 3);
cout << "在在索引4的位置插入数据3个3:";
print_list(list2) ;
// 清除list2中的第2个元素
list2.erase(++list2.begin());
cout<<"清除list2中的第2个元素 ";
print_list(list2) ;
cout << endl;
// 前后弹出元素
list1.pop_front();
list1.pop_back();
cout << "前后弹出元素:";
print_list(list1) ;
// 对list1排序
list1.sort();
cout << "对list1排序:";
print_list(list1);
// 交换
list1.swap(list2);
cout << "把list1与list2交换:" << endl;
print_list(list1);
print_list(list2);
// 合并,greater<int>为升序, less<int>为降序
// 但升序和降序的作用机制有点迷惑,暂时还没理清
list1.merge(list2, less<int>());
cout << "对list1与list2合并:";
print_list(list1);
// 集合的属性显示
cout << "第一个元素 :" << list1.front() << endl;
cout << "最后一个元素 :" << list1.back() << endl;
cout << "元素个数:" << list1.size() << endl;
cout << "是否为空:" << list1.empty() << endl;
cout << "能容纳的最大元素数量:" << list1.max_size() << endl;
//cout << ":" << list1. << endl;
// 删除连续的重复元素
list1.unique();
cout << "删除连续的重复元素:";
print_list(list1);
// 反转列表
list1.reverse();
cout << "反转列表:";
print_list(list1);
// 按照值删除所有元素1
list1.remove(1);
cout << "按照值删除所有元素1:";
print_list(list1);
// 删除所有小于5的数字
list1.remove_if(is_small_than_5);
cout << "删除所有小于5的数字:";
print_list(list1);
// 将list2中的值剪切到list1下标0开始的地方
// list2中原本的值将会被清空,list1中的值会后移而不是被覆盖
list2.assign(8,1);
list1.splice(list1.begin(), list2);
cout << "将list2中的值剪切到list1下标0开始的地方:";
print_list(list1);
// 清空
list1.clear();
cout << "清空元素:";
print_list(list1);
return 0;
}
运行得到:
4 8 3 1
1 3 8 4
在前方插入9:9 4 8 3 1
赋值8个1:1 1 1 1 1 1 1 1
在列表中插入数据3个9:1 9 9 9 1 1 1 1 1 1 1
在在索引4的位置插入数据3个3:1 9 9 9 3 3 3 1 1 1 1 1 1 1
清除list2中的第2个元素 1 9 9 3 3 3 1 1 1 1 1 1 1
前后弹出元素:4 8 3
对list1排序:3 4 8
把list1与list2交换:
1 9 9 3 3 3 1 1 1 1 1 1 1
3 4 8
对list1与list2合并:1 3 4 8 9 9 3 3 3 1 1 1 1 1 1 1
第一个元素 :1
最后一个元素 :1
元素个数:16
是否为空:0
能容纳的最大元素数量:768614336404564650
删除连续的重复元素:1 3 4 8 9 3 1
反转列表:1 3 9 8 4 3 1
按照值删除所有元素1:3 9 8 4 3
删除所有小于5的数字:9 8
将list2中的值剪切到list1下标0开始的地方:1 1 1 1 1 1 1 1 9 8
清空元素:
6. unordered_map 哈希表(无序)
map相当于java中的TreeMap,unordered_map相当于HashMap。
无论从查找、插入上来说,unordered_map的效率都优于hash_map,更优于map;
而空间复杂度方面,hash_map最低,unordered_map次之,map最大。
7. map 哈希表(有序)
map<int, string> map_1;
map_1[1] = "abc"
判断key是否存在于map中:
if (map_1.find(key) == map_1.end())
{cout << "没有key" << endl;}
8. set 集合(有序,去重)
用于去重与排序。
与python不同,c++的set可以用于排序。
自定义排序规则:
set<int, compare> sort_int;
// 自定义排序
struct compare
{
bool operator()(const int &a, constint &b) const
{
return a > b;
}
};
9. pair 对(包含两个有序数据的结构体)
pair<int, int>
通过a.first
与a.second
访问两个成员。