目录
3.2.2 foreach循环(简洁易用,不容易发生错误,适用于数组、集合遍历循环未知的情况下是不能进行增删操作的)
1.STL标准模板库
先上图,对上述六部分内容使用上注意点细节进行详解。
2.容器
2.1 string容器
string在我们C语言当中,char *表示指向字符数组地址的指针。使用string类完全不用考虑内存分配和释放,也不用担心约界崩溃。使用注意以下几点:
- 可以通过下表和at函数访问,使用at访问不需当心数组越界
- str.c_str()函数使用问题
可以查看以下源码:
pointer
_M_data() const
{ return _M_dataplus._M_p; }
既然是一个地址值,就一定是一个常量。所以在delete可以使得指针有所指。
我在实现此函数的时候是这样定义的char* Mystring::c_str(),如果不写引用,该函数将返回一个副本,期间不会涉及到对内容的修改,所以原指针不会同步状态给副本,会导致释放失败。
- 行为区别
size | length | capacity | reserve(来自algorithm头文件) | |
概念 | 返回的字符串个数 | 返回字符串长度 | 系统为字符串分配所在空间大小(容器存储数据个数) | 重设分配空间大小 |
二者没有区别,可以看一下源码 length源码: size源码: | 分配空间原则(不同编译器可能不同,g++测试) 当数据<15,系统开辟内存空间大小为15 当内存不够的时候,会按照原来系统的大小整数陪递增 15--30--60--120..... 注意:Visual Studio和VC分配方式可能不同 | reserve修改容量不能变小,只能变大。 比如原先容量15,reserve(16), 则容量变为30, reserve(1) 则容量仍为30 |
2.2 数组(vector array valarray)
内存的分配原理跟string是一样的,是连续的空间,如果空间不够用,会申请一个更大的连续的空间,同时迭代器失效
vector | ||
头文件 | #include <vector> using namespace std; | |
语法格式 | vector<int> vec; <>可以放基本数据类型、结构体、指针、对象 | |
构造函数 | vector(); | 无参数的构造 |
vector(size_type _Count); | n个元素 | |
vector( size_type num, const TYPE &val ); | 用num个val来初始化容器 | |
vector( const vector &from ); | 拷贝构造 | |
vector( input_iterator start, input_iterator end ); | 迭代器初始化 | |
定义vector的迭代器 | vector<int>::iterator ite; | begin() 指向头 end() 指向尾元素的下一个元素 |
空间属性 | empty() | 对象是否有元素 |
size() | 元素个数 | |
resize() | 重新设置元素个数 缩小时大小改变 容量不变, 放大时大小改变 容量改变 | |
reserve() | 修改容量,不能变小只能变大。想修改多少就修改多少,不像是string变量必须按照整数倍来增加 | |
capacity() | 按照初始化空间大小的整数倍增加。(比如初始化空间大小为5,当空间占用满再插入新的数据,空间容量会加倍) | |
查寻 注:相对于list、map来讲查询速度快(因为可以通过下标访问) | 输出全部元素 | 循环(迭代器、下表运算)、for_each |
输出单个元素: | at、[]下标运算、back(返回尾巴元素) 使用at的好处:检测下表是否越界,并做相应的处理 | |
增加 注:由于是数组,尾添加效率非常高(不考虑重新增加空间) 中间添加的效率很低(因为插入该元素后,后边的元素都要以此重新排列) | void push_back( const TYPE &val ); | 尾添加 |
iterator insert( iterator loc, const TYPE &val ); | 在指定迭代器的位置加入一个数据 | |
void insert( iterator loc, size_type num, const TYPE &val ); | 在某个迭代器后加入num个值为value的元素 | |
void insert( iterator loc, input_iterator start, input_iterator end ); | 在某个迭代器后加入另一个向量的中间一段 | |
删除 | void pop_back(); | 尾删除 |
iterator erase( iterator loc ); | 删除一个指定 | |
iterator erase( iterator start, iterator end ) | 删除一段指定 | |
void clear(); | 删除所有 | |
修改 | 利用输出的形式可以修改 | |
赋值函数(重新赋值,清除以前的) | ||
运算符重载 | v1 == v2 v1 != v2 v1 <= v2 v1 >= v2 v1 < v2 v1 > v2 | 所有相同位置的元素相等,比较的形式跟字符串一样 |
遍历(下面细说) | ||
排序 | ||
乱序 |
2.3 链表(list forward_list)
list是双向链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。
头文件 | #include <list> using namespace std; | |
语法格式 | list<int> lis; <>可以放基本数据类型、结构体、指针、对象 | 双向循环链表 |
构造函数 | list() | 无参数的构造 |
list( size_type _Count); | 多个元素,初始值为0 | |
list( size_type _Count, const Type& _Val); | 多个指定的值 | |
list( const _list& _Right); | 拷贝构造(用一个list初始化当前的list) | |
list( InputIterator _First, InputIterator _Last ); | 用另一个对象中间的一段对该链表初始化 | |
定义vector的迭代器 | list<int>::iterator ite; | begin() 指向头 end() 指向尾元素的下一个元素,不能进行加法运算,可以自加++ |
空间属性 | empty() | 对象是否有元素 |
size() | 元素个数 | |
resize() | 重新设置元素个数 缩小时大小改变 容量不变, 放大时大小改变 容量改变 | |
没有reserve(),因为是链表 | ||
没有capacity(),因为是链表 | ||
查寻 注:相对于数组来讲查询速度慢(因为需要遍历查询) | 输出全部元素 | 循环(迭代器、下表运算)、for_each |
输出单个元素: | 1.[]下标运算、2.back(返回尾巴元素) 3.front()(返回第一个元素) | |
增加 注:插入删除速度快,只需要修改前一个节点和后一个节点指向 | void push_front(const TYPE &val ) | 头添加 |
void push_back( const TYPE &val ); | 尾添加 | |
iterator insert( iterator loc, const TYPE &val ); | 在指定迭代器的位置加入一个数据 | |
void insert( iterator loc, size_type num, const TYPE &val ); | 在某个迭代器后加入num个值为value的元素 | |
void insert( iterator loc, input_iterator start, input_iterator end ); | 在某个迭代器后加入另一个向量的中间一段 | |
删除 | void pop_front() | 头删除 |
void pop_back(); | 尾删除 | |
iterator erase( iterator loc ); | 删除一个指定 | |
iterator erase( iterator start, iterator end ) | 删除一段指定 | |
void clear(); | 删除所有 | |
void remove( const TYPE &val ); | 删除所有跟参数相同的元素。如果是结构体需要重载== | |
void unique(void) | 删除list中重复的元素 | |
修改 | 利用迭代器修改 | |
赋值函数assign | ||
赋值=修改 | ||
运算符重载 | v1 == v2 v1 != v2 v1 <= v2 v1 >= v2 v1 < v2 v1 > v2 | 所有相同位置的元素相等,比较的形式跟字符串一样 |
void swap( list &from ); | 交换两个list的内容 | |
void reverse(); | reverse() 把list的元素翻转 | |
void sort(); | 默认从小到大排序,如果容器本身自带排序,那么使用的时候就可以不用选择排序算法 | |
void merge( list &lst ); | 合并两个list。 特点: 1.自动排序 2.重载小于号 3.两个链表必须有序(如果链表元素是升序的,就要用<号,return true,反之) | |
void splice( iterator pos, list &lst ); void splice( iterator pos, list &lst, iterator del ); void splice( iterator pos, list &lst, iterator start, iterator end ); | 拼接 |
forward_list是一个单向链表,只支持单向顺序访问,在链表的任何位置进行插入/删除操作都非常快(为了提高效率没有添加size函数)
一般通常使用中,对效率没有非常细微要求都可以。
,注意以下两点:
- forward_list不提供size()成员函数。
- forward_list没有指向最末元素,因此不提供back()、push_back()和pop_back()。
2.4 双端队列deque
deque属于段连续空间,
deque | vector | list | |
空间结构 | 段连续空间 | 连续空间 | 不连续空间 |
功能比较 | 1.随机位置插入/删除效率不高(数据量512) 2.支持随机访问[比vector慢,因为要做堆跳转(迭代器结构复杂,会降低访问效率) 3.支持头添加,支持尾添加 | 1.随机位置插入/删除效率低 2.随机访问效率高(下标运算) 3.不支持头添加,支持尾添加 | 1.随机位置插入/删除效率高 2.不支持随机访问 支持头添3.加,支持尾添加 |
使用选择 | 随机访问+头添加,就选deque,支持随机访问,即支持[]以及at(),但是性能没有vector好,可以在内部进行插入和删除操作,但性能不及list | 随机访问操作频率高,就选vector | 插入删除频率高,头尾添加,就选list |
行为对比 | 没有capacity和reserve | 有capacity和reserve | 没有capacity和reserve |
2.5 map
map属于一种关联容器,它保存的是一个键值,通过键值来排序,通过键值来查找访问。Map使用的是平衡二叉树结构
头文件 | #include <map> using namespace std; | |
语法格式 | map<xx , xx>mp <>可以放基本数据类型、结构体、指针、对象 | 键值对,有序,平衡二叉树 |
构造函数 | map() | 无参数的构造 |
map(const _map& _Right); | 一个参数 | |
map(InputIterator _First, InputIterator _Last); | 另一个对象的一段 | |
定义map的迭代器 | map<int,char>::iterator ite; | begin() 指向头 end() 指向尾元素的下一个元素,不能进行加法运算,可以自加++ first指向键值 second指向实值 |
空间属性 | empty() | 对象是否有元素 |
size() | 元素个数 | |
resize() | 重新设置元素个数 缩小时大小改变 容量不变, 放大时大小改变 容量改变 | |
count(const Key& _Key) | 得到某一个元素数量,或者说判断一个键值是否存在 | |
没有capacity(),reserve(),因为是链表 | ||
查寻 注:相对于数组来讲查询速度慢(因为需要遍历查询) | 输出全部元素 | for(ite; ite != mp.end(); ite++) { cout<<ite->first<<ite->second<<endl; } |
输出单个元素: | cout<<ite->first<<ite->second<<endl; | |
iterator find( const Key& _Key); | 有就返回所在节点迭代器,没找到则无法输出,崩溃 | |
增加 注:插入效率低于链表,因为涉及到排序 | pair <iterator, bool> insert( const TYPE &val ); | pair<键,值>,键值不能重复,实值可以重复。输出first和second |
insert( iterator pos, const TYPE &val ); | 迭代器可以++,但是不可以+4 | |
void insert( iterator loc, input_iterator start, input_iterator end ); | 在某个迭代器后加入另一个向量的中间一段 | |
删除 | iterator erase( iterator _Where); | 迭代器指定的删除 |
iterator erase(iterator _First,iterator _Last); | 迭代器指定的段删除 | |
size_type erase( const key_type& _Key); | 根据键值删除 | |
void clear(); | 删除所有 | |
void remove( const TYPE &val ); | 删除所有跟参数相同的元素。如果是结构体需要重载== | |
void unique(void) | 删除list中重复的元素 | |
修改(键值不能改,实值可以改) | 利用迭代器修改 | |
赋值函数assign | ||
赋值=修改 | ||
交换 | swap() | |
lower_bound(key) | 返回参数key位置,该位置的键值不小于key : key<=pos | |
upper_bound(key) | 返回位置,该位置的键值>key : key < pos | |
equal_range(key) | 返回这个区间 | |
无序容器(散列表) | unorder_map\unorder_multimap\unorder_set\unorder_multiset | |
hash_map | 哈希表 头文件<hash_map> |
3.几种简单算法
包含头文件#include <algorithm>
3.1排序
3.1.1 从小到大
template<class RandomAccessIterator>
void sort(RandomAccessIterator _First, RandomAccessIterator _Last );
示例:
void fun(int c)
{
cout<< c <<endl;
}
void vector_sort()
{
vector<int>vec(5);
vec.at(0) = 5;
vec.at(1) = 1;
vec.at(2) = 2;
vec.at(3) = 7;
vec.at(4) = 4;
sort(vec.begin(), vec.end()); //从小到大
for_each(vec.begin(), vec.end(), fun);
}
3.1.2 从大到小
template<class RandomAccessIterator, class Pr>
void sort( RandomAccessIterator _First, RandomAccessIterator _Last, BinaryPredicate _Comp);
其中参数三 greater<>() 可以指定从大到小
示例:
void fun(int c)
{
cout << c <<endl;
}
void vector_sort()
{
vector<int>vec(5);
vec.at(0) = 5;
vec.at(1) = 1;
vec.at(2) = 2;
vec.at(3) = 7;
vec.at(4) = 4;
sort(vec.begin(), vec.end(), greater<int>()); //从大到小
for_each(vec.begin(), vec.end(), fun);
}
3.1.3 随机排序
void random_shuffle(RandomAccessIterator _First, RandomAccessIterator _Last );
示例:
void vector_random_shuffle()
{
vector<int>vec(5);
vec.at(0) = 5;
vec.at(1) = 1;
vec.at(2) = 2;
vec.at(3) = 7;
vec.at(4) = 4;
sort(vec.begin(), vec.end()); //从小到大
for_each(vec.begin(), vec.end(), fun);
srand(time(0));
random_shuffle(vec.begin(), vec.end()); //利用随机数发生器,涉及到srand-随机种子
for_each(vec.begin(), vec.end(), fun);
}
3.2 遍历(三种for循环)
3.2.1 常规for循环( 在复杂的逻辑中效率高)
void look_vector_output_all()
{
vector<int>vec(5,10);
vec.at(4) = 15;
vector<int>::iterator ite = vec.begin();
vector<int>::iterator ite1 = vec.end();
for(ite; ite != vec.end(); ite++)
{
cout<< *ite<<endl;
}
}
3.2.2 简洁fore循环(简洁易用,不容易发生错误,适用于数组、集合遍历循环未知的情况下是不能进行增删操作的)
void look_vector_output_all()
{
vector<int>vec(5,10);
vec.at(4) = 15;
vector<int>::iterator ite = vec.begin();
vector<int>::iterator ite1 = vec.end();
for(auto vec_temp : vec)
{
cout<<vec_temp<<endl;
}
}
3.2.3 for_each循环
- foreach循环的非简化版,在C++11之前是没有auto的。
- for_each的重载形式支持多线程优化
- for_each更适合和c++20中的range搭配
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用 for_each 算法结合 lambda 表达式遍历容器中的元素
std::for_each(numbers.begin(), numbers.end(), [](int number){
std::cout << number << " ";
});
return 0;
}
4.迭代器
迭代器相当于char *,使用迭代器更重要的一点原因是与算法相结合,并且适用于所有容器
常规用法 :
- string:string::ierator ite;
- vector:vector<int>::iterator ite;
- list:list<Node>::iterator ite;
- for (ite = str.begin();ite != str.end(); ite++)
{
cout << *ite;
}
注意:end()是末尾元的指向的下一个元素(NULL)
迭代器失效:
原因:迭代器在创建完毕对象后指向对象首地址,当对象发生改变后,一定是重新申请空间。但此时迭代器指向没有发生改变,会导致迭代器失效。 因此如果对象发生了内存变化,一定要重新调用一遍迭代器。
容器类型 | 行为 | 迭代器是否失效 |
以string为例 | str.append():数据追加到原字符串尾部,当初始化空间不够的时候,会重新开辟新的空间将所有数据搬运到新空间 | 失效 |
str.insert():数据插入到原字符串指定位置,当初始化空间不够的时候,会重新开辟新的空间将所有数据搬运到新空间 | 失效 | |
str.erase(): 删除数据,但是初始地址不会改变 | 不失效 | |
str.assign():重新赋值,初始地址不会改变 | 不失效 | |
如果是数组,string等由空间容量的容器,当空间容量满的时候系统会重新分配空间,此时迭代器会失效。 如果是链表、Map,不存在空间容量之说,迭代器不会失效 |
5.适配器
6.分配器
7.仿函数
后续更新