[C++笔记]vector

news2024/10/7 6:42:52

vector

vector的说明文档

  1. vector是表示可变大小数组的序列容器(动态顺序表)。
  2. 就像数组一样,vector也采用连续的存储空间来储存元素。这就意味着可以用下标对vector的元素进行访问,和数组一样高效。与数组不同的是,它的大小可以动态改变——由容器自动处理。
  3. 底层而言,vector使用动态分配的数组来存储元素。当新元素插入时,这个数组可能需要被重新分配大小(扩容)。其做法为,分配一个新的数组,然后将所有元素移到这个新数组。就时间成本而言,这是一个代价较高的行为,因此不会在每次向容器添加元素时都重新分配大小。
  4. vector会分配一些额外的空间以应对可能需要的扩容,因此容器的实际容量大小可能大于容纳其元素所必须的存储空间的大小。不同的库采用不同的策略来权衡空间的使用(usage)与重新分配(reallocation)。但在任何情况下,重新分配都应该只发生在对数增长的大小间隔上,以使得尾插一个元素的时间复杂度为常数。
  5. 因此,相较于数组,vector占用了更多的存储空间以获得管理存储空间的能力,并以一种高效率的方式动态增长。
  6. 与其它动态序列容器相比(deque(双端队列),lists(双链表),forward_lists(单链表)), vector在访问其元素时非常高效(就像数组一样),而且从其末端添加或删除元素时也相对高效。对于在末端以外的位置插入或删除元素的操作,它的效率较低。而且与list和forward_lists相比,它的迭代器与引用不太一致。

string是负责管理字符类型的数组,而vector是负责管理任意类型的数组 。
string相比vector有太多冗余的接口,vector的设计相对更合理些。

vector构造函数

vector构造函数接口说明
vector()(重点)无参构造
vector (const vector& x); (重点)拷贝构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造

vector容量管理

接口接口说明
resize(重点)改变vector的size
reserve (重点)改变vector的capacity
size获取数据个数
capacity获取容量大小
empty判断是否为空
  1. capacity的代码在vs和g++下分别运行可以发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。vector每次增容增多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  2. reserve只负责开辟空间。如果明确知道需要用多少空间,可使用reserve来缓解vector增容代价较高的问题。
  3. resize在开空间的同时还会进行初始化,影响size。

vector增删查改

接口接口说明
push_back(重点) 尾插
pop_back(重点) 尾删
operator[](重点) 像数组一样访问
find查找。(注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间

vector迭代器

接口接口说明
begin +end(重点)获取第一个元素位置的iterator/const_iterator, 获取最后一个元素的下一位的iterator/const_iterator
rbegin + rend获取最后一个元素位置的reverse_iterator,获取第一个元素前一位的reverse_iterator

在这里插入图片描述

vector 迭代器失效问题(重点)

  1. 迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际上就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。
  2. 而迭代器失效,实际上就是迭代器底层所对应的指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果便是程序崩溃(即,若继续使用已经失效的迭代器,程序可能会崩溃)。

===
对于vector,可能会导致其迭代器失效的操作有:
1.会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。

2.指定位置元素的删除操作–erase

3.注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

-SGI STL中,迭代器失效后,代码不一定会崩溃,但运行结果肯定不
对。若it不在begin和end范围内,肯定会崩溃。

4.与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效

迭代器失效解决办法:在使用前,对迭代器重新赋值即可。

模拟实现vector

#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <assert.h>
using namespace std;

namespace my_vector {
	template<class T>
	class vector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector()
		{}

		vector(size_t n, const T& val = T()) /*由于要兼容所有类型,缺省值不能为0,而是调默认构造函数构造匿名对象*/
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i) {
				push_back(val);
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; ++i) {
				push_back(val);
			}
		}

		//复用实现深拷贝
		/*vector(const vector<T>& v){
			reserve(v.capacity());
			for (auto e : v){
				push_back(e);
			}
		}*/

		//原生方式实现深拷贝
		vector(const vector<T>& v){
			_start = new T[v.capacity()];
			//memcpy(_start, v._start, sizeof(T) * v.size());//memcpy是浅拷贝,不适用于自定义类型
			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)/*迭代器区间 [first,last)*/
		{
			while (first != last) {//这里不能比较大小,有的结构,如链表,前后节点的地址大小关系并不确定
				push_back(*first);
				++first;
			}
		}

		~vector() {
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}

		iterator begin() {
			return _start;
		}
		iterator end() {
			return _finish;
		}
		const_iterator begin() const {
			return _start;
		}
		const_iterator end() const {
			return _finish;
		}

		void resize(size_t n, T val = T()) {//由于要兼容所有类型,缺省值不能为0,而是调默认构造函数构造匿名对象 
			if (n < size()) {
				_finish = _start + n;//等效于删除数据
			}
			else {
				if (n > capacity()) {
					reserve(n);
					while (_finish != _start + n) {
						*_finish = val;
						++_finish;
					}
				}
			}

		}

		void reserve(size_t n) {
			if (n > capacity()) {
				size_t sz = size();//提前记录size
				T* tmp = new T[n];
				if (_start) {
					//memcpy(tmp, _start, sizeof(T) * size());//memcpy是浅拷贝,不适用于自定义类型
					for (size_t i = 0; i < sz; ++i){
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;//size()返回的是_finish - _start ,这里用size()的话会变成_finish=_start+_finish-_start ,_finish会变成0 ,只能用提前记录的sz
				_end_of_storage = _start + n;
			}
		}

		size_t capacity() const {
			return _end_of_storage - _start;
		}

		size_t size() const {
			return _finish - _start;
		}

		bool empty() {
			return _start == _finish;
		}

		T& operator[](size_t pos) {
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos) const {
			assert(pos < size());
			return _start[pos];
		}

		void push_back(const T& x) {
			if (_finish == _end_of_storage) {//容量检查
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			++_finish;
		}

		void pop_back() {
			assert(!empty());//确保非空
			--_finish;
		}

		iterator insert(iterator pos, const T& val) {
			assert(pos >= _start);
			assert(pos <= _finish);
			if (_finish == _end_of_storage) {//容量检查
				size_t len = pos - _start;//为防迭代器失效,需要在扩容前记录pos到_start的相对距离,以便在扩容后更新pos位置。
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;//扩容后用记录的相对距离更新pos位置,避免pos位置错误导致的迭代器失效(野指针型)
			}
			iterator end = _finish - 1;
			while (end >= pos) {
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;

			return pos;
		}

		iterator erase(iterator pos) {
			assert(pos >= _start);
			assert(pos < _finish);//pos不能等于_finish
			iterator start = pos + 1;
			while (start != _finish) {
				//挨个往前挪
				*(start - 1) = *start;
				++start;
			}
			--_finish;

			return pos;
		}

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	//===========类分隔线===========

	void func(const vector<int>& v) {
		cout << endl;

		for (size_t i = 0; i < v.size(); ++i) {
			cout << v[i] << " ";
		}
		cout << endl;

		vector<int>::const_iterator it = v.begin();
		while (it != v.end()) {
			cout << *it << " ";
			++it;
		}
		cout << endl;

		cout << endl;
	}

	void test_vector1() {
		vector<int> v1;
		v1.push_back(0);
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		func(v1);

		for (size_t i = 0; i < v1.size(); ++i) {
			cout << v1[i] << " ";
		}
		cout << endl;

		v1.pop_back();
		v1.pop_back();

		vector<int>::iterator it = v1.begin();
		while (it != v1.end()) {
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : v1) {
			cout << e << " ";
		}
		cout << endl;
	}

	template<class T>
	void f() {
		T x = T();
		cout << x << endl;
	}

	void test_vector2() {
		//虽然内置类型理论上没有构造函数,但为兼容模板,一般支持像自定义类型一样用构造函数
		//int i = int();//会初始化为0
		//int j = int(1);//会初始化为1
		f<int>();
		f<int*>();
		f<double>();
	}

	void test_vector3() {
		vector<int> v1;
		v1.push_back(0);
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		cout << v1.size() << endl;
		cout << v1.capacity() << endl;

		v1.resize(10);

		cout << v1.size() << endl;
		cout << v1.capacity() << endl;

		func(v1);

		v1.resize(3);

		func(v1);
	}

	void test_vector4() {
		vector<int> v1;
		v1.push_back(0);
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		//v1.push_back(4);


		//*v1.insert(v1.begin(), 9);

		auto pos = find(v1.begin(), v1.end(), 2);
		if (pos != v1.end()) {
			//pos = v1.insert(pos, 50);
			v1.insert(pos, 50);
		}

		//insert后的pos默认看作已失效(野指针),不能再使用,因为位置关系变了
		
		for (auto e : v1) {
			cout << e << " ";
		}
		cout << endl;
		
	}

	void test_vector5() {
		vector<int> v1;
		v1.push_back(0);
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		for (auto e : v1) {
			cout << e << " ";
		}
		cout << endl;

		auto pos = find(v1.begin(), v1.end(), 3);
		if (pos != v1.end()) {
			v1.erase(pos);
		}

		//类似于insert后,erase后pos也视作失效(野指针),因为位置关系也变了
		//(比如pos指向最后一个元素,erase这个元素后pos就指向了_finish,不再指向合法位置)
		//(*pos)++;//vs下会被检查到然后报错,linux下g++不检查但还是可能会出问题(行为结果未定义,与具体编译器实现有关)
		//string也会有迭代器失效的情况,但string的erase和insert更常用下标而不是迭代器,故问题不算明显。
		
		for (auto e : v1) {
			cout << e << " ";
		}
		cout << endl;
	}

	void test_vector6() {
		vector<int> v1;
		v1.push_back(0);
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		for (auto e : v1) {
			cout << e << " ";
		}
		cout << endl;

		//删除所有偶数:迭代器适合删除
		vector<int>::iterator it = v1.begin();
		while (it!=v1.end()){
			if (*it % 2 == 0) {
				it = v1.erase(it);//迭代器erase后失效,接收返回值以更新pos
			}
			else {
				++it;//迭代器未失效,正常++
			}
		}
		for (auto e : v1) {
			cout << e << " ";
		}
		cout << endl;
	}

	void test_vector7() {
		//vector<int> v1(10u, 5);//u表示无符号
		vector<int> v1(10, 5);
		for (auto e : v1) {
			cout << e << " ";
		}
		cout << endl;

		vector<int> v2(v1.begin() + 1, v1.end() - 1);
		for (auto e : v2){
			cout << e << " ";
		}
		cout << endl;

		std::string s1("hell word");
		vector<int> v3(s1.begin(), s1.end());
		for (auto e : v3){
			cout << e << " ";
		}
		cout << endl;

		int arr[] = {1,2,60,3,4,5};
		vector<int> v4(arr, arr + 4);
		for (auto e : v4){
			cout << e << " ";
		}
		cout << endl;

		v1.insert(v1.begin(), 8);
		for (auto e : v1){
			cout << e << " ";
		}
		cout << endl;

		sort(v1.begin(), v1.end());
		for (auto e : v1){
			cout << e << " ";
		}
		cout << endl;


		for (auto e : arr){
			cout << e << " ";
		}
		cout << endl;

		sort(arr, arr + sizeof(arr) / sizeof(int), greater<int>());//sort默认升序。除了能对迭代器的区间排序,还可以对数组排序。
		for (auto e : arr){
			cout << e << " ";
		}
		cout << endl;
	}

	void test_vector8(){
		vector<int> v1(10, 5);
		for (auto e : v1){
			cout << e << " ";
		}
		cout << endl;

		vector<int> v2(v1);
		for (auto e : v2){
			cout << e << " ";
		}
		cout << endl;

		vector<std::string> v3(3, "123456789123456789");
		for (auto e : v3){
			cout << e << " ";
		}
		cout << endl;

		vector<std::string> v4(v3);
		for (auto e : v4){
			cout << e << " ";
		}
		cout << endl;

		v4.push_back("1111111111");
		v4.push_back("2222222222");
		v4.push_back("3333333333");
		for (auto e : v4){
			cout << e << " ";
		}
		cout << endl;
	}

}
	

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1700页,卷S人的 Java《八股文》PDF手册,涨薪跳槽拿高薪就靠它了

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;又得准备面试了&#xff0c;不知道从何下手&#xff01; 不论是跳槽涨薪&#xff0c;还是学习提升&#xff01;先给自己定一个小目标&#xff0c;然后再朝着目标去努力就完事儿了&#xff01; 为了帮大家节约时间&a…

Mybatis一级缓存和二级缓存(带测试方法)

目录 一、什么是缓存 二、Mabtis一级缓存 &#xff08;1&#xff09;测试一级缓存 &#xff08;2&#xff09;清空一级缓存 三、Mybatis二级缓存 &#xff08;1&#xff09;开启二级缓存 &#xff08;2&#xff09;测试二级缓存 一、什么是缓存 缓存是内存当中一块存储数…

蓝桥杯嵌入式第十一届省赛题目解析

写完第十一届蓝桥杯嵌入式省赛题目&#xff0c;拿出来给大家参考参考&#xff0c;也是让大家一起测试看看有什么问题还需要改进&#xff0c;代码在最后喔。 目录 客观题&#xff1a; 程序设计题 &#xff1a; 题目解析&#xff1a; CubeMX配置 代码演示 &#xff1a; 客观…

Windows环境下实现设计模式——职责链模式(JAVA版)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows环境下如何编程实现职责链模式&#xff08;设计模式&#xff09;。 不知道大家有没有这样的感觉&#xff0c;看了一大堆编程和设计模式的书&#xff0c;却还是很难理解设计模式&#xff…

spring boot Websocket(使用笔记)

使用websocket有两种方式&#xff1a;1是使用sockjs&#xff0c;2是使用h5的标准。使用Html5标准自然更方便简单&#xff0c;所以记录的是配合h5的使用方法。 1、pom 核心是ServerEndpoint这个注解。这个注解是Javaee标准里的注解&#xff0c;tomcat7以上已经对其进行了实现&a…

学内核之十八:纸上得来终觉浅,绝知此事要躬行

目录 0 前言 1 ioremap、vmalloc与原子上下文 2 copy_to_user与进程上下文 3 fasync与指针初始化 4 wait_event_interruptible与条件变量 0 前言 大家都知道&#xff0c;内核开发跟应用开发&#xff0c;体验是完全不同的&#xff0c;尤其是驱动。一方面要掌握扎实的语言基…

MySQL 基本轮廓

目录 什么是数据库 主流数据库 基本使用 连接服务器 服务器管理 使用案例 创建数据库 使用数据库 创建数据库表 表中插入数据 查询表中的数据 服务器&#xff0c;数据库&#xff0c;表关系 MySQL架构 什么是数据库 存储数据用文件就可以了&#xff0c;为什么还要弄…

每日一问-ChapGPT-20230409-中医基础-四诊之望诊

文章目录每日一问-ChapGPT系列起因每日一问-ChapGPT-20230409-中医基础-四诊之望诊中医中的望闻问切介绍&#xff0c;以及对应的名家望诊的具体细节望诊拓展当日总结每日一问-ChapGPT系列起因 近来看了新闻&#xff0c;看了各种媒体&#xff0c;抖音&#xff0c;官媒&#xff…

【数据库原理 • 四】数据库设计和规范化理论

前言 数据库技术是计算机科学技术中发展最快&#xff0c;应用最广的技术之一&#xff0c;它是专门研究如何科学的组织和存储数据&#xff0c;如何高效地获取和处理数据的技术。它已成为各行各业存储数据、管理信息、共享资源和决策支持的最先进&#xff0c;最常用的技术。 当前…

jvm调优一:从源码级别了解jvm类加载机制

目录 一、类加载运行全过程 类加载器加载类的过程 二、类加载器和双亲委派机制 类加载器类型 类加载器初始化过程 双亲委派机制 为什么要设计双亲委派机制&#xff1f; 全盘负责委托机制 一、类加载运行全过程 当我们用java命令运行某个类的main函数启动程序时&#xff0c…

Kube-proxy 使用 iptables 模式时,通过 Service 服务发布入口如何到达 Pod ?

写在前面 被问到这个问题&#xff0c;整理相关的笔记当 kube-proxy 模式设置为 iptables 的时候&#xff0c;通过 SVC 服务发布入口如何到达 Pod&#xff1f;博文内容涉及&#xff1a; 问题简单介绍三种常用的服务发布方式到Pod报文路径解析 当前集群为版本为v1.25.1Demo 演示使…

linux内核结构以及内核模块编程

1、linux内核结构 1.1、单内核与微内核结构 1.1.1、什么是单内核结构和微内核结构 linux操作系统是一个单内核的结构&#xff0c;它的各个子系统之间可以直接调用 比如说文件系统、内存管理、进程管理以及网络系统和进程间通信它们互相之间可以直接调用只有一些核心的代码它…

记录npm的安装过程

一、访问官网&#xff08;https://nodejs.org/en&#xff09;&#xff0c;下载nodejs并安装&#xff1a; 然后一路点击next直到安装完成&#xff0c;环境变量已经自动添加好了&#xff1a; 通过设置环境变量&#xff0c;改变本地仓库地址&#xff1a; 可以看到&#xff0c;…

一条更新语句的执行流程又是怎样的呢?

当一个表上有更新的时候&#xff0c;跟这个表有关的查询缓存会失效&#xff0c;所以这条语句就会把表T上所有缓存结果都清空。这也就是我们一般不建议使用查询缓存的原因。 接下来&#xff0c;分析器会通过词法和语法解析知道这是一条更新语句。优化器决定要使用ID这个索引。然…

LNMP网站框架搭建(yum方式安装)

1. nginx 的yum安装 1.1 搭建nginx相关的yum源 注意&#xff1a;本次安装所获得的软件包都是来源于httpd源&#xff08;都是由该软件包厂商提供&#xff09;。所以切记不能像往常一样直接使用本地源去安装一切包 vim /etc/yum.repos.d/nginx.repo [nginx-stable] namenginx…

Linux--tty

Linux 终端(TTY) TTY 是 Teletype 或 Teletypewriter 的缩写&#xff0c;原来是指电传打字机&#xff0c;后来这种设备逐渐键盘和显示器取代。不管是电传打字机还是键盘显示器&#xff0c;都是作为计算机的终端设备存在的&#xff0c;所以 TTY 也泛指计算机的终端(terminal)设…

【CSS】更改用户界面样式 ① ( 更改鼠标样式 | 更改鼠标样式应用场景 | 代码示例 )

文章目录一、更改鼠标样式二、更改鼠标样式代码示例三、更改鼠标样式应用场景一、更改鼠标样式 为对象元素设置 cursor 样式 , 可以更改鼠标移动到该元素上的显示样式 ; cursor 样式常用属性值 : default : 默认鼠标样式 , 白色箭头鼠标 ;pointer : 小手形状 ;move : 移动 - …

C++——初始化列表 | explicit关键字 | static成员

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;初始化列表&#x1f33a;初始化列表的形式&#x1f33a;初始化列表的注意事项&#x1f337;explicit关键字&#x1f33a;单参数构造函数&#x1f33a;多参数构造函数&#x1f337;static成员&#x1f33a;stat…

SprigBoot学习笔记(五)

监控 监控的意义 可视化监控平台 监控原理 自定义监控指标 监控的意义 监控服务状态是否宕机 监控服务运行指标(内存、虚拟机、线程、请求等) 监控日志 管理服务(服务下线) 监控的实施方式 显示监控信息的服务器:用于获取服务信息,并显示对应的信息 运行的服务:启动时主动…

Node【五】内置模块 【http模块】

文章目录&#x1f31f;前言&#x1f31f;http模块&#x1f31f; 1.引入http模块&#x1f31f; 2.创建服务&#x1f31f; 3.添加头信息&#x1f31f; 4.搭建一个简单的服务器&#xff1a;&#x1f31f; 5.Request对象&#x1f31f; 6.Response对象&#x1f31f; 7.练习&#xff…