C++ STL vector详解

news2024/11/14 18:36:03

1. vector简介

template<class T, class Alloc = allocator<T>>
class vector;

vector是一个可以动态增长的数组,T是要存储的元素类型。vector可以像数组一样,用下标+[]来访问元素,如:

int arr[] = {1,2,3,4};
for (int i = 0; i < 4; ++i)
    cout << arr[i] << " ";
cout << endl;
vector<int> v = {1,2,3,4};
for (int i = 0; i < 4; ++i)
    cout << v[i] << " ";

输出:

1 2 3 4
1 2 3 4

2. vector的构造函数

本文不考虑allocator的问题,尽可能简化vector的使用方式。

2.1 无参构造

explicit vector();

最常见的构造,构造一个空的vector。

vector<int> v;

2.2 n个val

explicit vector(size_t n, const T& val = T());

用n个val来初始化vector。

vector<int> v1(5); // 用5个0来初始化vector
vector<int> v2(3, 1); // 用3个1来初始化vector

for (auto e : v1)
    cout << e << " ";
cout << endl;
for (auto e : v2)
    cout << e << " ";

输出:

0 0 0 0 0
1 1 1 

由于explicit的作用,下面的写法并不是调用这个构造函数,而是initializer_list。

vector<int> v = {3,1};

2.3 拷贝构造

vector(const vector<T>& v);

用一个vector来拷贝初始化另一个vector。

vector<int> v1 = {1,2,3,4,5};
vector<int> v2(v1);
vector<int> v3 = v1; // 这里的v3调用的也是拷贝构造,等价于vector v3(v1);
for (auto e : v1) cout << e << " ";
cout << endl;
for (auto e : v2) cout << e << " ";
cout << endl;
for (auto e : v3) cout << e << " ";

输出:

1 2 3 4 5 
1 2 3 4 5 
1 2 3 4 5 

2.4 迭代器构造

template<class InputIterator>
vector(InputIterator first, InputIterator last);

用任意类型的迭代器构造。

int arr[] = {1,2,3};
vector<int> v(arr, arr + sizeof(arr)/sizeof(arr[0]));
for(auto e : v) cout << e << " ";

输出:

1 2 3

2.5 列表初始化

vector(initializer_list<T> il);

使用列表初始化。

vector<int> v1 = {1,2,3};
vector<int> v2{4,5,6};
for (auto e : v1) cout << e << " ";
cout << endl;
for (auto e : v2) cout << e << " ";

输出:

1 2 3
4 5 6

3. vector的迭代器

vector的迭代器是随机访问迭代器(random access iterator),提供begin,end,rbegin,rend等接口。

iterator begin();
const_iterator begin() const;

iterator end();
const_iterator end() const;

reverse_iterator rbegin();
const_reverse_iterator rbegin() const;

reverse_iterator rend();
const_reverse_iterator rend() const;

举一个正向迭代器使用的例子:

vector<int> v = {1,2,3,4,5};
auto it = v.begin();
while (it != v.end)
{
    cout << *it << " ";
    ++it;
}

输出:

1 2 3 4 5

上述代码和下面的代码是等价的,因为范围for的底层也是用迭代器实现的。

vector<int> v = {1,2,3,4,5};
for (auto e : v)
    cout << e << " ";

4. vector的容量相关接口

4.1 size

size_t size() const;

获取数据个数。

vector<int> v = {1,2,3,4,5};
cout << v.size() << endl;

输出:

5

4.2 capacity

size_t capacity() const;

获取容量大小。

vector<int> v;
for (int i = 0; i < 30; ++i) v.push_back(i);
cout << v.capacity() << endl;

VS2022的环境下输出:

42

4.3 empty

bool empty() const;

判断vector是否为空。

vector<int> v;
cout << v.empty() << endl; // true
v.push_back(1);
cout << v.empty() << endl; // false

输出:

1
0

4.4 resize

void resize(size_t n, const T& val = T());

把vector的size改为n。若n小于当前size,则只保留前n个数据;若n大于当前size,则插入val,直到size为n。

vector<int> v = {1,2,3,4};

v.resize(6); // n大于当前size
for (auto e : v) cout << e << " ";
cout << endl;

v.resize(2); // n小于当前size
for (auto e : v) cout << e << " ";
cout << endl;

v.resize(4, 5); // n大于当前size
for (auto e : v) cout << e << " ";

输出:

1 2 3 4 0 0
1 2
1 2 5 5

4.5 reserve

void reserve(size_t n);

改变capacity,至少改为n,保留足够的空间。

vector<int> v;
v.reserve(100);
cout << v.capacity() << endl;

输出示例:

100

5. vector的增删查改

5.1 push_back和pop_back

void push_back(const T& val);
void pop_back();

尾插和尾删,push_back在vector的最后插入val,pop_back删除vector的最后一个元素。

vector<int> v = {1,2,3,4,5};
v.pop_back();
for (auto e : v) cout << e << " ";
cout << endl;
v.push_back(6);
for (auto e : v) cout << e << " ";

输出:

1 2 3 4
1 2 3 4 6

5.2 全局find + vector的迭代器

template<class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& val);

使用algorithm中的find,配合vector的迭代器,查找[first,last)中有没有val,若找到了,返回指向val的迭代器;若没找到,返回last。

vector<int> v = {1,2,3,4,5};
auto it = find(v.begin(), v.end(), 3);
if (it != v.end()) cout << *it << endl;
else cout << "没找到" << endl;

输出:

3

5.3 insert + erase

iterator insert(iterator pos, const T& val);
iterator erase(iterator pos);

插入和删除元素。insert负责在pos位置插入val,并返回指向val的迭代器。erase负责删除pos位置的值,并返回指向删除元素的下一个元素的迭代器。

配合find,在3前面插入30,再删除所有偶数的代码如下:

vector<int> v = {6,2,3,4,5,10,12};
auto it = find(v.begin(), v.end(), 3);
if (it != v.end()) 
{
    it = v.insert(it, 30);
    // 此时it指向30
    cout << *it << endl;
}
for (auto e : v) cout << e << " ";
cout << endl;

// 删除所有偶数
it = v.begin();
while (it != v.end())
{
    if (*it % 2 == 0) it = v.erase(it);
    else ++it;
}
for (auto e : v) cout << e << " ";

输出:

30
6 2 30 3 4 5 10 12
3 5

5.4 swap成员函数 + 全局函数

// 成员函数
void swap(vector<T> v);

// 全局函数
template<class T>
void swap(vector<T>& v1, vector<T>& v2);

交换2个vector。

vector<int> v1 = {1,2,3,4,5};
vector<int> v2 = {6,7,8,9,0};
for (auto e : v1) cout << e << " ";
cout << endl;
for (auto e : v2) cout << e << " ";
cout << endl;

v1.swap(v2);
for (auto e : v1) cout << e << " ";
cout << endl;
for (auto e : v2) cout << e << " ";
cout << endl;

swap(v1, v2);
for (auto e : v1) cout << e << " ";
cout << endl;
for (auto e : v2) cout << e << " ";

输出:

1 2 3 4 5
6 7 8 9 0
6 7 8 9 0
1 2 3 4 5
1 2 3 4 5
6 7 8 9 0

5.5 operator[]

T& operator[](size_t pos);
const T& operator[](size_t pos) const;

像数组一样,使用下标+[]访问vector。

vector<int> v = {1,2,3,4,5};
for (size_t i = 0; i < v.size(); ++i)
    cout << v[i] << " ";

输出:

1 2 3 4 5

6. 迭代器失效问题

6.1 场景1:插入 + 扩容

所有的插入操作都有可能导致扩容,从而导致迭代器失效,如resize、reserve、insert、assign、push_back等。这是因为,扩容的步骤是:

  1. 开辟一块新的更大的空间。
  2. 把旧的空间的数据拷贝到新的空间中去。
  3. 释放旧的空间。

而扩容前,迭代器指向了旧的空间,扩容后,旧的空间被释放了,迭代器指向的空间已经被销毁,迭代器失效。

下面的代码中,在reserve之后,迭代器it失效,出现野指针问题。

vector<int> v = {1,2,3,4,5};
auto it = v.begin();
v.reserve(100);
while (it != v.end()) cout << *it++ << " ";

6.2 场景2:删除

删除操作,如erase,会挪动数据覆盖删除,此时迭代器指向的元素可能已经改变,甚至指向非法的空间。如:

vector<int> v = {1,2,3,4,5};
auto it1 = v.end() - 2; // 指向4
auto it2 = v.end() - 1; // 指向5

v.erase(v.begin());

上面的代码中,erase之后,it1指向的元素已经不是4了(此时it1指向5),而it2指向了非法的空间。这是因为,erase的底层会覆盖删除1,会把1后面的元素向低地址处挪动1格。

erase前
1   2   3   4   5
            ^   ^
           it1 it2
erase后
2   3   4   5
            ^   ^
           it1 it2

6.3 解决方案

迭代器失效后,要对迭代器重新赋值。

下面的程序,本意是删除vector中所有的偶数,但是erase之后,迭代器失效了,程序的行为是未定义的。

vector<int> v = {2,4,5,6,7,8,10};
auto it = v.begin();
while (it != v.end())
{
    if (*it % 2 == 0)
        v.erase(it);

    ++it;
}

解决方案:在erase之后,对it重新赋值。注意到erase会返回指向删除元素的下一个元素的迭代器,当找到偶数并删除后,it应该接受erase的返回值;若it指向的不是偶数,it++即可。

vector<int> v = {2,4,5,6,7,8,10};
auto it = v.begin();
while (it != v.end())
{
    if (*it % 2 == 0)
        it = v.erase(it);
    else
        ++it;
}

for (auto e : v) cout << e << " ";

输出:

5 7

7. 练习

7.1 只出现一次的数字I

只出现一次的数字I原题链接icon-default.png?t=N7T8https://leetcode.cn/problems/single-number/description/使用范围for,取出所有数字,异或到一起。根据异或的特性,相同的数字会被抵消,最后的结果就是只出现一次的数字。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (auto n : nums) ret ^= n;
        return ret;
    }
};

7.2 杨辉三角

杨辉三角原题链接icon-default.png?t=N7T8https://leetcode.cn/problems/pascals-triangle/description/总共numRows行,每行有i+1个元素,两端的元素是1,其余元素(i,j)是(i-1,j-1)和(i-1,j)相加的结果。 遍历vv,类似于遍历二维数组,可以用下标+[],注意边界由size决定。对于vector类型,v[0]等价于v.front(),v[size()-1]等价于v.back()。

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> vv(numRows); // numRows行
        //for (int i = 0; i < vv.size(); ++i)
        for (int i = 0; i < numRows; ++i)
        {
            // 每行有i+1个元素
            vv[i].resize(i + 1);
            vv[i].front() = vv[i].back() = 1;
            //for (int j = 1; j < vv[i].size() - 1; ++j)
            for (int j = 1; j < i; ++j)
            {
                vv[i][j] = vv[i-1][j-1] + vv[i-1][j];
            }
        }

        return vv;
    }
};

7.3 删除有序数组中的重复项

删除有序数组中的重复项原题链接icon-default.png?t=N7T8https://leetcode.cn/problems/remove-duplicates-from-sorted-array/description/由于数组是有序的,考虑使用下标i遍历数组,若遇到和前一个元素不相同的元素,就存储到下标j对应的空间中。注意i和j都要从1开始。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int j = 1;
        for (int i = 1; i < nums.size(); ++i)
        {
            if (nums[i] != nums[i-1])
            {
                nums[j++] = nums[i];
            }
        }

        return j;
    }
};

7.4 只出现一次的数字II

只出现一次的数字II原题链接icon-default.png?t=N7T8https://leetcode.cn/problems/single-number-ii/description/最简单的想法是,用哈希表存储每个元素出现的次数(7.1、7.5和7.6也可以用这种方式解决)。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int, int> um;
        for (auto n : nums) ++um[n];
        for (auto& [n, cnt] : um)
            if (cnt == 1) return n;
        return 0;
    }
};

受到7.1题目的启发,考虑使用位运算求解本题。由于只有一个数字ret出现一次,其余数字出现三次,那么假设对所有数字二进制补码中的第i位求和为sum,就有两种情况:

  1. sum%3==0,说明ret的第i位是0;
  2. sum%3==1,说明ret的第i位是1。

这样就能获取到ret的每一位了。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 0; i < 32; ++i)
        {
            // 所有数字的第i位求和
            int sum = 0;
            for (auto n : nums)
                sum += ((n >> i) & 1);

            // 若求和后不是3的倍数,ret的第i位是1
            if (sum % 3)
                ret |= (1 << i);
        }

        return ret;
    }
};

7.5 只出现一次的数字III

只出现一次的数字III原题链接icon-default.png?t=N7T8https://leetcode.cn/problems/single-number-iii/description/可以使用哈希表解决,但最优解依然是使用位运算。若把所有数异或到一块得到sum,假设sum二进制中的第i位是1。那么就可以把所有数字分成两组,其中一组的第i位是1,另一组的第i位是0,那么只出现一次的两个数字就被分到了不同的组中。把其中一组的所有数异或到一块去,就能得到其中一个只出现一次的数字,再把这个数字异或sum就能得到另一个只出现一次的数字。

其中,n&-n可以取出n的二进制中最低位的1,但是n=INT_MIN不能这么算,因为-n越界了,要单独考虑。INT_MIN的最高位是1,其余位都是0。

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int sum = 0;
        for (auto n : nums) sum ^= n;

        // 取出sum最低位的1
        // INT_MIN最低位的1是它本身
        // n&-n可以取出n最低位的1
        int bit = sum == INT_MIN ? INT_MIN : sum & -sum;

        int i = 0;
        for (auto n : nums)
            if (n & bit)
                i ^= n;

        return {i, i ^ sum};
    }
};

7.6 数组中出现次数超过一半的数字

数组中出现次数超过一半的数字原题链接icon-default.png?t=N7T8https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&&tqId=11181&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking本题依然可以使用哈希表统计次数;也可以先排序,再找出中位数。

这里介绍一种算法:候选法。定义n为可能的众数,cnt为当前数字出现的次数。遍历数组,若当前元素和n相等,那么cnt++;若当前元素不等于n,那么cnt--。当cnt减到0时,就把n设置成当前元素,cnt设置成1。遍历完数组后,若有元素出现次数超过数组长度的一半,那么这个元素一定是n。由于题目描述中说明,一定有一个元素出现次数超过数组长度的一半,所以n为满足题目要求的元素。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int>& numbers) {
        int n = 0, cnt = 0;
        for (auto num : numbers)
            if (cnt == 0) n = num, cnt = 1;
            else n == num ? ++cnt : --cnt;

        return n;
    }
};

7.7 电话号码的数字组合

电话号码的数字组合原题链接icon-default.png?t=N7T8https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/维护一个字符串,取出每个数字字符对应的所有字母字符,并插入到字符串中。当digits中的所有数字字符都遍历完时,把得到的字符串插入到vector中,回退到上一层,找其余的字母排列。

如何取出每个数字对应的所有字母字符呢?可以考虑使用unordered_map,我这里使用vector,用下标对应数字,改下标对应的字符串表示所有可能的字母字符。建议定义成静态的成员变量,因为只需要维护一份vector,节省空间。

vector<string>和要维护的string建议先用reserve保留足够的空间,虽然本题用处不大,因为digits的size是在[0,4]的范围,扩容消耗不大。

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        if (digits.empty()) return {};

        vector<string> coms;
        // 计算容量
        int newCapacity = 1;
        for (auto ch : digits)
        {
            int n = ch - '0';
            newCapacity *= numToStr[n].size();
        }
        coms.reserve(newCapacity);

        string com;
        com.reserve(digits.size());
        Combinations(digits, 0, com, coms);
        return coms;
    }
private:
    void Combinations(const string& digits, int idx, string& com, vector<string>& coms)
    {
        if (idx == digits.size())
        {
            coms.push_back(com);
            return;
        }

        int n = digits[idx] - '0';
        const string& str = numToStr[n];
        for (auto ch : str)
        {
            com.push_back(ch);
            Combinations(digits, idx+1, com, coms);
            com.pop_back();
        }
    }

    static const vector<string> numToStr;
};

const vector<string> Solution::numToStr = {
    "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

8. 模拟实现vector

8.1 迭代器

我们可以用3个迭代器来维护vector(SGI版本的STL就是这么实现的),并且给nullptr的缺省值。

iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;

其中iterator就是原生的指针。

typedef T* iterator;
typedef const T* const_iterator;

其中,_start标识空间的起始位置,_finish标识最后一个有效数据的位置,_end_of_storage标识空间的结束位置。举个例子,假设此时vector中的size是4,capacity是8,那么内存的大概分布如下:

   1      2      3      4      ?      ?      ?      ?      !
   ^                           ^                           ^
_start                      _finish                     _end_of_storage

 那么几个关键位置就一目了然了。

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

8.2 构造函数和析构函数

注意,v(5,10)可能会匹配迭代器区间构造函数,而不是按照n个val初始化的构造函数,所以要提供一个vector(size_t, const T&)的版本。

vector() = default;

template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

vector(size_t n, const T& val = T())
{
	reserve(n);
	while (n--)
	{
		push_back(val);
	}
}

vector(int n, const T& val = T())
{
	reserve(n);
	while (size() < n)
	{
		push_back(val);
	}
}

~vector()
{
	if (_start)
	{
		delete[] _start;
		_start = _finish = _end_of_storage = nullptr;
	}
}

8.3 拷贝构造和operator=

对于拷贝构造,我没有用现代写法,因为使用迭代器构造时的扩容会有消耗。注意不能使用memcpy直接按字节拷贝,因为T可能是自定义类型,memcpy会导致浅拷贝。

void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}

vector(const vector<T>& v)
	:_start(new T[v.size()])
{
	_end_of_storage = _finish = _start + v.size();
	iterator it = begin();
	for (auto e : v)
	{
		*it++ = e;
	}
}

// 现代写法
//vector(const vector<T>& v)
//{
//	vector<T> tmp(v.begin(), v.end());
//	swap(tmp);
//}

vector<T>& operator=(vector<T> tmp)
{
	swap(tmp);
	return *this;
}

8.4 容量相关接口

reserve在扩容时,不能使用memcpy拷贝数据,因为T可能是自定义类型,memcpy会导致浅拷贝。另外,由于size和capacity是指针相减计算得来的,扩容后更新_finish时不能写_finish=_start+size(),因为此时的_start已经改变,但是_finish还未更新,size会算出错误的结果。正确的做法是,提前记录size。

size_t size() const
{
	return _finish - _start;
}

size_t capacity() const
{
	return _end_of_storage - _start;
}

bool empty()
{
	return _start == _finish;
}

void resize(size_t n, const T& val = T())
{
	if (n <= size())
	{
		_finish = _start + n;
		return;
	}

	if (n > capacity())
	{
		// 至少扩容2倍
		reserve(max(n, 2 * capacity()));
	}

	while (size() < n)
	{
		push_back(val);
	}
}

void reserve(size_t n)
{
	if (n > capacity())
	{
		// 扩容
		size_t sz = size();
		iterator tmp = new T[n];
		if (_start)
		{
			// 拷贝数据
			for (size_t i = 0; i < sz; ++i)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}

		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

8.5 访问相关接口

T& operator[](size_t pos)
{
	assert(pos < size());

	return _start[pos];
}

const T& operator[](size_t pos) const
{
	assert(pos < size());

	return _start[pos];
}

T& front()
{
	return *_start;
}

const T& front() const
{
	return *_start;
}

T& back()
{
	return *(_finish - 1);
}

const T& back() const
{
	return *(_finish - 1);
}

8.6 插入和删除

insert插入时,先挪动数据再插入。注意reserve会导致迭代器失效,需要提前记录pos相对于_start的偏移量,reserve后需要对pos重新赋值。erase直接挪动数据覆盖即可。

挪动数据不能使用memcpy,因为T可能是自定义类型,memcpy会导致浅拷贝。

void push_back(const T& val)
{
	insert(end(), val);
}

void pop_back()
{
	erase(end() - 1);
}

iterator insert(iterator pos, const T& val = T())
{
	assert(pos >= _start);
	assert(pos <= _finish);

	if (_finish == _end_of_storage)
	{
		// 扩容导致迭代器失效
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		pos = _start + len;
	}

	// 挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}

	*pos = val;
	++_finish;

	return pos;
}

iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

	// 挪动数据,覆盖删除
	iterator begin = pos + 1;
	while (begin < _finish)
	{
		*(begin - 1) = *begin;
		++begin;
	}

	--_finish;
	return pos;
}

8.7 测试

观察调试窗口:

8.8 完整实现

#include<iostream>
#include<assert.h>
using namespace std;

namespace xbl
{
	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() = default;

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		vector(size_t n, const T& val = T())
		{
			reserve(n);
			while (n--)
			{
				push_back(val);
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			while (size() < n)
			{
				push_back(val);
			}
		}

		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		vector(const vector<T>& v)
			:_start(new T[v.size()])
		{
			_end_of_storage = _finish = _start + v.size();
			iterator it = begin();
			for (auto e : v)
			{
				*it++ = e;
			}
		}

		// 现代写法
		//vector(const vector<T>& v)
		//{
		//	vector<T> tmp(v.begin(), v.end());
		//	swap(tmp);
		//}

		vector<T>& operator=(vector<T> tmp)
		{
			swap(tmp);
			return *this;
		}

		size_t size() const
		{
			return _finish - _start;
		}

		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		
		bool empty()
		{
			return _start == _finish;
		}

		void resize(size_t n, const T& val = T())
		{
			if (n <= size())
			{
				_finish = _start + n;
				return;
			}

			if (n > capacity())
			{
				// 至少扩容2倍
				reserve(max(n, 2 * capacity()));
			}

			while (size() < n)
			{
				push_back(val);
			}
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				// 扩容
				size_t sz = size();
				iterator tmp = new T[n];
				if (_start)
				{
					// 拷贝数据
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}

		T& operator[](size_t pos)
		{
			assert(pos < size());

			return _start[pos];
		}

		const T& operator[](size_t pos) const
		{
			assert(pos < size());

			return _start[pos];
		}

		T& front()
		{
			return *_start;
		}

		const T& front() const
		{
			return *_start;
		}

		T& back()
		{
			return *(_finish - 1);
		}

		const T& back() const
		{
			return *(_finish - 1);
		}

		void push_back(const T& val)
		{
			insert(end(), val);
		}

		void pop_back()
		{
			erase(end() - 1);
		}

		iterator insert(iterator pos, const T& val = T())
		{
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == _end_of_storage)
			{
				// 扩容导致迭代器失效
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = _start + len;
			}

			// 挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			++_finish;

			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			// 挪动数据,覆盖删除
			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}

			--_finish;
			return pos;
		}
	private:
		iterator _start = nullptr; // 有效数据起始位置
		iterator _finish = nullptr; // 有效数据结束位置
		iterator _end_of_storage = nullptr; // 空间结束位置
	};

	void test_vector()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		for (size_t i = 0; i < v.size(); ++i)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v1(v);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v2;
		v2 = v1;
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<string> vs1;
		vs1.push_back("11111");
		vs1.push_back("22222");
		vs1.push_back("33333");
		vs1.push_back("44444");
		vs1.push_back("55555");
		for (auto e : vs1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<string> vs2(vs1);
		for (auto e : vs2)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<string> vs3;
		vs3 = vs2;
		for (auto e : vs2)
		{
			cout << e << " ";
		}
		cout << endl;

		vs3.pop_back();
		for (auto e : vs3)
		{
			cout << e << " ";
		}
		cout << endl;
		vs3.pop_back();
		vs3.pop_back();
		vs3.pop_back();
		vs3.pop_back();
		//vs3.pop_back();
		vs3.push_back("666");
		vs3.push_back("777");
		vs3.push_back("888");
		for (auto e : vs3)
		{
			cout << e << " ";
		}
		cout << endl;

		auto pos = find(vs3.begin(), vs3.end(), "777");
		if (pos != vs3.end())
		{
			vs3.erase(pos);
		}
		else
		{
			cout << "没找到" << endl;
		}
		for (auto e : vs3)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v3;
		v3.resize(5);
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;

		v3.resize(3);
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;

		v3.resize(6, 1);
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v4(10, 2);
		for (auto e : v4)
		{
			cout << e << " ";
		}
		cout << endl;

		// 杨辉三角
		int height = 8;
		vector<vector<int>> vv1(8, vector<int>());
		for (int i = 0; i < vv1.size(); ++i)
		{
			vv1[i].resize(i + 1, 0);
			vv1[i].front() = vv1[i].back() = 1;
			for (int j = 1; j < vv1[i].size() - 1; ++j)
			{
				vv1[i][j] = vv1[i - 1][j - 1] + vv1[i - 1][j];
			}
		}

		for (const auto& v : vv1)
		{
			for (auto n : v)
			{
				cout << n << " ";
			}
			cout << endl;
		}

		vector<vector<int>> vv2(vv1);
		for (const auto& v : vv2)
		{
			for (auto n : v)
			{
				cout << n << " ";
			}
			cout << endl;
		}

		vector<vector<int>> vv3;
		vv3 = vv2;
		for (const auto& v : vv3)
		{
			for (auto n : v)
			{
				cout << n << " ";
			}
			cout << endl;
		}
	}
}

输出:

1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
11111 22222 33333 44444 55555
11111 22222 33333 44444 55555
11111 22222 33333 44444 55555
11111 22222 33333 44444
666 777 888
666 888
0 0 0 0 0
0 0 0
0 0 0 1 1 1
2 2 2 2 2 2 2 2 2 2
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1468202.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[极客挑战2019]HTTP

这道题考察的是http请求头字段的含义和使用&#xff1b; 具体如下 Referer:来源地址 User-Agent:客户端配置信息&#xff1a;浏览器类型、版本、系统类型等 X-Forwarded-For:代理地址&#xff0c;即数据发出的地址 开始解题&#xff1a;&#xff08;对我这初学者真的烧脑&a…

【机器学习】特征工程之特征选择

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

抖音视频评论数据提取软件|抖音数据抓取工具

一、开发背景&#xff1a; 在业务需求中&#xff0c;我们经常需要下载抖音视频。然而&#xff0c;在网上找到的视频通常只能通过逐个复制链接的方式进行抓取和下载&#xff0c;这种操作非常耗时。我们希望能够通过关键词自动批量抓取并选择性地下载抖音视频。因此&#xff0c;为…

Pycharm服务器配置与内网穿透工具结合实现远程开发的解决方法

文章目录 一、前期准备1. 检查IDE版本是否支持2. 服务器需要开通SSH服务 二、Pycharm本地链接服务器测试1. 配置服务器python解释器 三、使用内网穿透实现异地链接服务器开发1. 服务器安装Cpolar2. 创建远程连接公网地址 四、使用固定TCP地址远程开发 本文主要介绍如何使用Pych…

STM32CubeMX FOC工程配置(AuroraFOC)

一. 简介 哈喽&#xff0c;大家好&#xff0c;今天给大家带来基于AuroraFOC开发板的STM32CubeMX的工程配置&#xff0c;主要配置的参数如下: 1. 互补PWM输出 2. 定时器注入中断ADC采样 3. SPI配置 4. USB CDC配置 5. RT Thread配置 大家如果对这几部分感兴趣的话&#xff0c…

C语言——实用调试技巧——第2篇——(第23篇)

坚持就是胜利 文章目录 一、实例二、如何写出好&#xff08;易于调试&#xff09;的代码1、优秀的代码2、示范&#xff08;1&#xff09;模拟 strcpy 函数方法一&#xff1a;方法二&#xff1a;方法三&#xff1a;有弊端方法四&#xff1a;对方法三进行优化assert 的使用 方法五…

浅析DPDK内存管理:Mempool

文章目录 前言Mempool工作机制Mempool数据结构rte_mempool_opsMempool操作接口rte_mempool_create&#xff1a;创建Mempoolrte_mempool_get&#xff1a;申请对象rte_mempool_put&#xff1a;释放对象 相关参考 前言 DPDK提供了一套内存池管理机制&#xff0c;以提供高效分配固…

了解人工智能的13个细分领域

人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;作为当今最热门和前沿的技术之一&#xff0c;已经在各种领域发挥着越来越重要的作用。随着人工智能技术的不断进步和应用&#xff0c;AI的细分领域也越来越多。目前&#xff0c;根据AI的应用领域和特…

flink源码分析 - 获取调用位置信息

flink版本: flink-1.11.2 调用位置: org.apache.flink.streaming.api.datastream.DataStream#flatMap(org.apache.flink.api.common.functions.FlatMapFunction<T,R>) 代码核心位置: org.apache.flink.api.java.Utils#getCallLocationName() flink算子flatmap中调用了一…

【深度学习笔记】3_6 代码实现softmax-regression

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 3.6 softmax回归的从零开始实现 这一节我们来动手实现softmax回归。首先导入本节实现所需的包或模块。 import torch import torchvision import numpy as np import…

神经网络系列---权重初始化方法

文章目录 权重初始化方法Xavier初始化&#xff08;Xavier initialization&#xff09;Kaiming初始化&#xff0c;也称为He初始化LeCun 初始化正态分布与均匀分布Orthogonal InitializationSparse Initializationn_in和n_out代码实现 权重初始化方法 Xavier初始化&#xff08;X…

java面试题之SpringMVC篇

Spring MVC的工作原理 Spring MVC的工作原理如下&#xff1a; DispatcherServlet 接收用户的请求找到用于处理request的 handler 和 Interceptors&#xff0c;构造成 HandlerExecutionChain 执行链找到 handler 相对应的 HandlerAdapter执行所有注册拦截器的preHandler方法调…

Java知识点一

hello&#xff0c;大家好&#xff01;我们今天开启Java语言的学习之路&#xff0c;与C语言的学习内容有些许异同&#xff0c;今天我们来简单了解一下Java的基础知识。 一、数据类型 分两种&#xff1a;基本数据类型 引用数据类型 &#xff08;1&#xff09;整型 八种基本数…

计算机网络-网络互联与互联网(一)

1.常用网络互联设备&#xff1a; 1层物理层&#xff1a;中继器、集线器2层链路层&#xff1a;网桥、交换机3层网络层&#xff1a;路由器、三层交换机4层以上高层&#xff1a;网关 2.网络互联设备&#xff1a; 中继器Repeater、集线器Hub&#xff08;又叫多端口中继器&#xf…

一分钟 由浅入深 学会Navigation

目录 1.官网正式概念 1.1 初认知 2.导入依赖 2.1 使用navigation 2.2 safe Args插件-> 传递数据时用 3.使用Navigation 3.1 搭建初始框架 3.2 确定action箭头的属性 3.3 为Activity添加NavHostFragment控件 3.4 NavController 管理应用导航的对象 3.5 数据传递(单…

leetcode单调栈

739. 每日温度 请根据每日 气温 列表&#xff0c;重新生成一个列表。对应位置的输出为&#xff1a;要想观测到更高的气温&#xff0c;至少需要等待的天数。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 例如&#xff0c;给定一个列表 temperatures [73, …

基于SpringBoot的停车场管理系统

基于SpringBootVue的停车场管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 前台首页 停车位 个人中心 管理员界面 摘要 摘要&#xff1a;随着城市化进程的…

基于django的购物商城系统

摘要 本文介绍了基于Django框架开发的购物商城系统。随着电子商务的兴起&#xff0c;购物商城系统成为了许多企业和个人创业者的首选。Django作为一个高效、稳定且易于扩展的Python web框架&#xff0c;为开发者提供了便捷的开发环境和丰富的功能模块&#xff0c;使得开发购物商…

Java零基础 - 条件运算符

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

Vue(学习笔记)

什么是Vue Vue是一套构建用户界面的渐进式框架 构建用户界面&#xff1a; 基于数据渲染出用户可以看到的界面 渐进式&#xff1a; 所谓渐进式就是循序渐进&#xff0c;不一定非得把Vue中的所有API都学完才能开发Vue&#xff0c;可以学一点开发一点 创建Vue实例 比如就上面…