附一份slt30的源码链接
我们来简单看一下源码,采用的是stl30,沿用《STL源码剖析》(建议有一定基础的学习者阅读,初学者可以先跳过前两章,前两章讲的是空间配置器)采用的版本,方便学习和深入了解;
那我们在看源码时首先要关注什么呢?最忌讳的就是一头扎到细节里,一定要把握框架,比如我们今天看的vector是一个类,那么它的成员变量有哪些?各自都表征什么?(通过构造、插入等接口把握);以及主要的成员函数,比如析构等;看源码的时候可以结合变量名进行适当的猜测,再确认;不做完美主义者,不追求一遍就完全理解,留一些疑难杂症说不定看到后面自然就通了
1. 成员变量


下图出自《STL源码剖析》

也可以结合函数实现来推导

2. 模拟实现
cpp
#pragma once
namespace diy {
template<typename T>
class vector {
public:
//默认构造
vector() :
_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr) {
}
//迭代器
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;
}
size_t size() const {
return _finish - _start;
}
size_t capacity() const {
return _end_of_storage - _start;
}
~vector() {
if (_start) {
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
T& operator[](iterator pos) {
assert(pos >= _start && pos <= _finish);
return _start[pos];
}
const T& operator[](iterator pos) const{
assert(pos >= _start && pos <= _finish);
return _start[pos];
}
private://给缺省值,在构造的时候不需要进行=nullptr的初始化列表
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
}
写个打印用于查看测试结果
cpp
void print(const vector<int>& v) {
for (auto e : v)
cout << e;
cout << endl;
}
2.1 push_back
cpp
void push_back(const T& x) {
if (_finish == _end_of_storage) {
size_t newcapacity = capacity() == 0 ? 1 : 2 * capacity();
reserve(newcapacity);
}
*_finish = x;
_finish++;
}
void test_myvector() {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator it = v.begin() + 3;
v.insert(it, 3);
printf("v.begin()+3->%p\n", v.begin() + 3);
printf("it->%p\n", it);
}
输出结果
cpp
v.begin()+3->00000164CFBC435C
it->00000164CFBC6F9C
cpp
void test_myvector1() {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
print(v);
vector<int>::iterator p = v.begin() + 3;
v.insert(p, 5);
print(v);
*p = 10;//高危行为
// v.insert(p, 6); 直接断言
print(v);
}
输出结果
cpp
1234
12354
12354
这个地方不能传迭代器引用来解决外部迭代器失效问题,因为一方面begin()是传值返回,涉及拷贝,产生临时变量;其次,传的迭代器可能是v.begin()+3等,表达式的结果也是临时变量;如果改为const iterator& pos来解决这个问题,函数体内部没有办法更改pos,也不行
2.2 insert

cpp
iterator insert(iterator pos, const T& x) {
assert(pos >= _start && pos <= _finish);
if (_finish == _end_of_storage) {
size_t offset = pos - _start;
size_t newcapacity = capacity() == 0 ? 1 : 2 * capacity();
reserve(newcapacity);
pos = _start + offset;
}
iterator end = _finish-1;
while (end >= pos) {
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
void push_back(const T& x) {
insert(end(), x);//复用
}
2.3 erase
和上面insert类似,erase之后也涉及迭代器失效的问题,
cpp
void test_myvector2() {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
// v 1 2 2 3 5
auto it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0)
v.erase(it);
it++;
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
输出结果
cpp
1 3 5
cpp
void test_myvector2() {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
// v 1 2 2 3 5
auto it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0)
v.erase(it);
it++;
}
print();
}
输出结果
cpp
1 2 3 5
cpp
void test_myvector2() {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
// v 1 2 2 3 5 6
auto it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0)
v.erase(it);
it++;
}
print();
}
VS2022 断言错误
g++ Segmentation fault
也给我们启示,测试用例要尽可能的全,问题才能尽可能的暴露出来;
v 1 2 3 4 5

v 1 2 2 3 4 5

v 1 2 2 3 4 5 6

末尾的6erase之后,it和_finish的指向如上图,it再次进入for循环,解引用发现是偶数,然后就erase,断言错误
改成下面也不行,因为有的编译器可能直接强制检查,代码要考虑平台的可移植性,不能依赖编译器的个性化行为
cpp
while (it != v.end()) {
if (*it % 2 == 0)
v.erase(it);
else
it++;
}
这也是为什么返回值是iterator的原因,本质是我把下一个需要处理的数据迭代器返回给你,我们只需要拿来用,不需要去关心越界等问题

2.4 reserve
cpp
void reserve(size_t n) {
if (capacity() < n) {
size_t sz=_finish-_start;
T* tmp = new T[n];
if (_start) {
// memcpy(tmp, _start, sz * sizeof(T)); 对T是需要深拷贝的自定义类型失效
for (size_t i = 0; i < v.size(); i++) {
tmp[i] = v._start[i];//T如果是自定义类型,T的赋值运算符重载需要是深拷贝;if整个循环体实现的是vector的拷贝,=实现的是T的深拷贝
}
delete[] _start;//如果T是浅拷贝,这个地方会导致问题,因为delete[ ]首先会调用所存储自定义类型的析构,再释放这片空间,导致tmp存储自定义类型的指针都是野指针
}
_start = tmp;
_finish = _start + sz;//这个时候如果调用size()就会出问题,因为finish已经是野指针了
_end_of_storage = _start + n;
}
}
2.5 拷贝构造

cpp
vector(const vector<T>& v) ://构造函数统一需要进行初始化,因为reserve会用_finish-_start来算sz,要拷贝多少数据,如果不初始化,有可能是随机值会出问题
_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr) {
if (v._start == nullptr) {
_start = nullptr;
_finish = nullptr;
_end_of_storage = nullptr;
}
else {
T* tmp = new T[v.capacity()];
//memcpy(tmp, v._start, sizeof(T) * v.size()); 对T是需要深拷贝的自定义类型失效
for (size_t i = 0; i < v.size(); i++) {
tmp[i] = v._start[i];
}
_start = tmp;
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
}
vector(const vector<T>& v) ://需要进行初始化,因为reserve会用_finish-_start来算sz,要拷贝多少数据,如果不初始化,有可能是随机值会出问题
_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr) {
reserve(v.capacity());
for (auto e: v) {
push_back(e);
}
}
2.6 n个值进行构造
cpp
//手动实现
vector(size_t n, const T& val = T()) ://这个地方T如果是自定义类型,就调用T的默认构造函数;如果是内置类型,C++也做了处理,int()就是0,int(1)就是1
_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr) {
T* tmp = new T[n];
for (size_t i = 0; i < n; i++) {
tmp[i] = val;
}
_start = tmp;
_finish = _start + n;
_end_of_storage = _start + n;
}
//复用reserve
vector(size_t n, const T& val = T()) :
_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr) {
resize(n, val);
}
//重载
vector(int n, const T& val = T()) :
_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr) {
resize(n, val);
}
2.7 迭代器构造
cpp
template<class InputIterator>//因为不知道迭代器的具体类型
vector(InputIterator first, InputIterator last) {
while (first != last) {
push_back(*first);
first++;
}
}
void test_myvector3() {
string str("maritime");
vector<char> v(str.begin(), str.end());//其它类型的迭代器,数据类型能匹配上
for (auto e : v)
cout << e << " ";
cout << endl;
int a[] = { 1,2,3,4 };
vector<int> v1(a, a + 4);//原生指针作为迭代器
for (auto e : v1)
cout << e << " ";
cout << endl;
vector<char> v2(v.begin(), v.end());//同一类型的迭代器
for (auto e : v2)
cout << e << " ";
cout << endl;
}
cpp
//编译报错:无法取消引用类型为"InputIterator"的操作数
void test_myvector4() {
vector<int>(10, 2);//想构造10个2的vector,但是系统默认匹配给到了迭代器构造,因为n个val构造的n是size_t,那么语句的10和2默认是int,所以会走最匹配的迭代器构造,但是int不是指针,不能解引用
}
把10处理为10u,unsigned int就会是n个val构造,size_t是unsigned int
cpp
void test_myvector4() {
vector<int>(10u, 2);
}
下面也没问题,因为单参数构造函数支持隐式类型转换,"1111"可以转化为const string&,10从int转化为size_t,不能走迭代器构造
cpp
vector<string> v2(10, "1111");
那么是怎么解决这个问题的呢?函数重载,重载一版n为int的函数
cpp
vector(size_t n, const T& val = T()) :
_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr) {
resize(n, val);
}
//重载
vector(int n, const T& val = T()) :
_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr) {
resize(n, val);
}
2.8 赋值运算符重载
cpp
vector<T>& operator=(const vector<T>& v){
T* tmp = new T[v.capacity()];
//memcpy(tmp, v._start, sizeof(T) * v.size()); 对T是需要深拷贝的自定义类型失效
for (size_t i = 0; i < v.size(); i++) {
tmp[i] = v._start[i];
}
_start = tmp;
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
return *this;
}
牛刀小试
- 下面程序的输出结果正确的是( )
A.程序运行崩溃
B.1 2 3 4 5 0 6 7 8 9
C.1 2 3 4 5 6 7 8 9
D.1 2 3 4 6 7 8 9
cpp
int main(){
int ar[] ={1,2,3,4,0,5,6,7,8,9};
int n = sizeof(ar) / sizeof(int);
vector<int> v(ar, ar+n);
vector<int>::iterator it = v.begin();
while(it != v.end()){
if(*it != 0)
cout<<*it;
else
v.erase(it);
it++;
}
return 0;
}
A
- 下面关于迭代器失效的描述哪个是错误的( )(多选)
A.vector的插入操作一定会导致迭代器失效
B.vector的插入操作有可能不会导致迭代器失效
C.vector的删除操作只会导致指向被删除元素及后面的迭代器失效
D.vector的删除操作只会导致指向被删除元素的迭代器失效
AD
cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans=0,total;
for(int i=0;i<32;i++){
total=0;
for(auto e : nums){
total+= (e>>i)&1;
}
if(total%3){
ans|=(1<<i);
}
}
return ans;
}
};