目录
5.修改类型函数 pop_back find insert erase
6. 容量相关函数capacity resize reserve
今天将开启对C++STL的学习,STL作为强大的模版库,十分值得我们学习!在此途中,提升自己的C++代码能力。
标准模板库(Standard Template Library,STL)是惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。STL是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
STL版本
STL有六大组件:

vector是表示可变大小数组的序列容器。就像数组一样,vector也采用的连续存储空间来存储元素。也就意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
因为vector内部实现没有使用具体类型,而是给出模版,所以实例化一个vector变量时需要给出具体类型。记得包含头文件
- vector(const allocator_type& alloc = allocator_type());//函数原型
-
- void test_vector()
- {
- vector<int> v1;
- }
tip:其中的allocator是空间适配器,用于分配内存空间的,暂时不用深究。
- vector (size_type n, const value_type& val = value_type(),
- const allocator_type& alloc = allocator_type());
-
- void test_vector()
- {
- vector<int> v1(10, 1);
- }
- template <class InputIterator>
- vector (InputIterator first, InputIterator last,
- const allocator_type& alloc = allocator_type());
-
- void test_vector()
- {
- vector<int> v1(10, 1);
- vector<int> v2(v1.begin(), v1.end());
- }
- vector (const vector& x);
-
- void test_vector()
- {
- vector<int> v1(10, 1);
- vector<int> v2(v2);
- }
vector是一个类似数组的容器,物理存储上是连续的,那自然少不了下标访问。
- void test_vector1()
- {
- vector<int> v1;
- v1.push_back(1);
- v1.push_back(2);
- v1.push_back(3);
- v1.push_back(4);
-
- for (size_t i = 0; i < v1.size(); i++)
- {
- cout << v1[i] << " ";
- }
- cout << endl;
- }
运行结果如下:

使用迭代器前,我们需要了解什么是迭代器。
在C++中,迭代器(Iterator)是一种检查容器中元素并遍历元素的数据类型。迭代器提供了一种通用的方式来访问容器中的元素,而不需要关心容器底层的具体数据结构。通过迭代器,可以对容器进行遍历、读取、修改和删除元素等操作。
- void test_vector2()
- {
- vector<int> v1;
- v1.push_back(1);
- v1.push_back(2);
- v1.push_back(3);
- v1.push_back(4);
-
- vector<int>::iterator it1 = v1.begin();
- while (it1 != v1.end())
- {
- cout << *it1 << " ";
- ++it1;
- }
- cout << endl;
-
- it1 = v1.begin();
- while (it1 != v1.end())
- {
- ++*it1;//对容器元素进行修改
- cout << *it1 << " ";
- ++it1;
- }
- cout << endl;
-
- //全部展开成运算符重载函数
- it1.operator=(v1.begin());
- while (it1.operator!=(v1.end()))
- {
- cout << it1.operator*() << " ";
- it1.operator++();
- }
- cout << endl;
- }
上面最后一段代码将这些运算符写成调用函数的形式,也可以进行遍历,但是代码的可读性比较差。运行结果如下:

但是有些情况下,只是遍历数据进行打印,不想修改元素。如果使用一般迭代器iterator,会导致权限放大,误改其中的元素。我们可以使用const_iterator,对该类型的容器进行访问,即使不小心修改,系统也会报错。
- void test_vector3()
- {
- vector<int> v1;
- v1.push_back(1);
- v1.push_back(2);
- v1.push_back(3);
- v1.push_back(4);
-
- vector<int>::const_iterator it1 = v1.begin();
- while (it1 != v1.end())
- {
- //*it = 5 error,不可以修改
- cout << *it1 << " ";
- ++it1;
- }
- cout << endl;
- }
运行结果如下:

迭代器不仅可以正向遍历,还有反向迭代器,倒着遍历。跟正向迭代器类似,也有可修改和不可修改两种反向迭代器。
- void test_vector4()
- {
- vector<int> v1;
- v1.push_back(1);
- v1.push_back(2);
- v1.push_back(3);
- v1.push_back(4);
-
- vector<int>::reverse_iterator it1 = v1.rbegin();
- while (it1 != v1.rend())
- {
- *it1 += 4;
- cout << *it1 << " ";
- ++it1;
- }
- cout << endl;
-
- vector<int>::const_reverse_iterator it2 = v1.rbegin();
- while (it2 != v1.rend())
- {
- //*it2 = 1 error
- cout << *it2 << " ";
- ++it2;
- }
- cout << endl;
- }
运行结果如下:

范围for是一个比较实用的语法,支持遍历各种容器,但是只能正向遍历。
- void Test()
- {
- vector<int> v1;
- v1.push_back(1);
- v1.push_back(2);
- v1.push_back(3);
- v1.push_back(4);
-
- for (auto e : v1)
- {
- cout << e << " ";
- }
- cout << endl;
-
- //想要修改内容需要使用引用
- for (auto& e : v1)
- {
- e++;
- cout << e << " ";
- }
- cout << endl;
- }
运行结果如下:

之后的Print函数就是正向输出容器中的所有元素。
- void Print(vector<int>& v)
- {
- vector<int>::iterator it1 = v.begin();
- while (it1 != v.end())
- {
- cout << *it1 << " ";
- ++it1;
- }
- cout << endl;
- }
-
- void test_vector5()
- {
- vector<int> v1;
- v1.push_back(1);
- v1.push_back(2);
- v1.push_back(3);
- v1.push_back(4);
- Print(v1);
-
- v1.pop_back();
- v1.pop_back();
- Print(v1);
- }
运行结果如下:

find,insert和erase
- template <class InputIterator, class T>
- InputIterator find (InputIterator first, InputIterator last, const T& val);
- //1.
- iterator insert (iterator position, const value_type& val);
- //2.
- void insert (iterator position, size_type n, const value_type& val);
- //3.
- template <class InputIterator>
- void insert (iterator position, InputIterator first, InputIterator last);
insert函数原型如下,有三种类型。
- //1.
- iterator erase (iterator position);
- //2.
- iterator erase (iterator first, iterator last);
- void test_vector6()
- {
- int arr[] = { 1,2,3,4,5,6,};
- vector<int> v1(arr, arr + 6);
- Print(v1);
-
- vector<int>::iterator pos = find(v1.begin(), v1.end(), 4);
- v1.insert(pos, 20);
- Print(v1);
-
- pos = find(v1.begin(), v1.end(), 5);
- v1.insert(pos, 2, 10);
- Print(v1);
-
- vector<int> v2;
- v2.insert(v2.begin(), v1.begin(), v1.end());
- Print(v2);
-
- pos = find(v1.begin(), v1.end(), 6);
- v1.erase(pos);
- Print(v1);
-
- v2.erase(v2.begin(), --v2.end());
- Print(v2);
- }
运行结果如下:

- void TestExpand()
- {
- size_t sz;
- vector<int> v;
- 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';
- }
- }
- }
在VS环境下,扩容情况如下图所示。一开始,插入四个元素时,每次扩容只增加一个容量。插入第五个元素开始就是1.5倍扩容的速度。

在Linux环境下,我们可以看到是遵循2倍扩容的原则进行扩容。

void resize (size_type n, value_type val = value_type());
比如说,你创建了一个含有四个元素容器。如果用下标访问该容器元素,下标值范围是0~3,不能超出这个范围。当你想使用下标给第五个元素赋值,就会断言报错。此时就可以使用resize函数,将容器元素个数扩充,便可以使用下标给之后的元素赋值。
- void test_vector7()
- {
- int arr[] = { 1,2,3,4 };
- int size = sizeof(arr) / sizeof(int);
- vector<int> v1(arr, arr + size);
-
- //v1[4] = 0; //error
-
- v1.resize(8);
- v1[4] = 1;
- v1[5] = 2;
- v1[6] = 3;
- v1[7] = 4;
- Print(v1);
- }
运行结果如下:

void reserve (size_type n);
在上面测试不同平台下vector的扩容机制,扩容的本质是开辟新空间,将原来容器的内容拷贝过去,再释放原有的空间。如果要频繁插入数据,就会频繁的扩容,会造成许多消耗,所以如果知道要插入数据个数,可以一次性扩容完毕,减少不必要的消耗。
- void TestExpand()
- {
- size_t sz;
- vector<int> v;
- sz = v.capacity();
- v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
- 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';
- }
- }
- }
在VS中,运行结果如下:

- 模拟实现vector会用到类型模版,一般建议将实现的函数内容都放在一个头文件中。因为给定一个参数类型实例化vector容器时,这个过程发生编译的期间,如果函数的声明和定义分离,放在两个文件中,编译器无法根据这个类型进行代码分发。
- 直接创建一个vector.h的头文件。还有为了不和C++标准库里面的vector发生命名冲突,可以自己创建一个命名空间User。再包含上需要的头文件。
- #include
-
- namespace User
- {
- template <class T>
- class vector
- {
- //...
- };
-
- }
- #include
-
- namespace User
- {
- template <class T>
- class vector
- {
-
- private:
- iterator _start;
- iterator _finish;
- iterator _end_of_storage;
- };
-
- }

因为vector物理存储和逻辑存储都是连续的,所以我们可以将他的迭代器类型用原生指针实现。
- typedef T* iterator;
- typedef const T* const_iterator;
再提供begin和end函数,返回容器第一个元素的位置和末尾元素的后一位。其中const_iterator类型的迭代器,需要保证容器元素不能被修改,需要在函数后加上const,成为const成员函数。
- const_iterator begin() const
- {
- return _start;
- }
-
- const_iterator end() const
- {
- return _finish;
- }
-
- iterator begin()
- {
- return _start;
- }
-
- iterator end()
- {
- return _finish;
- }
- size_t size() const
- {
- return _finish - _start;
- }
-
- size_t capacity() const
- {
- return _end_of_storage - _start;
- }
- T& operator[](size_t i)
- {
- assert(i < size());
-
- return _start[i];
- }
-
- T& operator[](size_t i) const
- {
- assert(i < size());
-
- return _start[i];
- }
我们先完成一个尾插和扩容的函数,之后的构造函数就依靠这个接口进行初始化。
- void reserve(size_t n)
- {
- if (n > capacity())
- {
- T* tmp = new T[n];
- size_t oldsize = size();
- if (_start)
- {
- memcpy(tmp, _start, sizeof(T) * size());
- delete[] _start;
- }
-
- _start = tmp;
- _finish = _start + oldsize;
- _end_of_storage = _start + n;
- }
- }
-
- void push_back(const T& x)
- {
- if (_finish == _end_of_storage)
- {
- //两倍速扩容
- size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
- reserve(newcapacity);
- }
-
- *_finish = x;
- ++_finish;
- }
写一个测试函数,测试一下之前实现的各种接口函数,用正常的for循环遍历,迭代器遍历,还有范围for进行遍历。
- void test_vector1()
- {
- vector<int> v1;
- v1.push_back(1);
- v1.push_back(2);
- v1.push_back(3);
- v1.push_back(4);
-
- for (size_t i = 0; i < v1.size(); i++)
- {
- cout << v1[i] << " ";
- }
- cout << endl;
-
- vector<int>::iterator it = v1.begin();
- while (it != v1.end())
- {
- cout << *it << " ";
- ++it;
- }
- cout << endl;
-
- for (auto e : v1)
- {
- cout << e << " ";
- }
- cout << endl;
- }
测试结果如下:

但是reserve函数实现就没有问题吗?看看下面的测试函数
- void test_vector2()
- {
- vector
v1; - v1.push_back("xxxxxxxxxx");
- v1.push_back("xxxxxxxxxx");
- v1.push_back("xxxxxxxxxx");
- v1.push_back("xxxxxxxxxx");
- v1.push_back("xxxxxxxxxx");
-
- for (auto e : v1)
- {
- cout << e << " ";
- }
- cout << endl;
- }
当你运行这段代码的时候,打印前面四个元素会出现乱码。这是为什么呢?
如下图所示,tmp拷贝过来的内容,把指针变量也原封不动按字节拷贝,导致_start和tmp中的_str指针指向同一块空间。之后,会delete[ ] _start,而delete释放空间之前,会调用里面自定义类型的析构函数,再释放这个空间,这就会造成我们数据的丢失。

- void reserve(size_t n)
- {
- if (n > capacity())
- {
- T* tmp = new T[n];
- size_t oldsize = size();
- if (_start)
- {
- for (size_t i = 0; i < oldsize; i++)
- {
- //tmp[i] = _start[i];//传统写法
- std::swap(tmp[i], _start[i]);
- }
- delete[] _start;
- }
-
- _start = tmp;
- _finish = _start + oldsize;
- _end_of_storage = _start + n;
- }
- }
vector构造函数我们三个,其中一个是给定个数n,给定T类型的x变量,进行初始化。
- vector(size_t n, const T& x = T())
- {
- reserve(n);
- for (size_t i = 0; i < n; i++)
- {
- push_back(x)
- }
- }
-
- vector(int n, const T& val = T())
- {
- reserve(n);
- for (int i = 0; i < n; i++)
- {
- push_back(val);
- }
- }
写一个测试函数,用一个Print函数实现打印整个容器元素,之后打印就用这个Print函数。
- template <class T>
- void Print(const vector
& v) - {
- for (auto e : v)
- {
- cout << e << " ";
- }
- cout << endl;
- }
-
- void test_vector3()
- {
- vector<int> v1(4, 10);
- Print(v1);
-
- }

- template<class InputIterator>
- vector(InputIterator first, InputIterator last)
- {
- while (first != last)
- {
- push_back(*first);
- ++first;
- }
- }
写一个测试函数。
- void test_vector4()
- {
- vector<int> v1(4, 10);
- Print(v1);
-
- vector<int> v2(v1.begin(), v1.end());
- Print(v2);
- }

我们来看看这段代码,在STL的Vector可以支持带花括号的初始化。
- void test_vector5()
- {
- std::vector<int> v1 = { 1,2,3,4,5,6 };
- for (auto e : v1)
- {
- cout << e << " ";
- }
- cout << endl;
- }
结果如下:

这是为什么呢?因为C++有一个类型叫做initializer_list,如下面的代码实例,任何一个带花括号的里面是相同元素,用逗号分隔,就会是该类型。这个类型里面只有两个指针维护,一个指向第一个元素,另外一个指向最后一个元素。
- void Test()
- {
- auto il = { 1,2,3,4,5,6 };
- initializer_list<int> il1 = { 1,2,3,4,5,6 };
-
- cout << typeid(il).name() << endl;
- cout << sizeof(il) << endl;
- }

这个构造函数跟之前也是类似,只是传递的参数是initializer_list,直接用范围for尾插。
- vector(initializer_list
il) - {
- reserve(il.size());
- for (auto e : il)
- {
- push_back(e);
- }
- }
- class vector
- {
- public:
- vector() = default;
-
- private:
- iterator _start = nullptr;
- iterator _finish = nullptr;
- iterator _end_of_storage = nullptr;
- }
- ~vector()
- {
- if (_start)
- {
- delete[] _start;
- _start = _finish = _end_of_storage = nullptr;
- }
- }
拷贝构造函数,跟之前的构造函数类似,先扩容,再复用push_back函数。
- vector(const vector
& v) - {
- reserve(v.capacity());
- for (auto e : v)
- {
- push_back(e);
- }
- }
- void swap(vector
& v) - {
- std::swap(_start, v._start);
- std::swap(_finish, v._finish);
- std::swap(_end_of_storage, v._end_of_storage);
- }
-
- vector
& operator=(vector v) - {
- swap(v);
- return *this;
- }
- void insert(iterator pos, const T& x)
- {
- assert(pos >= _start);
- assert(pos <= _finish);
- if (_finish == _end_of_storage)
- {
- size_t len = pos - _start;//记录pos相对于start的位置
- size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
- reserve(newcapacity);
- pos = _start + len;
- }
-
- iterator end = _finish;
- while (end > pos)
- {
- *end = *(end - 1);
- --end;
- }
- *pos = x;
- ++_finish;
- }
-
- void erase(iterator pos)
- {
- assert(pos >= _start && pos < _finish);
-
- iterator end = pos + 1;
- while (end < _finish)
- {
- *(end - 1) = *end;
- ++end;
- }
- --_finish;
- }
- void test_vector2()
- {
- vector<int> v1;
- v1.push_back(1);
- v1.push_back(2);
- v1.push_back(3);
- v1.push_back(4);
-
- int x;
- cin >> x;
- std::vector<int>::iterator pos = find(v1.begin(), v1.end(), x);
- if (pos != v1.end())
- {
- v1.insert(pos, 1000);
- }
-
- for (auto& e : v1)
- {
- cout << e << " ";
- }
- cout << endl;
- }
- //...
- if (pos != v1.end())
- {
- pos = v1.insert(pos, 1000);
- }
- //...
所以insert的函数返回参数需要修改,返回第一个新插入的元素。
- iterator insert(iterator pos, const T& x)
- {
- assert(pos >= _start);
- assert(pos <= _finish);
- if (_finish == _end_of_storage)
- {
- size_t len = pos - _start;//记录pos相对于start的位置
- size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
- reserve(newcapacity);
- pos = _start + len;
- }
-
- iterator end = _finish;
- while (end > pos)
- {
- *end = *(end - 1);
- --end;
- }
- *pos = x;
- ++_finish;
- }
再看一下下面的代码,与insert类似,我们删除一个元素,会造成迭代器失效吗?
- void test_vector3()
- {
- std::vector<int> v1;
- v1.push_back(1);
- v1.push_back(2);
- v1.push_back(3);
- v1.push_back(4);
- for (auto& e : v1)
- {
- cout << e << " ";
- }
- cout << endl;
-
-
- int x;
- cin >> x;
- std::vector<int>::iterator pos = find(v1.begin(), v1.end(), x);
- if (pos != v1.end())
- {
- v1.erase(pos);
- cout << *pos << endl;
- }
-
- for (auto& e : v1)
- {
- cout << e << " ";
- }
- cout << endl;
- }
erase函数也需要修改一下,返回删除元素的后一个位置。
- iterator erase(iterator pos)
- {
- assert(pos >= _start && pos < _finish);
-
- iterator end = pos + 1;
- while (end < _finish)
- {
- *(end - 1) = *end;
- ++end;
- }
- --_finish;
-
- return pos + 1;
- }
通过这篇文章,你应该了解了vector的使用,和一些简单的底层模拟实现。不过纸上得来终觉浅,绝知此事要躬行,需要多加练习!
创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!
