Chapter 7: The vector class

Series Article Directory



foreword

vector is a class for variable-sized arrays


Introduction and use of vector

Introduction to vector

Documentation introduction of vector

insert image description here

  1. A vector is a sequence container representing a variable-sized array .
  2. Just like arrays, vectors also use contiguous storage space to store elements. That means that the elements of the vector can be accessed using subscripts, which is as efficient as an array. But unlike an array, its size can be changed dynamically, and its size will be automatically handled by the container.
  3. Essentially, vector uses a dynamically allocated array to store its elements. When new elements are inserted, the array needs to be resized in order to increase storage space. **The method is to allocate a new array and then move all elements to this array. **In terms of time, this is a relatively expensive task, because the vector does not re-allocate the size every time a new element is added to the container.
  4. vector allocation space strategy: vector will allocate some additional space to accommodate possible growth, because the storage space is larger than the actual storage space required. Different libraries employ different strategies for weighing space usage and reallocation. But in any case, the reallocation should be of logarithmically growing interval size, so that inserting an element at the end is done in constant time complexity.
  5. Therefore, vector takes up more storage space, in order to gain the ability to manage storage space, and grow dynamically in an efficient way.
  6. Compared with other dynamic sequence containers (deque, list and forward_list), vector is more efficient when accessing elements, and adding and removing elements at the end is relatively efficient . For other deletion and insertion operations that are not at the end, it is less efficient. Better than list and forward_list unified iterators and references
  7. The difference between vector and string : char is used in vector, although they all store char in an array at the bottom layer, they are still different. The space pointed to by the s object has '\0' at the end. Both string and vector have their own special requirements (interface), so the functions are different.

The use of vector

1. The definition of vector

insert image description here
insert image description here

(constructor) constructor declaration Interface Description
vector() (emphasis) No parameter construction
vector(size_type n, const value_type& val = value_type()) Construct and initialize n vals
vector (const vector& x); (emphasis) copy construction
vector (InputIterator first, InputIterator last); Initialize construction using iterators

Vector construction code demonstration

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

	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 ch : v)
	{
    
    
		cout << ch << " ";
	}
	cout << endl;
  
	vector<int> copy_v(v);
	for (auto ch : copy_v)
	{
    
    
		cout << ch << " ";
	}
	cout << endl;
}

int main()
{
    
    
	test_vector1();
	return 0;
}
void test_vector2()
{
    
    
	vector<int> v1(10, 1);
	for (auto e : v1)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	vector<int> v2(v1.begin(), v1.end());//迭代器区间左闭右开
	for (auto e : v2)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
  
	string s1("hello world");
	vector<int> s2(s1.begin(), s1.end());//可以传其他容器的迭代器
	for (auto e : s2)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}

2. The use of vector iterator

Except for string and vector, the remaining containers use iterators
insert image description here

The use of iterators Interface Description
begin +end (emphasis) Get the iterator/const_iterator of the first data position, get the iterator/const_iterator of the next position of the last data
rbegin + rend Get the reverse_iterator of the last data position, get the reverse_iterator of the previous position of the first data

insert image description here

insert image description here

Vectot's iterator uses code demonstration

void test_vector3()
{
    
    
	vector<int> v;
	//vector<int> ::reverse_iterator rit = v.rbegin();
	auto rit = v.rbegin();
	while (rit != v.rend())
	{
    
    
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

3. Vector space growth problem

insert image description here

capacity space Interface Description
size Get the number of data
capacity Get the size of the capacity
empty Determine whether it is empty
resize (emphasis) Change the size of the vector
reserve (emphasis) Change the capacity of the vector
  1. When the capacity code is run under vs and g++ respectively, it will be found that the capacity under vs increases by 1.5 times, and g++ increases by 2 times. This question is often investigated. Don’t think that the capacity of the vector is doubled. The specific increase is defined according to the specific needs. vs is the PJ version STL, and g++ is the SGI version STL.
  2. Reserve is only responsible for opening up space. If you know how much space is needed, reserve can alleviate the cost defect of vector capacity expansion.
  3. Resize will also be initialized while opening the space, which will affect the size.
  4. Try not to shrink the capacity. C++ does not support in-place shrinking. Generally, a new space will be re-opened, the data will be copied, and the original space will be released at last.
void test_vector4()
{
    
    
	vector<char> v;
	cout << v.max_size() << endl;
}
void TestVectorExpand()
{
    
    
	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';
		}
	}
}
//结果
making v grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141

//g++运行结果:linux下使用的STL基本是按照2倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{
    
    
	vector<int> v;
	size_t sz = v.capacity();
	v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
	cout << "making bar 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';
		}
	}
}

4. Add, delete, check and modify vector

insert image description here

Vector add, delete, check and modify Interface Description
push_back (emphasis) tail plug
pop_back (emphasis) tail delete
find find. (Note that this is an algorithm module implementation, not a member interface of vector)
insert Insert val before position
erase Delete the data at the position
swap Exchange the data space of two vectors
operator[] (emphasis) access like an array

Vector insertion and deletion operation code demonstration

insert image description here
insert image description here

5. Other interfaces of vector

insert image description here

Can be used for all containers

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

	vector<int>::iterator pos = find(v.begin(), v.end(), 3);

	if (pos != v.end())
	{
    
    
		v.insert(pos, 20);
	}

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

	pos = find(v.begin(), v.end(), 3);
    //迭代器失效要重新找
	v.erase(pos);

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

Iterator ranges are left-closed and right-open

6. The use of vector in OJ

1. Yang Hui Triangle OJ

insert image description here

insert image description here

2. Phone number letter combination OJ

vector depth analysis

built-in type constructor

Built-in types also have constructors, which are used to implement interval construction

	template<class T>
	void f()
	{
    
    
		T x = T();//调用默认构造
		cout << x << endl;
	}

	void test_vector2()
	{
    
    
		//内置类型有构造函数
		int i = int();
		int j = int(1);
		
		//int* p = int* ();

		f<int>();
		f<int*>();
		f<double>();
	}
//[first, last)
template <class InputIterator>//允许类的成员函数再是函数模板
vector(InputIterator first, InputIterator last)
{
    
    
    while (first != last)
    {
    
    
        push_back(*first);
        ++first;
    }
}

Iterator invalidation problem

inert()

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

			if (_finish == _end_of_storage)
			{
    
    //扩容
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//迭代器pos失效
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
    
    
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;

		}

insert image description here

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

			if (_finish == _end_of_storage)
			{
    
    //扩容
				size_t = len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);

				//扩容后更新,解决pos失效问题,让pos指向新空间的元素地址
				pos = _start + len; 
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
    
    
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;

		}
void insert(iterator pos, const T& val)
{
    
    //不能用引用来传入拷贝的临时对象,因为其具有常性
    assert(pos <= _finish);
    assert(pos >= _start);

    if (_finish == _end_of_storage)
    {
    
    
        size_t  len = pos - _start;
        reserve(capacity() == 0 ? 4 : capacity() * 2);
        pos = _start + len; 
    }

    iterator end = _finish - 1;
    while (end >= pos)
    {
    
    
        *(end + 1) = *end;
        --end;
    }
    *pos = val;
    ++_finish;
}	

void test_vector4()
{
    
    
  vector<int> v1;
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  v1.push_back(5);
  
  for (size_t i = 0; i < v1.size(); ++i)
  {
    
    
        cout << v1[i] << " ";
  }
  cout << endl;

  yyf::iterator pos = find(v1.begin(), v1.end(), 2);
  v1.insert(pos, 6);

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

  (*pos)++;//想让2+1,但是pos指向了新插入的元素6

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

}

insert image description here

iterator insert(iterator pos, const T& val)
{
    
    //不能用引用来传入拷贝的临时对象,因为其具有常性
    assert(pos <= _finish);
    assert(pos >= _start);

    if (_finish == _end_of_storage)
    {
    
    //扩容
        size_t  len = pos - _start;
        reserve(capacity() == 0 ? 4 : capacity() * 2);

        //扩容后更新,解决pos失效问题
        pos = _start + len; 
    }

    iterator end = _finish - 1;
    while (end >= pos)
    {
    
    
        *(end + 1) = *end;
        --end;
    }
    *pos = val;
    ++_finish;

    return pos;
}

But it is better not to do this, so we think that pos is invalid after insert and can no longer be used

erase()

void test_vector5()
	{
    
    
		std::vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

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

		auto pos = find(v1.begin(), v1.end(), 2);

		if (pos != v1.end())
		{
    
    
			v1.erase(pos);
		}
		(*pos)++;
		//vs下std的pos失效了,不能访问并且进行强制检查

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

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

    iterator start = pos + 1;
    while(start != _finish)
    {
    
    
        *(start - 1) = *start;
        ++start;
    }
    --_finish;

}

void test_vector6()
{
    
    
    yyf::vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.push_back(5);

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

    // 要求删除所有的偶数
    std::vector<int>::iterator it = v1.begin();
    while (it != v1.end())
    {
    
    
        if (*it % 2 == 0)
        {
    
    
            it = v1.erase(it);//g++中std的erase会指向删除数据的后一个数据的新位置
        }
        else
        {
    
    
            ++it;
        }
    }
  
    for (size_t i = 0; i < v1.size(); ++i)
    {
    
    
        cout << v1[i] << " ";
    }
    cout << endl;
}
iterator erase(iterator pos)
{
    
    
    assert(pos <= _finish);
    assert(pos >= _start);

    iterator start = pos + 1;
    while(start != _finish)
    {
    
    
        *(start - 1) = *start;
        ++start;
    }
    --_finish;


    return pos;
}

After erasing, we think that pos is invalid and can no longer be used.
There will be a mandatory check under vs, and the iterator cannot be accessed after erasing.
g++ does not enforce checks, which will lead to a series of undefined results

Anonymous object lifetime extension

vector(size_t n, const T& val = T())
{
    
    	
    
}
  1. const T& val = T() is to create an anonymous object, through the constructor, and then reference
  1. The anonymous object life cycle is only in the current line, because no one will use it after this line
  2. A const reference will extend the life cycle of the anonymous object to the end of the reference object field, because the reference is an alias, which is the anonymous object
    object
  3. Anonymous objects and temporary objects are const

vector memory layout

insert image description here

insert image description here

deep copy vs shallow copy

//拷贝构造
vector(const vector<T>& v)
{
    
    
    reserve(v.capacity());
    //memcpy(_start, v._start, sizeof(T) * v.size());
    for (size_t i = 0; i < v.size(); ++i)
    {
    
    
        _start[i] = v._start[i];
    }
    _finish = _start + v.size();
    _end_of_storage = _start + v.capacity();
}

//赋值重载
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<T>& operator=(vector<T> v)
{
    
    //先构造拷贝了一个v,然后进行引用交换,最后析构掉原来的*this.
    swap(v);
    return *this;
}

//扩容
void reserve(size_t n)
{
    
    
    if (n > capacity())
    {
    
    
        //提前记录size
        size_t sz = size();
        T* tmp = new T[n];
        //开新空间
        if (_start)
        {
    
    
            //memcpy(tmp, _start, sizeof(T) * size());
            for (size_t i = 0; i < sz; ++i)
            {
    
    
                tmp[i] = _start[i];
            }
            //拷贝旧数据
            delete[] _start;
        }
        _start = tmp;
        _finish = tmp + sz;
        _end_of_storage = tmp + n;

    }
}


void test_vector11()
{
    
    //类模板类型为自定义类型时要深拷贝
    yyf::vector<std::string> v3(3, "111111111111111111111");
    for (auto e : v3)
    {
    
    
        cout << e << " ";
    }
    cout << endl;

    vector<std::string> v4(v3);
    for (auto e : v4)
    {
    
    
        cout << e << " ";
    }
    cout << endl;

    v4.push_back("2222222222222222222");
    v4.push_back("2222222222222222222");
    v4.push_back("2222222222222222222");
    for (auto e : v4)
    {
    
    
        cout << e << " ";
    }
    cout << endl;



    class Solution {
    
    
    public:
        vector<vector<int>> generate(int numRows) {
    
    
            vector<vector<int>> nums;
            nums.resize(numRows, vector<int>());
            for (int i = 0; i < nums.size(); i++)
            {
    
    
                nums[i].resize(i + 1, 0);
                nums[i][0] = nums[i][nums[i].size() - 1] = 1;
            }
            for (size_t i = 0; i < nums.size(); ++i)
            {
    
    
                for (size_t j = 0; j < nums[i].size(); ++j)
                {
    
    
                    if (nums[i][j] == 0)
                    {
    
    
                        nums[i][j] = nums[i - 1][j] + nums[i - 1][j - 1];
                    }
                }
            }
            return nums;
        }
    };


    yyf::vector<vector<int>> ret = Solution().generate(5);
    for (size_t i = 0; i < ret.size(); ++i)
    {
    
    
        for (size_t j = 0; j < ret[i].size(); ++j)
        {
    
    
            cout << ret[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

memcpy is a copy of memory in binary format. It copies the contents of one memory space to another memory space intact.
If the copied element is a custom type, memcpy is efficient and error-free.
If the object involves resource management, you must not use memcpy to copy between objects, because memcpy is a shallow copy, otherwise it may cause wild pointer problems or even program crashes.

Summarize

vector is a class that can resize arrays of any type.
It will be the top of the mountain, and you can see all the small mountains. ——Du Fu

Guess you like

Origin blog.csdn.net/yanyongfu523/article/details/131839955