如何在C ++中实现向量

时间:2010-06-17 18:42:25

标签: c++ c data-structures

我正在考虑如何从头开始实现std :: vector。

它如何调整向量的大小?

realloc似乎只适用于普通的旧结构,或者我错了吗?

9 个答案:

答案 0 :(得分:38)

它是一个简单的模板类,它包装了一个本机数组。 使用malloc / realloc。相反,它使用传递的分配器(默认情况下为std::allocator)。

通过分配新数组并从旧数组中复制构造新数组中的每个元素来完成调整大小(这样对非POD对象是安全的)。为避免频繁分配,通常它们遵循非线性增长模式。

UPDATE:在C ++ 11中,如果可以存储类型,将移动元素而不是复制构造。

除此之外,它还需要存储当前的“大小”和“容量”。大小是向量中实际有多少元素。容量是可以在向量中的数量。

因此,作为起点,矢量需要看起来像这样:

template <class T, class A = std::allocator<T> >
class vector {
public:
    // public member functions
private:
    T*                    data_;
    typename A::size_type capacity_;
    typename A::size_type size_;
    A                     allocator_;
};

另一个常见的实现是存储指向数组不同部分的指针。这会使end()(不再需要添加)的成本略微降低,而代价是稍微昂贵的size()调用(现在需要减法)。在这种情况下,它可能看起来像这样:

template <class T, class A = std::allocator<T> >
class vector {
public:
    // public member functions
private:
    T* data_;         // points to first element
    T* end_capacity_; // points to one past internal storage
    T* end_;          // points to one past last element
    A  allocator_;
};

我相信gcc的libstdc ++使用后一种方法,但这两种方法同样有效且符合要求。

注意:这忽略了一个常见的优化,其中空基类优化用于分配器。我认为这是一个实施细节的质量,而不是正确性。

答案 1 :(得分:4)

调整向量大小需要分配一大块空间,并将现有数据复制到新空间(因此,可以复制放置在向量中的项目的要求)。

请注意,它使用new [] - 它使用传递的分配器,但是需要分配原始内存,而不是数组像new []这样的对象。然后,您需要使用placement new来构建对象。 [编辑:好吧,你可以在技术上使用new char[size],并将其用作原始内存,但我无法想象有人会像这样编写分配器。]

当当前分配耗尽并且需要分配新的内存块时,与旧的大小相比,大小必须增加一个常量因子,以满足分摊的常量复杂度的要求push_back。虽然许多网站(等)称这个尺寸加倍,但1.5到1.6的因素通常效果更好。特别是,这通常会提高重用已释放块的机会,以便将来进行分配。

答案 2 :(得分:3)

来自Wikipedia,与任何答案一样好。

  

典型的向量实现在内部包含指向的指针   一个动态分配的数组,[2]和可能的数据成员持有   矢量的容量和大小。矢量的大小指的是   元素的实际数量,而容量是指大小   内部数组。插入新元素时,如果是新大小   向量变得大于其容量,重新分配   发生。[2] [4]这通常会导致向量分配新的   存储区域,将先前保存的元素移动到新区域   存储,并释放旧区域。因为地址了   元素在此过程中发生变化,任何引用或迭代器   向量中的元素变为无效。[5]使用无效的   引用导致未定义的行为

答案 3 :(得分:2)

它分配一个新数组并复制一切。因此,如果你不得不经常这样做,扩展它是非常低效的。如果必须使用push_back(),请使用reserve()。

答案 4 :(得分:2)

答案 5 :(得分:1)

你需要用“普通的旧结构”来定义你的意思。

realloc本身只会创建一个未初始化的内存块。它没有对象分配。对于C结构,这就足够了,但是对于C ++,它不会。

这并不是说你不能使用realloc。但是如果你要使用它(注意你不会在这种情况下完全重新实现std::vector!),你需要:

  1. 确保您在整个班级中始终使用malloc/realloc/free
  2. 使用“placement new”初始化内存块中的对象。
  3. 在释放内存块之前,明确调用析构函数来清理对象。
  4. 这实际上非常接近我的实现中的向量(GCC / glib),除了它使用C ++低级例程::operator new::operator delete来执行原始内存管理而不是malloc并且,使用这些原语重写realloc例程,并将所有这些行为委托给可以用自定义实现替换的分配器对象。

    由于vector是一个模板,如果你想要一个引用,你实际上应该看看它的来源 - 如果你能超越下划线的优势,它应该不会太难阅读。如果你在使用GCC的Unix机器上,请尝试查找/usr/include/c++/version/vector或其附近。

答案 6 :(得分:0)

您可以通过调整数组实现来实现它们。 当数组变满时,创建一个大小为两倍的数组,并将所有内容复制到新数组。不要忘记删除旧数组。

对于从向量中删除元素,在数组变为四分之一满时进行大小调整。当一个人尝试以数组大小的一半重复插入和删除时,这种策略可以防止任何性能故障。

可以在数学上证明插入的分摊时间(平均时间)对于n次插入仍然是线性的,这与正常静态数组的渐近相同。

答案 7 :(得分:0)

    ///Implement Vector class
    class MyVector {
        int *int_arr;
        int capacity;
        int current;
    public:
        MyVector() {
            int_arr = new int[1];
            capacity = 1;
            current = 0;
        }
        void Push(int nData);
        void PushData(int nData, int index);
        void PopData();
        int  GetData(int index);
        int  GetSize();
        void Print();
    };

    void MyVector::Push(int data)
    {
        if (current == capacity){
            int *temp = new int[2 * capacity];
            for (int i = 0; i < capacity; i++)
            {
                temp[i] = int_arr[i];
            }

            delete[] int_arr;
            capacity *= 2;

            int_arr = temp;
        }
        int_arr[current] = data;
        current++;
    }
    void MyVector::PushData(int data, int index)
    {
        if (index == capacity){
            Push(index);
        }
        else
            int_arr[index] = data;
    }
    void MyVector::PopData(){
        current--;
    }

    int MyVector::GetData(int index)
    {
        if (index < current){
            return int_arr[index];
        }
    }

    int MyVector::GetSize()
    {
        return current;
    }

    void MyVector::Print()
    {
        for (int i = 0; i < current; i++) {
            cout << int_arr[i] << " ";
        }
        cout << endl;
    }
    
    int main()
    {
        MyVector vect;
        vect.Push(10);
        vect.Push(20);
        vect.Push(30);
        vect.Push(40);

        vect.Print();

        std::cout << "\nTop item is "
            << vect.GetData(3) << std::endl;

        vect.PopData();
        vect.Print();

        cout << "\nTop item is "
            << vect.GetData(1) << endl;
        return 0;
    }

答案 8 :(得分:-4)

realloc仅适用于堆内存。在C ++中,您通常希望使用免费商店。