首页 > 编程语言 >C++入门 vector深度剖析及模拟实现

C++入门 vector深度剖析及模拟实现

时间:2024-06-23 11:27:58浏览次数:3  
标签:入门 start back C++ v1 vector push 拷贝

目录

vector析构函数模拟实现

vector赋值拷贝模拟实现

vector拷贝构造模拟实现

vector构造函数模拟实现

类模板的成员函数

n个val构造

单参数和多参数对象隐式类型转换

使用memcpy拷贝问题


在上两篇有关vector的模拟实现中,还有构造,拷贝构造,赋值拷贝以及析构函数没有实现,本篇主要实现四个函数。  

 


vector析构函数模拟实现

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

如果start指向空间,那么就释放掉该空间,并给其余成员变量置为空指针nullptr。


vector赋值拷贝模拟实现

void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}

// v1 = v2
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}

直接交换两个自定义类型变量里的成员变量即可。


vector拷贝构造模拟实现

// 强制编译器生成默认的
vector() = default;

// v2(v1)
vector(const vector<T>& v)
{
	reserve(v.capacity());
	for (auto e : v)
	{
		push_back(e);
	}
}

例如要用v1拷贝构造v2,先开辟一个和v1大小相同的空间,再把v1里的元素依次尾插至v2。


vector构造函数模拟实现

类模板的成员函数

// 类模板的成员函数
// 函数模板 -- 目的支持任意容器的迭代器区间初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

类模板的成员函数也可以构造函数模板。使用方法如下图所示:

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

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

它和拷贝构造的区别是可以使拷贝的空间可控,如果我们只要第二个元素开始进行拷贝,可以begin + 1。

此处的InputIterator意味着任意的迭代器都可以使用,例如下代码所示:

	string s("hello");
	vector<int> v3(s.begin(), s.end());
	for (auto e : v3)
	{
		cout << e << " ";
	}
	cout << endl;

这里利用了string的迭代器,又因为是要int类型的v3,所以hello中五个字符按照ascii码转换成int类型进行拷贝v3。


n个val构造

vector(size_t n, const T& val = T())    //T()不能用0代替
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

此处T( )不能用0代替,此处为匿名对象,如果模板为string,list的时候,用0作为缺省值就会发生错误。此外,C++对内置类型进行了优化升级,也有自己的构造。

// C++内置类型进行了升序,也有构造
int i = 0;
int j(1);
int k = int();
int x = int(2);

就是为了兼容上述n个val构造的函数,是函数能实现更多的初始化。以下代码发生了错误:

vector<int> v3(10, 1);
for (auto e : v3)
{
	cout << e << " ";
}
cout << endl;


单参数和多参数对象隐式类型转换

以A类型为例:

class A
{
public:
	A(int a1 = 0)
		:_a1(a1)
		, _a2(0)
	{}

	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{}
private:
	int _a1;
	int _a2;
};

// 单参数和多参数对象隐式类型转换
// 省略赋值符号
A aa1(1, 1);         //一个参数
A aa2 = { 2, 2 };    //两个参数
A aa9{ 2, 2 }; // 不要
const A& aa8 = { 1,1 };	//引用的是中间产生的临时变量

A aa3(1);
A aa4 = 1;

那么vector也可以用容器{ } 来进行初始化吗?

vector<int> v1{ 1,2,3,4,5,6 };

这里的隐式类型转换,跟上面不一样,这里参数个数不固定。

	void test_vector()
	{
		// 这里的隐式类型转换,跟上面不一样,这里参数个数不固定
		vector<int> v1({ 1,2,3,4,5,6 });  //直接构造
		vector<int> v2 = { 10, 20, 30 };  //隐式类型转换

		auto il1 = { 1, 2, 3, 4, 5, 6 };
		initializer_list<int> il2 = { 1, 2, 3 };
		cout << typeid(il1).name() << endl;  //class std::initializer_list<int>
		cout << sizeof(il2) << endl;	//内部为两个指针,指向它的开始和结束
		for (auto e : il1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<A> v3 = { 1, A(1), A(2,2), A{1}, A{2,2}, {1}, {2,2} };
	}

 这里我们要模拟实现一个initializer_list

vector(initializer_list<T> il)
{
	reserve(il.size());
	for (auto e : il)
	{
		push_back(e);
	}
}

使用memcpy拷贝问题

	void test_vector9()
	{
		vector<string> v1;
		v1.push_back("111111111111111111");
		v1.push_back("111111111111111111");
		v1.push_back("111111111111111111");
		v1.push_back("111111111111111111");
		v1.push_back("111111111111111111");
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

此处发生了错误,打印出了其他值,大概原因可以考虑为扩容出现问题了。 

memcpy都是浅拷贝,现_str与原_str指向的同一个空间,delete空间释放后,tmp的_str也跟着小时,这就导致了现在的_str成为了野指针。解决办法如下所示:

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t oldsize = size();
		T* tmp = new T[n];

		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * oldsize);

			for (size_t i = 0; i < oldsize; i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}

		_start = tmp;
		_finish = _start + oldsize;
		_end_of_storage = _start + n;
	}
}

 这里调用string的赋值拷贝,开辟一个新的空间,将原string里的值搬到新开辟空间的string,并且两者指向不同的空间,这样delete原vector就没影响了。

结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

标签:入门,start,back,C++,v1,vector,push,拷贝
From: https://blog.csdn.net/fen_0108/article/details/139893159

相关文章

  • MySQL入门学习-连接查询.CROSS JOIN
        CROSSJOIN(交叉连接):返回左表中的所有行,左表中的每一行与右表中的所有行组合,返回的行数等于左表行数乘以右表行数。一、连接查询包括:1.CROSSJOIN(交叉连接):返回左表中的所有行,左表中的每一行与右表中的所有行组合。2.INNERJOIN(内连接):返回左表和右表......
  • MySQL入门学习-连接查询.RIGHT JOIN
        RightsJoin是MySQL中的一种连接查询类型,用于根据右表中的匹配条件,将左表中的数据与右表中的数据进行连接。    一、与其他连接查询相比,RightsJoin具有以下特点:1.连接方向:RightsJoin以右表为主表,左表为从表。连接结果将包含右表中的所有行,而左表......
  • AI职场写作|为你所做的学习过滤,入门与进阶
    GPT的使用门槛已经变得越来越低,之前文章中推荐的2款GPT零成本应用平台,已经不需要考虑网络、成本问题了。学会跟GPT对话,是一项要尽快掌握的技能。未来最快实现的,可能不是AI取代我们,而是不会使用AI的人,逐渐被懂得使用AI的人所取代。在未来,各种AI的应用,在使用上,肯定会变......
  • C++ 20新特性之改进的位操作
    ......
  • 【小沐学GIS】Google的kml文件的读写(C++、Python)
    文章目录1、简介1.1kml简介1.2功能点1.2.1地标1.2.2地面叠加层1.2.3路径1.2.4多边形2、下载和编译3、C++测试4、Python测试4.1安装库4.2测试14.2测试24.3测试3结语1、简介https://developers.google.cn/kml/documentation/kmzarchives?hl=zh-cn1.1kml......
  • 12. Lammps入门in文件vscode高亮插件-Lammps Syntax Highlighting
    来源:“码农不会写诗”公众号链接:Lammps入门in文件vscode高亮插件-LammpsSyntaxHighlighting文章目录01LammpsSyntaxHighlighting02安装03效果LammpsSyntaxHighlighting  工欲善其事必先利其器,Lammps语法高亮插件不仅是美观视觉必备,也是命令学习、......
  • C++ 结构体对齐详解
    目录前言一、为什么要对结构体进行对齐操作?二、基本概念三、对齐规则四、示例讲解1.简单的变量对齐2.结构体包含有结构体的对齐结构体成员详细解析五、使用指令改变对齐方式__attribute__((packed))#pragmapack(push,n)#pragmapack(pop)六、总结前......
  • 2024年华为OD机试真题-生成哈夫曼树-(C++/Java/python)-OD统一考试(C卷D卷)
    题目描述给定长度为n的无序的数字数组,每个数字代表二叉树的叶子节点的权值,数字数组的值均大于等于1。请完成一个函数,根据输入的数字数组,生成哈夫曼树,并将哈夫曼树按照中序遍历输出。为了保证输出的二叉树中序遍历结果统一,增加以下限制:二叉树节点中,左节点权值小于右节点......
  • python入门基础知识(错误和异常)
    本文部分内容来自菜鸟教程Python基础教程|菜鸟教程(runoob.com) 本人负责概括总结代码实现。以此达到快速复习目的目录语法错误异常异常处理try/excepttry/except...elsetry-finally语句抛出异常用户自定义异常内置异常类型常见的标准异常类型语法错误P......
  • 【C++进阶学习】第三弹——菱形继承和虚拟继承——菱形继承的二义性和数据冗余问题
    继承(上):【C++进阶学习】第一弹——继承(上)——探索代码复用的乐趣-CSDN博客继承(下):【C++进阶学习】第二弹——继承(下)——挖掘继承深处的奥秘-CSDN博客前言:在前面,我们已经讲过继承的相关知识,今天我们来将一个由继承拓展出来的很重要的知识,那就是——菱形继承和虚拟继承及相关知......