std::vector

这里主要介绍下reserce/resize、push_back/emplace_back、shrink_to_fit/clear等接口;

1. reserve and resize

       C++的vector对象可以通过reserve方法来设置vector对象的容量,通过resize方法来改变vector对象的大小。reserve所设置的容量指的是vector容器中可容纳元素的预留空间个数,但并不真正创建元素对象。resize则是直接改变vector容器中元素的个数,并且创建对象或者销毁空间。

       resize()函数和容器的size息息相关。调用resize(n)后,容器的size即为n。至于是否影响capacity,取决于调整后的容器的size是否大于capacity。

reserve()函数和容器的capacity息息相关。调用reserve(n)后,若容器的capacity<n,则重新分配内存空间,从而使得capacity等于n。

如果capacity>=n呢?capacity无变化。

       从两个函数的用途可以发现,容器调用resize()函数后,所有的空间都已经初始化了,所以可以直接访问。

        而reserve()函数预分配出的空间没有被初始化,所以不可访问。

#include <iostream>
#include <vector>
#include <string>
#include <map>
 
using ValueType = std::string;
const ValueType dv = "aaaaaaaaaaaaaaaaaaaaaaaa";
constexpr int maxSize = 10;
 
class Widget {
public:
	Widget(ValueType value = dv) : value{value}
	{
	    std::cout << "Widget(ValueType) constructor: " << this << std::endl;
	}
 
	Widget(const Widget& w) : value{w.value}
	{
	    std::cout<<"copy constructor" << std::endl;
	}
	Widget& operator=(const Widget& rhs)
	{
        if (this != &rhs)
        {
	        value = rhs.value;//assign new resource
        }
 
	    std::cout << "copy assignment constructor" << std::endl;
	    return *this;
	}
	//这里移动构造函数定义为noexcept
	Widget(Widget&& w) noexcept: value{std::move(w.value)}
	{
		std::cout << "move constructor" << std::endl;
	}
	Widget& operator=(Widget&& rhs)
	{
        if (this != &rhs)
        {
	        value = std::move(rhs.value);//assign new resource
        }
	    std::cout << "move assignment constructor" << std::endl;
	    return *this;
	}
 
    ~Widget()
	{
        if (value.empty())
        {
            std::cout << "0~destructor:" << this << std::endl;
        }
        else
        {
            value.clear();
	        std::cout << "~destructor:" << this << std::endl;
        }
	}
	void print(){std::cout << "value:" << value << " : " << this << std::endl;}
private:
	ValueType value{};
};

void test_reserve()
{
    std::vector<Widget> v{Widget{"aaaaaaaaaaaaaaaaaaaaaaaa"}, Widget{"bbbbbbbbbbbbbbbbbbbbbbbb"}, Widget{"ccccccccccccccccccccccc"}};
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    
    v.reserve(2);//当reserve的数据空间小于capacity的时候什么都不做;
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    
    //当reserve的数据空间大于capacity时,vector的capacity变为reserve指定的大校
    v.reserve(5);//由于要重新分配比 3 更大的存储空间,这里再次调用的移动构造函数(如果移动构造函数没有noexcept,则调用拷贝构造函数)
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    
    v.reserve(4);
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    
    v.reserve(2);
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
}

void test_resize()
{
    std::vector<Widget> v{Widget{"aaaaaaaaaaaaaaaaaaaaaaaa"}, Widget{"bbbbbbbbbbbbbbbbbbbbbbbb"}, Widget{"ccccccccccccccccccccccc"}};
    std::cout << std::endl;
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    v.resize(5);//vector重新分配空间(需要移动/拷贝原数据),resize的空间大于size()时,会构造 5 - size()个数据项(这里是构造2个新的Widget)
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    
    v.resize(4);//destory多余指定size的数据项
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    
    v.resize(2);//destory多余指定size的数据项
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
}

int main()
{
    test_reserve();
    std::cout << "----------------------" << std::endl;
    test_resize();
    
    return 0;
}

output:

2. vector(count, value) vs reserve(n)

这里有一个点需要注意下:vector( size_type count,

                                                        const T& value, 

                                                        const Allocator& alloc = Allocator() );

和 vector.reserve的区别, 这个vector会构造数据项,而reserve不构造数据项,只是预留了空间;

#include <iostream>
#include <vector>

void test_vector_constructor_vs_reserve()
{
    std::vector<int> v1(2,3);
    std::cout << "size()=" << v1.size() << ", capacity()=" << v1.capacity() << std::endl << std::endl;
    for(const auto i : v1)
    {
        std::cout << i << ' ';
    }
    std::cout << std::endl;
    
    std::vector<int> v2{};
    v2.reserve(2);//当reserve的数据空间小于capacity的时候什么都不做;
    std::cout << "size()=" << v2.size() << ", capacity()=" << v2.capacity() << std::endl << std::endl;
}

int main()
{
    test_vector_constructor_vs_reserve();
    
    return 0;
}

 3. shrink_to_fit vs clear

shrink_to_fit() : 它减少容器的容量以适应其大小并销毁超出容量的所有元素。它会尝试重新分配内存并释放未使用的多余内存

clear():从容器擦除所有元素。此调用后 size() 返回零。使任何指代所含元素的引用、指针或迭代器失效。任何尾后迭代器也会失效。  保持 vector 的 capacity() 不变。

#include <iostream>
#include <vector>
#include <string>
 
using ValueType = std::string;
const ValueType dv = "aaaaaaaaaaaaaaaaaaaaaaaa";
constexpr int maxSize = 10;
 
class Widget {
public:
	Widget(ValueType value = dv) : value{value}
	{
	    std::cout << "Widget(ValueType) constructor: " << this << std::endl;
	}
 
	Widget(const Widget& w) : value{w.value}
	{
	    std::cout<<"copy constructor" << std::endl;
	}
	Widget& operator=(const Widget& rhs)
	{
        if (this != &rhs)
        {
	        value = rhs.value;//assign new resource
        }
 
	    std::cout << "copy assignment constructor" << std::endl;
	    return *this;
	}
	//这里移动构造函数定义为noexcept
	Widget(Widget&& w) noexcept: value{std::move(w.value)}
	{
		std::cout << "move constructor" << std::endl;
	}
	Widget& operator=(Widget&& rhs)
	{
        if (this != &rhs)
        {
	        value = std::move(rhs.value);//assign new resource
        }
	    std::cout << "move assignment constructor" << std::endl;
	    return *this;
	}
 
    ~Widget()
	{
        if (value.empty())
        {
            std::cout << "0~destructor:" << this << std::endl;
        }
        else
        {
            value.clear();
	        std::cout << "~destructor:" << this << std::endl;
        }
	}
	void print(){std::cout << "value:" << value << " : " << this << std::endl;}
private:
	ValueType value{};
};

void test_shrink_to_fit()
{
    std::vector<Widget> v;

    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    v.emplace_back(std::string(24, 'a'));
    v.emplace_back(std::string(24, 'b'));
    v.emplace_back(std::string(24, 'c'));
    v.emplace_back(std::string(24, 'd'));
    v.emplace_back(std::string(24, 'e'));
    
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    
    v.shrink_to_fit();//按照我测试结果,测试capacity=8, size为添加的5个元素;
                      //shrink_to_fit会释放掉多余的vector空间,使得capacity()==size();
                      //这里可能会重新分配空间,具体依赖于编译器的实现
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
}

void test_clear()
{
    std::vector<Widget> v{Widget{"aaaaaaaaaaaaaaaaaaaaaaaa"}, Widget{"bbbbbbbbbbbbbbbbbbbbbbbb"}, Widget{"ccccccccccccccccccccccc"}};
    
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    std::cout << "call clear():" << std::endl;;
    v.clear();//会把所有数据项destory掉,此时size()大小为0, capacity则不变;
    std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
    
}

int main()
{
    test_shrink_to_fit();
    std::cout << std::endl << "----------------------" << std::endl << std::endl;
    test_clear();
    
    return 0;
}

4. push_back vs emplace_back

push_back:是插入数据到vector,这里会构造一个临时的数据项,这个临时数据项参数满足push_back(T&& value)接口,通过移动构造函数把这个临时的数据项移动到vector对应的位置上;

emplace_back:是通过完美转发参数,在vector对应的位置上就地构造,只需要调用一次构造函数就可以;这种情况下,效率上来说要比push_back高一些;

对于push_back和emplace_back来说,如果传入的数据项本身是个左值,那么他们效率上都是一样的。因为这两个接口都只需要调用一次拷贝构造函数。代码如下:

#include <iostream>
#include <vector>
#include <string>
#include <map>
 
using ValueType = std::string;
const ValueType dv = "aaaaaaaaaaaaaaaaaaaaaaaa";
constexpr int maxSize = 10;
 
class Widget {
public:
	Widget(ValueType value = dv) : value{value}
	{
	    std::cout << "Widget(ValueType) constructor: " << this << std::endl;
	}
 
	Widget(const Widget& w) : value{w.value}
	{
	    std::cout<<"copy constructor" << std::endl;
	}
	Widget& operator=(const Widget& rhs)
	{
        if (this != &rhs)
        {
	        value = rhs.value;//assign new resource
        }
 
	    std::cout << "copy assignment constructor" << std::endl;
	    return *this;
	}
	Widget(Widget&& w) : value{std::move(w.value)}
	{
		std::cout << "move constructor" << std::endl;
	}
	Widget& operator=(Widget&& rhs)
	{
        if (this != &rhs)
        {
	        value = std::move(rhs.value);//assign new resource
        }
	    std::cout << "move assignment constructor" << std::endl;
	    return *this;
	}
 
    ~Widget()
	{
        if (value.empty())
        {
            std::cout << "0~destructor:" << this << std::endl;
        }
        else
        {
            value.clear();
	        std::cout << "~destructor:" << this << std::endl;
        }
	}
	void print(){std::cout << "value:" << value << " : " << this << std::endl;}
private:
	ValueType value{};
};
void push_vs_empalce_with_rvalue_item()
{
    std::vector<Widget> v{};
    v.reserve(10);
    std::cout << std::endl;
    
    std::string str(24, 'a');
    v.push_back(str);
    std::cout << std::endl;
    
    v.emplace_back(str);
    std::cout << std::endl;
    
}
void push_vs_empalce_with_lvalue_item()
{
    Widget w1("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
    Widget w2("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
    std::vector<Widget> v{};
    v.reserve(10);
    
    std::cout << std::endl;
    v.push_back(w1);
    std::cout << std::endl;
    
    v.emplace_back(w2);
    std::cout << std::endl;
}

int main()
{
    push_vs_empalce_with_rvalue_item();
    std::cout << std::endl << "-------------------------" << std::endl << std::endl;
    push_vs_empalce_with_lvalue_item();
    
    return 0;
}

output:

5 vector::reserve() 和 vector()  区别

std::vector<int> v1; v1.reserve(10) 和 std::vector<int> v2(10); 是两种不同的初始化方式,它们的行为和适用场景有显著区别。以下是对它们的详细对比和示例说明:


1. 内存分配与初始化

std::vector<int> v1; v1.reserve(10)
  • 行为:

    • v1 是一个空的 vector,初始时不包含任何元素。

    • v1.reserve(10) 仅为 v1 分配至少能容纳 10 个元素的内存空间,但不会创建或初始化任何元素。

    • v1.size() 仍为 0,v1.capacity() 至少为 10。

  • 适用场景:

    • 当你需要预先分配内存以避免频繁重新分配,但暂时不需要实际元素时使用。

    • 适合需要逐步添加元素的场景。

std::vector<int> v2(10)
  • 行为:

    • v2 是一个包含 10 个元素的 vector,每个元素被值初始化为 int()(即 0)。

    • v2.size() 为 10,v2.capacity() 至少为 10。

  • 适用场景:

    • 当你需要一个固定大小的 vector,并且所有元素需要初始化为默认值(如 0)时使用。

    • 适合需要立即使用元素的场景。


2. 访问元素

std::vector<int> v1; v1.reserve(10)
  • 行为:

    • v1 是空的,不能直接通过下标访问元素(如 v1[0]),否则会导致未定义行为。

    • 必须通过 push_back 或 emplace_back 添加元素后才能访问。

  • 示例 1 :

std::vector<int> v1;
v1.reserve(10);

// v1[0] = 1; // 错误:未定义行为,因为 v1 是空的
v1.push_back(1); // 正确:添加元素
std::cout << v1[0]; // 正确:输出 1
std::vector<int> v2(10)
  • 行为:

    • v2 包含 10 个元素,可以直接通过下标访问元素(如 v2[0])。

    • 所有元素初始化为 0

  • 示例:

std::vector<int> v2(10);

std::cout << v2[0]; // 正确:输出 0
v2[0] = 1; // 正确:修改元素
std::cout << v2[0]; // 正确:输出 1

3. 添加元素

std::vector<int> v1; v1.reserve(10)
  • 行为:

    • 可以通过 push_back 或 emplace_back 添加元素,直到达到 capacity()

    • 添加元素不会触发重新分配,因为内存已经预先分配。

  • 示例:

std::vector<int> v1;
v1.reserve(10);

for (int i = 0; i < 10; ++i) {
    v1.push_back(i); // 添加元素
}
std::cout << v1.size(); // 输出 10
std::vector<int> v2(10)
  • 行为:

    • v2 已经有 10 个元素,直接修改现有元素即可。

    • 如果需要添加更多元素,可以使用 push_back 或 emplace_back,但可能会触发重新分配。

  • 示例:

std::vector<int> v2(10);

for (int i = 0; i < 10; ++i) {
    v2[i] = i; // 修改现有元素
}
v2.push_back(10); // 添加新元素,可能触发重新分配
std::cout << v2.size(); // 输出 11

4. 性能对比

std::vector<int> v1; v1.reserve(10)
  • 优点:

    • 避免频繁的内存重新分配,适合需要逐步添加元素的场景。

  • 缺点:

    • 需要显式添加元素,不能直接访问未初始化的内存。

std::vector<int> v2(10)
  • 优点:

    • 直接初始化所有元素,适合需要立即使用元素的场景。

  • 缺点:

    • 如果不需要所有元素,可能会浪费初始化时间。

完整代码示例:

#include <iostream>
#include <vector>

int main() {
    // 示例 1: 使用 reserve
    std::vector<int> v1;
    v1.reserve(10); // 预分配内存

    for (int i = 0; i < 10; ++i) {
        v1.push_back(i); // 逐步添加元素
    }

    std::cout << "v1: ";
    for (int i : v1) {
        std::cout << i << " ";
    }
    std::cout << "\n";

    // 示例 2: 使用固定大小初始化
    std::vector<int> v2(10); // 初始化 10 个元素,值为 0

    for (int i = 0; i < 10; ++i) {
        v2[i] = i; // 直接修改元素
    }

    std::cout << "v2: ";
    for (int i : v2) {
        std::cout << i << " ";
    }
    std::cout << "\n";

    return 0;
}

6 vector中删除指定元素

从vector<int> v1 = {7, 8, 9, 10, 9, 10};中删除指定的元素9,哪些算法是正确的:

//algorithm A:

for (auto iter = v1.begin(); iter != v1.end();iter++)
{
    if (*iter == 9)
    {
        v1.erase(iter);
    }
}

//algorithm B:
for (auto iter = v1.begin(); iter != v1.end();iter++)
{
    if (*iter == 9)
    {
        iter=v1.erase(iter);
    }
}

//algorithm C:
for (auto iter = v1.begin(); iter != v1.end();)
{
    if (*iter == 9)
    {
        iter = v1.erase(iter);
    }
    else
    {
        iter++;
    }
}

//algorithm D:
for(auto iter=v1.begin(); iter!=v1.end(); )
{
    if( *iter == 9)
    {  
        auto& iter2 = iter;
        v1.erase(iter2);
    }
    else
    {
        iter ++ ;
    }
}

 

算法C和D是正确的

algorithm A:

for (auto iter = v1.begin(); iter != v1.end();iter++)
{
    if (*iter == 9)
    {
        v1.erase(iter);//执行完这行代码后,iter就编程了野指针,对一个野指针进行iter++是错误的;
    }
}

algorithm B:

for (auto iter = v1.begin(); iter != v1.end();iter++)
{
    if (*iter == 9)
    {
        iter=v1.erase(iter);
    }
}

这段代码也是错误的:1)无法删除两个连续的"3"; 2)当3位于vector最后位置的时候,也会出错(在veci.end()上执行 ++ 操作)

#include <iostream>

#include <string>

#include <vector>

struct A

{

int i;   //key

std::string s;

};

int main(void)

{

    std::vector<A> va{{10, "aaa"},{10, "bbb"}, {10, "ccc"}, {10, "ddd"}, {10, "hhh"},{6, "eee"}, {8, "fff"} /*, {10, "segment fault"}*/};

    for (auto iter = va.begin(); iter != va.end(); iter++)

{

if ((*iter).i == 10)

{

iter = va.erase(iter);

}

}

    for(const auto v : va)

    {

        std::cout << "{" << v.i << "," << v.s << "} ";

    }

}

Result:  {10,bbb} {10,ddd} {6,eee} {8,fff}

在一次循环内部,当执行了 va.erase() 后, iter 指针会自动指向被删除了的元素的下一个位置,这个时候再执行循环体中的 iter++,就会导致 iter 直接指向了下下个元素! 当第一个10被删除后, iter 指向了第三个10,并且将其删除,然后又指向了第5个10,将其删除。因此结果中,第二个和第四个10就被保留了,没有删除干净。

其实,这种写法的后果不仅仅是删除不干净那么简单:如果在数组最后的位置还有一个10呢?当最后一个10被删除后, iter 直接指向了数组的最后一位数字的后一位,超出了数组的界限,就会引发程序崩溃!

如果要手动实现这个算法从后面开始查找,总体移动的元素数量比从开头查找要移动的数量少。

C++20中可以使用std::erase_if来实现了。 

vector.erase()函数的常见陷阱_vector.erase(it++)-CSDN博客

vector中erase用法注意事项_vector erase后begin会变吗-CSDN博客

后续再介绍vector插入数据的性能等;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值