目录
STL是什么
STL的内容
容器
vector
建立vector
嵌套vector
其他操作
deque
关于deque和vector的选择
关于随机访问
其他操作
list
list与其他容器的区别
建立list
string
C++标准库中的字符串
C风格的字符串
pair
建立pair
访问pair
pair的常见用途
map
map的特点
建立map
嵌套map
删除map中的元素
set
关于set和map的一些特性
set的特点
建立set与删除元素
STL(Standard Template Library)是C++标准库中的一个重要组成部分,它提供了一组丰富的通用数据结构和算法模板,包括容器、迭代器、算法等。STL的设计目标是提供高效、可靠的数据结构和算法,并且易于使用和扩展。
STL的优点在于它是基于泛型编程的思想设计的,即算法和数据结构的实现是独立于具体类型的,可以通过模板参数来适配不同的数据类型。这使得STL非常灵活,可以适用于各种场景下的数据处理需求。同时,STL的实现经过精心设计和优化,具有高效、稳定的性能表现。
名称 | 功能 | 内容 |
---|---|---|
容器(Container) | 用于存储和管理数据的类模板 | vector、list、set、map等 |
迭代器(Iterator) | 用于访问容器中元素的类模板 | 指针的一种泛化 |
算法(Algorithm) | 对容器中元素进行操作的函数模板 | 排序、查找、复制、删除等 |
此外,STL还提供了一些辅助性工具,如函数对象、适配器等。
仿函数:行为类似函数,可作为算法的某种策略。
适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
空间配置器:负责空间的配置与管理。
vector是一个动态数组,可以存储和管理一系列元素。与静态数组相比,vector的大小是可以动态调整的,可以在运行时进行元素的插入、删除、访问等操作。
在容器中,向量是看似一维的存储形式,可以理解为长度可变的数组,只不过在尾部增删数据的时候效率最高,其他位置增删数据则效率较低。
先通过一段代码来了解vector
注意事项:
1.需要添加头文件才可以使用vector
2.命名格式:vector<数据类型>容器名
#include
#include //需要添加头文件才可以使用vector
using namespace std;
// 程序的主函数
int main()
{
vector V; //命名格式:vector<数据类型>容器名
V.push_back(1); //存放数据1
V.push_back(2);
V.push_back(1);
V.push_back(2);
cout << V[0] << endl; //输出第一个数据:1
system("pause");
return 0;
}
数组可以嵌套,同样,vector也可以,形似二维数组,但功能更为强大。
同样通过代码来了解嵌套vector
注意事项:
规范嵌套格式
#include
#include
using namespace std;
// 程序的主函数
int main()
{
vector> V; //嵌套vector
vector sub_V; //里层vector
sub_V.push_back(1);
sub_V.push_back(2);
sub_V.push_back(1);
V.push_back(sub_V); //将里层vector存放到外层
cout << V[0][1] << endl; //输出第一个里层vector的第二个数据:2
system("pause");
return 0;
}
int size = vec1.size(); //元素个数
bool isEmpty = vec1.empty(); //判断是否为空
vec1.insert(vec1.end(),5,3); //从vec1.back位置插入5个值为3的元素
vec1.pop_back(); //删除末尾元素
vec1.erase(vec1.begin(),vec1.end());//删除之间的元素,其他元素前移
cout<<(vec1==vec2)?true:false; //判断是否相等==、!=、>=、<=...
vector::iterator iter = vec1.begin(); //获取迭代器首地址
vector::const_iterator c_iter = vec1.begin(); //获取const类型迭代器
vec1.clear(); //清空元素
增加了迭代器之后的效果(施工ing)
#include
#include
using namespace std;
// 程序的主函数
int main()
{
vector V;
V.push_back(1);
V.push_back(2);
V.push_back(3);
for (vector::iterator it = V.begin(); it != V.end(); it++)
cout << *it << " ";
cout << endl;
cout << "==========================" << endl;
V.insert(V.begin() + 2,10);
for (vector::iterator it = V.begin(); it != V.end(); it++)
cout << *it << " ";
system("pause");
return 0;
}
从前后两端都可以进行数据的插入和删除操作,同时支持数据的快速随机访问。
从deque对象的开始位置插入和删除元素的时间是固定的,而不像vector中那样是线性时间。所以多数操作发生在序列的起始和结尾处,则应考虑使用deque数据结构。
但对元素的随机访问和在序列中部指向线性时间的插入和删除操作,vector容器执行更快些。
随机访问是什么?
随机访问是指在数据结构中以任意顺序直接访问元素的能力。具体来说,随机访问允许我们通过索引或位置来获取数据结构中的任意元素,而不需要按照特定的顺序遍历整个数据结构。
随机访问的特点是通过确定的位置或索引值可以直接访问目标元素,而不需要依次遍历数据结构中的每个元素。这种直接访问的方式使得随机访问具有高效性,时间复杂度通常为O(1)。例如,在数组中,我们可以通过索引值来随机访问特定位置的元素。
相比之下,顺序访问是指按照特定的顺序一个接一个地访问数据结构中的元素。顺序访问需要从数据结构的开头开始逐个遍历元素,直到找到目标元素或到达末尾。顺序访问的效率相对较低,时间复杂度通常为O(n),其中n是数据结构中元素的数量。
deque支持随机访问的底层逻辑是什么?
deque支持随机访问的原理是基于底层实现的数组或链表。
在底层实现中,如果deque内部使用数组来存储元素,那么随机访问就可以通过索引来实现。通过索引,可以直接访问数组中的任意一个元素,时间复杂度为O(1)。这是因为数组中的元素在内存中是连续存储的,可以通过偏移量直接计算出目标元素的内存地址。
另一种情况是deque内部使用链表来存储元素。链表中的每个节点都包含了当前元素的值以及指向前一个节点和后一个节点的指针。对于链表实现的deque,随机访问需要从头节点开始遍历,直到找到目标位置的节点。由于链表中的元素不是连续存储的,所以随机访问的时间复杂度为O(n),其中n是目标位置与头节点之间的距离。
支持随机访问的容器有哪些?
vector、deque、array、string
除了这些容器,还有一些关联容器(如map和set)不直接支持随机访问,因为它们是基于红黑树等数据结构实现的。虽然不能直接通过索引来访问元素,但它们提供了其他高效的查找和遍历操作。
建立deque和其他操作与vector类似,看以下代码可知
#include
#include
using namespace std;
// 程序的主函数
int main()
{
deque D;
D.push_back(1);
D.push_back(2);
D.push_back(3);
for (deque::iterator it = D.begin(); it != D.end(); it++)
cout << *it << " ";
cout << endl;
cout << "============在其索引2的位置插入10:" << endl;
D.insert(D.begin() + 2,10);
for (deque::iterator it = D.begin(); it != D.end(); it++)
cout << *it << " ";
cout << endl;
cout << "============在其头部插入0:" << endl;
D.push_front(0);
for (deque::iterator it = D.begin(); it != D.end(); it++)
cout << *it << " ";
cout << endl;
cout << "============在其头部弹出0:" << endl;
D.pop_front();
for (deque::iterator it = D.begin(); it != D.end(); it++)
cout << *it << " ";
system("pause");
return 0;
}
1. assign() 设置双向队列的值
2. at() 返回指定的元素
3. back() 返回最后一个元素
4. begin() 返回指向第一个元素的迭代器
5. clear() 删除所有元素
6. empty() 返回真如果双向队列为空
7. end() 返回指向尾部的迭代器
8. erase() 删除一个元素
9. front() 返回第一个元素
10. get_allocator() 返回双向队列的配置器
11. insert() 插入一个元素到双向队列中
12. max_size() 返回双向队列能容纳的最大元素个数
13. pop_back() 删除尾部的元素
14. pop_front() 删除头部的元素
15. push_back() 在尾部加入一个元素
16. push_front() 在头部加入一个元素
17. rbegin() 返回指向尾部的逆向迭代器
18. rend() 返回指向头部的逆向迭代器
19. resize() 改变双向队列的大小
20. size() 返回双向队列中元素的个数
21. swap() 和另一个双向队列交换元素
Operators []:可以使用[]操作符访问双向队列中单个的元素。
列表是用双向链表实现的,指的是既可以从链表的头部开始搜索找到链表的尾部,也可以进行反向搜索,从尾部到头部的链表。这使得list在任何位置插入和删除元素都变得非常高效,但是随机访问速度变得非常慢,因为保存的地址是不连续的,所以list没有重载[]运算符,也就是说,访问list元素的时候,再也不像vector和deque那么方便。
#include
#include
using namespace std;
// 程序的主函数
int main()
{
//list的创建和初始化
list lst1; //创建空list
list lst2(3); //创建含有三个元素的list
list lst3(3, 2); //创建含有三个元素的值为2的list
list lst4(lst3); //使用lst3初始化lst4
list lst5(lst3.begin(), lst3.end()); //同lst4
cout << "lst4中的元素有:" << endl;
for (list::iterator it = lst4.begin(); it != lst4.end(); it++)
cout << *it << " ";
cout << endl;
cout << "lst5中的元素有:" << endl;
for (list::iterator it = lst5.begin(); it != lst5.end(); it++)
cout << *it << " ";
cout << endl;
system("pause");
return 0;
}
特别注意,由于list的底层是双向链表,因此insert操作无法直接像vector和deque一样直接插入数据,只能通过迭代器的自加移动到相应位置,再插入数据。
string专题新鲜出炉,戳这戳这:很全很详细的string整理
专门用于保存字符。随机访问快。尾部插入删除快。
常见操作有:
1、string(const char *s)
:将 string 对象初始化为 s 指向的字符串
string str("hello");
2、string(size_type n,char c)
:创建一个包含 n 个元素的 string 对象,其中每个元素都被初始化为字符 c
string str(10, 'a');
3、string(const string &str)
:将一个 string 对象初始化为 string 对象 str(复制构造函数)
string str("abcde");
string str2(str);
4、string()
:创建一个默认的 string 对象,长度为 0(默认构造函数)
string str;
5、使用C语言风格字符串处理string对象
string str = "hello!";
6、获取string对象的长度,C语言中使用strlen()来获取字符串长度,C++中使用str.size()
或str.length()
.
string str("hello!");
int len1 = str.size();
int len2 = str.length();
7、将一个 string 对象赋值给另一个 string 对象
string str("hello!");
string str2;
str2 = str;
8、string 对象的拼接
C 语言中使用 strcat、strncat 函数来进行字符串拼接操作,C++中可以采用以下方式
string str1("hello");
string str2("world");
string str3 = str1 + str2;string str("hello");
string str2("world");
str += str2;
str += 'a';
str += "abcd";string str("hello");
string str2("world");
str.append(str2);
str.append("abcd");
9、string.push_back() 函数来在一个 string 对象后面附加一个字符
string str("hello");
char ch = 'a';
str.push_back(ch);
10、使用 string.substr() 函数来获取子串
string str("hello");
string str2 = str.substr(3,2)
11、访问 string 字符串的元素
string str("hello");
cout << str[2] << endl;
cout << str.at(2) << endl;
12、使用 string.insert()
进行插入操作
string& insert(size_t pos,const string&str);
// 在位置 pos 处插入字符串 strstring& insert(size_t pos,const string&str,size_t subpos,size_t sublen);
// 在位置 pos 处插入字符串 str 的从位置 subpos 处开始的 sublen 个字符string& insert(size_t pos,const char * s);
// 在位置 pos 处插入字符串 sstring& insert(size_t pos,const char * s,size_t n);
// 在位置 pos 处插入字符串 s 的前 n 个字符string& insert(size_t pos,size_t n,char c);
// 在位置 pos 处插入 n 个字符 citerator insert (const_iterator p, size_t n, char c);
// 在 p 处插入 n 个字符 c,并返回插入后迭代器的位置iterator insert (const_iterator p, char c);
// 在 p 处插入字符 c,并返回插入后迭代器的位置
13、使用 string.erase() 进行元素删除操作
string& erase (size_t pos = 0, size_t len = npos); // 删除从 pos 处开始的 n 个字符
iterator erase (const_iterator p); // 删除 p 处的一个字符,并返回删除后迭代器的位置
iterator erase (const_iterator first, const_iterator last); // 删除从 first 到last 之间的字符,并返回删除后迭代器的位置
14、使用 string.swap() 函数交换两个字符串
string str1 = "hello";
string str2 = "HELLO";
str1.swap(str2);
15、 string.back()
获取或修改字符串最后一个字符,string.front()
获取或修改字符串第一个字符
string str("abcd");
char b = str.back();
str.back() = 'e';
16、string.pop_back()
删除字符串最后一个元素
#include
#include
using namespace std;
int main ()
{
string str1 = "runoob";
string str2 = "google";
string str3;
int len ;
// 复制 str1 到 str3
str3 = str1;
cout << "str3 : " << str3 << endl;
// 连接 str1 和 str2
str3 = str1 + str2;
cout << "str1 + str2 : " << str3 << endl;
// 连接后,str3 的总长度
len = str3.size();
cout << "str3.size() : " << len << endl;
return 0;
}
#include
#include
using namespace std;
int main ()
{
char str1[13] = "runoob";
char str2[13] = "google";
char str3[13];
int len ;
// 复制 str1 到 str3
strcpy( str3, str1);
cout << "strcpy( str3, str1) : " << str3 << endl;
// 连接 str1 和 str2
strcat( str1, str2);
cout << "strcat( str1, str2): " << str1 << endl;
// 连接后,str1 的总长度
len = strlen(str1);
cout << "strlen(str1) : " << len << endl;
return 0;
}
pair是将2个数据组合成一组数据,又不想因此定义结构体,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。 pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。
typedef pair abc;//作用同下结构体
typedef struct pair{
double first;
double second;
} abc;
将类型定义写在前面,后面用小括号内两个元素的方式
pair ("pair",5);
使用自带的make_pair函数
make_pair(“asdf”,6);
pair中只有两个元素,分别是first和second,只需要按正常结构体的方式去访问即可
#include
#include
#include
using namespace std;
int main()
{
pair p;
p.first = "asdf";
p.second = 6;
cout< ("zxcv",666);
cout<
map容器和python中的字典非常类似,或者说一模一样。都是通过键值对的方式来存储和访问数据的,底层是通过红黑树来实现的。
官方文件
- map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
- 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:typedef pair
value_type; - 在内部,map中的元素总是按照键值key进行比较排序的。
- map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
- map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
- map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))
- map中的的元素是键值对。
- map中的key是唯一的,并且不能修改。
- 默认按照小于的方式对key进行比较。
- map中的元素如果用迭代器去遍历,可以得到一个有序的序列。
- map的底层为平衡搜索树(红黑树),查找效率比较高 O ( l o g 2 N )
- 支持[]操作符,operator[]中实际进行插入查找。
先看一段代码
#include
#include
其中key的值不代表其位置,所以浮点型float也可以
#include
#include
虽然说是像python的字典,但是格式反而感觉像索引值变为key值的多维列表/小声吐槽
#include
#include
map中的键值对不一定是按照创建的顺序保存数据,map会按照key的值内部进行排序,但是保持其键值对的对应关系不变。
set是关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变。C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。
为何map和set的插入删除效率比用其他序列容器高?
set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点也OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。
为何每次insert之后,以前保存的iterator不会失效?
iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。
当数据元素增多时,set的插入和搜索速度变化如何?
在set中查找是使用二分查找,也就是说,如果有16个元素,最多需要比较4次就能找到结果,有32个元素,最多比较5次。那么有10000个呢?最多比较的次数为log10000,最多为14次,如果是20000个元素呢?最多不过15次。当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/14的搜索时间而已。
从中文名就可以明显地看出,在set中不会存在重复的元素,若是保存相同的元素,将直接视为无效,看一段与vector比较的代码
#include
#include
#include
using namespace std;
// 程序的主函数
int main()
{
vector ivec;
for (vector::size_type i = 0; i != 10; i++) {
ivec.push_back(i);
ivec.push_back(i);
}
set iset(ivec.begin(), ivec.end());
cout << "向量中的元素为:" << endl;
for (vector::iterator it = ivec.begin(); it != ivec.end(); it++)
{
cout << *it << " ";
}
cout << endl;
cout << "集合中的元素为:" << endl;
for (set::iterator it = iset.begin(); it != iset.end(); it++)
{
cout << *it << " ";
}
cout << endl;
cout << "向量的大小为:" << endl;
cout << ivec.size() << endl;
cout << "集合的大小为:" << endl;
cout << iset.size() << endl;
system("pause");
return 0;
}
#include
#include
#include
#include
using namespace std;
// 程序的主函数
int main()
{
set set1;
set1.insert("the");
//删除集合
while (!set1.empty())
{
//获取头部
set::iterator it = set1.begin();
//打印头部元素
cout << *it << endl;
//从头部删除元素
set1.erase(set1.begin());
}
setset2;
for (int i = 100; i < 110; i++)
set2.insert(i);
cout << "set2中5出现的次数为:";
cout << set2.count(5) << endl;
set2.clear();
cout << "set2清除之后的大小为:";
cout << set2.size() << endl;
system("pause");
return 0;
}
set可以直接通过insert()
方法添加数据,而数据内部是自动排序的,所以不用担心数据的顺序问题,当然也可以像map那样,通过迭代器添加到指定位置,查询set中有无该数据可以直接使用count()
方法,有则返回1
,0
施工ing