C++STL---Vector、List所要掌握的基本知识
绪论
拼着一切代价,奔你的前程。 ——巴尔扎克;本章主要围绕vector和list的使用,以及容器底层迭代器失效问题,同时会有对原码的分析和模拟实现其底层类函数。话不多说安全带系好,发车啦(建议电脑观看)。
附:红色,部分为重点部分;蓝颜色为需要记忆的部分(不是死记硬背哈,多敲);黑色加粗或者其余颜色为次重点;黑色为描述需要
1.Vector
vector 重要成员函数 | 具体有 |
---|---|
构造函数 | vetor() |
析构函数 | ~vector() |
迭代器类函数 | begin()、end()、rbegin()、rend()、… |
容量类函数 | resize()、size()、capacity()、reserve()、… |
元素访问类函数 | operator[]、front()、back()、… |
修改类函数 | push_back()、pop_back()、inser()、erase()、clear()、… |
非成员函数 | swap()、relational operator() |
——————————————
Vector就像一个动态的数组也就是顺序表(有些时候可以想象成常规数组),也就是在写好的类中进行一定的函数操作,其中函数操作也就是上面所写。
1.1构造函数
函数作用 | 函数原型 |
---|---|
rang(迭代器) | template <class InputIterator>vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type()); |
default(无参的全缺省的) | explicit vector (const allocator_type& alloc = allocator_type()); |
fill(初始化vector为n个value值) | explicit vector (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type()); |
copy(拷贝构造) | vector (const vector& x); |
附:其中的const allocator_type& alloc = allocator_type())为空间配置器,这里提供了可以修改的缺省值,当你可以修改使用自己的内存池,基本不用修改STL内部提供的内存池 |
//初始化vector的方法
vector<int> v;//缺省
vector<int> v1(10,0);//初始化0个10
vector<int> v2(v1.begin(),v1.end());//迭代器从begin到end初始化
vector<int> v3(v2);//拷贝构造
1.2析构函数
在使用中不用去管因为其会自动执行释放构造函数所申请的空间
1.3迭代器类函数
知识点:
1.3.1begin()、end()、rbegin()、rend()
此处begin、end和string中的差不多用来指向结构中的数据,begin指向vector的首元素(类似于数组的首元素)、end指向的是数组最后一个元素的下一个位置,相反的rbegin指向的是数组的最后一个元素,而rend指向的是第一个元素的前一个位置
其中可以发现begin指向vector内的数据,而end则不会指向vector内
练习使用:
vector<int> v(10, 1);
v.push_back(2);
vector<int>::iterator it = v.begin();//指向vector首元素的1
while (it != v.end())
{
cout << *it << ' ';
it++;
}
//正向打印出所有元素:1 1 1 1 1 1 1 1 1 1 2 类似于指针
cout << endl;
vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << ' ';
rit++;//注意此处仍然是++而不是--
}
//反向的打印出所有元素:2 1 1 1 1 1 1 1 1 1 1 类似于指针
附:cbegin、cend、crbeing、crend 是针对于const类型的,用法一样就不展开讲了。
1.4容量类函数
知识点:
1.4.1size()
函数原型:size_type size() const;
功能:查看vector中有几个元素。
vector<int> v(10,1);
v.size();//返回10
1.4.2capacity()
函数原型:size_type capacity() const;
功能:查看vector的capacity。
vector<int> v(10,1);
v.capacity();//在VS2019IDE环境下每次扩容1.5倍 1 ~ 2 ~ 3 ~ 6 ~ 9 ~ 13 ....
1.4.3reserve()
函数原型:void reserve (size_type n);
功能:设置vector的capacity,可提前把capacity设置好避免不断动态扩容的消耗
1.4.4resize()
函数原型:void resize (size_type n, value_type val = value_type());
改变vector的size(size决定了vector容器中的元素个数)
一般来说capacity申请扩展后都不会轻易的缩回
1.4.5empty()
函数原型:bool empty() const;
功能:查看vector内是否有数据,若返回true表示size == 0,否则放回false;
1.4.6shrink_to_fit()
函数原型:void shrink_to_fit();
功能:把capacity缩小到size大小(这条函数不一定执行最终是否执行还得看编译器他是无约束力的(non-binding))
1.5元素访问类函数
知识点:
1.5.1operator[]
函数原型 |
---|
reference operator[] (size_type n); |
const_reference operator[] (size_type n) const; |
功能:像数组一样来访问vector中的元素
如:第一个元素v[0]
…
1.5.2at()
函数原型 |
---|
reference at (size_type n); |
const_reference at (size_type n) const; |
功能:访问vector中的元素,但一般来说更多的用operator[]
如:v.at(0);
访问第一个元素
1.5.3front()、back()
front函数原型 | back函数原型 |
---|---|
reference front(); | reference back(); |
const_reference front() const | reference back(); |
front的功能:找到vector中第一个元素并返回
如:vector<int> v(10,1); v.fornt();//指向向首元素1
back的功能:同理就是找到vector的最后一个元素
如:vector<int> v(10,1); v.back();//指向向最后一个元素1
写到这我觉得没必要再写了(有点浪费时间),对于这些函数可以自行查资料,里面有解释以及函数原型!c++官网,下面我将只写函数的用处,但仍然会把一些细节给写出来!
1.6修改类函数
作用 | 具体函数 |
---|---|
尾插 | push_back() |
尾删 | pop_back() |
清除数据 | clear() |
将新的内容覆盖老的内容 | assign() |
在某个位置删除数据 | erase() |
在某处插入数据 | insert() |
交换(直接交换两个对象所指向的地址) | swap() |
附:对于容器中我们若想在指定位置插入的话其实他并没有find函数来找到所对应的任意位置,对此在c++库中的算法库中其实有一个通用的find()查找函数
函数原型为:
template <class InputIterator, class T>
InputIterator find (InputIterator first, InputIterator last, const T& val);
用法如下:
vector<int> v;//在其中存储着1 2 3 4 5 6 7 find(v.begin(),v.end(),3);//传进迭代器区间,在其中找3,最后返回对应迭代器
这样就能找到容器中的数据并且进行一定的操作。
非成员函数 |
---|
vector比较重载函数:relational operators |
交换函数(用处是当两个是vector要进行交换时,去调用vector修改类中的swap,他是std交换函数的重载类):swap() |
对于vector来说,还有个比较重要的点:
vector迭代器失效问题
在vector使用中可能会出现两种迭代器失效的情况:
- 当插入数据后的迭代器失效问题:因为在插入数据的情况下,有可能会发生异地扩容,当发生异地时就会导致原本迭代器所指向的位置为一个野指针也就是所说的迭代器失效,所以我们要注意的是迭代器在插入数据后要给迭代器重新赋值,并且在模拟实现插入函数中我们可能会扩容此处也要记得防止迭代器失效而记录迭代器位置,并作出一定的改变。
iterator insert(iterator pos, const T& x)
{
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _endofstorage)
{
size_t n = pos - _start;//记录相对位置防止迭代器在扩容后失效
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + n;
}
iterator it = _finish;
while (it > pos)
{
*it = *(it - 1);
it--;
}
*it = x;
_finish++;
return _start;
}
- 当删除数据时我们要注意的是,他可能也会有迭代器失效的问题,因为在VS2019IDE下规定当一个迭代器所指向的位置被删除后,该迭代器就已经失效了,我们不能再次的去使用若使用就会报错,而在CentOs7 g++下迭代器会指向物理下的下一个位置(如:1234 删除2后3覆盖了2的位置迭代器就会指向4),此处解决的方法为:更新迭代器让它接收erase的返回值,在c++中规定了erase删除后返回迭代器指向被删除数据的下一个位置,这样才能正确的删除数据, 如:
it = erase(it)//删除后需要接收返回的迭代器
。
vector深浅拷贝问题
当有异地扩容的时候,我们需要去将数据进行拷贝,此处如果是内置类型的话直接进行拷贝然后释放就行了,而对于像string类这种有深浅拷贝问题的就需要注意,我们若用memcpy直接进行的拷贝就会出深浅拷贝问题,对此我们在reserve扩容函数中就需要注意改变写法具体如下。
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t sz = size();
if (_start)
{
//memcpy(tmp, _start, sz * sizeof(T)); 此处对于自定义类型来说是一个浅拷贝了
for(size_t i = 0; i < size();i++)
{
tmp[i] = _start[i];//逐个元素的进行赋值操作就能避免
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;//这里不用size()因为这样会导致为nullptr ,
_endofstorage = _start + n;
}
}
vector反向迭代器的实现
对于vector反向迭代器来说,我们不能像正向一样的直接用一个指针指向start,他们需要从后往前来走(也就表示当reverse_iterartor ++ 时并不是往前走的而是往后的),这样我们就需要重新分装一个反向迭代器的类,并且对操作符++、–重载改变其原理。对此在一个类中我们还需要去实现一些其他的操作符,具体如下:
template<class iterator>
class reverse_iterator
{
typedef reverse_iterator<iterator> Self;
reverse_iterator(iterator t)
:it(t)
{}
Self& operator++()
{
it--;
return *this;
}
Self operator++(int)
{
Self tmp (it);
it--;
return tmp;
}
Self& operator--()
{
it++;
return *this;
}
Self operator--(int)
{
reverse_iterator tmp = it;
it++;
return tmp;
}
Self& operator*()
{
reverse_iterator tmp = it;
return *(--tmp);
}
bool operator!=(const Self& t)
{
return it != t.it;
}
bool operator==(const Self& t)
{
return it == t.it;
}
private:
iterator it;
};
模拟实现Vector:
#pragma once
#include<iostream>
#include<stdio.h>
#include<assert.h>
using namespace std;
namespace bit
{
template<class T>
class vector
{
public:
// Vector的迭代器是一个原生指针
typedef T* iterator;
typedef const T* const_iterator;
typedef reverse_iterator<iterator> reverse_iterator;
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator cbegin() const
{
return _start;
}
const_iterator cend() const
{
return _finish;
}
// construct and destroy
//
//对于直接的构造不做如何处理
vector()
{}
vector(size_t n, const T& value = T())
{
reserve(n);
while (n--) {
push_back(value);
}
}
vector(int n, const T& value = T())
{
reserve(n);
while (n--) {
push_back(value);
}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
assert(first && last);
while(first != last)
{
push_back(*first);
first++;
}
}
//现代写法:
vector(const vector<T>& v)
{
vector<T> tmp(v.cbegin(),v.cend());
swap(tmp);
}
//传统写法:
//vector(const vector<T>& v)
//{
// //开辟一个空间
// reserve(v.capacity());
// //把数据放进去
// for (auto& e : v)
// {
// push_back(e);
// }
//}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
//void operator++(int)
//{
// swap(v);
// return *this;
//}
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
capacity
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t sz = size();
if (_start)
{
//memcpy(tmp, _start, sz * sizeof(T)); 此处对于自定义类型来说是一个浅拷贝了
for(size_t i = 0; i < size();i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;//这里不用size()因为这样会导致为nullptr ,
_endofstorage = _start + n;
}
}
void resize(size_t n, const T& value = T())
{
if (n < capacity())
{
_finish = _start + n;
}
else {
reserve(n);
while (_finish < _start + n)
{
*_finish = value;
++_finish;
}
}
}
/access///
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
/modify/
void push_back(const T& x)
{
/* if (_finish == _endofstorage)
{
int cp = capacity() == 0 ? 4 : capacity() * 2;
reserve(cp);
}
*_finish = x;
_finish++;*/
insert(end(), x);
}
void pop_back()
{
_finish--;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
iterator insert(iterator pos, const T& x)
{
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _endofstorage)
{
size_t n = pos - _start;//防止迭代器在扩容后失效
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + n;
}
iterator it = _finish;
while (it > pos)
{
*it = *(it - 1);
it--;
}
*it = x;
_finish++;
return _start;
}
iterator erase(iterator pos)
{
assert(pos < _finish);
assert(pos >= _start);
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos;
}
private:
//给他们初始值是为了预防
iterator _start = nullptr; // 指向数据块的开始
iterator _finish = nullptr; // 指向有效数据的尾
iterator _endofstorage = nullptr; // 指向存储容量的尾
};
template<class iterator>
class reverse_iterator
{
typedef reverse_iterator<iterator> Self;
reverse_iterator(iterator t)
:it(t)
{}
Self& operator++()
{
it--;
return *this;
}
Self operator++(int)
{
Self tmp (it);
it--;
return tmp;
}
Self& operator--()
{
it++;
return *this;
}
Self operator--(int)
{
reverse_iterator tmp = it;
it++;
return tmp;
}
Self& operator*()
{
reverse_iterator tmp = it;
return *(--tmp);
}
bool operator!=(const Self& t)
{
return it != t.it;
}
bool operator==(const Self& t)
{
return it == t.it;
}
private:
iterator it;
};
}
2.List
在STL的list其实就是数据结构中的带头双向循环列表(若不清楚的建议看看数据机构基础之链表)
list 重要成员函数 | 具体有 |
---|---|
构造函数 | list() |
析构函数 | ~list() |
迭代器类函数 | begin()、end()、rbegin()、rend()、… |
容量类函数 | size()、empty()、… |
元素访问类函数 | front()、back() |
修改类函数 | push_back()、pop_back()、push_front()、inser()、erase()、clear()、… |
操作类函数 | sort() 、 reverse() 、remove() 、… |
非成员函数 | swap()、relational operator() |
同样的在list中也不会去详细的简述函数(这些都是熟能生巧的多去查文档),会在后面写道一些细节内容以及函数的功能
2.1构造函数、析构函数
构造函数( (constructor)) | 作用 |
---|---|
list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list() | 构造空的list |
list (const list& x) | 拷贝构造函数 |
list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |
析构函数不用自己调用,其内部主要包括把所借的内存归还给操作系统操作
2.2迭代器类函数
具体函数 | 作用 |
---|---|
begin() | 返回第一个元素的迭代器 |
end() | 返回最后一个元素下一个位置的迭代器 |
rbegin() | 返回第一个元素的reverse_iterator,即end位置 |
rend | 返回最后一个元素下一个位置的reverse_iterator,即begin位置 |
注意点 | 在反向迭代器中++,是表示往前走 而不是往后 ; 同理的-- 则表示往后走 |
2.3容量类函数
具体函数 | 作用 |
---|---|
empty() | 检测list是否为空,是返回true,否则返回false |
size() | 返回list中有效节点的个数 |
2.4元素访问类函数
具体函数 | 作用 |
---|---|
front() | 返回list的第一个节点中值的引用 |
back() | 返回list的最后一个节点中值的引用 |
2.5修改类函数
具体函数 | 作用 |
---|---|
push_back(const value_type& val) | 在list尾部插入值为val的元素 |
pop_back() | 删除list中最后一个元素 |
push_front(const value_type& val) | 在list首元素前插入值为val的元素 |
pop_front() | 删除list中第一个元素 |
iterator inser(iterator position, const value_type& val) | 在pos位置(此处是迭代器)处插入值为val的元素,还能插入一次性n个元素以及用迭代器插入一段数据 |
iterator erase(iterator position) | 删除pos迭代器所指向位置的元素,还能一次性删除一段数据erase (iterator first, iterator last) |
clear() | 清空list中的有效元素 |
swap(list& x)) | 交换两个list中的元素 |
void resize (size_type n, value_type val = value_type()) | 提前初始化n个空间初始值默认为value_type()//整形为0… |
2.5.1list 删除操作时的迭代器失效问题:
list在插入数据时并不会迭代器失效,而在删除时因为当前位置的迭代器所指向的地址空间已经被释放,而形成了迭代器失效。
所以为避免这种情况删除的实现会返回一个迭代器(也就是被删除数据的下一个位置)并且我们需要用接收这个迭代器才能解决失效问题(例:iterator it = l.erase(l.begin()))
通过上面的分析我们能知道当list删除数据后其原本的迭代器会指向被删除的数据的下一个数据,并且返回该位置!对此来实现list的删除:
iterator erase(iterator pos)
{
Node* cur = pos._Node;
Node* prev = cur->_Prev;
Node* next = cur->_Next;//一开始记录删除数据的下一个位置
prev->_Next = next;
next->_Prev = prev;
_size--;
return iterator(next);//返回next
}
2.6操作类函数
具体函数 | 作用 |
---|---|
void splice (iterator position, list& x) | 拼接两个list,还能x指定中的i位置的一个元素拼接,或者是用迭代器确定区域(first,last]进行拼接 |
remove(valua) | 删除list中所有等于value值的元素 |
template <class Predicate> remove_if(Predicate pred) | 删除所有满足pred仿函数(也可能是函数指针)的元素 |
unique() | 删除重复的值 |
merge(list& x) | 拼接两个list,他有第二个参数为仿函数(或函数指针) |
sort() | 对list中的数据进行排序,默认为升序,可以改变传递仿函数来自定义Compare比较函数(方法) |
reverse() | 逆序把list中的数据倒过来 |
2.7list的实现
#pragma once
#include<iostream>
using namespace std;
namespace bite
{
// List的节点类
template<class T>
struct ListNode
{
ListNode(const T& val = T())
:_val(val)
,_Prev(nullptr)
,_Next(nullptr)
{}
ListNode<T>* _Prev;
ListNode<T>* _Next;
T _val;
};
//List的迭代器类
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
public:
ListIterator(Node* node = nullptr)
:_Node(node)
{}
//ListIterator(const Self& l);
T& operator*()
{
return _Node->_val;
}
Ptr operator->()
{
return &_Node->_val;
}
Self& operator++()
{
_Node = _Node->_Next;
return *this;
}
//后置
Self operator++(int)
{
Self tmp(*this);//拷贝构造
//其中self是类型、*this是一个具体的对象
_Node = _Node->_Next;
return tmp;
}
//前置
Self& operator--()
{
_Node = _Node->_Prev;
return *this;
}
//后置需要一个类型来占位
Self& operator--(int)
{
Self tmp(*this);
_Node = _Node->_Prev;
return tmp;
}
bool operator!=(const Self& l)
{
return _Node != l._Node;
}
bool operator==(const Self& l)
{
return _Node == l._Node;
}
Node* _Node;
};
template<class T, class Ref, class Ptr>
struct RIterator
{
typedef ListNode<T> Node;
typedef RIterator<T, Ref, Ptr> Self;
public:
RIterator(Node* node = nullptr)
:_Node(node)
{}
//ListIterator(const Self& l);
T& operator*()
{
return _Node->_val;
}
Ptr operator->()
{
return &_Node->_val;
}
Self& operator++()
{
_Node = _Node->_Prev;
return *this;
}
//后置
Self operator++(int)
{
Self tmp(*this);//拷贝构造
//其中self是类型、*this是一个具体的对象
_Node = _Node->_Prev;
return tmp;
}
//前置
Self& operator--()
{
_Node = _Node->_Next;
return *this;
}
//后置需要一个类型来占位
Self& operator--(int)
{
Self tmp(*this);
_Node = _Node->_Next;
return tmp;
}
bool operator!=(const Self& l)
{
return _Node != l._Node;
}
bool operator==(const Self& l)
{
return _Node == l._Node;
}
Node* _Node;
};
//list类
template<class T>
class list
{
public:
typedef ListNode<T> Node;
typedef ListIterator<T, T&, T*> iterator;//将
typedef ListIterator<T, const T&, const T&> const_iterator;
typedef RIterator<T, T&, T*> reverse_iterator;//将
public:
///
// List的构造
void empty_init()
{
_Head = new Node;//头节点
_Head->_Next = _Head;
_Head->_Prev = _Head;
}
list()
{
empty_init();
}
list(int n, const T& value = T())
{
for(int i = 0; i<n;i++)
{
push_back(value);
}
}
template <class Iterator>
list(Iterator first, Iterator last)
{
Iterator it = first;
while (it != last)
{
push_back(*it);
++it;
}
}
void swap(list<T>& l)
{
std::swap(_Head, l._Head);
std::swap(_size, l._size);
}
list(const list<T>& l)
{
empty_init();
for (auto nd : l)
{
push_back(nd);
}
}
list<T>& operator=(list<T> l)
{
swap(l);
return *this;
}
~list()
{
clear();//将申请的空间返还
delete _Head;
_Head = nullptr;
}
///
// List Iterator
iterator begin()
{
//return iterator(_Head->_Next);
return _Head->_Next;
}
iterator end()
{
//return iterator(_Head);
return _Head;
}
const_iterator begin()const
{
return _Head->_Next;
}
const_iterator end()const
{
return _Head;
}
reverse_iterator rbegin()
{
return reverse_iterator(_Head->_Prev);
}
reverse_iterator rend()
{
return reverse_iterator(_Head);
}
// List Capacity
size_t size()const
{
return _size;
}
bool empty()const
{
return _size == 0;
}
List Access
T& front()
{
return _Head->_Next->_val;
}
const T& front()const
{
return _Head->_Next->_val;
}
//
T& back()
{
return _Head->_Prev->_val;
}
//
const T& back()const
{
return _Head->_Prev->_val;
}
// List Modify
void push_back(const T& val)
{
//Node* tail = _Head->_Prev;
//Node* newnode = new Node(val);
//tail->_Next = newnode;
//newnode->_Prev = tail;
//_Head->_Prev = newnode;
//newnode->_Next = _Head;
//_size++;
insert(end(), val);
}
void pop_back()
{
/* Node* tail = _Head->_Prev;
tail->_Prev->_Next = _Head;
_Head->_Prev = tail->_Prev;
delete tail;
_size--;*/
erase(--end());
}
void push_front(const T& val)
{
//Node* head = _Head->_Next;
//Node* newnode = new Node(val);
//head->_Prev = newnode;
//newnode->_Next = head;
//_Head->_Next = newnode;
//head->_Prev = _Head;
//_size++;
insert(begin(), val);
}
void pop_front()
{
/* Node* head = _Head->_Next;
head->_Next->_Prev = _Head;
_Head->_Next = head->_Next;
delete head;
_size--;*/
erase(begin());
}
在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._Node;
Node* newnode = new Node(val);
Node* prev = cur->_Prev;
prev->_Next = newnode;
newnode->_Prev = prev;
newnode->_Next = cur;
cur->_Prev = newnode;
_size++;
return iterator(newnode);
}
删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
Node* cur = pos._Node;
Node* prev = cur->_Prev;
Node* next = cur->_Next;
prev->_Next = next;
next->_Prev = prev;
_size--;
return iterator(next);
}
void clear()
{
iterator it = begin();
while(it != end())
{
it = erase(it);
}
//for(auto it : )
_size = 0;
}
private:
Node* _Head;
size_t _size;
};
}
本章完。预知后事如何,暂听下回分解。
如果有任何问题欢迎讨论哈!
如果觉得这篇文章对你有所帮助的话点点赞吧!
持续更新大量C++细致内容,早关注不迷路。