第八站:vector及其常用操作【上】

vector介绍:

vector是表示可变大小数组的序列容器。

vector与数组的相似点与不同点:

相同点:都是采用连续存储空间来存储元素,这就意味着可以采用下标对vector进行访问,和数组一样高效

不同点:vector的大小是可以动态改变的,而且它的大小会被容器自动处理,vector 底层依靠一块动态分配的数组存储所有元素。 当插入新元素的时候且存储空间不够的时候,数组会自动重新分配一个更大的新数组,然后将所有元素都移到这个数组中,代价较高。 因此每当一个新的元素加入到容器的时候,vector并不会每次都重新分配内存。 总结:相较于数组,vector会占用更多的内存,以此换取自动内存管理,高效动态扩容的能力

vector分配空间策略:

vector会预先分配一部分额外内存,预留扩容空间,所以vector的容量(capacity)通常大于当前实际存储元素的大小(size)

注意:C++ 标准仅规定 vector 尾插均摊 O (1),未限定扩容倍数,各 STL 实现策略不同:
  1. Windows VS 的 PJ(MSVC)STL 采用 1.5 倍扩容,计算方式为旧容量 + 旧容量 / 2,内存碎片更少;但是增容次数更多,效率更低,因为每次增容都要付出代价
  2. Linux GCC 的 libstdc++ 基于经典 SGI STL 实现,采用 2 倍扩容,逻辑简单、扩容次数少,但是浪费的空间更多;

因此不能固化认为 vector 一定 2 倍扩容,扩容因子由标准库实现决定。同时,增多少是一种选择,各有利弊,均衡一点。

注意:

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。 对size没有影响

resize在开空间的同时还会进行初始化为0,影响size,不会影响底层的容量capacity。

java 复制代码
举例:
//n < 当前 size:截断尾部多余元素,销毁超出部分
//n > 当前 size:尾部新增元素,内置类型默认值初始化(int 补 0)
//resize(n, val):扩容时新增的元素统一用 val 填充

#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> myvector;

  // set some initial content:
  for (int i=1;i<10;i++) myvector.push_back(i);

  myvector.resize(5);
  myvector.resize(8,100);
  myvector.resize(12);

  std::cout << "myvector contains:";
  for (int i=0;i<myvector.size();i++)
    std::cout << ' ' << myvector[i];
  std::cout << '\n';

  return 0;
}
//输出:myvector contains: 1 2 3 4 5 100 100 100 0 0 0 0

vector的简单使用

1.创建一个vector数组
java 复制代码
vector<数据类型> 数组名;
2.向数组中插入元素
java 复制代码
数组名.push_back(值);
3.返回vector中元素的个数
java 复制代码
数组名.size();
4.遍历数组

假设数组名为v

4.1.for循环遍历:operator\[\] +size
java 复制代码
for(size_t i = 0; i < v.size(); i++){
    cout << v[i] << " ";
}
cout << endl;//换行
4.2.迭代器
java 复制代码
vector<int>::iterator it = v.begin();
while(it != v.end())
{
    cout << *it << " ";
    it++;
}
cout << endl;
4.3.范围for(被编译器替换成迭代器方式遍历来支持)
java 复制代码
for(auto e : v)
{
    cout << e << " ";
}
cout << endl;
4.4.关于只读数组的遍历
java 复制代码
vector<int>::const_iterator it = v.begin();
while(it != v.end()){
   // *it = 1;会报错
    cout << *it << " ";
    it++;
}
cout << endl;
4.5.关于数组的逆置输出
java 复制代码
//用到的迭代器函数:reverse_iterator rbegin();const_reverse_iterator rbegin() const;

vector<int>::reverse_iterator rit = v.rbegin();
while(rit != v.rend())
{
    cout << *rit << " ";
    rit++;
}
cout << endl;
5.赋值运算符=的使用:

vector 赋值 operator=, 赋值会覆盖目标容器全部内容,目标 size、内容完全和右侧容器保持一致,capacity 会根据实现重新分配适配

java 复制代码
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> foo (3,0);//创建包含 3 个元素,每个元素值都为 0
  std::vector<int> bar (5,0);//创建包含 5 个元素,每个元素值都为 0

  bar = foo;//bar 丢弃原来 5 个 0,完全复制 foo 的内容,foo 不变,size 依旧是 3
  foo = std::vector<int>();//std::vector<int>() 调用无参构造,生成一个空临时 vector(size=0,无任何元素)

  std::cout << "Size of foo: " << int(foo.size()) << '\n';//Size of foo: 0
  std::cout << "Size of bar: " << int(bar.size()) << '\n';//Size of foo: 3
  return 0;
}
注意:

1.int(foo.size()) 强制转换原因size() 返回值类型是无符号整数size_t,直接输出没问题,这里强转 int 是为了避免无符号 / 有符号类型警告。

  1. vector 的=是**深拷贝**,两个容器内存完全独立,后续修改 foo 不会影响 bar
6.关于给数组赋值时访问越界时,不同访问方式可能出现的情况
6.1.用\[\]访问时
java 复制代码
v[值下标] = 给定值;//会报断言错误,常使用
6.2.用at访问
java 复制代码
v.at(值下标) = 给定值;//会报异常错误
7.插入数据
java 复制代码
v.insert(v.begin(), 值);//头插
v.insert(v.end(),值);//尾插
8.删除数据
java 复制代码
v.erase(v.begin());//头删
v.erase(v.end());//尾删
9.寻找数组中特定值的下标,使用find函数
java 复制代码
vector<int>::iterator pos = find(v.begin(), c.end(), 值);
if(pos != v.end())//pos到了v.end()表示没找到
{
    v.erase(pos);//删除pos下标对应的值
}
    
10.排序函数sort的使用
java 复制代码
sort(v.begin(), v.end());

vector 迭代器失效问题:

迭代器失效,实际就是迭代器底层对应指针所指向的 空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃

举例:1,插入元素崩溃
java 复制代码
//1.插入数据引起迭代器的失效,因为vector容量不够时会扩容,扩容后原来的空间会被释放
//而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
//空间,而引起代码运行时崩溃。
//解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可;或将迭代器操作放在插入元素后计算
void test1() {
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	//计算迭代器
	vector<int>::iterator i1 = v.begin();
	v.push_back(6);
	v.push_back(7);
	//迭代器遍历数组
	while (i1 != v.end()) {
		cout << *i1 << " ";
		i1++;
	}
	cout << endl;
}
举例:2,删除元素崩溃及修正方法
java 复制代码
//2.删除元素崩溃
void test2() {
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	v.push_back(7);
	//计算迭代器
	vector<int>::iterator i1 = v.begin();
	//删除容器中的所有偶数
	/*while (i1 != v.end()) {
		if (*i1 % 2 == 0) {
			v.erase(i1);
		}
		i1++;
	}
	//删除i1后,i1就失效了,i1的位置不对了,再++不可以:vs下报错,编译检查出来的;gcc下可能会报错,可能正常运行,但可能有偶数没删掉
	//这样写程序会崩溃
	*/

//修正写法

	while (i1 != v.end()) {
		if (*i1 % 2 == 0) {
			i1 = v.erase(i1);//erase会返回删除的i1的下一个位置的迭代器
		}
		else {
			i1++;
		}
	}
	//遍历
	for (auto e : v) {
		cout << e << " ";
	}
	cout << endl;
}

注意: resize、reserve、insert、assign、 push_back等 ,都有可能是迭代器失效。

memset的使用:按字节处理拷贝内容

java 复制代码
memset(a, 0, sizeof(int)*10);//合理,每一位都是0
memset(a, 1, sizeof(int)*10);//不合理,int类型有4个字节,如果每一位都赋予00000001的话,总的结果不是原来的值

小知识:c++中变量初始化

java 复制代码
int i = int();//i = 0
int j = int(1);//j = 1
double d = double();//d=0.0
double e = double(1.1);//e = 1.1

vector模拟实现

https://gitee.com/papaya-2/test.c.4/commit/efa1d4c71285a08dc8625104e0f969c2de6e7d46

相关推荐
Sunsets_Red2 小时前
浅谈博弈论
c++·学习·编程·博弈论·信息学竞赛·巴巴博弈
ALex_zry2 小时前
C++26 std::inplace_vector 详解:零堆分配的定容向量
开发语言·c++
卷无止境2 小时前
C++ 类型转换变迁之路:从隐式转换到 static_cast
c++·后端
h_a_o777oah3 小时前
【动态规划】区间 DP :三层循环逻辑与模板实现细节(洛谷 P1880)
c++·算法·动态规划·acm·区间dp·化环为链·石子合并
QiLinkOS3 小时前
第三视觉理解徐玉生与他的商业活动(32)
大数据·c++·人工智能·算法·开源协议
汉克老师4 小时前
GESP2026年6月认证C++一级( 第一部分选择题(8-15))精讲
c++·continue·运算符·while·break·gesp1级·模运算
鹏易灵4 小时前
C++——7.类与对象,掌握封装、继承、多态.详解
开发语言·c++·算法
Mortalbreeze4 小时前
深入 Linux 线程机制(三):线程互斥——竞争条件与互斥锁的本质
linux·运维·服务器·c++·算法
汉克老师4 小时前
GESP2026年6月认证C++一级( 第一部分选择题(1-7))精讲
c++·运算符·表达式·赋值·gesp1级·输入与输出·单循环