目录
[2.1 构造函数(Constructor)](#2.1 构造函数(Constructor))
[2.1.1. 默认构造函数 (Default Constructor)](#2.1.1. 默认构造函数 (Default Constructor))
[2.1.2 填充构造函数 (Fill Constructor)](#2.1.2 填充构造函数 (Fill Constructor))
[2.1.3 范围构造函数 (Range Constructor)](#2.1.3 范围构造函数 (Range Constructor))
[2.1.4 拷贝构造函数 (Copy Constructor)](#2.1.4 拷贝构造函数 (Copy Constructor))
[2.2 迭代器(Iterator)](#2.2 迭代器(Iterator))
[2.2.1 begin 和 end](#2.2.1 begin 和 end)
[2.2.2 rbegin 和 rend](#2.2.2 rbegin 和 rend)
[2.3 容量控制函数(Capacity)](#2.3 容量控制函数(Capacity))
[2.3.1 size](#2.3.1 size)
[2.3.2 resize](#2.3.2 resize)
[2.3.3 capacity](#2.3.3 capacity)
[2.3.4 empty](#2.3.4 empty)
[2.3.5 reserve](#2.3.5 reserve)
[2.4 元素访问函数(Element access)](#2.4 元素访问函数(Element access))
[2.4.1 operator[]](#2.4.1 operator[])
[2.4.2 at](#2.4.2 at)
[2.5 修改函数(Modifiers)](#2.5 修改函数(Modifiers))
[2.5.1 assign](#2.5.1 assign)
[2.5.2 push_back](#2.5.2 push_back)
[2.5.3 pop_back](#2.5.3 pop_back)
[2.5.4 insert](#2.5.4 insert)
[2.5.5 erase](#2.5.5 erase)
[2.5.6 swap](#2.5.6 swap)
[2.5.7 clear](#2.5.7 clear)
[3.1 基本实现代码](#3.1 基本实现代码)
[3.2 疑难点分析](#3.2 疑难点分析)
[3.2.1 用initializer_list构造函数](#3.2.1 用initializer_list构造函数)
[3.2.2 memcpy的浅拷贝问题](#3.2.2 memcpy的浅拷贝问题)
[3.2.3 迭代器区间构造函数与int冲突问题](#3.2.3 迭代器区间构造函数与int冲突问题)
(图像由AI生成)
0.前言
在前面的博客中,我们介绍了string
类的使用与模拟实现,深入探讨了其在C++编程中的重要性和具体应用。然而,string
类仅仅是C++标准模板库(STL)中的一个组成部分。为了更全面地了解STL的强大功能,本篇博客将聚焦于另一个核心容器类------vector
。vector
类在现代C++编程中扮演着至关重要的角色,其灵活的动态数组特性、丰富的成员函数以及高效的内存管理,使其成为开发者处理可变大小数组的首选工具。本文将详细介绍vector
类的基本概念、使用方法、内部实现以及常见问题的解决方案,帮助读者全面掌握这一重要的容器类。
1.vector介绍
(本部分内容参考:vector - C++ Reference (cplusplus.com))
vector
是C++标准模板库(STL)中的一个序列容器,表示可以动态改变大小的数组。
与数组类似,vector
使用连续的存储位置来存储其元素,这意味着可以使用常规指针的偏移量来高效地访问其元素。不同于数组,vector
的大小可以动态改变,其存储空间由容器自动管理。
内部工作原理
内部来说,vector
使用一个动态分配的数组来存储其元素。当插入新元素时,这个数组可能需要重新分配以增加大小,这涉及到分配一个新的数组并将所有元素移动到新数组中。这是一个相对耗时的操作,因此vector
不会在每次添加元素时都重新分配内存。
为了高效管理存储,vector
容器可能会预先分配一些额外的存储空间,以应对可能的增长。因此,容器的实际容量可能大于严格存储其元素所需的容量(即其大小)。库可以实现不同的增长策略,以在内存使用和重新分配之间取得平衡,但无论如何,重新分配应仅在容量按对数增长的间隔时发生,从而确保在vector
末尾插入单个元素的操作可以提供摊销常数时间复杂度。
因此,与数组相比,vector
消耗更多内存以换取能够高效地管理存储和动态增长的能力。
与其他动态序列容器的比较
与其他动态序列容器(如deque
、list
和forward_list
)相比,vector
在访问其元素方面非常高效(就像数组一样),在从末尾添加或删除元素方面也相对高效。对于涉及在末尾以外的位置插入或删除元素的操作,vector
的性能不如其他容器,并且其迭代器和引用的稳定性也不如list
和forward_list
。
容器属性
- 序列容器:序列容器中的元素按照严格的线性序列排序。可以通过其在序列中的位置访问单个元素。
- 动态数组:允许直接访问序列中的任何元素,甚至通过指针算术操作,并且提供了相对快速的在序列末尾添加/移除元素的功能。
- 分配器感知:容器使用一个分配器对象来动态处理其存储需求。
2.vector使用
2.1 构造函数(Constructor)
| default (1) | explicit vector (const allocator_type& alloc = allocator_type());
|
| fill (2) | explicit vector (size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type());
|
| range (3) | template <class InputIterator> vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());
|
copy (4) | vector (const vector& x); |
---|
vector
类提供了多种构造函数,以便在不同的场景中灵活地创建vector
对象。以下是几种主要的构造函数及其使用方法:
2.1.1. 默认构造函数 (Default Constructor)
cpp
explicit vector (const allocator_type& alloc = allocator_type());
这个构造函数创建一个空的vector
,可以选择性地指定一个内存分配器。
示例代码:
cpp
std::vector<int> first; // 创建一个空的int类型vector
2.1.2 填充构造函数 (Fill Constructor)
cpp
explicit vector (size_type n, const value_type& val = value_type(),
const allocator_type& alloc = allocator_type());
这个构造函数创建一个包含n
个元素的vector
,每个元素的初始值为val
,并可以选择性地指定一个内存分配器。
示例代码:
cpp
std::vector<int> second(4, 100); // 创建一个包含4个元素的vector,每个元素的值为100
2.1.3 范围构造函数 (Range Constructor)
cpp
template <class InputIterator>
vector (InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type());
这个构造函数使用迭代器区间[first, last)
的元素创建一个vector
,并可以选择性地指定一个内存分配器。
示例代码:
cpp
std::vector<int> third(second.begin(), second.end()); // 使用second的迭代器区间创建vector
2.1.4 拷贝构造函数 (Copy Constructor)
cpp
vector (const vector& x);
这个构造函数创建一个与x
相同的vector
。
示例代码:
cpp
std::vector<int> fourth(third); // 使用third创建一个新的vector副本
下面是一个完整的示例代码,展示了上述几种构造函数的使用方法:
cpp
// constructing vectors
#include <iostream>
#include <vector>
int main ()
{
// constructors used in the same order as described above:
std::vector<int> first; // empty vector of ints
std::vector<int> second (4,100); // four ints with value 100
std::vector<int> third (second.begin(),second.end()); // iterating through second
std::vector<int> fourth (third); // a copy of third
// the iterator constructor can also be used to construct from arrays:
int myints[] = {16,2,77,29};
std::vector<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );
std::cout << "The contents of fifth are:";
for (std::vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
代码解释:
first
是一个空的vector
,使用默认构造函数创建。second
是一个包含4个元素的vector
,每个元素的值为100,使用填充构造函数创建。third
是通过遍历second
的所有元素创建的vector
,使用范围构造函数创建。fourth
是third
的副本,使用拷贝构造函数创建。fifth
是通过从数组myints
中复制元素创建的vector
,同样使用范围构造函数。
2.2 迭代器(Iterator)
迭代器是一个重要的工具,使我们能够遍历和操作容器中的元素。vector
类提供了多种迭代器,以便在不同的场景中灵活地操作元素。以下是几种主要的迭代器及其使用方法:
2.2.1 begin
和 end
begin
:返回指向vector
第一个元素的迭代器。如果vector
是空的,这个迭代器等于end
。end
:返回指向vector
末尾元素之后一个位置的迭代器。这个位置不包含在vector
中,因此不能被解引用。
示例代码:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {1, 2, 3, 4, 5};
std::cout << "Vector elements: ";
for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
在这个示例中,begin
和end
迭代器用于遍历vector
中的所有元素,并输出它们的值。
2.2.2 rbegin
和 rend
rbegin
:返回指向vector
最后一个元素的反向迭代器。反向迭代器允许我们从vector
的末尾向前遍历。rend
:返回指向vector
第一个元素之前一个位置的反向迭代器。这使得我们可以使用反向迭代器来访问vector
中的元素。
示例代码:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {1, 2, 3, 4, 5};
std::cout << "Vector elements in reverse order: ";
for (std::vector<int>::reverse_iterator rit = myvector.rbegin(); rit != myvector.rend(); ++rit)
std::cout << ' ' << *rit;
std::cout << '\n';
return 0;
}
在这个示例中,rbegin
和rend
反向迭代器用于反向遍历vector
中的所有元素,并输出它们的值。
2.3 容量控制函数(Capacity)
vector
类提供了一些函数用于管理和控制容器的容量和大小。这些函数能够帮助我们在处理动态数组时更加高效和灵活。以下是主要的容量控制函数及其使用方法:
2.3.1 size
size
函数返回当前vector
中元素的数量。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {1, 2, 3, 4, 5};
std::cout << "The size of myvector is: " << myvector.size() << '\n';
return 0;
}
在这个示例中,size
函数返回vector
中的元素数量,即5。
2.3.2 resize
resize
函数改变vector
的大小。如果新的大小大于当前大小,则添加默认值元素;如果新的大小小于当前大小,则移除多余的元素。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {1, 2, 3, 4, 5};
myvector.resize(3); // 将大小调整为3,移除多余元素
std::cout << "After resize to 3, myvector contains:";
for (int n : myvector) std::cout << ' ' << n;
std::cout << '\n';
myvector.resize(5, 100); // 将大小调整为5,新增元素值为100
std::cout << "After resize to 5 with default value 100, myvector contains:";
for (int n : myvector) std::cout << ' ' << n;
std::cout << '\n';
return 0;
}
代码解释:
- 第一次调用
resize
将vector
的大小从5调整为3,移除了多余的元素。 - 第二次调用
resize
将vector
的大小调整为5,新增的两个元素值为100。
2.3.3 capacity
capacity
函数返回vector
分配的存储容量,即vector
可以存储多少元素而不需要重新分配内存。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector;
myvector.reserve(10); // 预留容量
std::cout << "The capacity of myvector is: " << myvector.capacity() << '\n';
return 0;
}
在这个示例中,reserve
函数预留了10个元素的容量,然后capacity
函数返回vector
当前的容量。
2.3.4 empty
empty
函数检查vector
是否为空。如果vector
不包含任何元素,则返回true
;否则返回false
。
使用示例:
cpp
#include <vector>
int main() {
std::vector<int> myvector;
if (myvector.empty()) {
std::cout << "myvector is empty.\n";
} else {
std::cout << "myvector is not empty.\n";
}
myvector.push_back(1);
if (myvector.empty()) {
std::cout << "myvector is empty.\n";
} else {
std::cout << "myvector is not empty.\n";
}
return 0;
}
代码解释:
- 初始时,
myvector
是空的,因此empty
函数返回true
。 - 在向
vector
添加一个元素后,empty
函数返回false
。
2.3.5 reserve
reserve
函数请求分配至少能够存储指定数量元素的内存容量。如果指定容量小于当前容量,则不会改变vector
的容量。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector;
myvector.reserve(10); // 请求分配容量
std::cout << "Capacity after reserve(10): " << myvector.capacity() << '\n';
myvector.push_back(1);
std::cout << "Capacity after push_back: " << myvector.capacity() << '\n';
return 0;
}
代码解释:
reserve(10)
请求分配至少10个元素的内存容量。- 调用
push_back
函数向vector
添加一个元素后,容量保持不变,因为vector
已经有足够的空间来存储新元素。
2.4 元素访问函数(Element access)
vector
类提供了一些函数用于访问和操作存储在容器中的元素。以下是主要的元素访问函数及其使用方法:
2.4.1 operator[]
operator[]
是一个重载的下标操作符,用于访问指定位置的元素。该操作符不进行边界检查,因此如果访问越界,将导致未定义行为。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {10, 20, 30, 40, 50};
std::cout << "Element at index 2 is: " << myvector[2] << '\n'; // 输出 30
myvector[2] = 100; // 修改元素值
std::cout << "Element at index 2 after modification is: " << myvector[2] << '\n'; // 输出 100
return 0;
}
代码解释:
- 使用
myvector[2]
访问并输出索引为2的元素(初始值为30)。 - 通过
myvector[2] = 100
修改索引为2的元素值。 - 再次访问并输出修改后的元素值(新值为100)。
2.4.2 at
at
函数用于访问指定位置的元素,与operator[]
不同的是,at
会进行边界检查。如果指定的位置超出范围,将抛出一个out_of_range
异常。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {10, 20, 30, 40, 50};
try {
std::cout << "Element at index 2 is: " << myvector.at(2) << '\n'; // 输出 30
myvector.at(2) = 100; // 修改元素值
std::cout << "Element at index 2 after modification is: " << myvector.at(2) << '\n'; // 输出 100
// 尝试访问超出范围的元素
std::cout << "Element at index 10 is: " << myvector.at(10) << '\n';
} catch (const std::out_of_range& e) {
std::cerr << "Out of range error: " << e.what() << '\n';
}
return 0;
}
代码解释:
- 使用
myvector.at(2)
访问并输出索引为2的元素(初始值为30)。 - 通过
myvector.at(2) = 100
修改索引为2的元素值。 - 再次访问并输出修改后的元素值(新值为100)。
- 尝试访问超出范围的元素
myvector.at(10)
时,捕获并处理out_of_range
异常,输出错误信息。
2.5 修改函数(Modifiers)
vector
类提供了多种修改其内容的方法。以下是主要的修改函数及其使用方法:
2.5.1 assign
assign
函数用于为vector
分配新内容,替换其当前内容并调整其大小。它有多个重载版本,可以接受不同类型的输入参数。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector;
// 使用 count 和 value 版本的 assign
myvector.assign(7, 100); // 7 个值为 100 的元素
std::cout << "myvector contains:";
for (int i : myvector)
std::cout << ' ' << i;
std::cout << '\n';
// 使用迭代器区间版本的 assign
std::vector<int> anothervector = {1, 2, 3, 4, 5};
myvector.assign(anothervector.begin(), anothervector.end());
std::cout << "myvector now contains:";
for (int i : myvector)
std::cout << ' ' << i;
std::cout << '\n';
return 0;
}
代码解释:
- 第一次调用
assign
使用7个值为100的元素替换vector
内容。 - 第二次调用
assign
使用另一个vector
的元素替换vector
内容。
2.5.2 push_back
push_back
函数在vector
的末尾添加一个元素。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector;
myvector.push_back(10);
myvector.push_back(20);
myvector.push_back(30);
std::cout << "myvector contains:";
for (int i : myvector)
std::cout << ' ' << i;
std::cout << '\n';
return 0;
}
代码解释:
- 依次向
vector
的末尾添加10、20和30。
2.5.3 pop_back
pop_back
函数删除vector
末尾的元素。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {10, 20, 30};
myvector.pop_back();
std::cout << "myvector contains:";
for (int i : myvector)
std::cout << ' ' << i;
std::cout << '\n';
return 0;
}
代码解释:
- 删除
vector
末尾的元素(30)。
2.5.4 insert
insert
函数用于在指定位置插入元素,有多个重载版本,支持插入单个元素、多个元素或一个迭代器区间的元素。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {10, 20, 30};
// 插入单个元素
myvector.insert(myvector.begin(), 5);
// 插入多个相同元素
myvector.insert(myvector.end(), 2, 100);
// 插入另一个 vector 的元素
std::vector<int> anothervector = {1, 2, 3};
myvector.insert(myvector.begin() + 1, anothervector.begin(), anothervector.end());
std::cout << "myvector contains:";
for (int i : myvector)
std::cout << ' ' << i;
std::cout << '\n';
return 0;
}
代码解释:
- 在
vector
开头插入单个元素5。 - 在
vector
末尾插入两个值为100的元素。 - 在
vector
第二个位置插入另一个vector
的元素。
2.5.5 erase
erase
函数用于删除指定位置的元素或一个元素区间。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {10, 20, 30, 40, 50};
// 删除单个元素
myvector.erase(myvector.begin() + 1); // 删除第二个元素
// 删除一个区间的元素
myvector.erase(myvector.begin(), myvector.begin() + 2); // 删除前两个元素
std::cout << "myvector contains:";
for (int i : myvector)
std::cout << ' ' << i;
std::cout << '\n';
return 0;
}
代码解释:
- 删除
vector
中第二个元素(20)。 - 删除
vector
中前两个元素(10和30)。
2.5.6 swap
swap
函数用于交换两个vector
的内容。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> first = {1, 2, 3};
std::vector<int> second = {4, 5, 6, 7};
first.swap(second);
std::cout << "first contains:";
for (int i : first)
std::cout << ' ' << i;
std::cout << '\n';
std::cout << "second contains:";
for (int i : second)
std::cout << ' ' << i;
std::cout << '\n';
return 0;
}
代码解释:
- 交换
first
和second
的内容。
2.5.7 clear
clear
函数用于清空vector
的所有元素。
使用示例:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector = {10, 20, 30};
myvector.clear();
std::cout << "myvector contains " << myvector.size() << " elements.\n";
return 0;
}
代码解释:
- 清空
vector
的所有元素,size
将为0。
3.vector的模拟实现
3.1 基本实现代码
cpp
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef T& reference;
typedef const T& const_reference;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
public:
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator cbegin() const
{
return _start;
}
const_iterator cend() const
{
return _finish;
}
// 默认构造函数
vector()
{
_start = nullptr;
_finish = nullptr;
_end_of_storage = nullptr;
}
// 带初始大小和初始值的构造函数
vector(size_t n, const T& val = T())
{
_start = new T[n];
for (size_t i = 0; i < n; i++)
{
_start[i] = val;
}
_finish = _start + n;
_end_of_storage = _start + n;
}
// 带整数大小和初始值的构造函数
vector(int n, const T& val = T())
{
_start = new T[n];
for (size_t i = 0; i < n; i++)
{
_start[i] = val;
}
_finish = _start + n;
_end_of_storage = _start + n;
}
// 拷贝构造函数
vector(const vector<T>& v)
{
_start = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
// 区间构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
size_t n = last - first;
_start = new T[n];
for (size_t i = 0; i < n; i++)
{
_start[i] = *first;
++first;
}
_finish = _start + n;
_end_of_storage = _start + n;
}
// 初始化列表构造函数
vector(std::initializer_list<T> l)
{
size_t n = l.size();
_start = new T[n];
auto it = l.begin();
for (size_t i = 0; i < n; i++)
{
_start[i] = *it;
it++;
}
_finish = _start + n;
_end_of_storage = _start + n;
}
// 赋值运算符
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
T* tmp = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++)
{
tmp[i] = v._start[i];
}
delete[] _start;
_start = tmp;
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
return *this;
}
// 析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
// 返回元素个数
size_t size() const
{
return _finish - _start;
}
// 返回容量
size_t capacity() const
{
return _end_of_storage - _start;
}
// 预留空间
void reserve(size_t n)
{
if (n > capacity())
{
size_t size = this->size();
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < size; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + size;
_end_of_storage = _start + n;
}
}
// 调整大小
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else if (n > size())
{
if (n > capacity())
{
reserve(n);
}
for (size_t i = size(); i < n; i++)
{
_start[i] = val;
}
_finish = _start + n;
}
}
// 检查是否为空
bool empty() const
{
return _start == _finish;
}
// 下标操作符
reference operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const_reference operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
// 返回第一个元素的引用
reference front()
{
return *_start;
}
const_reference front() const
{
return *_start;
}
// 返回最后一个元素的引用
reference back()
{
return *(_finish - 1);
}
const_reference back() const
{
return *(_finish - 1);
}
// 在末尾添加元素
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
size_t n = capacity() == 0 ? 1 : 2 * capacity();
reserve(n);
}
*_finish = x;
++_finish;
}
// 删除末尾元素
void pop_back()
{
assert(_start < _finish);
--_finish;
}
// 插入元素
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _end_of_storage)
{
size_t n = pos - _start;
size_t newcapacity = capacity() == 0 ? 1 : 2 * capacity();
reserve(newcapacity);
pos = _start + n;
}
iterator end = _finish;
while (end > pos)
{
*end = *(end - 1);
--end;
}
*pos = x;
++_finish;
return pos;
}
// 删除指定位置的元素
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
++begin;
}
--_finish;
return pos;
}
// 删除区间元素
iterator erase(iterator first, iterator last)
{
assert(first >= _start && last <= _finish && first <= last);
iterator begin = last;
while (begin < _finish)
{
*(first) = *begin;
++first;
++begin;
}
_finish = first;
return first;
}
// 交换内容
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
// 清空内容
void clear()
{
_finish = _start;
}
};
3.2 疑难点分析
3.2.1 用initializer_list构造函数
在C++11中,引入了一种新的类型std::initializer_list
,它允许我们使用初始化列表的方式来初始化容器和对象。initializer_list
提供了一种简单的语法,用于在构造对象时直接列出其元素,例如在构造vector
对象时可以像这样:vector<int> v = {1, 2, 3, 4, 5};
。
std::initializer_list
类型提供了一些基本的成员函数,例如size()
可以返回列表中元素的数量,begin()
和end()
则分别返回指向列表开头和末尾的迭代器。
下面是一个实现initializer_list
构造函数的示例:
cpp
// 初始化列表构造函数
vector(std::initializer_list<T> l)
{
size_t n = l.size();
_start = new T[n]; // 分配足够的内存来存储初始化列表中的元素
auto it = l.begin();
for (size_t i = 0; i < n; i++)
{
_start[i] = *it; // 将初始化列表中的元素逐个复制到_vector_中
it++;
}
_finish = _start + n; // 设置_finish_指向最后一个元素的下一个位置
_end_of_storage = _start + n; // 设置_end_of_storage_指向分配的内存末尾
}
在这个构造函数中:
size_t n = l.size();
:首先获取初始化列表的大小,即需要存储的元素数量。_start = new T[n];
:分配一个大小为n
的数组,作为vector
的存储空间。auto it = l.begin();
:获取初始化列表的起始迭代器。for (size_t i = 0; i < n; i++) { _start[i] = *it; it++; }
:使用循环将初始化列表中的元素逐个复制到vector
中。迭代器it
从初始化列表的开头开始,依次访问每个元素,并将其赋值到_start
数组中对应的位置。_finish = _start + n;
:设置_finish
指向最后一个元素的下一个位置,这样可以表示当前vector
的实际大小。_end_of_storage = _start + n;
:设置_end_of_storage
指向分配的内存末尾,表示当前vector
的容量。
通过这种方式,我们可以使用初始化列表来方便地构造vector
对象,使代码更加简洁和直观。
3.2.2 memcpy的浅拷贝问题
在实现vector
的reserve
函数时,如果我们直接使用memcpy
来拷贝内存,而不是逐个元素复制,会引发一些问题,尤其是当vector
的元素类型是std::string
或其他复杂类型时。为了更好地理解这个问题,我们首先需要了解memcpy
和逐个元素复制的区别。
memcpy
与逐个元素复制的区别
memcpy
是一种高效的内存拷贝方法,用于复制原始字节流。但是,它只是简单地将内存块从一个位置复制到另一个位置,而不考虑元素的构造和析构。这意味着,对于像std::string
这样具有内部动态内存管理和析构函数的类型,memcpy
不会正确地处理这些类型的内部资源。
相反,逐个元素复制(例如使用赋值运算符或拷贝构造函数)会确保每个元素的正确构造和析构。对于std::string
等复杂类型,这种方法会确保其内部资源(如动态分配的内存)被正确管理。
reserve
函数的实现
在vector
的reserve
函数中,我们需要为新的容量分配内存,并将现有元素复制到新分配的内存中。下面是一个正确实现的示例:
cpp
template <class T>
class vector
{
// ... other members and functions ...
// 预留空间
void reserve(size_t n)
{
if (n > capacity())
{
size_t size = this->size();
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < size; i++)
{
tmp[i] = _start[i]; // 逐个元素复制
}
delete[] _start;
}
_start = tmp;
_finish = _start + size;
_end_of_storage = _start + n;
}
}
// ... other members and functions ...
};
但是,如果我们使用memcpy
来拷贝内存,例如:
cpp
#include <cstring> // for std::memcpy
void reserve(size_t n)
{
if (n > capacity())
{
size_t size = this->size();
T* tmp = new T[n];
if (_start)
{
std::memcpy(tmp, _start, size * sizeof(T)); // 使用memcpy来拷贝内存
delete[] _start;
}
_start = tmp;
_finish = _start + size;
_end_of_storage = _start + n;
}
}
这种实现对于std::string
等复杂类型会产生问题,因为memcpy
只是简单地复制内存,而不会调用这些类型的拷贝构造函数。这可能导致:
-
浅拷贝问题 :对于
std::string
,memcpy
只会复制指向内部字符数组的指针,而不会复制字符数组本身。这会导致多个std::string
对象共享同一块内存,当其中一个对象被修改或销毁时,可能会影响其他对象,导致未定义行为。 -
资源泄漏 :如果源对象拥有动态分配的资源(如
std::string
的内部字符数组),使用memcpy
进行复制后,新的对象不会拥有这些资源的所有权,可能会导致资源泄漏或重复释放。 -
破坏对象的状态 :一些类型可能在对象构造和析构时进行状态管理,
memcpy
不会调用这些类型的构造和析构函数,从而破坏对象的状态。
因此,对于需要正确管理资源的复杂类型,应该避免使用memcpy
来进行内存拷贝,而应使用逐个元素复制的方法,以确保每个元素的构造和析构函数被正确调用。这样可以避免潜在的浅拷贝问题和资源管理问题,确保vector
的正确和安全使用。
3.2.3 迭代器区间构造函数与int冲突问题
在实现vector
类的构造函数时,我们可能会遇到不同构造函数之间的冲突问题。特别是当我们有一个带有初始大小和初始值的构造函数和一个接受迭代器区间的构造函数时,这种冲突尤为明显。
假设我们有如下两个构造函数:
cpp
// 带初始大小和初始值的构造函数
vector(size_t n, const T& val = T())
{
_start = new T[n];
for (size_t i = 0; i < n; i++)
{
_start[i] = val;
}
_finish = _start + n;
_end_of_storage = _start + n;
}
// 区间构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
size_t n = last - first;
_start = new T[n];
for (size_t i = 0; i < n; i++)
{
_start[i] = *first;
++first;
}
_finish = _start + n;
_end_of_storage = _start + n;
}
当我们调用vector<int> v(10, 10);
时,编译器可能会选择调用区间构造函数而不是带初始大小和初始值的构造函数,因为int
类型可以与模板参数InputIterator
匹配。这会导致编译错误或意外行为。
为了解决这个问题,我们可以使用SFINAE(Substitution Failure Is Not An Error)技术,即在模板参数中添加类型检查,使得模板匹配只在特定条件下成立。这可以通过std::enable_if
和类型特征(如std::is_integral
)来实现。
cpp
#include <type_traits>
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef T& reference;
typedef const T& const_reference;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
public:
// 默认构造函数
vector()
{
_start = nullptr;
_finish = nullptr;
_end_of_storage = nullptr;
}
// 带初始大小和初始值的构造函数
template<typename U = T>
vector(size_t n, const T& val = T(), typename std::enable_if<std::is_integral<U>::value>::type* = 0)
{
_start = new T[n];
for (size_t i = 0; i < n; i++)
{
_start[i] = val;
}
_finish = _start + n;
_end_of_storage = _start + n;
}
// 区间构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last, typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0)
{
size_t n = last - first;
_start = new T[n];
for (size_t i = 0; i < n; i++)
{
_start[i] = *first;
++first;
}
_finish = _start + n;
_end_of_storage = _start + n;
}
// ... 其他成员函数 ...
};
代码解释
-
带初始大小和初始值的构造函数:
template<typename U = T>
:使用一个默认模板参数U
,默认值为T
。typename std::enable_if<std::is_integral<U>::value>::type* = 0
:仅当U
是整型时,这个构造函数才有效。
-
区间构造函数:
template<class InputIterator>
:接受两个迭代器参数。typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0
:仅当InputIterator
不是整型时,这个构造函数才有效。
通过这种方式,我们可以避免带初始大小和初始值的构造函数和区间构造函数之间的冲突,确保在调用vector<int> v(10, 10);
时,编译器会正确选择带初始大小和初始值的构造函数,而不会误调用区间构造函数。
4.小结
通过对C++中vector
类的深入探讨,我们了解了其构造函数、迭代器、容量控制函数、元素访问函数和修改函数的具体实现和使用方法。特别是对initializer_list
和memcpy
浅拷贝问题的分析,及解决迭代器区间构造函数与整数类型冲突的方法,让我们对如何正确高效地使用和实现vector
有了更全面的理解。希望通过本文,大家能够更好地掌握vector
的用法,在实际编程中发挥其强大功能。