当前位置: 首页 > article >正文

string的模拟实现

本文中会来进行对STL库中的SGI版本string的模拟实现,并解释其中遇到的一些问题。

为了避免与标准库中的string类重复,因此使用自己的命名空间。

成员变量

private:
	//const char* _str;
	char* _str;
	size_t _size;
	size_t _capacity;

构造函数

先简单的写一个构造函数:

string() 
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{}

string (const char* str)
	:_str(str) // err
	,_size(strlen(str))
	, _capacity(strlen(str))
{}

在成员变量中给的是char*,但是拷贝构造中给予的是const char*,这里有权限放大的问题不能初始化。当我们给成员变量中添加const后可以解决报错的问题,但是又会产生其他的问题,当打印之中的字符串时:

const char* c_str() // 打印C风格的字符串(将string中的数据打印至"\0")
{
	return _str;
}

void test_string1()
{
	string s1;
	string s2("hello world");
	cout << s1.c_str() << endl; // 会崩溃的问题是,cout自动识别类型const char* 会对字符串进行解引用就会露出null报错
	cout << s2.c_str() << endl;
}

char& operator[](size_t pos) // 使用了const之后也无法进行类似于下面这样的修改操作
{
	assert(pos < _size);
	return _str[pos];
}

因此,在这里不能把常量字符串直接赋给string类,解决的方法就是new出新的空间。在上述的初始化列表中,不建议多次使用strlen,strlrn是一个O(N)的接口 ,同时不建议使用成员变量来初始化成员变量。做出如下的修改:

string() 
	:_str(new char[1]) // 不能不添加[],在析构时可以统一管理
	, _size(0)
	, _capacity(0)
{
	_str[0] = '\0';
}

string(const char* s)
	//:_str(str)
	:_size(strlen(s))
	//, _capacity(strlen(str))
{
	_capacity = _size == 0 ? 3 : _size;
	_str = new char[_capacity + 1]; // 需要给string末尾添加"\0"留下位置
	strcpy(_str, s);
}


// 最后综合成一个带有缺省参数的构造函数

// 缺省参数:
// string(const char* str = nullptr) // 1、nullptr 崩溃 会解引用
// string(const char* str = '\0') // 2、类型不匹配,char被转换成为int,str也被当做空指针,崩溃
// string(const char* str = "\0") // 3、可以当时没有必要
string(const char* s = "") // 常量字符串默认以"\0"结束
	//:_str(str) // 权限的放大不能将 char* 的变量赋给const char*
	:_size(strlen(s))
	//, _capacity(strlen(str)) // strlen是一个O(N)的接口不宜连续调用
{
	_capacity = _size == 0 ? 3 : _size;
	_str = new char[_capacity + 1];
	strcpy(_str, s);
}

拷贝构造

在拷贝构造中需要注意的就是深浅拷贝的问题, 如果使用的是浅拷贝,首先修改拷贝的string类会对原string类造成影响,其次就是在析构是同一块空间会析构两次就会报错。

string(const string& s)
	:_size(s._size)
	, _capacity(s._capacity)
{
	_str = new char[_capacity + 1];
	strcpy(_str, s._str);
}

赋值

赋值与拷贝构造一样都需要进行深拷贝,但是还有不同的地方:需要注意进行赋值前左操作数的空间大小的问题,这里可以先统一的进行临时的拷贝,对原string对象进行析构,再将临时拷贝的重新赋值给原来的string对象。

string& operator=(const string& s)
{
	if (this != &s) // 处理自己给自己赋值
	{
		char* tmp = new char[s._capacity + 1];
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}

	return *this;
}

遍历string 

遍历string的方式有很多,首先可以使用前文中所写的[ ],来进行遍历这与之前学习的数组非常的相似。在我们学习完&后一些函数的传参会写成如下的形式:

void Print(const string& s) // const对象
{
	for (size_t i = 0; i < s.size(); ++i) 
    {
        cout << s[i] << " ";
    }
    cout << endl;
}

const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

之前只编写了一个非const函数的[ ],然而const对象无法调用非const对象的成员函数,这里会有权限的放大,因此需要对[ ]进行重载。

迭代器

迭代器也可以进行遍历的操作,在这里我们编写的string迭代器使用的是指针:

typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

const_iterator begin() const // 迭代器也是有const的问题,因此也需要重载
{
	return _str;
}

const_iterator end() const
{
	return _str + _size;
}

string::iterator it = s1.begin();
while (it != s1.end())
{
	(*it)--;
	++it;
}
cout << endl;

// 范围for的底层使用的就是迭代器,使用我们自己的迭代器也可以运行范围for
// 加入将迭代器中的begin改为Begin后,迭代器就会报错
for (auto ch : s)// 范围for的基底为迭代器, 不支持范围for因为是const对象,不能调用非const的函数
{
	cout << ch << " ";
}
cout << endl;

reserve、resize

void reserve(size_t n)
{
	if (n > _capacity) // 只有当修改的空间大于原先的空间时才进行操作
	{
		char* tmp = new char[n + 1]; // 需要多开辟一个空间用来存放"\0"
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;

		_capacity = n;
	}
}

void resize(size_t n, char ch = '\0')
{
	if (n > _size) // 同样需要考虑n和size的不同的情况
	{
		if (n > _capacity)
        {
        	reserve(n);
        }
    
		for (size_t i = _size; i < n; ++i) // 通过遍历的方式进行初始化
		{
			_str[i] = ch;
		}

		_size = n;
		_str[_size] = '\0';
	}
	else
	{
		// 删除数据保留前N个
		_str[n] = '\0';
		_size = n;
	}
}

尾插

void push_back(char ch)
{
	if (_size + 1 >= _capacity)
	{
		reserve(_capacity * 2); // 空间越界的问题
	}
	_str[_size] = ch;
	++_size;
	_str[_size + 1] = '\0'; // 在末尾添加"\0",打印时可以终止
}

void append(const char* s)
{
	size_t len = strlen(s);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	strcpy(_str + _size, s);
	//strcat(_str, s); // 不好需要遍历字符串寻找"\0"
	_size += len;
}

插入删除

void insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size + 1 > _capacity)
	{
		reserve(2 * _capacity);
	}

	size_t end = _size + 1;
	while (end > pos) // 当end = -1时由于是无符号整形数据因此会变为无符号整形的最大值,这里也不可以使用int类型,会有整形提升的问题
	{
		_str[end] = _str[end - 1];
		--end;
	}

	_str[pos] = ch;
	++_size;
}


void insert(size_t pos, const char* s)
{
	assert(pos <= _size);
	size_t len = strlen(s);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	size_t end = _size + len;
	while (end > pos + len - 1) // 当len = 1 时就会与插入字符一致
	{
		_str[end] = _str[end - len];
		--end;
	}

	// 拷贝插入
	strncpy(_str + pos, s, len);
	_size += len;
}

void earse(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	if ((len == npos) || (pos + len >= _size)) // 分两种情况,当len==npos、pos+len>_size或
	{
		_str[pos] = '\0';
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
}

find

size_t find(char ch, size_t pos = 0)
{
	assert(pos < _size);
	for (size_t i = pos; i < _size; ++i)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}

	return npos;
}

size_t find(const char* s, size_t pos = 0)
{
	assert(pos < _size);
	char* ret = strstr(_str, s);
	if (ret == nullptr)
	{
		return npos;
	}
	else
	{
		return ret - _str;
	}
}

静态成员变量npos

static const size_t npos;

// 可以
//static const size_t npos = -1; // 添加了const之后就可以在类内初始化,只针对整形

/*	static const size_t N = 10;
int _a[N];*/

// 不可以
//static const double dpos = 1.1;

正常写法:
类内声明:static const size_t npos;
类外定义:size_t string::npos = -1;

流插入

ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch; // 在打印的时候,'\0'可能不会打出来
	}

	return out;
}

流提取

istream& operator>>(istream& in, string& s)
{
	//s.clear();

	//char ch = in.get(); // 获取每一个字符
	in >> ch;
	//while (ch != ' ' && ch != '\n') // 空格和换行没有进入缓冲区,空格和换行被视为多个值之间的间
	//{
	//	s += ch; // 当输入的字符串比较长时,需要频繁的扩容
	//	//in >> ch;
	//	ch = in.get();
	//}

    // 预先开辟空间防止频繁的扩容
	s.clear();
	char ch = in.get();
	char buff[128];
	size_t i = 0;
	//in >> ch;
	while (ch != ' ' && ch != '\n') // 空格和换行没有进入缓冲区,空格和换行被视为多个值之间的间隔
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[127] = '\0';
			s += buff;
			i = 0;
		}
		ch = in.get();
	  }
 	if (i != 0)
	{
		buff[i] = '\0';
		s += buff;
	}

	return in;
}

http://www.kler.cn/a/1513.html

相关文章:

  • 【LeetCode Hot100 贪心算法】 买卖股票的最佳时机、跳跃游戏、划分字母区间
  • 第四、五章补充:线代本质合集(B站:小崔说数)
  • 腾讯云AI代码助手编程挑战赛-图片转换工具
  • 【2024华为OD-E卷-100分-boss的收入】(题目+思路+JavaC++Python解析)
  • 中国科技统计年鉴EXCEL版(2021-2023年)-社科数据
  • 回顾2024年重磅AI发布汇总
  • 第一次认真周赛总结
  • 【RabbitMQ笔记10】消息队列RabbitMQ之死信队列的介绍
  • 宝塔控制面板常用Linux命令大全
  • IntelliJIDEA 常用快捷键
  • 2023年Android现代开发
  • 用Java解决华为OD机试考题,目标300+真题,清单奉上,祝你上岸
  • oracle 删除表空间(tablespace)及数据文件的方法
  • 基于Vue+Vue-cli+webpack搭建渐进式高可维护性前端实战项目
  • 【C++】智能指针
  • OpenHarmony之cJSON库使用介绍
  • 蓝桥杯C++组怒刷50道真题(填空题)
  • ElasticSearch-第二天
  • 8大核心语句,带你深入python
  • 离散Hopfield神经网络的分类——高校科研能力评价
  • django-celery-beat搭建定时任务
  • Redis限流接口防刷
  • 全网最火爆,Python接口自动化测试-接口数据 RSA 加密和签名实现(超详细)
  • Linux - 进程控制(创建和终止)
  • ES6新特性--变量声明
  • Django4.0新特性-主要变化