本专栏目的
- 更新C/C++的基础语法,包括C++的一些新特性
前言
- STL无疑是C++史上一个重要的发明,未来我将更新STL有关的知识点,入门绝对够了(看目录就知道了👀)
- 这是第一篇,讲STL、迭代器
- C语言后面也会继续更新知识点,如内联汇编;
- 欢迎收藏 + 关注,本人将会持续更新。
文章目录
- STL基本概念
- 容器
- 算法
- 迭代器
- 迭代器
- 数组范围指针
- 自定义容器(练习,理解迭代器)
- MyArray
- 另一种遍历方式
- 修改版一种遍历方式
- 迭代器类
STL基本概念
STL
:标准模板类库
- 容器篇: 封装好的数据结构 用来存储或者操作数据
- 迭代器篇:遍历容器的(内置类)
- 仿函数: 排序准则,比较准则
- 算法篇: 特殊操作的容器(排序,查找…)
STL
需要掌握的内容
- 学会创建对象
- 学会访问数据
- 学会去看标准库的类以及类中的一些成员函数
STL
六大件:
-
容器(Container)
-
算法(Algorithm)
-
迭代器(iterator)
-
仿函数(Function object)
-
适配器(Adaptor)
-
空间配制器(allocator)
在C++标准中,STL被组织为下面的13个头文 件:<algorithm>、<deque>、<functional>、<iter>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack> 和<utility>。
容器
概念:封装好的数据结构
在STL
中,容器主要分为序列式容器和关联式容器
序列式容器:即以线性排列(类似普通数组的存储方式)来存储某一指定类型(例如 int、double 等)的数据,该类容器并不会自动对存储的元素按照值的大小进行排序。
关联式容器:非线性排列,在存储元素时会为每个元素在配备一个键,整体以键值对的方式存储到容器中,可以通过键值直接找到对应的元素,而无需遍历整个容器,另外,关联式容器在存储元素,默认会根据各元素键值的大小做升序排序。
算法
STL
算法分为:质变算法和非质变算法。
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
迭代器
迭代器:是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物。
作用:连接算法与容器,在STL
中,实现了算法和容器相分离的特点,迭代像一个桥梁,连通了算法与容器。
迭代器的种类:
种类 | 功能 | 操作 |
---|---|---|
输入迭代器 | 提供对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 提供对数据的读写访问 | 读写,支持++ |
前向迭代器 | 提供读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 提供读写操作,并能向前和向后操作 | 读写,支持++、–, |
随机访问迭代器 | 提供读写操作,并能以跳跃的方式访问容器的任意数据,是功能最强的迭代器 | 读写,支持++、–、[n]、-n、<、<=、>、>= |
迭代器
简而言之,迭代器的作用是用来访问容器中的元素的, 可以理解为指针。
数组范围指针
以数组遍历为例:
//容器
int arr[5] = { 1,2,3,4,5 };
- 下标法遍历
for (int i = 0; i < 5; i++)
{cout << arr[i] << " ";
}
- 基于范围的for循环
for(auto& v : arr)
{cout << v << " ";
}
☑️ 提问 为啥能使用基于范围的for循环遍历数组呢?
因为只要知道了数组的开始和结束就能去遍历了,如下:
- 指针法遍历
int* iter = arr;
for (iter = arr; iter != arr + 5; iter++)
{cout << *iter << " ";
}
- 迭代器方式
using iterator = int*;
iterator begin = arr;
iterator end = arr + 5;
for (iterator it = begin; it != end; it++)
{cout << *it << " ";
}
自定义容器(练习,理解迭代器)
MyArray
🏚 我们先自己写一个MyArray容器。(满足基于类型的for循环,则支持一点简单的迭代器)
template<typename _T, size_t _size>
class MyArray
{
public:MyArray(std::initializer_list<_T> arr){int i = 0;for (auto v : arr) {m_array[i++] = v;}}_T& operator[](size_t index) {return m_array[index];}private:_T m_array[_size]{ _T() };
};
在上面的代码中,我们提供了size()和[]运算符重载,所以可以通过下标去遍历SArray。
MyArray<int, 10> array = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };for (int i = 0; i < 10; i++) {std::cout << array[i] << " ";
}
std::cout << std::endl;
另一种遍历方式
那么对于SArray来说,能使用基于范围的for循环吗?
- 基于范围的for循环是方便访问容器。基于范围的for循环又需要begin和end函数才能工作
for (auto& v : nums)
{cout << v << " ";
}
报错了!
它说,没有找到begin和end函数,有由于基于范围的for循环又需要begin和end函数才能工作.
那么begin和end函数是什么呢?
其实就是数组开始的指针和结束的指针,🎃 解决方法: 加上begin
和end
函数,如下:
// : 本质,有begin和end地址[ )
_T* begin() {return m_array;
}
_T* end() {return m_array + _size;
}
修改版一种遍历方式
这一种方式,就是讲上面的函数进行修改,修改如下:
using iterator = _T*;
iterator begin() {return m_array;
}
iterator end() {return m_array + _size;
}
这个时候,就可以这样遍历了,如下:
for (MyArray<int, 10>::iterator it = array.begin(); it != array.end(); it++) {std::cout << *it << " ";
}
std::cout << "\n";
这种方法看起来有点复杂,==但是,==这个就是迭代器(iterator),看着很复杂,但是随着后面学习,就会发现这样好处也有很多,最明显的一点就是:获取元素指针方便了。
其他: 上面说到,迭代器有很多种,接下来我们来看一下这个案例:
// 加const修饰
const MyArray<int, 10> array = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };for (auto v : array) {std::cout << v << " ";
}
std::cout << std::endl;
这个时候,有出问题了,如下:
👀 解释: 找不到对应的begin
和end
,因为这个对象用了const
修饰,那我们就写一版本的带const
的begin
和end
,如下:
using const_iterator = const _T*;
const_iterator begin() const {return m_array;
}
const_iterator end()const {return m_array + _size;
}
迭代器有很多不同的种类,不同的种类对应着不同的应用场景,需要多用、多写,熟悉之后可以参考:侯捷老师STL源码讲解。
迭代器类
在C++STL中,基本上所有迭代器都是通过类来实现的,这样就非常方便的去处理遍历操作了,这里写一个含迭代器类的链表(参考某一位大神):
#include <iostream>template<typename T>
class _SForwardList_iterator;template<typename T>
struct Node
{Node(const T& v) :data(v), next{ nullptr } {}T data;Node* next;
};template<typename T>
class SForwardList
{
public:SForwardList() {}SForwardList(const std::initializer_list<T>& list){for (auto& v : list){push_back(v);}}~SForwardList(){Node<T>* curNode = _head;Node<T>* delNode = nullptr;while (curNode){delNode = curNode;curNode = curNode->next;delete[] delNode;}}void push_back(const T& value){Node<T>* node = new Node<T>(value);if (!_head){_head = node;_tail = node;}else{_tail->next = node;_tail = node;}}friend std::ostream& operator<<(std::ostream& out, const SForwardList& list){Node<T>* curNode = list._head;out << "SForwardList(";while (curNode){out << curNode->data;if (curNode->next)out << ",";curNode = curNode->next;}out << ")";return out;}using iterator = _SForwardList_iterator<T>;iterator begin() { return iterator(_head); }iterator end() { return iterator(nullptr); }private:Node<T>* _head{ nullptr };Node<T>* _tail{ nullptr };
};template<typename T>
class _SForwardList_iterator {
public:_SForwardList_iterator() : _ptr(nullptr) {}explicit _SForwardList_iterator(Node<T>* ptr) : _ptr(ptr) {}bool operator!=(const _SForwardList_iterator& it) const {return _ptr != it._ptr;}_SForwardList_iterator& operator++() {if (_ptr) _ptr = _ptr->next;return *this;}T& operator*() const {return _ptr->data;}private:Node<T>* _ptr;
};int main()
{SForwardList<int> list = { 1,2,3,4,5,6,7,8 };for (auto v : list) {std::cout << v << " ";}return 0;
}