1.vector(类模板)
-
vector是表示可变大小数组的序列容器。
-
就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
-
vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
-
vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
-
vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
-
与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。
2.vector的使用
2.1构造函数
cpp
void test()
{
//无参构造
vector<int> v1;
//构造并初始化n个val
vector<int> v2(10, 6);//10个6
vector<int> v3(10);//10个0
//拷贝构造
vector<int> v4(v2);
//initializer_list初始化构造
vector<int> v5 = { 5,4,3,6,7 };
//使用迭代器进行初始化构造
vector<int> v6(v5.begin() + 2, v5.end());
//构造二维数组
vector<vector<int>> vv(5, vector<int>(6, 6));
for (int i = 0; i < vv.size(); i++)
{
for (int j = 0; j < vv[0].size(); j++)
{
cout << vv[i][j] << ' ';
}
cout << endl;
}
}
2.2 operator=
用于赋值(不是拷贝构造)
cpp
void test4()
{
//operator=
vector<char> v1 = { 'a', 'b', 'c' };
vector<char> v2;
for (auto e : v1)
{
cout << e << ' ';
}
cout << endl;
for (auto e : v2)
{
cout << e << ' ';
}
cout << endl;
v2 = v1;
for (auto e : v1)
{
cout << e << ' ';
}
cout << endl;
for (auto e : v2)
{
cout << e << ' ';
}
}
2.3 begin() 和 end()
begin()用于返回第一个有效数据的迭代器
end()用于放回最后一个有效数据的下一位的迭代器
cpp
void test1()
{
vector<int> v{ 1,2,3,4,5,6 };
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << ' ';
it++;
}
}
2.4 rbegin() 和 rend()
rbegin()用于返回最后一个有效数据的反向迭代器
rend()用于返回第一个有效数据的前一位的反向迭代器
cpp
void test2()
{
vector<int> v{ 1,2,3,4,5,6 };
vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << ' ';
rit++;
}
}
reverse_iterator类型的迭代器++向左偏移,--向右偏移
2.5 cbegin(), cend(), crbegin(), crend()
cbegin()和cend():无论对象是否为const对象,返回类型都是const_iterator
crbegin()和crend():无论对象是否为const对象,返回类型都是const_reverse_iterator
2.6 size()
用于返回vector对象中有效数据的个数(元素的个数),它不一定等于容量大小
2.7 capacity()
用于返回vector对象中的容量大小
2.8 empty()
判断vector对象是否为空,空返回true,非空返回false
2.9 reserve()
用于请求更改将容量改为n,当n大于当前容量时,该函数会导致容器重新分配其存储,并将容量增加到n并拷贝原数据,释放原空间;其他情况下(n小于等于当前容量),该函数不会导致容量重新分配。
cpp
void test10()
{
size_t sz;
vector<int> v;
v.reserve(100);
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';
cout << &v[0] << endl;//每次扩容都是异地扩容
}
}
}
2.10 resize()
用于调整vector对象有效数据的个数至n个;
n小于当前的size时,有效数据减少至前n个,超出部分删除(并销毁);
n大于当前的size时,通过尾插来将有效数据增加至n个,将新数据初始化为val;
n大于当前的capacity时,会自动重新分配容量。
cpp
void test3()
{
vector<int> v(9);
cout << v.size() << endl;
cout << v.capacity() << endl;
cout << v.empty() << endl;
v.clear();
cout << v.empty() << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
v.resize(5);//默认0填充
v.reserve(16);
cout << v.size() << endl;
cout << v.capacity() << endl;
for (int e : v)
{
cout << e << ' ';
}
}
2.11 shrink_to_fit()
用于请求将容器减少至其有效数据个数的大小,可能会导致空间重新分配
cpp
void test11()
{
vector<int> v = { 0,1,2,3,4,5,6,7,8,9 };
cout << v.size() << ' ' << v.capacity() << endl;
v.reserve(19);
cout << v.size() << ' ' << v.capacity() << endl;
cout << &v[0] << endl;
//请求缩小容量以适应其大小
v.shrink_to_fit();
cout << v.size() << ' ' << v.capacity() << endl;
cout << &v[0] << endl;
}
2.12 operator[]
用于通过下标进行访问,返回引用
cpp
void test12()
{
//[]访问
vector<int> v(10);
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << ' ';
}
cout << endl;
for (int i = 0; i < v.size(); i++)
{
v[i] = i;
}
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << ' ';
}
}
2.13 at()
用于通过小标进行访问,返回引用
与operator[]的区别:operator[]使用assert暴力检查越界情况,at越界会抛出out_of_range异常
cpp
void test13()
{
//at
vector<int> v(10);
for (int i = 0; i < v.size(); i++)
{
cout << v.at(i) << ' ';
}
cout << endl;
for (int i = 0; i < v.size(); i++)
{
v.at(i) = i;
}
for (int i = 0; i < v.size(); i++)
{
cout << v.at(i) << ' ';
}
}
2.14 front() 和 back()
front:返回第一个有效数据的引用(不能对空vector使用front)
back:返回最后一个有效数据的引用(不能对空vector使用back)
cpp
void test14()
{
vector<int> v = { 1,2,3,4,5 };
//front访问
cout << v.front() << endl;
//改变front返回的对象
v.front() = 11;
cout << v.front() << endl;
//back访问
cout << v.back() << endl;
//改变back返回的对象
v.back() = 55;
cout << v.back() << endl;
//空容器内使用front和back是未定义行为
}
2.15 data()
返回一个直接指针,该指针指向vector对象内部用于存储数据的内存数组,即指向第一个有效数据
cpp
void test15()
{
vector<int> v = { 1,2,3 };
//data返回数组第一个元素的指针
cout << v.data() << endl;
cout << &v[0] << endl;
*v.data() = 11;
cout << *v.data() << endl;
}
2.16 assign()
重新分配新的内容,覆盖原有的数据,并相应修改其大小
cpp
void test16()
{
vector<int> v = { 1,2,3,4,5 };
vector<int> v1 = { 6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 };
cout << v.capacity() << endl;
cout << v.size() << endl << endl;
cout << v1.capacity() << endl;
cout << v1.size() << endl << endl;
//范围分配新内容
v1.assign(v.begin() + 2, v.end());
cout << v1.capacity() << endl;
cout << v1.size() << endl;
for (auto e : v1)
{
cout << e << ' ';
}
cout << endl << endl;
//n个元素作为新内容
v1.assign(16, 99);
cout << v1.capacity() << endl;
cout << v1.size() << endl;
for (auto e : v1)
{
cout << e << ' ';
}
}
2.17 push_back() 和 pop_back()
push_back:用于尾插数据
pop_back:用于尾删数据
cpp
void test17()
{
//push_back
vector<int> v;
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
for (auto e : v)
{
cout << e << ' ';
}
//pop_back
while (!v.empty())
{
v.pop_back();
}
cout << endl << v.empty() << endl;
}
2.18 insert()
用于在pos位置插入数据
cpp
template <class t>
void print(vector<t>& v)
{
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
}
void test18()
{
//insert
vector<int> v = { 0,1,2,3,4,5,6,7,8,9 };
//iterator insert (iterator position, const value_type& val);--在迭代器指向的位置插入,右边数据右移
cout << *v.insert(v.begin() + 5, 55) << endl;//放回该位置的迭代器
print(v);
// void insert (iterator position, size_type n, const value_type& val);--在迭代器位置开始插入n个数据,其他数据右移
v.insert(v.begin() + 3, 3, 666);
print(v);
//template <class inputiterator>//--函数模板--迭代器指定范围插入
//void insert(iterator position, inputiterator first, inputiterator last);
list<int> ls;
for (int i = 0; i < 5; i++)
{
ls.push_back(i + 30);
}
v.insert(v.begin() + 8, ls.begin(), ls.end());
print(v);
vector<int> v1 = { 1,2,3,4,5 };
vector<int>::iterator it = v1.begin();
//迭代器失效问题
//v1.insert(it, 2);
//cout << *it << endl;
it = v1.insert(it, 2);
cout << *it << endl;
}
2.19 erase()
用于删除部分数据
cpp
void test19()
{
vector<int> v = { 0,1,2,3,4,5 };
//iterator erase (iterator position);
//返回一个迭代器,指向元素的新位置,该元素紧跟在函数调用擦除的最后一个元素之后。
cout << *v.erase(v.begin() + 2) << endl;
print(v);
//范围erase
//iterator erase (iterator first, iterator last);
cout << *v.erase(v.begin(), v.begin() + 3) << endl;
print(v);
}
2.20 swap()
用于交换数据
cpp
void test21()
{
//swap
vector<int> v1 = { 1,2,3 };
vector<int> v2 = { 4,5,6 };
v1.swap(v2);
print(v1);//4,5,6
print(v2);//1,2,3
}
2.21 clear()
清除所有数据
cpp
void test22()
{
//clear
vector<int> v = { 1,2,3 };
v.clear();
cout << v.empty() << endl;
}
2.22 emplace()
用于在指定位置构造并插入新数据,它通过将传递的参数转发给构造函数来实现新数据的就地构造。若插入后大小超出当前容量,则会发生自动重新分配存储空间。在向量的任意位置插入数据会导致后续数据的移动,因此相较于其他序列容器,效率较低。
在C++中,选择使用emplace而非insert的情况主要有以下几点:
-
构造新对象:当你需要在容器中插入一个新对象,并且希望通过传递构造参数来直接在容器中构造该对象时,使用emplace是更合适的选择。emplace允许直接传递构造函数的参数,而不需要先创建对象再插入。
-
避免不必要的拷贝或移动:使用insert时,通常需要先创建一个对象,然后将其拷贝或移动到容器中。emplace则可以避免这种额外的拷贝或移动操作,因为它在容器中直接构造对象。
-
性能考虑:在性能敏感的场合,尤其是当插入的对象较大或复杂时,使用emplace可以减少不必要的开销,从而提高性能。
-
支持可变参数:如果你需要插入的对象的构造函数接受可变数量的参数,emplace可以直接转发这些参数,而insert则不支持这种灵活性。
cpp
void test24()
{
//emplace
vector<int> v = { 0,1,2,3,4,5 };
for (int i = 0; i < v.size(); i++)
{
cout << &v[i] << endl;
}
auto it = v.emplace(v.begin() + 3, 100);
print(v);
for (int i = 0; i < v.size(); i++)
{
cout << &v[i] << endl;
}
cout << *it << endl;
}
2.23 emplace_back
用于末尾构造并插入,与push_back不同, emplace_back直接在容器中构造对象,而不是先构造对象再将其拷贝或移动到容器中。这可以提高效率,特别是对于需要进行昂贵拷贝或移动操作的对象。
2.24 get_allocator
主要用于获取与容器关联的分配器(allocator),分配器的主要职责是负责动态内存的分配和释放,使得 C++ 标准库的容器能够高效地管理内存。
cpp
int main()
{
std::vector<int> myvector;
int* p;
unsigned int i;
// allocate an array with space for 5 elements using vector's allocator:
p = myvector.get_allocator().allocate(5);
// construct values in-place on the array:
for (i = 0; i < 5; i++)
myvector.get_allocator().construct(&p[i], i);
std::cout << "The allocated array contains:";
for (i = 0; i < 5; i++)
std::cout << ' ' << p[i];
std::cout << '\n';
// destroy and deallocate:
for (i = 0; i < 5; i++)
myvector.get_allocator().destroy(&p[i]);
myvector.get_allocator().deallocate(p, 5);
return 0;
}
2.25 关系运算符重载(非成员函数)
用于比较
2.26 swap()(非成员函数)
2.27 vector<bool>
vector<bool>是 C++ 标准模板库(STL)中提供的一个特殊化的std::vector容器。与其他类型的 vector不同,vector<bool>并不直接存储布尔值,而是采用了一种特化的实现方式,以节省内存使用。
具体来说,vector<bool>会使用位压缩的方式,将多个布尔值压缩存储为单个位(bit),即每一个布尔值只占用 1 位,而不是通常情况下的 1 字节(8 位)。这一特性使得 vector<bool> 的内存使用更加高效,当存储大量布尔值时,可以显著减少内存占用。
然而,这种特殊化也带来了一些不便,例如:
-
vector<bool> 的元素类型是一个代理对象(proxy object),而不是真正的 bool 类型。这种实现方式会导致某些常见的操作(如取地址)变得复杂。
-
由于使用位处理,vector<bool> 的访问性能可能比普通的 vector<T> 低,因为每次访问都需要进行一些位运算。
在需要存储布尔值的情况下,如果对内存效率敏感并且接受其带来的不便(如性能和使用复杂性),可以使用 vector<bool>。否则,通常建议使用 std::vector<char> 或 std::vector<uint8_t>等其他类型,以保证更方便的语义和更好的性能。
3.vector扩容问题
测试代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。
这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
resize在开空间的同时还会进行初始化,影响size。
cpp
void test8()
{
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';
cout << &v[0] << endl;//每次扩容都是异地扩容
}
}
}
void test10()
{
size_t sz;
vector<int> v;
v.reserve(100);
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';
cout << &v[0] << endl;//每次扩容都是异地扩容
}
}
}
4.迭代器失效问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了 ,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)
vector可能引起迭代器失效的操作有:
1.会引起其底层空间改变的操作 ,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back,emplace,emplace_back等。
2.指定位置元素的删除操作--erase:erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了
迭代器失效的解决办法:在使用前,对迭代器重新赋值即可。
cpp
void test20()
{
vector<int> v = { 0,1,2,3,4,5 };
vector<int>::iterator it = v.begin();
//迭代器失效问题
//v.erase(it);
//cout << *it << endl;
it = v.erase(it);
cout << *it << endl;
}
5.内存检查
系统对内存的检查是一种抽查机制,动态开辟的通常在delete/free的时候检查,所以没有delete/free的时候越界使用可能暴漏不出来问题
6.find算法
vector自生没有find,不过算法库里面有find函数模板
7.= default
= default 是 C++11 中引入的一种特殊语法,用于指示编译器生成一个类的默认构造函数、拷贝构造函数、拷贝赋值运算符、移动构造函数或移动赋值运算符 。通过使用 = default,程序员可以显式指定希望编译器生成的函数,而不是手动实现这些函数。
8.调用问题
解决关键是:编译器的选择问题
解决1:
解决2:
9.initializer_list
std::initializer_list
是 C++11 引入的一个标准库类模板,用于支持列表初始化(List Initialization)和统一初始化。它允许你使用花括号 {}
语法来初始化具有多个元素的对象,提供了一种简单且易于使用的方式来创建和传递多个值。
std::initializer_list
实际上是一个轻量级的对象,可以承载一个常量数组的引用和大小信息。它常用在构造函数和函数参数中,使得我们能够方便地传递一组值。
cpp
#include <iostream>
#include <initializer_list>
void printValues(std::initializer_list<int> values)
{
for (auto value : values)
{
std::cout << value << " ";
}
std::cout << std::endl;
}
int main()
{
printValues({10, 20, 30, 40}); // 传递initializer_list
return 0;
}
cpp
#include <iostream>
#include <initializer_list>
class MyVector
{
public:
MyVector(std::initializer_list<int> values)
{
for (auto value : values)
{
// 处理每个传入的值
std::cout << value << " ";
}
std::cout << std::endl;
}
};
int main()
{
MyVector vec = {1, 2, 3, 4, 5}; // 使用initializer_list初始化
return 0;
}
注意:
- std::initializer_list 只能用于常量对象(const)
2.使用 std::initializer_list 可以方便地进行传参和初始化。
- 支持不同类型的容器(如std::vector,std::array 等)支持列表初始化。
10.vector的部分模拟实现
cpp
#pragma once
#include <string>
#include <vector>
#include <assert.h>
#include <iostream>
#include <algorithm>
#include <list>
namespace myVector
{
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef T* reverse_iterator;
typedef const T* const_reverse_iterator;
//迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
const_iterator cbegin() const
{
return _start;
}
const_iterator cend() const
{
return _finish;
}
//迭代器区间初始化
//函数模板--目的是支持任意容器可以进行迭代器区间初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//这里的缺省值为什么这么写?
//因为T可能是自定义类型,无参构造并引用匿名对象
//对于内置类型:
//c以前的构造: int i = 0;//0
//c++现在可以: int j(1);//1
// int k = int();//0
// int x = int(2);//2
//c++内置类型进行了升级,也有构造,为了兼容这样的场景:
//当模板函数参数可以是自定义类型或者内置类型,给缺省值的形式进行统一
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//vector<int> v = {1,2,3,4,5};
vector(std::initializer_list<T> il)
{
reserve(il.size());
for (auto e : il)
{
push_back(e);
}
}
//default强制编译器生成默认成员函数
vector() = default;
vector(const vector<T>& v)
{
//提前扩容,减少扩容次数
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//利用传值会进行拷贝再直接交换,局部变量v会自动销毁
//现代写法
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
//析构
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();
T* tmp = new T[n];
if (_start)
{
//memcpy是浅拷贝
//memcpy(tmp, _start, sizeof(T) * oldsize);
//改为深拷贝
for (size_t i = 0; i < oldsize; i++)
{
tmp[i] = _start[i];//赋值是深拷贝
}
delete[] _start;
}
_start = tmp;
_finish = oldsize + _start;
_end_of_storage = _start + n;
}
}
size_t capacity() const
{
return _end_of_storage - _start;
}
size_t size() const
{
return _finish - _start;
}
T& operator[](size_t i)
{
assert(i < size() && i >= 0);
return *(_start + i);
}
const T& operator[](size_t i) const
{
assert(i < size() && i >= 0);
return *(_start + i);
}
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = x;
++_finish;
}
void pop_back()
{
assert(size() > 0);
--_finish;
}
iterator insert(iterator pos, const T& x)
{
if (_finish == _end_of_storage)
{
//防止迭代器因扩容导致失效
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
//更新新的迭代器
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
}
private:
iterator _start;//有效数据的起始位置,闭区间
iterator _finish;//最后的有效数据的下一位,开区间
iterator _end_of_storage;//这段容量的最后位置的下一个,开区间
};
}