深入篇【C++】手搓模拟实现vector类(详细剖析接口底层实现原理):【200行代码实现】

news2024/10/6 12:21:18

深入篇【C++】手搓模拟实现vector类(详细剖析接口底层实现原理)【200行代码实现】

  • 【vector类模拟实现代码】
  • Ⅰ.构造/析构
      • 1.vector()
      • 2.operator=
      • 3.~string()
  • Ⅱ.访问遍历
      • 1.operator[]
      • 2.begin()/end()
  • Ⅲ.增操作
      • 1.push_back()
      • 2.insert()
  • Ⅳ.删操作
      • 1.erase()
      • 2.pop_back()
  • Ⅴ.查操作
      • 1.size()
      • 2.capacity()
  • Ⅵ.改操作
      • 1.reserve()
      • 2.resize()

【vector类模拟实现代码】

#pragma once

#include <string.h>
//vector的模拟实现
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <assert.h>
using namespace std;
namespace tao
{
	template <class T>//定义一个模板T
	class vector
	{
		
	public:
		typedef T* iterator;//将T* 重命名为iterator
		typedef const T* const_iterator;
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}
		//成员函数
		vector()//空值构造
			: _start(nullptr)
			, _finish(nullptr)
			, _endstroage(nullptr)
		{}
		vector(const vector<T>& v)//深拷贝
			: _start(nullptr)
			, _finish(nullptr)
			, _endstroage(nullptr)
		{
			_start = new T[v.size()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start+v.size();
			_endstroage = _start+v.capacity();
		}
		void swap(vector<T> v)
		{
			std::swap(_start , v._start);
			std::swap(_finish ,v._finish);
			std::swap(_endstroage ,v._endstroage);
		}
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endstroage = nullptr;
			}
		}
		void reserve(size_t n)
		{
			size_t sz = size();
			if (n > capacity())
			{
				T* temp = new T[n];//首先开空间
				if (_start != nullptr)
				{
					//将数据拷贝到temp去
					memcpy(temp, _start, sizeof(T) * sz);
					//删除原来空间
					delete[] _start;
				}
				//最后将空间赋值给_start
				_start = temp;
				_finish = _start + sz;
				//这里有一个问题,size()的计算是用_finish -start 而这里的start已经改变,而finish还没有改变
				//最后计算finish就变成空了,最终的问题在于start改变了,所有在之前要保留一份size()的数据
				_endstroage = _start + n;
			}
		}
		void push_back(const T& val)
		{
			//首先要考虑是否扩容
			if (_finish == _endstroage)
			{
				size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
				reserve(newcapacity);
			}
			*_finish = val;
			++_finish;

		 }
		size_t capacity() const
		{
			return _endstroage - _start;
		}
		size_t size() const
		{
			return _finish - _start;
		}
		T& operator[](size_t pos)
		{
			assert(pos <= size());
			return _start[pos];
		}
		const T& operator[](size_t pos) const
		{
			assert(pos <= size());
			return _start[pos];
		}
		iterator insert(iterator pos,const T &val)
		{
			assert(pos >= _start && pos <= _finish);
			//首先考虑扩容----这里有一个问题:迭代器失效
			//当迭代器扩容时,这里的pos迭代器就相当于失效了,因为原来的空间被释放了,pos也就变成野指针了。
			//需要将将pos迭代器恢复,需要更新pos的新位置。
			if (_finish == _endstroage)
			{
				size_t len = pos - _start;
				size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
				reserve(newcapacity);
				pos = _start + len;
			}
			//使用迭代器的好处就是可以避免string那样头插时,挪动数据,下标要小于0的问题,因为迭代器是一个地址,不可以为0的
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			_finish++;
			//insert 中的扩容迭代器失效,外部迭代器的解决方法是使用返回值,将pos位置返回过去,再用迭代器接收,就可以对pos位置上的内容再访问了
			return pos;
			//指向新插入位置的迭代器
		}
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos <= _finish);

			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;
			//返回的是删除元素的下一个位置的迭代器
		}
		void pop_back()
		{
			erase(--end());
		}
		void resize(size_t n, const T& val=T())//这里的val可以给缺省值的,当给定时,使用给定值,不给定时使用缺省值,缺省值给的是T类型的构造函数
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);//不用管原来的容量多少,reserve会判断是否需要扩容
				//填值
				while (_finish != n + _start)
				{
					*_finish = val;
					++_finish;
				}
			}
		}
	private:
       //成员变量
		iterator _start;//指向开头位置的迭代器
		iterator _finish;//指向真实数据的最后位置
		iterator _endstroage;//指向容量的的最后位置

	};

}

这里vector的实现需要用到模板,因为vector就是用模板实例化出各种类型的vector。
在这里插入图片描述
根据源码,它是按照上图的方式来进行处理的,成员变量是三个迭代器,start迭代器指向开头位置,finish迭代器指向最后一位有效数据的后面,endstroage迭代器指向的是有效容量的后面位置。
而这里的
size=_finish-start;
capcaity=endstorage-start;
迭代器是一种新的类型,需要自己定义,我们这里用typedef在类里定义迭代器的
typedef T* iterator;将T* 重命名为iterator

Ⅰ.构造/析构

1.vector()

1.vector最常用的构造就是无参构造了。这里只需要将vector的成员变量都初始化成空就可以了。

vector()//空值构造
			: _start(nullptr)
			, _finish(nullptr)
			, _endstroage(nullptr)
		{}

拷贝构造对于自定义类型需要使用深度拷贝,不能浅拷贝。
这里我们跟string类的拷贝构造类似,首先要初始化,给对象开空间,然后将值拷贝过去。对应的成员变量要一致。

vector(const vector<T>& v)//深拷贝
			: _start(nullptr)
			, _finish(nullptr)
			, _endstroage(nullptr)
		{
			_start = new T[v.size()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start+v.size();
			_endstroage = _start+v.capacity();
		}

2.operator=

①赋值运算符重载比如v1=v;将v赋值给v1,这里的v1和v都是存在的对象。可以用类似于string类里的赋值重载。首先用tmp开空间,将值拷贝到tmp中去,释放原来的空间,最后再将temp赋值给对象。
②不过我们可以用一个更简单的方式,我们知道对象v传过来函数用形参接收,我们想要就是这个形参的空间大小和数据,然后将原有的v1空间释放,将形参的对象的空间赋给v。我们这里可以直接使用swap函数让v1和v直接交换。这样v1就获得v的空间大小和数据了。最后交换完后的v1就变成形参了,函数结束后v1就会被销毁,即原来的空间被释放。

void swap(vector<T> v)//将v和原对象数据和空间交换
		{
			std::swap(_start , v._start);
			std::swap(_finish ,v._finish);
			std::swap(_endstroage ,v._endstroage);
		}
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			//交换完后,原对象的空间会因为变成形参后函数结束自动销毁。
			return *this;
		}

3.~string()

判断一下statr是否为空指针,如果为空指针那就不用释放了,如果不为空指针说明还有数据。需要释放,new[] 与delete[]配合使用,释放完后,将指针都置空。防止变成野指针。

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

Ⅱ.访问遍历

1.operator[]

使用下标和方括号来访问变量和遍历。首先我们需要知道对象的大小。而获取大小即finish-start即可。这里我们封装成函数size().
获取pos位置上的数据,而数据是在指针指向的地方,也就是_start[pos]。

     	T& operator[](size_t pos)
		{
			assert(pos <= size());//断言判断一下pos位置是否合法
			return _start[pos];
		}
		const T& operator[](size_t pos) const//用于const修饰的对象访问和遍历。
		{
			assert(pos <= size());
			return _start[pos];
		}

2.begin()/end()

利用迭代器访问和遍历对象也是很常见的,迭代器我们知道是用typedef T* iterator定义的,将T* 重命名为iterator,其本质上可以看成不同类型的指针。
begin()就是返回指向开头位置的迭代器
end()就是返回指向最后一个数据的后面的迭代器。
迭代器有很多类型,还有const修饰的迭代器,即const T类型的 利用typedef const T const_iterator.将const T*重命名为const_iterator.

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

Ⅲ.增操作

①尾插一个数据,首先需要判断是否要扩容,可以直接使用reserve()函数扩容,请上改操作里查看reserve()的实现。这里直接使用。这里需要讨论一下,容量是否为0,如果为0那么直接给它开辟4个空间,如果不是0那么就按照两倍的扩容。
②如果不需要扩容直接在finish位置插入数据,记得插入完,finish需要往后挪动一下。

1.push_back()

void push_back(const T& val)
		{
			//首先要考虑是否扩容
			if (_finish == _endstroage)
			{
				size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
				reserve(newcapacity);
			}
			*_finish = val;
			++_finish;

		 }

2.insert()

①相比较string类的insert这里vector就不太一样了,因为string中的insert使用的还是下标,而这里使用的是迭代器。在pos位置插入val。
①首先需要判断pos位置的合法性
②再判断是否需要扩容
③将数据挪动,从最后一个数据开始挪动,给pos位置留出位置。
④将数据插入pos位置,finish需要往后挪动一下。
⑤最后将pos返回也就是返回新插入数据的位置。
要考虑到如果扩容了,那肯定是异地扩容,那原来pos指向的原空间会因为扩容后,原空间释放,pos就变成野指针了。即pos迭代器失效了,不能再访问pos位置上的数据了,这里的解决方法是要扩容后,要更新pos位置。
使用迭代器的好处就是不用考虑头插时下标要小于0的问题了(string类里遇到的问题).因为迭代器是一个地址,不可以为0的。

iterator insert(iterator pos,const T &val)
		{
			assert(pos >= _start && pos <= _finish);
			//首先考虑扩容----这里有一个问题:迭代器失效
			//当迭代器扩容时,这里的pos迭代器就相当于失效了,因为原来的空间被释放了,pos也就变成野指针了。
			//需要将将pos迭代器恢复,需要更新pos的新位置。
			if (_finish == _endstroage)
			{
				size_t len = pos - _start;//记录pos位置在哪
				size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
				reserve(newcapacity);
				pos = _start + len;//扩容完更新pos位置,防止变成野指针。
			}
			//使用迭代器的好处就是可以避免string那样头插时,挪动数据,下标要小于0的问题,因为迭代器是一个地址,不可以为0的
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			_finish++;
			//insert 中的扩容迭代器失效,外部迭代器的解决方法是使用返回值,将pos位置返回过去,再用迭代器接收,就可以对pos位置上的内容再访问了
			return pos;
			//指向新插入位置的迭代器
		}

Ⅳ.删操作

1.erase()

①相比较string类里的erase这里vector的erase也不一样了,用的是迭代器作为位置,而不是下标。
②首先判断pos位置是否合法
③挪动数据,将pos位置覆盖。从前往后挪动。
④挪动完后,将finish往前挪动。
⑤返回pos,这里返回的是被删除数据的下一个元素的位置。

iterator erase(iterator pos)
		{
			assert(pos >= _start && pos <= _finish);

			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;
			//返回的是删除元素的下一个位置的迭代器
		}

2.pop_back()

尾删,可以直接复用erase,删除位置也就是end()前面的位置。因为end()指向的是最后一个数据的后面位置。

void pop_back()
		{
			erase(--end());
		}

Ⅴ.查操作

vector类里没有直接查找的函数比如find,不过在算法里有find,使用迭代器就可以用。
①size()数据的大小,其实就是finish-start。

1.size()

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

capacity()容量的大小,其实就是endstorage-statr。

2.capacity()

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

Ⅵ.改操作

1.reserve()

①扩容逻辑其实很简单,首先要判断要开辟的空间是否比原空间要大。如果小的话就不用开了。
②首先利用temp保存n大小空间
③然后将原对象数据拷贝到tmp去(如果原对象是空的,那就不用拷贝了直接将空间赋给对象即可)
④拷贝完将原空间释放。
⑤最后将tmp空间和数据赋给_start。_finish位置要更新。
这里要注意finish位置如何更新呢?很多人会这样写:_finish=_start+size();注意这样是不可以的。为什么呢?
因为在更新finish之前,statrt的位置已经改变了,不再指向原来的位置,而finish还是指向原来的空间位置。而size()=finish-start.最终计算出来finish就等于空了。问题就出在start位置已经改变了。所以size()里计算的就不是数据的大小了。我们应该先保存一份start没有改变的数据,一开始就记录一个数据大小,最后再加上就可以了。

void reserve(size_t n)
		{
			size_t sz = size();
			if (n > capacity())
			{
				T* temp = new T[n];//首先开空间
				if (_start != nullptr)
				{
					//将数据拷贝到temp去
					memcpy(temp, _start, sizeof(T) * sz);
					//删除原来空间
					delete[] _start;
				}
				//最后将空间赋值给_start
				_start = temp;
				_finish = _start + sz;
				//这里有一个问题,size()的计算是用_finish -start 而这里的start已经改变,而finish还没有改变
				//最后计算finish就变成空了,最终的问题在于start改变了,所有在之前要保留一份size()的数据
				_endstroage = _start + n;
			}
		}

2.resize()

①resize()是vector最常用的几个接口,可以给对象纪创建空间又可以初始化,一般来说初始化的值是缺省值,不给定的时就使用缺省值初始化,给定值时就用这个值初始化。通常默认是用0初始化,但是这里不可以用0初始化,因为不一定为vector<int>类型,还可能是其他类型。所以这里给的是T()。
②T() 本质是一个匿名对象,会自动调用默认构造。对于自定义类型,就会调用默认构造。但对于内置类型呢?好像内置类型没有构造函数吧? 因为有了模板,内置类型升级了,也有了类似构造函数,就比如int i=int()。这里默认int()是0,而int j=int(1),这里给j初始化的就是1了。
③resize()可以分成三种情况,第一种n<size()时,肯定不需要扩容。第二种size()<n<capacity(),这种情况也不需要扩容,直接将多余的初始化即可,第三章n>capacity,这种情况就需要扩容了,然后将多余的初始化。
④不过这里可以将二三情况合并,不管需不需扩容,都使用reserve扩容到n,因为reserve会自动检查是否需要扩容。
⑤最后就需要填值了,从finish开始填值到n+_start位置。

void resize(size_t n, const T& val=T())//这里的val可以给缺省值的,当给定时,使用给定值,不给定时使用缺省值,缺省值给的是T类型的构造函数
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);//不用管原来的容量多少,reserve会判断是否需要扩容
				//填值
				while (_finish != n + _start)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

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

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

相关文章

uniapp安卓签名证书生成,签名证书的SHA1,SHA256,MD5获取

uniapp安卓证书生成有两种方式&#xff0c;一种是去dcloud开发者中心生成证书&#xff0c;另一种是安装jre环境&#xff0c;自己生成证书 第一种 dcloud生成证书 去该项目对应的应用处&#xff0c;生成证书需要等几分钟&#xff0c;生成后可以查看证书信息 第二种 自己生成…

优维科技通过TMMi3级认证,软件测试能力迈上新台阶

近日&#xff0c;优维科技正式通过国际软件测试成熟度模型集成&#xff08;TMMi&#xff09;3级认证&#xff0c;标志着优维科技的软件测试能力、风险应对水平、产品质量管理水平、测试技术创新能力迈上新台阶&#xff0c;获得国际权威组织认可。 TMMi全称为Test Maturity Mode…

反向传播笔录

文章目录 反向传播概述反向传播-前向过程反向传播-反向过程反向传播概述 为了有效的计算梯度,我们使用反向传播。 链式法则: 给定一组neural network参数 θ \theta θ, 我们把一个training data

[JVM] 4. 运行时数据区(1)-- 概述

一、JVM整体结构回顾 类加载子系统将class文件的静态代码加载到内存中&#xff0c;执行引擎需要与这块内存进行交互&#xff0c;从而使用这些数据。 存放这块数据的内存被称为运行时数据区&#xff08;Runtinme Data Area&#xff09;。 一个JVM只能有一个运行时环境&#xff0…

华为机试(JAVA)真题Od【A卷+B卷】2023最新版

目录 一、机考攻略二、机考重要性三、下面&#xff0c;哪吒将华为OD机试真题归归类&#xff0c;让大家一目而了然。四、下面分享一道**“2022Q4 100分的路灯照明问题”**&#xff0c;提前体验一下华为OD机试的**“恐怖如斯”**。1、题目描述2、输入描述3、输出描述4、解题思路特…

二、DDL-2.表操作-创建查询

一、查询所有表 1、查询当前数据库所有表 首先进入数据库itheima&#xff1a; use itheima; 查看itheima数据库的所有表&#xff1a; show tables; ——该数据库是新建的&#xff0c;下面没有表 切换到sys数据库&#xff0c;查看sys下的所有表&#xff1a; 2、查询表结构、…

【FPGA】基于C5的第一个SoC工程

文章目录 前言SoC的Linux系统搭建 前言 本文是在毕业实习期间学习FPGA的SoC开发板运行全连接神经网络实例手写体的总结。 声明&#xff1a;本文仅作记录和操作指南&#xff0c;涉及到的操作会尽量细致&#xff0c;但是由于文件过大不会分享文件&#xff0c;具体软件可以自行搜…

2023年NOC决赛-加码未来编程赛项决赛模拟题-Python模拟题--卷6

第一题 题目:回文字符串是指正序(从左向右)和倒序(从右向左)读都是一样的字符串。 输入一个字符串,在只考虑字母的前提下,判断该字符串是否为回文字符串 【输入格式】输入数据只有一行,一个字符串 s 【输出格式】True 或者 False 在只考虑字母(区分大小写)的情况…

Linux5.17 Ceph应用

文章目录 计算机系统5G云计算第四章 LINUX Ceph应用一、创建 CephFS 文件系统 MDS 接口1.服务端操作2.客户端操作 二、创建 Ceph 块存储系统 RBD 接口三、创建 Ceph 对象存储系统 RGW 接口四、OSD 故障模拟与恢复 计算机系统 5G云计算 第四章 LINUX Ceph应用 一、创建 CephF…

学会快速排序库函数qsort的使用以及实现

qsort的使用使用细节一完成代码 qsort的实现&#xff08;用冒泡排序&#xff09;写法一写法二完整代码 qsort的使用 qsort函数的官方介绍: 点这里 qsort函数需要包含头文件<stdlib.h> qsort函数有四个参数&#xff0c;逐一介绍 base&#xff1a;指向数组中要排序的第一…

趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小黄&#xff0c;独角兽企业的Java开发工程师&#xff0c;CSDN博客专家&#xff0c;阿里云专家博主&#x1f4d5;系列专栏&#xff1a;Java设计模式、Spring源码系列、Netty源码系列、Kafka源码系列、JUC源码…

大数据平台测试-git常用操作(白盒测试基础)

一、前言 学习Git是非常有价值和重要的&#xff0c;无论是一个个人开发者还是在团队中进行协作开发。以下是一些学习Git的原因&#xff1a; 版本控制&#xff1a;Git是目前最流行的分布式版本控制系统&#xff0c;可以帮助你跟踪、管理和控制代码的版本。你可以轻松地回退到先…

gee架设教程

1:GameCenter 设置 1.1服务器控制 1.2 账号 1.3.1 配置向导 - 基本设置 1.3.2 配置向导 - 登录网关 1.3.3 配置向导 - 角色网关 1.3.4 配置向导 - 游戏网关 1.3.5 配置向导 - 登录服务器 1.3.6 配置向导 - 数据库服务器 1.3.7 配置向导 - 日志服务器 1.3.8 配置向导 - 主服务器…

SAP/ABAP(一)

一、什么是ERP ERP 是企业资源规划&#xff08;Enterprise Resource Planning&#xff09;的缩写&#xff0c;它指的是一种集成化的管理软件系统&#xff0c;用于协调企业内部各个部门的活动&#xff0c;并与外部供应商、客户以及其他利益相关方进行信息和业务流程的交互。 二、…

4.4.tensorRT基础(1)-模型推理时动态shape的具体实现要点

目录 前言1. 动态shape2. 补充知识总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 基础-模型推理时动态 shape 的…

科大讯飞星火认知大模型实在是太牛逼了吧,可以和人类进行自然交流,解答问题,高效完成各领域认知智能需求。

星火认知大模型&#xff1a;探索人工智能的无限可能 在21世纪的今天&#xff0c;人工智能技术已经逐渐成为推动社会发展的重要力量。作为一种模拟人类智能的技术手段&#xff0c;人工智能在各个领域都展现出了强大的应用潜力。而在这个领域中&#xff0c;星火认知大模型无疑是…

awk用法--一次性匹配文件中的多个文本,保存成不同的参数

功能描述&#xff1a; 需要从某个文件中读取多个指标的数据&#xff0c;并保存下来&#xff0c;读取的时候需要一次性读取出多个数据&#xff0c;之后将数据写入到结果文件 代码示例 主要逻辑&#xff1a; 1、 匹配包含MemTotal的字符串&#xff0c;并将匹配到的行的倒数第二列…

【电子学会】2023年05月图形化三级 -- 绘制多彩五角星

绘制多彩五角星 1. 准备工作 &#xff08;1&#xff09;选择背景stars、角色Pencil&#xff1b; &#xff08;2&#xff09;将角色Penci的中心点设为笔尖。 2. 功能实现 &#xff08;1&#xff09;将画笔粗细设为3&#xff0c;画笔的颜色和初始位置自定义&#xff0c;绘制边…

【gis插件】arcgis插件界址点编号工具、C#实现思路

数据&#xff1a;界址点图层、宗地图层 要求&#xff1a;找出宗地对应的所有界址点号&#xff0c;对这些界址点号以J1开始按顺序排列 要找出宗地所对应的所有界址点号&#xff0c;这里只要执行一个标识 即可得到这样得到的结果。 难点在于对界址点的编号&#xff0c;经过检查…

c语言小项目——通讯录初阶

通讯录中阶&#xff1a;点这里 通讯录&#xff08;初阶&#xff09; 项目简介项目中遇到的难点1.给复杂结构体初始化错误写法正确写法 2.枚举和switch可以结合一下&#xff0c;方便查看switch的case中是什么功能&#xff0c;double3.ShowContact中printf的新认知4.FindByName加…