CD44.【C++ Dev】vector模拟实现(3)

目录

1.erase

测试代码1

测试代码2

检测VS中删除后原迭代器是否可以访问

insert和erase的迭代器总结

2.pop_back

 测试代码

3.resize

测试代码 

4.operator=

测试代码

5.深拷贝问题解决

解决方法

测试代码

6.构造函数的其他形式的实现

测试代码

测试代码

7.STL源码的构造函数中有趣的地方


承接CD43.vector模拟实现(2)文章

1.erase

参数保持一样的类型:

之前在CD43.vector模拟实现(2)文章提到过迭代器失效问题,因此返回类型为iterator,erase在某些情况下会缩容,为了节省存储空间或者有其他的目的,删除元素的算法和CD38.【C++ Dev】string类的模拟实现(2)文章的算法一样,

注意返回值的要求:指向被删除元素之后的位置

如果不缩容,是这样写的:

iterator erase(iterator position)
{
    //position最大为finish-1
	assert(position >= start && position < finish);
	iterator i = position+1;
	while (i < finish)
	{
		*(i-1) = *i;
		i++;
	}
	finish--;
	return position;
}

测试代码1

void test8()
{
	mystl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.erase(v.begin() + 1);
	return;
}

运行结果:

删除前:

删除后:

测试代码2

void test9()
{
	mystl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	auto pos = v.begin();
	while (v.size())
		pos=v.erase(pos);//规范写法
}

运行结果:退出代码为0,正常结束

检测VS中删除后原迭代器是否可以访问

只需要验证删除后,能否利用原迭代器继续删除数据

void test10()
{
	std::vector<int> v;
	for (size_t i=0;i<9;i++)
		v.push_back(i);
	auto pos = v.begin()+3;
	v.erase(pos);
	v.erase(pos);
	return;
}

第一次删除没有问题,但第二次删除会报错,尽管pos仍然指向原来的内存数组

现认为erase有可能不存在迭代器失效问题,仍然能访问,但是通常认为迭代器失效,因为认为访问的不是原来的值 

某些内存紧张的系统上erase后可能会异地缩容,导致使用迭代器访问的不是原来的值

insert和erase的迭代器总结

vector的erase和insert的参数迭代器使用后后,不能再访问这个迭代器,通常认为失效,访问结果是未定义的(可能可以访问,也有可能不能访问)

2.pop_back

参数保持一样的类型:

既然是尾删,那就要删除end()的前一个元素

写法1:

void pop_back()
{
	erase(--end());//end()返回的是迭代器,需要重载operator--
}

注意:一定不能写成erase(--finish)!!!否则erase的断言条件成立,position==finish

写法2:

void pop_back()
{
	erase(finish-1);
    //不用在pop_back中finish--,erase中处理了
}

 测试代码

void test11()
{
	mystl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.pop_back();
	return;
}

删除前:

删除后:

3.resize

参数保持一样的类型:

分析函数的参数

void resize(size_type n, value_type val = value_type())

第一个是元素个数n,第二个参数用到了缺省参数+匿名对象value_type()

对于自定义类型,匿名对象不用解释,但对于内置类型(例如int类型)来说怎么理解?

C++中将内置类型升级为模版,有自己的构造函数,例如以下代码:

int main()
{
	int val1 = int(1);
	int val2 = int();//匿名int对象
	std::cout << "val1=" << val1 << std::endl;
	std::cout << "val2=" << val2 << std::endl;
	return 0;
}

运行结果:

所以resize无论接收到的是内置类型的模版,还是自定义类型的模版都能很好的处理.

resize的算法和之前CD39.【C++ Dev】string类的模拟实现(3)文章的string的resize类似.

void resize(size_type n, value_type val = value_type())
{
	if (n < capacity())
		finish = start + n;
	else
	{
		//如果vector不为空,reserve不会修改start和finish的相对位置关系
		reserve(n);
		while (finish!=start+n)
		{
			*finish = val;
			finish++;
		}
	}
}

测试代码 

void test12()
{
	mystl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.resize(6, 0xFFFFFFFF);
	return;
}

运行结果: 

4.operator=

operator=不能借助浅拷贝,必须手动实现深拷贝,和CD40.【C++ Dev】string类的模拟实现(4)文章的string的operator=实现思想一样

注意要用const修饰,避免权限放大 

可以不像上面图片那样使用引用,可以直接使用vector<T> x,调用拷贝构造函数

void swap(vector<T> tmp)
{
	std::swap(start, tmp.start);
	std::swap(finish, tmp.finish);
	std::swap(end_of_storage, tmp.end_of_storage);
}

vector& operator= (vector<T> x)
{
	swap(x);//交换3个成员变量
	return *this;
}

测试代码

void test13()
{
	mystl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	mystl::vector<int> tmp=v;
	for (auto& a : tmp)
		std::cout << a << " ";
	return;
}

运行结果:

5.深拷贝问题解决

CD42.vector模拟实现(1)文章提到了一个问题,本文解决

当vector的模版是内置类型时,增删查改没有问题,但如果是自定义类型呢?

void test14()
{
	mystl::vector<std::string> v;
	v.push_back("123123123123123123123");
	v.push_back("456456456456456456");
	v.push_back("789789789789789789");
	v.push_back("123123123123123123123");
	v.push_back("456456456456456456");
	v.push_back("789789789789789789");
	for (auto a : v)
		std::cout << a << " ";
	return;
}

运行结果报错:

如果读者对魔数0xDDDDDDDD比较熟悉,应该指的是被释放(deallocated v.解除分配)的空间,而程序又想访问,会造成访问权限的冲突

 (来自https://www.softwareverify.com/blog/unusual-memory-bit-patterns/文章)

下断点到test14函数的return处,F11单步执行,会发现是执行delete[] start后报错:

思路:检查是哪个函数提前释放了start指向的空间

进一步分析:哪个功能需要释放start指向的空间? 答:异地扩容,需要将旧空间的数据拷贝到新空间,再释放原空间,很有可能是start的值仍然指向旧空间,导致访问发生冲突

异地扩容在reserve函数中,查其bug:

问题在memove,虽然vector是深拷贝,但vector上存储了string对象,调用delete[] start时,会自动调用string的析构函数,释放string的空间,导致string对象是浅拷贝

验证0xDDDDDDDD的产生:

解决方法

删掉memmove,手动调用operator=复制成员变量,进行深拷贝

if (start)
{
	for (size_type i = 0; i < len; i++)
	{
		tmp[i] = start[i];
	}
	delete[] start;
}

运行结果:
 

当然,拷贝构造函数也要修改:

vector(const vector<value_type>& x)
	:start(0), finish(0), end_of_storage(0)
{
	reserve(x.capacity());
	for (size_type i = 0; i < x.size(); i++)
	{
		start[i] = x.start[i];
	}
	finish = start + x.size();

测试代码

void test15()
{
	mystl::vector<std::string> v;
	v.push_back("123123123123123123123");
	v.push_back("456456456456456456");
	v.push_back("789789789789789789");
	v.push_back("123123123123123123123");
	v.push_back("456456456456456456");
	v.push_back("789789789789789789");
	mystl::vector<std::string> tmp = v;
	for (auto a : tmp)
		std::cout << a << " ";
	return;
}

运行结果:

6.构造函数的其他形式的实现

CD42.vector模拟实现(1)文章只实现的第一种写法,这里实现剩下两种

fill(2):省去空间配置器

explicit vector(size_type n, const value_type& val = value_type())
	:start(nullptr)
	,finish(nullptr)
	,end_of_storage(nullptr)
{
	resize(n, val);
}

注:explicit关键字在CD24.【C++ Dev】类和对象(15) 初始化列表(下) 对象隐式类型转换和explicit文章讲过,是为了禁止类对象之间的隐式转换

测试代码

void test16()
{
	mystl::vector<int> v(3,0xFFFFFFFF);
	for (auto a : v)
		std::cout << a << " ";
	return;
}

运行结果:

range(3):省去空间配置器

注意不能写成

vector (iterator first, iterator last);

 如果定义为iterator类型,就只能使用vector类型来初始化,不具有适用性

template <class InputIterator>
         vector (InputIterator first, InputIterator last);

注意:first和last定义为InputIterator,

区间是左闭右开的,即[first,last)

template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
	InputIterator i = first;
	while (i != last)
	{
		push_back(*i);
		i++;
	}
}

测试代码

void test17()
{
	mystl::vector<std::string> v;
	v.push_back({ "12" });
	v.push_back({ "23" });
	v.push_back({ "34" });
	v.push_back({ "45" });
	mystl::vector<std::string> tmp(v.begin()+1,v.end()-1);
	for (auto a : tmp)
		std::cout << a << " ";
	return;
}

运行结果:

如果写成这样: 

vector(iterator first, iterator last)
{
	iterator i = first;
	while (i != last)
	{
		push_back(*i);
		i++;
	}
}

测试代码:

void test18()
{
	std::list<int> ls;
	ls.push_back(1);
	ls.push_back(2);
	ls.push_back(3);
	mystl::vector<int> v(ls.begin(), ls.end());
	for (auto a : v)
		std::cout << a << " ";
	return;
}

list迭代器不能像数组一样可以连续访问(后面文章会讲),所以会报错

如果改成InputIterator,就没有问题:

7.STL源码的构造函数中有趣的地方

(来自SGI STL v3.0的源码)

画框的n定义了三种类型size_type、int和long,这样做的原因是什么?

运行代码无法通过编译

void test19()
{
	mystl::vector<int> v(5,3);
	return;
}

报错信息: 

 模板实例化上下文(最早的实例化上下文)为
1>        C:\Users\Administrator\source\repos\myvector\test.cpp(219,22):
1>        查看对正在编译的函数 模板 实例化“mystl::vector<int>::vector<int>(InputIterator,InputIterator)”的引用
1>        with
1>        [
1>            InputIterator=int
1>        ]
1>            C:\Users\Administrator\source\repos\myvector\test.cpp(219,22):
1>            请参阅 "test19" 中对 "mystl::vector<int>::vector" 的第一个引用

使用者想匹配explicit vector(size_type n, const value_type& val = value_type()),但是编译器匹配错了.

原因:对于常数5和3,编译器默认认为是int类型,由于size_type nconst value_type& val类型上有差别,但是InputIterator first和InputIterator last在类型上一致,编译器会匹配最符合的那个

解决方法1:让first和last类型不一样即可

例如mystl::vector<int> v(5u,3),其中u代表5为unsigned int类型,即重定义后的size_type类型

解决方法2:STL库的解决方法: 定义一个重载函数

参数n类型为int: 

explicit vector(int n, const value_type& val = value_type())
	:start(nullptr)
	, finish(nullptr)
	, end_of_storage(nullptr)
{
	resize(n, val);
}

运行结果:

参数n类型为long:

explicit vector(long n, const value_type& val = value_type())
	:start(nullptr)
	, finish(nullptr)
	, end_of_storage(nullptr)
{
	resize(n, val);
}

### 计算机网络原理与模拟实验 学习计算机网络原理及其相关的模拟实验可以通过多种途径实现,尤其是借助专门的工具和书籍来提升理解能力。以下是关于如何通过 NS3 和其他资源开展计算机网络原理的学习及相关实验的内容。 #### 使用 NS3 进行路由模拟实验 NS3 是一种强大的离散事件网络仿真器,广泛应用于研究和教育领域。在《NS3 路由模拟实验》中,主要目标是帮助学生熟悉 NS3 的基本操作并掌握基于 **距离矢量算法 (Distance Vector Algorithm)** 的动态路由机制[^1]。此实验通常涉及创建简单的网络拓扑图,并观察数据包在网络中的传输路径变化情况。 为了完成此类实验,可以按照如下方式设置环境: ```bash sudo apt-get install ns3 # 安装 NS3 工具链 ns3 configure --enable-examples --enable-tests # 配置支持例子程序运行测试功能 cd ~/ns-3-dev/scratch # 切换到自定义脚本目录下编写新的场景文件 nano my-first-simulation.cc # 创建一个新的 C++ 文件用于描述特定需求下的通信过程 ``` 上述命令展示了安装配置以及开发个人化仿真的初步流程。需要注意的是,在正式动手之前应当仔细阅读官方文档或者查阅相关资料确保每一步骤都正确无误地被执行[^3]。 #### 参考教材推荐 对于希望深入探索理论知识的人来说,《计算机网络》这本经典著作不可错过。它由谢希仁教授编撰而成,作为国内高校普遍使用的教科书之一,其体系严谨且覆盖全面。而与其相辅相成的一部作品便是郭雅主编出版的《计算机网络实验指导书》,这本书籍不仅提供了丰富的实践案例还特别注重培养学生解决实际问题的能力[^2]。 另外还有另一份名为《计算机网络原理》的实验指南可供选用,其中提到的具体步骤如启动网络模拟器软件并通过加载预先构建好的 netmap 来重现指定条件下的虚拟世界同样值得借鉴。 #### 总结建议 综上所述,如果想要扎实地掌握计算机网络的基础概念并且能够灵活运用这些知识点去分析复杂现象的话,则需要结合理论学习加动手练习两条腿走路才行。利用像 NS3 这样的先进平台来进行反复试验验证假设不失为一条捷径;与此同时也不要忽视传统纸质材料里蕴含的价值所在——它们往往能提供更加系统化的讲解框架供我们参考模仿从而达到事半功倍的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangcoder

赠人玫瑰手有余香,感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值