1.什么是vector?
在C++中,std::vector是标准模板库(STL)中的一种动态数组容器,它可以存储任意类型的元素,并且能够自动调整大小。std::vector提供了许多方便的成员函数,使得对数组的操作更加简单和高效。
vector声明:
template <class T, class Alloc = allocator<T> > ;
这是 std::vector 的一般模板定义。它使用了两个模板参数 T 和 Alloc,其中 T 表示容器中存储的元素类型,Alloc 表示容器的内存分配器类型,默认为 std::allocator。该模板定义了一个通用的 vector 类模板,用于存储任意类型的元素。
template <class Alloc> class vector<bool,Alloc>;
这是特化版本的 std::vector,用于存储 bool 类型的元素。这是因为 C++ 中的 std::vector 不能直接存储布尔类型,而是使用了一种特殊的实现来节省空间。这个特化版本接受一个模板参数 Alloc,表示容器的内存分配器类型,但是对于 std::vector 来说,这个分配器类型实际上不会影响它的内部实现,因为 std::vector 会进行特殊优化来节省空间。
总结:
template <class T, class Alloc = allocator<T> > ;
是 std::vector 的一般模板定义,用于存储任意类型的元素。
template <class Alloc> class vector<bool,Alloc>;
是特化版本的 std::vector,用于存储 bool 类型的元素。
特点和优势:
动态数组:std::vector内部实现了动态内存管理,可以根据需要自动调整存储容量,当需要添加新元素时,会自动分配更多的内存空间。
随机访问:可以通过下标或迭代器来访问元素,支持快速的随机访问。
自动扩容:当元素个数超过当前容量时,std::vector会自动重新分配更大的内存块,以便容纳更多元素。
连续存储:std::vector的元素在内存中是连续存储的,这样可以提高访问效率。
支持动态调整大小:可以使用 push_back() 和 pop_back() 方法在尾部添加或删除元素,也可以使用 resize() 方法动态调整容器大小。
与其它动态序列容器相比(deque, list and forward_list): vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好
使用std::vector前需要包含头文件 <vector>
,例如:#include <vector>
。
下面是一个使用std::vector的简单示例:
#include <iostream> #include <vector> int main() { std::vector<int> vec; // 声明一个存储int类型的vector // 在vector中添加元素 vec.push_back(10); vec.push_back(20); vec.push_back(30); // 遍历vector并输出元素 for (const auto& element : vec) { std::cout << element << " "; } std::cout << std::endl; return 0; }
输出:
10 20 30
2.vector的使用
成员类型参照
2.1 vector构造函数
explicit vector (const allocator_type& alloc = allocator_type()); //构造一个空容器,不包含任何元素。 explicit vector (size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type());//构造一个包含n 个元素的容器。每个元素都是val的副本。 template <class InputIterator> vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type()); //构造一个包含与范围[first,last)一样多的元素的容器,其中每个元素均从该范围内的相应元素按相同的顺序构造。 vector (const vector& x);//构造一个容器,其中包含x中每个元素的副本(按相同顺序)。
参数:
alloc
分配器对象。
容器保留并使用该分配器的内部副本。
成员类型allocator_type是容器使用的内部分配器类型,在向量中定义为其第二个模板参数( Alloc )的别名。
如果allocator_type是默认分配器(没有状态)的实例,则这是不相关的。
n
初始容器大小(即构造时容器中的元素数量)。
成员类型size_type是无符号整型。
val
填充容器的值。容器中的n 个元素中的每一个都将被初始化为该值的副本。
成员类型value_type是容器中元素的类型,在向量中定义为其第一个模板参数 ( T ) 的别名。
first, last
将迭代器输入到范围中的初始位置和最终位置。使用的范围是[first,last) ,包括first和last之间的所有元素,包括first指向的元素但不包括last指向的元素。
函数模板参数InputIterator应该是一个输入迭代器类型,它指向可以构造value_type对象的类型的元素。
x
另一个相同类型的向量对象(具有相同的类模板参数T和Alloc),其内容可以被复制或获取。
il
一个initializer_list对象。
这些对象是根据初始值设定项列表声明符自动构造的。
成员类型value_type是容器中元素的类型,在向量中定义为其第一个模板参数 ( T ) 的别名。
构造函数的分析和使用
explicit vector (const allocator_type& alloc = allocator_type());
这是默认构造函数,用于创建一个空的std::vector对象。它可以使用默认的分配器allocator_type,也可以通过参数alloc指定自定义的分配器。
explicit vector (size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type());
这个构造函数用于创建包含n个元素的std::vector对象,所有元素都使用val进行初始化。n表示初始化的元素数量,val表示初始化的元素值,alloc表示使用的分配器。
template <class InputIterator> vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());
这是一个模板构造函数,可以通过迭代器范围来创建std::vector对象。它接受两个参数 first 和 last,分别表示迭代器的起始和结束位置。在构造函数内,使用 first 和 last 迭代器所指向的元素范围来初始化std::vector对象,alloc表示使用的分配器。
vector (const vector& x);
这是拷贝构造函数,用于创建一个新的std::vector对象,它与参数x是相同类型的,内容也相同。在创建新的std::vector对象时,会复制x中的元素到新对象中,但是新对象与原对象是独立的,修改一个对象不会影响另一个对象。
示例:
#include <iostream> #include <vector> int main () { //构造函数的使用顺序与上述相同: std::vector<int> first; // 默认构造 std::vector<int> second (4,100); // 初始化四个值为 100 的整数 std::vector<int> third (second.begin(),second.end()); // 使用迭代器的拷贝 std::vector<int> fourth (third); // 拷贝构造 // 迭代器构造函数也可用于从数组构造: int myints[] = {16,2,77,29}; std::vector<int> fifth (myints, myints + sizeof(myints) / sizeof(int) ); return 0; }
这里再提一下赋值运算符的重载
vector& operator= (const vector& x);
类的拷贝赋值运算符重载函数,它用于将一个已存在的 std::vector 对象 x 的内容复制给另一个已存在的 std::vector 对象,并返回赋值后的对象的引用。
该运算符重载函数使得两个 std::vector 对象的内容相同,但是它们是独立的,修改一个对象不会影响另一个对象。
示例:
std::vector<int> vec1 = {1, 2, 3, 4}; std::vector<int> vec2 = {5, 6, 7}; vec1 = vec2; // 将 vec2 的内容赋值给 vec1
在上面的示例中,vec1 的内容将变成 {5, 6, 7},与 vec2 相同。vec2 本身不受影响。
2.2 vector迭代器(Iterators)函数
2.2.1 begin()
iterator begin();
const_iterator begin() const;
iterator begin();
和 const_iterator begin() const;
是 std::vector 类的成员函数,分别用于获取可修改元素的迭代器和获取常量元素的迭代器。
iterator begin();
: 返回一个指向 std::vector 容器中第一个元素的可修改迭代器。通过这个迭代器,可以对容器中的元素进行修改。
const_iterator begin() const;
: 返回一个指向 std::vector 容器中第一个元素的常量迭代器。通过这个迭代器,可以遍历容器中的元素,但不能对容器中的元素进行修改。
使用示例:
#include <vector> #include <iostream> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 使用 begin() 获取可修改迭代器 std::vector<int>::iterator it = myVector.begin(); // 使用 begin() 获取常量迭代器 std::vector<int>::const_iterator cit = myVector.begin(); // 修改第一个元素的值 *it = 10; // 错误:常量迭代器不允许修改元素的值 // *cit = 20; // 输出容器中的元素 for (int num : myVector) { std::cout << num << " "; } return 0; }
在上面的示例中,我们创建了一个存放整数的 std::vector 对象 myVector,然后使用 begin() 获取可修改迭代器 it 和常量迭代器 cit。通过可修改迭代器 it,我们可以修改容器中的元素值;但是通过常量迭代器 cit,我们不能修改容器中的元素值。最后,我们遍历并输出容器中的元素,输出结果将是 “10 2 3 4 5”。
2.2.2 end()
iterator end();
const_iterator end() const;
iterator end();
和const_iterator end() const;
是 std::vector 类的成员函数,分别用于获取可修改元素的迭代器和获取常量元素的迭代器,指向容器中最后一个元素的下一个位置。
iterator end();
: 返回一个指向 std::vector 容器中最后一个元素的下一个位置的可修改迭代器。通过这个迭代器,可以对容器中的元素进行修改。
const_iterator end() const;
: 返回一个指向 std::vector 容器中最后一个元素的下一个位置的常量迭代器。通过这个迭代器,可以遍历容器中的元素,但不能对容器中的元素进行修改。
使用示例:
#include <vector> #include <iostream> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 使用 end() 获取可修改迭代器 std::vector<int>::iterator it = myVector.end(); // 使用 end() 获取常量迭代器 std::vector<int>::const_iterator cit = myVector.end(); // 修改最后一个元素的值 *(--it) = 100; // 错误:常量迭代器不允许修改元素的值 // *(--cit) = 200; // 输出容器中的元素 for (int num : myVector) { std::cout << num << " "; } return 0; }
在上面的示例中,我们创建了一个存放整数的 std::vector 对象 myVector,然后使用 end() 获取可修改迭代器 it 和常量迭代器 cit。通过可修改迭代器 it,我们可以修改容器中的元素值;但是通过常量迭代器 cit,我们不能修改容器中的元素值。最后,我们遍历并输出容器中的元素,输出结果将是 “1 2 3 4 100”。注意,在修改最后一个元素的值时,我们使用了 --it 将迭代器移动到最后一个元素的位置。因为 end() 返回的迭代器指向容器中最后一个元素的下一个位置,所以在修改最后一个元素时,需要先将迭代器移动到最后一个元素的位置。
2.2.3 rbegin()
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rbegin();
和const_reverse_iterator rbegin() const;
是 std::vector 类的成员函数,用于获取逆向迭代器。
reverse_iterator rbegin();
: 返回一个指向 std::vector 容器中最后一个元素的可修改逆向迭代器。逆向迭代器是一种特殊的迭代器,它可以从容器的末尾向前遍历元素。
const_reverse_iterator rbegin() const;
: 返回一个指向 std::vector 容器中最后一个元素的常量逆向迭代器。通过这个逆向迭代器,可以从容器的末尾向前遍历元素,但不能对容器中的元素进行修改。
使用示例:
#include <vector> #include <iostream> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 使用 rbegin() 获取可修改逆向迭代器 std::vector<int>::reverse_iterator rit = myVector.rbegin(); // 使用 rbegin() 获取常量逆向迭代器 std::vector<int>::const_reverse_iterator crit = myVector.rbegin(); // 修改最后一个元素的值 *rit = 100; // 错误:常量逆向迭代器不允许修改元素的值 // *crit = 200; // 输出容器中的元素 for (int num : myVector) { std::cout << num << " "; } return 0; }
在上面的示例中,我们创建了一个存放整数的 std::vector 对象 myVector,然后使用 rbegin() 获取可修改逆向迭代器 rit 和常量逆向迭代器 crit。通过可修改逆向迭代器 rit,我们可以修改容器中的元素值;但是通过常量逆向迭代器 crit,我们不能修改容器中的元素值。最后,我们遍历并输出容器中的元素,输出结果将是 “100 4 3 2 1”。注意,在修改最后一个元素的值时,我们直接通过 *rit 修改,因为逆向迭代器指向的位置即为最后一个元素。
2.2.4 rend()
reverse_iterator rend();
const_reverse_iterator rend() const;
reverse_iterator rend();
和 const_reverse_iterator rend() const;
是 std::vector 类的成员函数,用于获取逆向迭代器的结束位置。
reverse_iterator rend();
: 返回一个指向 std::vector 容器中的逆向迭代器的结束位置的可修改逆向迭代器。逆向迭代器的结束位置实际上是容器中的第一个元素之前的位置。
const_reverse_iterator rend() const;
: 返回一个指向 std::vector 容器中的逆向迭代器的结束位置的常量逆向迭代器。通过这个逆向迭代器,可以从容器的第一个元素之前的位置向后遍历元素,但不能对容器中的元素进行修改。
使用示例:
#include <vector> #include <iostream> int main() { std::vector<int> myVector = { 1, 2, 3, 4, 5 }; // 使用 rend() 获取可修改逆向迭代器的结束位置 std::vector<int>::reverse_iterator ritEnd = myVector.rend(); // 使用 rend() 获取常量逆向迭代器的结束位置 std::vector<int>::const_reverse_iterator critEnd = myVector.rend(); // 遍历输出容器中的元素 std::cout << "Using const_reverse_iterator: "; for (std::vector<int>::const_reverse_iterator crit = myVector.rbegin(); crit != critEnd; ++crit) { //*rit += 1; //错误:常量逆向迭代器不允许修改元素的值 std::cout << *crit << " "; } std::cout << std::endl; // 遍历输出容器中的元素 std::cout << "Using reverse_iterator: "; for (std::vector<int>::reverse_iterator rit = myVector.rbegin(); rit != ritEnd; ++rit) { *rit += 1;// 从后往前修改每一个元素的值 std::cout << *rit << " "; } std::cout << std::endl; return 0; }
在上面的示例中,我们使用 rend() 分别获取了可修改逆向迭代器的结束位置 ritEnd 和常量逆向迭代器的结束位置 critEnd。然后,我们分别使用这两个结束位置的逆向迭代器来遍历输出容器中的元素。注意,在使用逆向迭代器遍历容器时,要将结束条件设置为 rit != ritEnd 或 crit != critEnd,以避免越界访问容器的第一个元素之前的位置。输出结果将是:
Using const_reverse_iterator: 5 4 3 2 1 Using reverse_iterator: 6 5 4 3 2
2.2.5 cbegin()、cend()、crbegin()和crend() C++11
在 C++11 标准中,std::vector 类模板引入了以下成员函数用于获取常量迭代器:
cbegin()
: 返回指向 std::vector 容器中第一个元素的常量迭代器。
cend()
: 返回指向 std::vector 容器中最后一个元素之后位置的常量迭代器。
crbegin()
: 返回指向 std::vector 容器中最后一个元素的常量逆向迭代器。
crend()
: 返回指向 std::vector 容器中第一个元素之前位置的常量逆向迭代器。
这些函数在 std::vector 中的使用示例如下:
#include <vector> #include <iostream> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 使用 cbegin() 获取常量迭代器的开始位置 std::vector<int>::const_iterator citBegin = myVector.cbegin(); // 使用 cend() 获取常量迭代器的结束位置 std::vector<int>::const_iterator citEnd = myVector.cend(); // 使用 crbegin() 获取常量逆向迭代器的开始位置 std::vector<int>::const_reverse_iterator critRBegin = myVector.crbegin(); // 使用 crend() 获取常量逆向迭代器的结束位置 std::vector<int>::const_reverse_iterator critREnd = myVector.crend(); // 遍历输出容器中的元素 std::cout << "Using const_iterator: "; for (std::vector<int>::const_iterator cit = citBegin; cit != citEnd; ++cit) { std::cout << *cit << " "; } std::cout << std::endl; // 遍历输出容器中的元素 std::cout << "Using const_reverse_iterator: "; for (std::vector<int>::const_reverse_iterator crit = critRBegin; crit != critREnd; ++crit) { std::cout << *crit << " "; } std::cout << std::endl; return 0; }
在上面的示例中,我们使用 cbegin() 和 cend() 获取常量迭代器的开始和结束位置,以及使用 crbegin() 和 crend() 获取常量逆向迭代器的开始和结束位置。然后,我们分别使用这些常量迭代器来遍历输出容器中的元素。输出结果将是:
Using const_iterator: 1 2 3 4 5 Using const_reverse_iterator: 5 4 3 2 1
需要注意的是,常量迭代器意味着在遍历容器时不能修改容器中的元素,只能读取。
2.3 vector容量函数
在 C++ 的 std::vector 类模板中,有以下成员函数用于管理容器的大小和容量:
size_type size() const;
: 返回当前 std::vector 容器中实际存储的元素个数。
size_type max_size() const;
: 返回 std::vector 容器可能包含的最大元素数,这个值取决于编译器和系统的限制。
void resize (size_type n, value_type val = value_type());:
改变 std::vector 容器中元素的个数,可以增加或缩减元素个数。
size_type capacity() const;
: 返回当前 std::vector 容器在不重新分配内存的情况下可以存储的最大元素个数。
bool empty() const;
: 检查 std::vector 容器是否为空,如果为空返回 true,否则返回 false。
void reserve (size_type n);
: 请求 std::vector 容器预留至少容纳指定个数的元素的内存空间,但并不实际创建元素。
void shrink_to_fit();
: 要求 std::vector 容器缩小其内存使用,使其适合其当前存储的元素数,但并不保证一定能缩小到需要的大小。(C++11)
下面对每个函数进行简要介绍:
size()
: 返回当前 std::vector 容器中实际存储的元素个数。对于一个具有 n 个元素的 std::vector,size() 将返回 n。
max_size()
: 返回 std::vector 容器可能包含的最大元素数,这个值取决于编译器和系统的限制。通常情况下,这个值是非常大的,远远超过实际需要的元素个数。
resize()
: 改变 std::vector 容器中元素的个数。如果新的大小比当前大小小,会删除多余的元素;如果新的大小比当前大小大,会用默认值或指定的值填充新元素。
capacity()
: 返回当前 std::vector 容器在不重新分配内存的情况下可以存储的最大元素个数。capacity() 不会改变 std::vector 中的元素个数。
empty()
: 检查 std::vector 容器是否为空,如果为空返回 true,否则返回 false。空容器意味着 size() 返回值为 0。
reserve()
: 请求 std::vector 容器预留至少容纳指定个数的元素的内存空间,但并不实际创建元素。这个函数通常用于避免频繁的内存分配和释放操作。
shrink_to_fit()
: 要求 std::vector 容器缩小其内存使用,使其适合其当前存储的元素数,但并不保证一定能缩小到需要的大小。这个函数通常在删除大量元素后调用,以释放不再需要的内存。
这些函数在 std::vector 容器中的使用示例如下:
#include <vector> #include <iostream> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; std::cout << "Size: " << myVector.size() << std::endl; std::cout << "Max Size: " << myVector.max_size() << std::endl; myVector.resize(8, 10); // 增加元素并用值 10 填充 std::cout << "Resized Size: " << myVector.size() << std::endl; std::cout << "Capacity: " << myVector.capacity() << std::endl; std::cout << "Is Empty: " << (myVector.empty() ? "Yes" : "No") << std::endl; myVector.reserve(10); // 预留内存,但不实际创建元素 std::cout << "New Capacity After Reserve: " << myVector.capacity() << std::endl; myVector.shrink_to_fit(); // 尝试缩小内存使用 std::cout << "New Capacity After Shrink: " << myVector.capacity() << std::endl; return 0; }
输出结果将是:
Size: 5 Max Size: 4611686018427387903 Resized Size: 8 Capacity: 8 Is Empty: No New Capacity After Reserve: 8 New Capacity After Shrink: 8
注意:capacity() 和 size() 的区别在于,capacity() 表示在不重新分配内存的情况下 std::vector 可以容纳的最大元素个数,而 size() 表示实际存储在 std::vector 中的元素个数。
提到这里就不得不提一下vector的扩容机制
使用capacity扩容时在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
代码如下:
#include <iostream> #include <vector> using namespace std; 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'; } } } int main() { TestVectorExpand(); return 0; }
VS2022运行结果
g++运行结果
面对这类问题,可以使用reserve缓解vector增容的代价缺陷问题或者使用resize,在开空间的同时还会进行初始化,影响size。
如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
就可以避免边插入边扩容导致效率低下的问题了
#include <iostream> #include <vector> using namespace std; 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'; } } } int main() { TestVectorExpandOP(); return 0; }