C++第十八弹---string的模拟实现(上)

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1、string结构的创建

2. 构造函数的实现

1.1、无参构造函数

1.2、有参构造函数

1.3、析构函数

3、赋值操作符重载的实现

4、容量操作的实现

5、元素访问的实现

总结


上两弹我们学习了如何使用库中的string类,但是我们作为程序员不仅仅只是会用,还得了解其中的底层原理,因此接下来的内容将一步步的讲解如何模拟实现。

1、string结构的创建

实现有关string的函数功能,前提得有一个string这样的类,因此第一步是创建一个string类,但是为了与库函数的string进行区分,此时可以将类创建在命名空间中。那么string的成员变量有哪些呢?首先需要一个存放字符串地址的指针存储有效数据个数的变量以及存放容量的变量

注意:建议将成员变量放在私有权限中,更好的保护数据。 

namespace lin
{
	class string
	{
    public:
            //以下为成员函数
	private://保护成员变量,用private
		char* _str;//存放字符串指针
		int _size;//字符串有效数据个数
		int _capacity;//字符串容量
	};
}

2. 构造函数的实现

根据前面构造函数的使用我们可以知道,构造函数有很多个,但是有一些思想基本是一致的,因此博主只对几个常见的构造函数和析构函数进行模拟实现了。

1.1、无参构造函数

根据前面的实现我们知道,无参构造函数时指有效数据个数为0,但是字符串中是存放了'\0'的,因此我们需要开辟一个字节的空间。

string()
     //初始化列表
	:_str(new char)//给_str开辟一个字节空间
	, _size(0)//初始化有效数据为0
	, _capacity(1)//容量为1
    {
		_str[0] = '\0';//将开辟的空间初始化值为'\0'
    }

我们模拟实现了这个构造函数,但是我们需要通过测试才知道是否正确,因此博主推荐的方法是使用多文件,在test.cpp文件中进行对函数的测试,string.h的文件中进行实现函数。

 调试的测试结果如下:

1.2、有参构造函数

1、参数为C语言格式的字符串拷贝函数

拷贝函数那么需要将类中的所有成员变量均拷贝过去,有效数据个数为传参的字符串长度,容量也直接等于有效字符串长度即可。

string(const char* str)
	:_str(new char[strlen(str)+1])//开辟字符串长度+1字节的空间
	,_size(strlen(str))//大小为字符串长度
	,_capacity(strlen(str))//容量为字符串长度
{
	strcpy(_str, str);//将字符串的内容拷贝到_str中
}

根据我们的直觉,一般就会直接写以上的代码,但是此代码似乎很繁琐,求了三次字符串长度,我们可不可以进行优化一些呢?

博主的答案是当然可以,我们构造函数的初始化不一定要在初始化列表,也是可以在函数体进行初始化的,还有此代码跟上面的无参构造函数都会开辟新的空间,以及拷贝内容,那么能不能通过缺省参数修改成一个公用的函数呢? 

优化一(单独版本):

string(const char* str)
	:_size(strlen(str))//大小为字符串长度
{
	_capacity = _size;
	_str = new char[_capacity + 1];//开辟空间比字符串长度大1,存放\0
	strcpy(_str, str);//拷贝字符串
}

优化二(合并版本):

string(const char* str = "")
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

2、参数为类对象的拷贝构造函数

此处为了能够满足多文件实现思想,将有效数据个数以及容量封装成一个函数,那么类外也可以访问数据个数以及容量,因此代码如下。

string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s.size();
	_capacity = s.capacity();
}
size_t capacity() const
{
	return _capacity;//类内封装获取容量函数
}

size_t size() const
{
	return _size;//类内封装有效数据个数函数
}

1.3、析构函数

前面构造函数我们是手动开辟的空间,因此需要释放空间。

~string()
{
	delete[] _str;//释放空间
	_str = nullptr;//手动置空,防止野指针
	_size = _capacity = 0;//大小置0
}

3、赋值操作符重载的实现

需要赋值的类的有效数据个数有三种情况:

1、为空串(空间不够)

2、比拷贝的有效数据个数少(空间不够)

3、比拷贝的有效数据个数多(空间足够)

因此此处选择一个通用的解决办法,直接开辟一个新的空间,将原空间的数据拷贝给新空间,然后销毁原空间。

string& operator= (const string& s)
{
	char* tmp = new char[s._capacity + 1];//开辟一个新空间,大小为s的容量+1
	strcpy(tmp, _str);//将原空间的数据拷贝给新空间
	delete[] _str;//释放原空间
	//下面拷贝类中的成员变量数据
	_str = tmp;
	_size = s._size;
	_capacity = s._capacity;

	return *this;//返回string类
}

4、容量操作的实现

注意:返回字符串的长度和容量是不会修改值的,因此可以用const修饰。

 容量的常见函数如下:

1、size_t size() const;

//字符串有效数据个数(字符串长度)

2、size_t length() const;

//字符串有效数据个数(字符串长度)

3、size_t capacity() const;

//字符串容量

4、void resize(size_t n);

//扩大字符空间至n字节,大于原来大小则用\0填充

5、void reserve(size_t n = 0);

//扩大容量

6、void clear();

//清空字符串

7、bool empty() const;

//判断是否为空串

size_t capacity() const
{
	return _capacity;
}

size_t size() const
{
	return _size;
}
size_t length() const
{
	return _size;//长度和大小相等的,直接返回大小即可
}
void clear()
{
	_size = 0;//将有效数据个数置0
	_str[_size] = '\0';//第0个位置给\0
}
void reserve(size_t n)
{
	if (n > _capacity)//n大于原来容量才需要扩容
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);

		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}
void resize(size_t n, char ch = '\0')
{
	if (n <= _size)//n小于原来大小,直接给n位置\0
	{
		_str[n] = '\0';
		_size = n;//大小修改为n
	}
	else
	{
		reserve(n);//先扩容再赋值
		for (size_t i = _size; i < n; i++)
		{
			_str[i] = ch;
		}
		_str[n] = '\0';
		_size = n;
	}
}
bool empty() const
{
	return _size == 0;//大小为0则为空,否则不为空
}

5、元素访问的实现

1、char& operator[] (size_t pos);

//下标访问元素,可修改值
const char& operator[] (size_t pos) const;

//下标访问元素,不可修改值
2、char& at (size_t pos);

//函数访问元素,可修改值

const char& at (size_t pos) const;

//函数访问元素,不可修改值
3、char& back();

//函数访问尾元素,可修改值

const char& back() const;

//函数访问尾元素,不可修改值
4、char& front();

//函数访问头元素,可修改值

const char& front() const;

//函数访问头元素,不可修改值

char& operator[] (size_t pos)
{
	assert(pos < _size);//断言,pos小于_size则没问题,否则报错
	return _str[pos];//返回pos位置元素
}
const char& operator[] (size_t pos) const
{
	assert(pos < _size);
	return (const char&)_str[pos];//强转成const char&类型
}
char& back()
{
	return _str[_size - 1];
}
const char& back() const
{
	return (const char&)_str[_size - 1];
}
char& front()
{
	return _str[0];
}
const char& front() const
{
	return (const char&)_str[0];
}

总结


本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

  • 162
    点赞
  • 139
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 120
    评论
好的,以下是一个简单的 C++ 程序,用于模拟实现 Go-back-N 协议: ```c++ #include <iostream> #include <vector> using namespace std; // 数据包结构体 struct Packet { int seq; // 序号 string data; // 数据 }; // 发送方发送数据 void sender(vector<Packet>& packets, int window_size, int timeout) { int next_seq = 0; // 下一个要发送的序号 int base = 0; // 窗口起始位置 int unacked = 0; // 已发送但未确认的数据包数量 // 模拟循环发送数据包 while (true) { // 发送窗口内的未确认数据包 while (unacked < window_size && next_seq < packets.size()) { cout << "发送数据包 " << next_seq << endl; // 模拟发送数据包 // send_packet(packets[next_seq]); next_seq++; unacked++; } // 等待确认或超时 // receive_ack(timeout); // 检查是否有确认 if (unacked > 0) { cout << "等待确认..." << endl; // 模拟接收确认 bool received_ack = true; if (received_ack) { cout << "接收到确认 " << base << endl; base++; unacked--; } else { cout << "未接收到确认,超时重传" << endl; next_seq = base; unacked = 0; } } // 检查是否已发送完毕 if (base == packets.size()) { break; } } } // 接收方接收数据 void receiver(vector<Packet>& packets, int window_size, int timeout) { int expected_seq = 0; // 期望接收的序号 int base = 0; // 窗口起始位置 // 模拟循环接收数据包 while (true) { // 模拟接收数据包 Packet packet; // 接收到的数据包 // receive_packet(packet); // 如果接收到的数据包的序号等于期望接收的序号 if (packet.seq == expected_seq) { cout << "接收到数据包 " << expected_seq << ",并发送确认" << endl; // 模拟发送确认 // send_ack(expected_seq); expected_seq++; base++; // 将窗口向前滑动,以便接收更多数据包 while (packets[base].seq < expected_seq && base < packets.size()) { base++; } } else { cout << "接收到数据包 " << packet.seq << ",但不是期望接收的序号" << endl; // 模拟发送确认 // send_ack(expected_seq - 1); } // 检查是否已接收完毕 if (base == packets.size()) { break; } } } int main() { // 创建数据包 vector<Packet> packets; packets.push_back({0, "Hello"}); packets.push_back({1, "World"}); packets.push_back({2, "Go-back-N"}); packets.push_back({3, "Protocol"}); packets.push_back({4, "Simulation"}); packets.push_back({5, "Example"}); // 设置窗口大小和超时时间 int window_size = 3; int timeout = 500; // 启动发送方和接收方 sender(packets, window_size, timeout); receiver(packets, window_size, timeout); return 0; } ``` 上述程序实现了一个简单的 Go-back-N 协议模拟。在程序中,发送方和接收方都使用了一个循环来模拟数据包的发送和接收过程。在发送过程中,发送方会根据窗口大小和超时时间来控制发送的数据包数量,并等待接收到确认或超时后重传。在接收过程中,接收方会等待接收到数据包后发送确认,并将窗口向前滑动以便接收更多的数据包。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 120
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小林熬夜学编程

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值