目录
一、STL的容器类介绍
1、何为容器
(1)顾名思义,容器就是盛放东西的东西,这里被盛放的一般是数据对象,用来盛放的是容器类
(2)计算机中一切皆是数据,数据存储只能在内存中,而容器类是用做容器的内存的管理方法
(3)容器类的内核就是:数据结构 (数据在内存中的排放、存储方法)+ 算法(数据的运算、处理,如排序)
数据结构 -> 类的成员变量
算法 ->类的成员方法
(4)C语言语法内置的数组和结构体,就是语言源生支持的容器(但并未提供相应的算法,不过通过库函数补充了部分算法)
(5)C++容器通过类库方式提供,容器类库被模板技术泛化后,就是STL容器了。STL一般指标准模板库。 标准模板库(Standard Template Library,STL)
2、STL有哪些容器类
(1)序列容器。
元素在容器中的位置同元素的值无关,即容器不是排序的(类似于C数组)。包括array、vector、deque、list、forward_list等几个。
(2)排序容器。
数据插入时即自动按照值从小到大排列好。包括set、multiset、map、mutilmap等。
(3)哈希容器。
哈希容器中的元素是未排序的,元素的位置由哈希函数确定,即遵守一定规则的<key,value>对式存储。包括unordered_set、unordered_map、hash_set、hash_multiset、hash_map、hash_multimap等
3、容器类如何学习
(1)容器类就是STL的核心,STL其他技术点都围绕容器类开展
(2)可见STL的本质其实就是一套模板技术泛化类型的C++基本数据结构和算法类库
(3)本部分文章会集中细致讲几个STL序列容器array、vector等,其他容器类似情况就简略讲过了
(4)第一层为学会使用stl容器,第二层为能合理使用stl容器,第三层为理解stl背后设计,第四层为自己能写新的stl容器。
二、容器类array的初步使用
1、array的特性
(1)array是定长、同类型多元素、内存中连续排布的一种容器
(2)array其实就是C语言数组的C++ template封装,定义于头文件array
2、array的学习方法
(1)参考文档:https://zh.cppreference.com/w/cpp/container/array
(2)挨个理解文档相关所有元素
隐式声明与显示声明:https://www.cnblogs.com/snandy/archive/2011/03/04/1970777.html
3、array的构造和初始化
(1)和C数组兼容的初始化方式
(2)需要C++11或以上标准来支持
linux中使用g++编译时需加上:-std=c++11选项
低版本的ubuntu需要,高版本的ubuntu安装的g++并不需要
(3)支持默认的拷贝构造
#include <iostream>
#include <array>
using namespace std;
int main(void)
{
array<int, 3> a1; // 定义了但是未初始化,猜测值应该是随机的
// array<int ,3> a2(1, 3, 5); //错误,因为C++无法提供无数个构造函数来匹配
array<int, 3> a2{1, 3, 5}; // 可以,属于聚合初始化
array<int, 3> a3 = {1, 3, 5}; // 初始化赋值,和a2效果一样
array<int, 3> a4 = a3; // 拷贝构造
return 0;
}
4、array的元素访问
(1)at方法
(2)operator[]实现的C数组式访问
(3)front和back方法返回第1个和最后1个元素
(4)data返回真实存储内存中首元素首地址的值
如果array的元素访问越界了,那么编译时没问题,但是运行时会抛出异常
#include <iostream>
#include <array>
using namespace std;
int main(int argc, char *argv[])
{
array<int, 10> a;
array<int, 3> a1{1, 2, 3};
array<int, 4> a2 = {6, 7, 8, 9}, a3;
array<int, 0> a4;
a3 = a2;
a[0] = 4;
a.operator[](1) = 5;
a.at(2) = 6;
int *p = a.data();
*(p+3) = 7;
a.back() = 8;
cout << "a[0] = " << a[0] << endl;
cout << "a.operator[](1) = " << a[1] << endl;
cout << "a.at(2) = " << a[2] << endl;
cout << "*(p+3) = " << a[3] << endl;
cout << "a.back() = " << a[9] << endl;
cout << "a.front() = " << a[0] << endl;
cout << endl;
cout << "a.empty() = " << a.empty() << endl;
cout << "a.size() = " << a.size() << endl;
cout << "a.max_size() = "<< a.max_size() << endl;
cout << endl;
cout << "a1.empty() = " << a1.empty() << endl;
cout << "a1.size() = " << a1.size() << endl;
cout << "a1.max_size() = "<< a1.max_size() << endl;
cout << endl;
cout << "a4.empty() = " << a4.empty() << endl;
cout << "a4.size() = " << a4.size() << endl;
cout << "a4.max_size() = "<< a4.max_size() << endl;
return 0;
}
5、array的容量设置和获取
(1)容量设置只能在定义时一次设定,且必须设定,设定后再不能改
(2)empty:若容器为空则为 true ,否则为 false
(3)size:容器中的元素数量。
(4)max_size:返回根据系统或库实现限制的容器可保有的元素最大数量
6、操作
(1)fill:以指定值填充容器
(2)swap:交换内容
#include <iostream>
#include <array>
using namespace std;
int main(int argc, char *argv[])
{
array<int, 3> a;
array<int, 3> b = {1, 2, 3};
a.fill(6);
cout << "a[0] = " << a[0] << endl;
cout << "a[1] = " << a[1] << endl;
cout << "a[2] = " << a[2] << endl;
cout << endl;
a.swap(b);
cout << "a[0] = " << get<0>(a) << endl;
cout << "a[1] = " << get<1>(a) << endl;
cout << "a[2] = " << get<2>(a) << endl;
cout << endl;
cout << "b[0] = " << get<0, int, 3>(b) << endl;
cout << "b[1] = " << get<1, int, 3>(b) << endl;
cout << "b[2] = " << get<2, int, 3>(b) << endl;
std::swap<int, 3>(a, b);
cout << "b[0] = " << get<0, int, 3>(b) << endl;
cout << "b[1] = " << get<1, int, 3>(b) << endl;
cout << "b[2] = " << get<2, int, 3>(b) << endl;
return 0;
}
7、非成员函数
(1)operator重载函数
(2)get
(3)swap
(4)to_array (C++20 起),低版本编译器并不支持
#include <iostream>
#include <array>
int main(int argc, char *argv[])
{
std::array<int, 3> alice{1, 2, 3};
std::array<int, 3> bob{7, 8, 9};
std::array<int, 3> eve{1, 2, 3};
std::cout << std::boolalpha;
// 比较不相等的容器
std::cout << "alice == bob returns " << (alice == bob) << '\n';
std::cout << "alice != bob returns " << (alice != bob) << '\n';
std::cout << "alice < bob returns " << (alice < bob) << '\n';
std::cout << "alice <= bob returns " << (alice <= bob) << '\n';
std::cout << "alice > bob returns " << (alice > bob) << '\n';
std::cout << "alice >= bob returns " << (alice >= bob) << '\n';
std::cout << '\n';
// 比较相等的容器
std::cout << "alice == eve returns " << (alice == eve) << '\n';
std::cout << "alice != eve returns " << (alice != eve) << '\n';
std::cout << "alice < eve returns " << (alice < eve) << '\n';
std::cout << "alice <= eve returns " << (alice <= eve) << '\n';
std::cout << "alice > eve returns " << (alice > eve) << '\n';
std::cout << "alice >= eve returns " << (alice >= eve) << '\n';
return 0;
}
8、辅助类tuple_size、tuple_element
tuple_size提供作为编译时常量表达式访问 std::array 中元素数量的方法。
tuple_element使用类 tuple 接口,提供 array 元素类型的编译时带下标访问
#include <iostream>
#include <array>
using namespace std;
template <typename T>
void test(T t)
{
int a[tuple_size<T>::value];// 能用于编译时
cout << tuple_size<T>::value << endl;
}
int main(int argc, char *argv[])
{
array<int, 3> a = {1, 2, 3};
array<string, 4> b = {"one", "two", "three", "four"};
定义 a 并获取位于位置 0 的元素类型,类型为int
using T = tuple_element<0, decltype(a)>::type; //这里的using作用类似于C中的typedef,给数据类型起别名
T c = 6;
cout << is_same<T, int>::value << '\n';//判断T是否为int,是否都是int类型
test(a);
test(b);
return 0;
}
三、迭代器的引入
1、迭代器是干嘛的
(1)迭代器就是能 通过移动 来 遍历处理 的 一种机制
(2)C语言中遍历数组元素,用指针*p++方式,指针变量就是遍历迭代器
/*
for (p=第1个元素; p!=最后一个元素; p++)
{
*p就是每一个元素
}
*/
(3)思考:结构体元素能够通过指针运算来遍历?
因为结构体中元素的类型一般是不同的,无法得知访问下一个元素,地址的偏移量是多少
2、关于迭代器的分析
(1)每种容器理论上都可以被遍历,不存在不能被遍历的容器
(2)每种容器的遍历实现都可能不同,要结合容器和元素的特点来具体实现
(3)迭代器内部原理肯定是通过指针操作(地址运算)来实现
(4)迭代器就是C++为我们设计的一个高层次的“指针”,高层指针是面向容器中的元素的,实现对容器中元素的遍历。其工作时最终会映射到底层指针(即我们C语言中使用指针的方法)
3、C++实际是这么设计迭代器的
(1)所有的迭代器有一个共同基类(接口),规定了迭代器的基本行为规范接口
(2)每个容器类中均包含了一个专属化迭代器成员变量,这个专属化迭代器专门针对该容器的特点实现了迭代器应该有的所有接口
(3)需要遍历某STL容器时,只需要直接调出该容器的这个迭代器成员变量直接用即可,固定名字为iterator
4、典型的迭代器用法
(1)代码实战,用迭代器来实现遍历array
#include <iostream>
#include <array>
using namespace std;
int main(int argc, char *argv[])
{
array<int, 3> a = {1, 2, 3};
array<int, 3>::iterator iter;
for(iter = a.begin(); iter != a.end(); iter++)
{
cout << "iter = " << iter << endl;
cout << "*iter = " << *iter << endl;
}
return 0;
}
(2)begin()和end()方法是得到容器遍历的开始和结尾的套路化方法
注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。