目录
前言 STL简介
1. 什么是STL
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架
2. STL的版本
原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本--所有STL实现版本的始祖。
P. J. 版本
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
RW版本
由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
SGI版本
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。3. STL的六大组件
4. STL的重要性
STL在将来面试、笔试和工作的时候都非常重要,网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。
5. 如何学习STL
6. STL的缺陷
1. STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出来已经相隔了13年,STL才进一步更新。
2. STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
3. STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
4. STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语法本身导致的。
一、vector的介绍及使用
注意:作者能力有限,无法根据英文文档翻译出较为准确的中文意思,vector具体相关知识参考网站 cplusplus.com/reference/vector/vector/
1.1 vector的介绍
1.1.1 认识vector
vector是表示大小可变的数组的序列容器
1. 与数组类似,vector也采用的连续存储空间来存储元素,可以采用下标对vector的元素进行访问,但是它的大小是可以动态改变的,而且它的大小会被容器自动处理
2 本质上,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小以此来增加存储空间
3 vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大(不同的库采用不同的策略权衡空间的使用和重新分配)
4 与其它动态序列容器相比(deque、 list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好1.1.2 成员类型
1.1.3 成员函数一览
vector的许多成员函数都与string类似(左边一栏为函数名,右边一栏为简要作用的描述)
1.1.4 非成员函数重载
1.2 vector的使用
注意:一些常见的、通用的不再赘述(例如:size()求大小、capacity()求容量、~vector()析构、push_back()尾插 等不作详细介绍,主要介绍接口有些复杂但是比较常用的一些函数)
1.2.1 构造、析构与赋值操作符重载
接口展示:
void test_vector1() { // 构造 Construct vector vector<int> v1; vector<int> v2(10, 0); vector<int> v3(v2.begin(), v2.end()); string str("hello world"); vector<int> v4(str.begin(), str.end()); vector<int> v5(v4); // 遍历 operator[] 迭代器iterator 循环for(size_t) 范围for(auto) for (size_t i = 0; i < v3.size(); i++) { cout << v3[i] << " "; } cout << endl; vector<int>::iterator it = v4.begin(); while (it != v4.end()) { cout << *it << " "; it++; } cout << endl; for (auto e : v5) { cout << e << " "; } cout << endl; // 析构 // ~vector() // 赋值 vector<int> v7(7, 0); vector<int> v8(8, 0); v8 = v7; v7 = vector<int>(); cout << "Size of v7: " << int(v7.size()) << endl; cout << "Size of v8: " << int(v8.size()) << endl; } output: 0 0 0 0 0 0 0 0 0 0 104 101 108 108 111 32 119 111 114 108 100 104 101 108 108 111 32 119 111 114 108 100 Size of v7: 0 Size of v8: 7
1.2.2 reserve 与 resize
接口展示:
void test_vector2() { size_t sz; vector<int> v; // v.reserve(100); // v.resize(100); sz = v.capacity(); cout << "making v grow:\n"; for (int i = 0; i < 100; ++i) { v.push_back(i); if (sz != v.capacity()) { sz = v.capacity(); cout << "capacity changed: " << sz << '\n'; } } } void test_vector3() { vector<int> v1; cout << v1.max_size() << endl; vector<int> v; v.reserve(100); // size = 0 capacity 100 v.resize(100); // size = 100 capacity 100 for (int i = 0; i < 100; i++) { v[i] = i; } for (auto e : v) { cout << e << " "; } cout << endl; } output: making v grow: capacity changed: 1 capacity changed: 2 capacity changed: 3 capacity changed: 4 capacity changed: 6 capacity changed: 9 capacity changed: 13 capacity changed: 19 capacity changed: 28 capacity changed: 42 capacity changed: 63 capacity changed: 94 capacity changed: 141 1073741823 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
1.2.3 insert、erase 与 find
接口展示:
void test_vector4() { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); // insert for (auto e : v) { cout << e << " "; } cout << endl; v.insert(v.begin(), 0); for (auto e : v) { cout << e << " "; } cout << endl; // find auto it = find(v.begin(), v.end(), 3); if (it != v.end()) { v.insert(it, 30); } for (auto e : v) { cout << e << " "; } cout << endl; it = find(v.begin(), v.end(), 3); if (it != v.end()) { v.erase(it); } for (auto e : v) { cout << e << " "; } cout << endl; // clear & shrink_to_fit cout << v.size() << endl; cout << v.capacity() << endl; v.clear(); // size = 0 v.shrink_to_fit(); // size == 0 ? capacity = 0 : capacity-- cout << v.size() << endl; cout << v.capacity() << endl; } output: 1 2 3 4 0 1 2 3 4 0 1 2 30 3 4 0 1 2 30 4 5 6 0 0
extra train
1. 二叉树的前序遍历
// 144.二叉树的前序遍历 class Solution { public: void preorder(TreeNode* root, vector<int>& v) { if(root == nullptr) return; v.push_back(root->val); preorder(root->left, v); preorder(root->right, v); } vector<int> preorderTraversal(TreeNode* root) { vector<int> vec; preorder(root, vec); return vec; } };
2. 只出现一次的数字
//136. 只出现一次的数字 class Solution { public: int singleNumber(vector<int>& nums) { int val = 0; for (auto e : nums) { val ^= e; } return val; } };
3. 杨辉三角
//118. 杨辉三角 class Solution { public: vector<vector<int>> generate(int numRows) { // 二维数组 vector<vector<int>> vv; vv.resize(numRows); for(size_t i = 0; i < vv.size(); i++) { vv[i].resize(i+1, 0); vv[i][0] = vv[i][vv[i].size()-1] = 1; } for(size_t i = 0; i < vv.size(); i++) { for(size_t j = 0; j < vv[i].size(); j++) { if(vv[i][j] == 0) { vv[i][j] = vv[i-1][j] + vv[i-1][j-1]; } } } return vv; } };
二、vector的深度剖析及模拟实现
2.1 vector的深度剖析
2.1.1 memcpy 浅拷贝问题
void reserve(size_t n) { if (n > capacity()) { // 创建新的空间 T* tmp = new T[n]; size_t sz = size(); if (_start) { // 拷贝数据 并 删除旧的空间 memcpy(tmp, _start, sizeof(T) * n); //浅拷贝 delete[] _start; } _start = tmp; _finish = _start + sz; _endofstorage = _start + n; } }
2.2.2 iterator 迭代器失效问题
insert 迭代器失效
void insert(iterator pos, const T& x) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _endofstorage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; }
为了防止迭代器失效,需要我们记录_start与pos的相对位置,及时更新pos。具体的实现方法见<vector的模拟实现>
erase 迭代器失效
void erase(iterator pos) { assert(pos >= _start); assert(pos <= _finish); vector<int>::iterator it = pos - 1; while (it < _finish) { *(it - 1) = *it; ++it; } --_finish; } void test_vector() { // 测试三组用例 // 1 2 3 4 5 output: 1 3 5 // 1 2 3 4 5 6 output: 崩溃 // 2 2 3 4 5 output: 2 3 5 std::vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); //v.push_back(6); for (auto e : v) { cout << e << " "; }cout << endl; auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) { v.erase(it); // it = v.erase(it); } ++it; } for (auto e : v) { cout << e << " "; }cout << endl; } erase后的迭代器无法正常使用,为了防止出现问题,我们可以记录erase后的迭代器并返回接受,更新原来的迭代器。具体的实现方法见<vector的模拟实现>
2.2 vector的模拟实现
#pragma once #include <assert.h> namespace myvector { // 模板 template<class T> class vector { public: // 迭代器(反向迭代器未实现) typedef T* iterator; typedef const T* const_iterator; iterator begin() { return _start; } iterator end() { return _finish; } const_iterator begin() const { return _start; } const_iterator end() const { return _finish; } vector() // 注意:该构造函数不能删去,有其它函数需要使用它 {} template <class InputIterator> vector(InputIterator first, InputIterator last) { while (first != last) { push_back(*first); ++first; } } // size_t 类型的 n vector(size_t n, const T& val = T()) {// const T& val 的缺省参数最好还是给 T() reserve(n); // 扩容,检查都交给reserve for (size_t i = 0; i < n; i++) { push_back(val); } } // int 类型的 n(为了更好地匹配使用) vector(int n, const T& val = T()) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } } // copy构造 vector(const vector<T>& v) { reserve(v.capacity()); for (auto& e : v) { push_back(e); } } // 初始化列表 vector() :_start(nullptr) ,_finish(nullptr) ,_endofstorage(nullptr) {} // 析构 ~vector() { delete[] _start; _start = _finish = _endofstorage = nullptr; } void swap(vector<T>& v) { // “swap”前面加上“std::”确保调用库中的swap std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_endofstorage, v._endofstorage); } // 赋值操作符重载 vector<T>& operator=(vector<T> tmp) { // 赋值需要传参,传参调用copy构造 // 实际上是copy构造帮助完成了赋值 swap(tmp); return *this; } void push_back(const T& x) { /*if (_finish == _endofstorage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } *_finish = x; ++_finish;*/ // 借用 insert insert(end(), x); } // []操作符重载(可读可写) T& operator[](size_t pos) { assert(pos < size()); return _start[pos]; } // []操作符重载(只读) const T& operator[](size_t pos) const { assert(pos < size()); return _start[pos]; } // 容量 size_t capacity() const { return _endofstorage - _start; } // 大小 size_t size() const { return _finish - _start; } void reserve(size_t n) { if (n > capacity()) { // 创建新的空间 T* tmp = new T[n]; size_t sz = size(); if (_start) { // 拷贝数据 并 删除旧的空间 //memcpy(tmp, _start, sizeof(T) * n); //浅拷贝 for (size_t i = 0; i < sz; i++) { tmp[i] = _start[i]; } delete[] _start; } _start = tmp; _finish = _start + sz; _endofstorage = _start + n; } } //void resize(size_t n, T val = T()) 两种写法是一致的 void resize(size_t n, const T& val = T()) { if (n <= size()) { _finish = _start + n; } else { reserve(n); while (_finish < _start + n) { *_finish = val; _finish++; } } } // 插入 void insert(iterator pos, const T& x) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _endofstorage) { size_t len = pos - _start; // reserve扩容导致迭代器pos失效(pos指向的原来的_start已经释放了) reserve(capacity() == 0 ? 4 : capacity() * 2); // 及时更改pos pos = _start + len; } iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; } // 删除(返回值用于解决迭代器失效的问题) iterator erase(iterator pos) { assert(pos >= _start); assert(pos <= _finish); vector<int>::iterator it = pos - 1; while (it < _finish) { *(it - 1) = *it; ++it; } --_finish; return pos; } private: iterator _start; iterator _finish; iterator _endofstorage; }; }
extra train
1. 只出现一次的数字 II
137. 只出现一次的数字 II - 力扣(LeetCode)
//137. 只出现一次的数字 II // 数电解法 class Solution137I { public: int singleNumber(vector<int>& nums) { int a = 0, b = 0; for (auto e : nums) { b = ~a & (b ^ e); a = ~b & (a ^ e); } return b; } }; // 按位解法 class Solution137II { public: int singleNumber(vector<int>& nums) { int ans = 0; for (int i = 0; i < 32; ++i) { int total = 0; for (auto num : nums) { // 取出num的第i个二进制位 total += ((num >> i) & 1); // & 按位与 } if (total % 3) { ans |= (1 << i); // | 按位或 } } return ans; } };
2. 电话号码的字母组合
// 17. 电话号码的字母组合 class Solution17 { const char* numStrArr[10] = { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" }; public: void Combine(const string& digits, int i, vector<string>& ret, string combineStr) { if (i == digits.size()) { ret.push_back(combineStr); return; } int num = digits[i] - '0'; string str = numStrArr[num]; for (size_t j = 0; j < str.size(); j++) { Combine(digits, i + 1, ret, combineStr + str[j]); } } vector<string> letterCombinations(string digits) { vector<string> v; if (digits.empty()) { return v; } string str; Combine(digits, 0, v, str); return v; } };