vecto底层模拟实现

附一份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;

}

牛刀小试

  1. 下面程序的输出结果正确的是( )
    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

  1. 下面关于迭代器失效的描述哪个是错误的( )(多选)
    A.vector的插入操作一定会导致迭代器失效
    B.vector的插入操作有可能不会导致迭代器失效
    C.vector的删除操作只会导致指向被删除元素及后面的迭代器失效
    D.vector的删除操作只会导致指向被删除元素的迭代器失效

AD

137. 只出现一次的数字 II

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;
    }
};
相关推荐
一切尽在,你来2 小时前
C++多线程教程-1.2.2 C++标准库并发组件的设计理念
开发语言·c++
m0_561359672 小时前
代码热更新技术
开发语言·c++·算法
兩尛2 小时前
c++知识点1
java·开发语言·c++
冉佳驹2 小时前
C++11 ——— 列表初始化、移动语义、可变参数模板、lamdba表达式、function包装器和bind包装器
c++·可变参数模板·移动构造·移动赋值·function包装器·bind包装器·lamdba表达式
xu_yule2 小时前
算法基础—组合数学
c++·算法
Tansmjs3 小时前
C++中的工厂模式变体
开发语言·c++·算法
naruto_lnq3 小时前
多平台UI框架C++开发
开发语言·c++·算法
爱装代码的小瓶子3 小时前
【C++与Linux基础】文件篇(8)磁盘文件系统:从块、分区到inode与ext2
linux·开发语言·c++
naruto_lnq3 小时前
分布式日志系统实现
开发语言·c++·算法