C++ vector类

目录

0.前言

1.vector介绍

2.vector使用

2.1 构造函数(Constructor)

2.1.1. 默认构造函数 (Default Constructor)

2.1.2 填充构造函数 (Fill Constructor)

2.1.3 范围构造函数 (Range Constructor)

2.1.4 拷贝构造函数 (Copy Constructor)

2.2 迭代器(Iterator)

2.2.1 begin 和 end

2.2.2 rbegin 和 rend

2.3 容量控制函数(Capacity)

2.3.1 size

2.3.2 resize

2.3.3 capacity

2.3.4 empty

2.3.5 reserve

2.4 元素访问函数(Element access)

2.4.1 operator[]

2.4.2 at

2.5 修改函数(Modifiers)

2.5.1 assign

2.5.2 push_back

2.5.3 pop_back

2.5.4 insert

2.5.5 erase

2.5.6 swap

2.5.7 clear

3.vector的模拟实现

3.1 基本实现代码

3.2 疑难点分析

3.2.1 用initializer_list构造函数

3.2.2 memcpy的浅拷贝问题

3.2.3 迭代器区间构造函数与int冲突问题

4.小结


(图像由AI生成) 

0.前言

在前面的博客中,我们介绍了string类的使用与模拟实现,深入探讨了其在C++编程中的重要性和具体应用。然而,string类仅仅是C++标准模板库(STL)中的一个组成部分。为了更全面地了解STL的强大功能,本篇博客将聚焦于另一个核心容器类——vectorvector类在现代C++编程中扮演着至关重要的角色,其灵活的动态数组特性、丰富的成员函数以及高效的内存管理,使其成为开发者处理可变大小数组的首选工具。本文将详细介绍vector类的基本概念、使用方法、内部实现以及常见问题的解决方案,帮助读者全面掌握这一重要的容器类。

1.vector介绍

(本部分内容参考:vector - C++ Reference (cplusplus.com))

vector是C++标准模板库(STL)中的一个序列容器,表示可以动态改变大小的数组。

与数组类似,vector使用连续的存储位置来存储其元素,这意味着可以使用常规指针的偏移量来高效地访问其元素。不同于数组,vector的大小可以动态改变,其存储空间由容器自动管理。

内部工作原理

内部来说,vector使用一个动态分配的数组来存储其元素。当插入新元素时,这个数组可能需要重新分配以增加大小,这涉及到分配一个新的数组并将所有元素移动到新数组中。这是一个相对耗时的操作,因此vector不会在每次添加元素时都重新分配内存。

为了高效管理存储,vector容器可能会预先分配一些额外的存储空间,以应对可能的增长。因此,容器的实际容量可能大于严格存储其元素所需的容量(即其大小)。库可以实现不同的增长策略,以在内存使用和重新分配之间取得平衡,但无论如何,重新分配应仅在容量按对数增长的间隔时发生,从而确保在vector末尾插入单个元素的操作可以提供摊销常数时间复杂度。

因此,与数组相比,vector消耗更多内存以换取能够高效地管理存储和动态增长的能力。

与其他动态序列容器的比较

与其他动态序列容器(如dequelistforward_list)相比,vector在访问其元素方面非常高效(就像数组一样),在从末尾添加或删除元素方面也相对高效。对于涉及在末尾以外的位置插入或删除元素的操作,vector的性能不如其他容器,并且其迭代器和引用的稳定性也不如listforward_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)

explicit vector (const allocator_type& alloc = allocator_type());

这个构造函数创建一个空的vector,可以选择性地指定一个内存分配器。

示例代码:

std::vector<int> first; // 创建一个空的int类型vector

2.1.2 填充构造函数 (Fill Constructor)

explicit vector (size_type n, const value_type& val = value_type(),
                 const allocator_type& alloc = allocator_type());

这个构造函数创建一个包含n个元素的vector,每个元素的初始值为val,并可以选择性地指定一个内存分配器。

示例代码:

std::vector<int> second(4, 100); // 创建一个包含4个元素的vector,每个元素的值为100

2.1.3 范围构造函数 (Range Constructor)

template <class InputIterator>
vector (InputIterator first, InputIterator last,
        const allocator_type& alloc = allocator_type());

这个构造函数使用迭代器区间[first, last)的元素创建一个vector,并可以选择性地指定一个内存分配器。

示例代码:

std::vector<int> third(second.begin(), second.end()); // 使用second的迭代器区间创建vector

2.1.4 拷贝构造函数 (Copy Constructor)

vector (const vector& x);

这个构造函数创建一个与x相同的vector

示例代码:

std::vector<int> fourth(third); // 使用third创建一个新的vector副本

下面是一个完整的示例代码,展示了上述几种构造函数的使用方法:

// 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,使用范围构造函数创建。
  • fourththird的副本,使用拷贝构造函数创建。
  • fifth是通过从数组myints中复制元素创建的vector,同样使用范围构造函数。

2.2 迭代器(Iterator)

迭代器是一个重要的工具,使我们能够遍历和操作容器中的元素。vector类提供了多种迭代器,以便在不同的场景中灵活地操作元素。以下是几种主要的迭代器及其使用方法:

2.2.1 beginend

  • begin:返回指向vector第一个元素的迭代器。如果vector是空的,这个迭代器等于end
  • end:返回指向vector末尾元素之后一个位置的迭代器。这个位置不包含在vector中,因此不能被解引用。

示例代码:

#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;
}

在这个示例中,beginend迭代器用于遍历vector中的所有元素,并输出它们的值。

2.2.2 rbeginrend

  • rbegin:返回指向vector最后一个元素的反向迭代器。反向迭代器允许我们从vector的末尾向前遍历。
  • rend:返回指向vector第一个元素之前一个位置的反向迭代器。这使得我们可以使用反向迭代器来访问vector中的元素。

示例代码:

#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;
}

在这个示例中,rbeginrend反向迭代器用于反向遍历vector中的所有元素,并输出它们的值。

2.3 容量控制函数(Capacity)

vector类提供了一些函数用于管理和控制容器的容量和大小。这些函数能够帮助我们在处理动态数组时更加高效和灵活。以下是主要的容量控制函数及其使用方法:

2.3.1 size

size函数返回当前vector中元素的数量。

使用示例:

#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的大小。如果新的大小大于当前大小,则添加默认值元素;如果新的大小小于当前大小,则移除多余的元素。

使用示例:

#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;
}

代码解释:

  • 第一次调用resizevector的大小从5调整为3,移除了多余的元素。
  • 第二次调用resizevector的大小调整为5,新增的两个元素值为100。

2.3.3 capacity

capacity函数返回vector分配的存储容量,即vector可以存储多少元素而不需要重新分配内存。

使用示例:

#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

使用示例:

#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的容量。

使用示例:

#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[]是一个重载的下标操作符,用于访问指定位置的元素。该操作符不进行边界检查,因此如果访问越界,将导致未定义行为。

使用示例:

#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异常。

使用示例:

#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分配新内容,替换其当前内容并调整其大小。它有多个重载版本,可以接受不同类型的输入参数。

使用示例:

#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的末尾添加一个元素。

使用示例:

#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末尾的元素。

使用示例:

#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函数用于在指定位置插入元素,有多个重载版本,支持插入单个元素、多个元素或一个迭代器区间的元素。

使用示例:

#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函数用于删除指定位置的元素或一个元素区间。

使用示例:

#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的内容。

使用示例:

#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;
}

代码解释:

  • 交换firstsecond的内容。

2.5.7 clear

clear函数用于清空vector的所有元素。

使用示例:

#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 基本实现代码

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构造函数的示例:

// 初始化列表构造函数
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_指向分配的内存末尾
}

在这个构造函数中:

  1. size_t n = l.size();:首先获取初始化列表的大小,即需要存储的元素数量。
  2. _start = new T[n];:分配一个大小为n的数组,作为vector的存储空间。
  3. auto it = l.begin();:获取初始化列表的起始迭代器。
  4. for (size_t i = 0; i < n; i++) { _start[i] = *it; it++; }:使用循环将初始化列表中的元素逐个复制到vector中。迭代器it从初始化列表的开头开始,依次访问每个元素,并将其赋值到_start数组中对应的位置。
  5. _finish = _start + n;:设置_finish指向最后一个元素的下一个位置,这样可以表示当前vector的实际大小。
  6. _end_of_storage = _start + n;:设置_end_of_storage指向分配的内存末尾,表示当前vector的容量。

通过这种方式,我们可以使用初始化列表来方便地构造vector对象,使代码更加简洁和直观。

3.2.2 memcpy的浅拷贝问题

在实现vectorreserve函数时,如果我们直接使用memcpy来拷贝内存,而不是逐个元素复制,会引发一些问题,尤其是当vector的元素类型是std::string或其他复杂类型时。为了更好地理解这个问题,我们首先需要了解memcpy和逐个元素复制的区别。

memcpy与逐个元素复制的区别

memcpy是一种高效的内存拷贝方法,用于复制原始字节流。但是,它只是简单地将内存块从一个位置复制到另一个位置,而不考虑元素的构造和析构。这意味着,对于像std::string这样具有内部动态内存管理和析构函数的类型,memcpy不会正确地处理这些类型的内部资源。

相反,逐个元素复制(例如使用赋值运算符或拷贝构造函数)会确保每个元素的正确构造和析构。对于std::string等复杂类型,这种方法会确保其内部资源(如动态分配的内存)被正确管理。

reserve函数的实现

vectorreserve函数中,我们需要为新的容量分配内存,并将现有元素复制到新分配的内存中。下面是一个正确实现的示例:

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来拷贝内存,例如:

#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只是简单地复制内存,而不会调用这些类型的拷贝构造函数。这可能导致:

  1. 浅拷贝问题:对于std::stringmemcpy只会复制指向内部字符数组的指针,而不会复制字符数组本身。这会导致多个std::string对象共享同一块内存,当其中一个对象被修改或销毁时,可能会影响其他对象,导致未定义行为。

  2. 资源泄漏:如果源对象拥有动态分配的资源(如std::string的内部字符数组),使用memcpy进行复制后,新的对象不会拥有这些资源的所有权,可能会导致资源泄漏或重复释放。

  3. 破坏对象的状态:一些类型可能在对象构造和析构时进行状态管理,memcpy不会调用这些类型的构造和析构函数,从而破坏对象的状态。

因此,对于需要正确管理资源的复杂类型,应该避免使用memcpy来进行内存拷贝,而应使用逐个元素复制的方法,以确保每个元素的构造和析构函数被正确调用。这样可以避免潜在的浅拷贝问题和资源管理问题,确保vector的正确和安全使用。

3.2.3 迭代器区间构造函数与int冲突问题

在实现vector类的构造函数时,我们可能会遇到不同构造函数之间的冲突问题。特别是当我们有一个带有初始大小和初始值的构造函数和一个接受迭代器区间的构造函数时,这种冲突尤为明显。

假设我们有如下两个构造函数:

// 带初始大小和初始值的构造函数
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)来实现。

#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;
    }

    // ... 其他成员函数 ...
};

代码解释

  1. 带初始大小和初始值的构造函数

    • template<typename U = T>:使用一个默认模板参数U,默认值为T
    • typename std::enable_if<std::is_integral<U>::value>::type* = 0:仅当U是整型时,这个构造函数才有效。
  2. 区间构造函数

    • template<class InputIterator>:接受两个迭代器参数。
    • typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0:仅当InputIterator不是整型时,这个构造函数才有效。

通过这种方式,我们可以避免带初始大小和初始值的构造函数和区间构造函数之间的冲突,确保在调用vector<int> v(10, 10);时,编译器会正确选择带初始大小和初始值的构造函数,而不会误调用区间构造函数。

4.小结

通过对C++中vector类的深入探讨,我们了解了其构造函数、迭代器、容量控制函数、元素访问函数和修改函数的具体实现和使用方法。特别是对initializer_listmemcpy浅拷贝问题的分析,及解决迭代器区间构造函数与整数类型冲突的方法,让我们对如何正确高效地使用和实现vector有了更全面的理解。希望通过本文,大家能够更好地掌握vector的用法,在实际编程中发挥其强大功能。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

封装了一个iOS水平方向瀑布流布局

首先查看效果图 是支持多分区的 思路就是和竖直方向上的瀑布流布局是一样的&#xff0c; 只不过这里记录的列是水平方向的&#xff0c;同时要记录下 当前最小的x 所在的列&#xff0c;其实原理和竖直方向上的是相同的 &#xff0c;下面贴出代码 父类layout中的代码 // // …

08-Fortran基础--Fortran内置函数分类总结

08-Fortran基础--Fortran内置函数分类总结 0 引言1 Fortran内置函数1.1 常用到数学函数1.2 字符串函数&#xff1a;1.3 数组函数&#xff1a;1.4 数值查询函数1.5 文件操作函数&#xff1a; 2 结语 0 引言 Fortran是一种很古老的编程语言&#xff0c;但它仍然广泛使用于科学计算…

使用LoRA进行高效微调:基本原理

Using LoRA for efficient fine-tuning: Fundamental principles — ROCm Blogs (amd.com) [2106.09685] LoRA: Low-Rank Adaptation of Large Language Models (arxiv.org) Parametrizations Tutorial — PyTorch Tutorials 2.3.0cu121 documentation 大型语言模型&#xf…

基于地理坐标的高阶几何编辑工具算法(7)——矩形绘制

文章目录 工具步骤应用场景示意图算法原理工具步骤 点击矩形绘制工具,点击三个点完成矩形绘制。 应用场景 用于在地图上快速绘制任意方向的矩形。 示意图 算法原理 点第一个点确定矩形的一个角点P1,也作为平移后的坐标原点,生成平移矩阵。点第二个点P2,确定矩形的一条边…

Studio 3T 2024.3 (macOS, Linux, Windows) - MongoDB 的专业 GUI、IDE 和 客户端,支持自然语言查询

Studio 3T 2024.3 (macOS, Linux, Windows) - MongoDB 的专业 GUI、IDE 和 客户端&#xff0c;支持自然语言查询 The professional GUI, IDE and client for MongoDB 请访问原文链接&#xff1a;https://sysin.org/blog/studio-3t/&#xff0c;查看最新版。原创作品&#xff…

CentOS 7.9 邮箱部署——Postfix+Dovecot详细

PostfixDovecot 文章目录 PostfixDovecot资源列表基础环境一、部署DNS二、部署postfix和dovecot2.1、配置postfix2.2、配置dovecot2.3、创建邮件用户 三、发送邮件测试3.1、windows安装poxmail3.2、登录邮箱3.3、发送接收邮件 四、搭建SSL认证加密4.1、生成私钥4.2、生成公钥4.…

状态转换图

根据本章开头讲的结构化分析的第3条准则,在需求分析过程中应该建立起软件系统的行为模型。状态转换图(简称为状态图)通过描绘系统的状态及引起系统状态转换的事件,来表示系统的行为。此外,状态图还指明了作为特定事件的结果系统将做哪些动作(例如,处理数据)。因此,状态图提供了…

iMX6ULL 嵌入式linux开发 | 4G无线广播终端实现方案介绍

现有的有线广播&#xff0c;如村上的大喇叭&#xff0c;需要布线&#xff0c;施工麻烦。借助现有的4G网络&#xff0c;传输音频流完全没问题&#xff0c;4G网络结合流媒体技术和MQTT消息传递实现设备间的同步推拉流。这种方案可以避免有线布线的麻烦&#xff0c;同时实现4G无线…

基于springboot+vue的智慧外贸平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

Elasticsearch 分析器(内置分析器,自定义分析器,IK分析器)

Elasticsearch 分析器&#xff08;内置分析器&#xff0c;自定义分析器&#xff0c;IK分析器&#xff09; 内置分析器使用分析器自定义分析器中文分析器&#xff08;IK分析器&#xff09;安装使用添加词典 内置分析器 官网&#xff1a;https://www.elastic.co/guide/en/elasti…

如何确保大模型 RAG 生成的信息是基于可靠的数据源?

在不断发展的人工智能 (AI) 领域中&#xff0c;检索增强生成 (RAG) 已成为一种强大的技术。 RAG 弥合了大型语言模型 (LLM) 与外部知识源之间的差距&#xff0c;使 AI 系统能够提供更全面和信息丰富的响应。然而&#xff0c;一个关键因素有时会缺失——透明性。 我们如何能够…

翻译《The Old New Thing》- What‘s the deal with the EM_SETHILITE message?

Whats the deal with the EM_SETHILITE message? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20071025-00/?p24693 Raymond Chen 2007年10月25日 简要 文章讨论了EM_SETHILITE和EM_GETHILITE消息在文档中显示为“未实现”的原因。这些…

Redis开发实战

单机部署安装 服务端下载&#xff0c;安装&#xff0c;启动去官网下载最新的版本&#xff1a;http://redis.io/download &#xff0c;这里用的是3.0.2解压后&#xff0c;进入解压好的文件夹redis的安装非常简单&#xff0c;因为已经有现成的Makefile文件&#xff0c;所以直接先…

NASA数据集——阿尔法喷气式大气实验甲醛(HCHO)数据

Alpha Jet Atmospheric eXperiment Formaldehyde Data 简介 阿尔法喷气式大气实验甲醛数据 阿尔法喷气式大气实验&#xff08;AJAX&#xff09;是美国国家航空航天局艾姆斯研究中心与 H211, L.L.C. 公司的合作项目&#xff0c;旨在促进对加利福尼亚、内华达和太平洋沿岸地区的…

从0开始带你成为Kafka消息中间件高手---第一讲

从0开始带你成为Kafka消息中间件高手—第一讲 网站的用户行为日志&#xff0c;假设电商网站&#xff0c;我现在需要买一个阅读架&#xff0c;看书的架子 京东&#xff0c;我平时比较喜欢用的是京东&#xff0c;送货很快&#xff0c;自营商品&#xff0c;都是放在自己的仓库里…

【字典树(前缀树) 异或 离线查询】1707. 与数组中元素的最大异或值

本文涉及知识点 字典树&#xff08;前缀树&#xff09; 位运算 异或 离线查询 LeetCode1707. 与数组中元素的最大异或值 给你一个由非负整数组成的数组 nums 。另有一个查询数组 queries &#xff0c;其中 queries[i] [xi, mi] 。 第 i 个查询的答案是 xi 和任何 nums 数组…

阿里巴巴最新研究突破:自我演化大模型,打破性能天花板

获取本文论文原文PDF&#xff0c;请在公众号【AI论文解读】留言&#xff1a;论文解读AI论文解读 原创作者 | 柏企 引言&#xff1a;自我进化的新篇章 在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的发展正迎来一场革命性的变革。传统的训练模式依赖…

从0开始学统计-方差分析

1.什么是方差分析&#xff1f; 方差分析&#xff08;ANOVA&#xff0c;Analysis of Variance&#xff09;是一种统计方法&#xff0c;用于比较三个或三个以上组之间的平均值是否存在显著差异。它适用于以下情况&#xff1a; &#xff08;1&#xff09; 当我们有三个或三个以上…

LLMs之PEFT之Llama-2:《LoRA Learns Less and Forgets LessLoRA学得更少但遗忘得也更少》翻译与解读

LLMs之PEFT之Llama-2&#xff1a;《LoRA Learns Less and Forgets LessLoRA学得更少但遗忘得也更少》翻译与解读 导读&#xff1a;该论文比较了LoRA与完全微调在代码与数学两个领域的表现。 背景问题&#xff1a;微调大规模语言模型需要非常大的GPU内存。LoRA这一参数高效微调方…

.NET 一款内部最新的免杀WebShell

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…