C++之string

C++之string

string的模拟实现

insert插入字符

代码如下:

C++ 复制代码
void string::insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	
	size_t end = _size ;
	while (end >= pos)
	{
		_str[end+1] = _str[end ];
		--end;
	}
	_str[pos] = ch;
	_size++;
}

这里面在头插的情况下,编译器会报错

因为size_t是无符号,头插会因为C语言数据存储的原因,数据超过了最大程度

调试的时候可以尝试条件断点

就是写入一段无关的程序,但是打断点的时候,必须要内容

对上面的代码优化得到了:

C++ 复制代码
void string::insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	
	int end = _size ;
	while (end >=(int) pos)//类型转换,否则数据要进行整型提升
	{
		_str[end+1] = _str[end ];
		--end;
	}
	_str[pos] = ch;
	_size++;
}

但是pos==0也存在问题,所以不能出现等号

进一步优化得到:

C++ 复制代码
void string::insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//这里举个例子,相当于放在hello world\0的'\0'后面
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	_size++;
}

insert插入字符串

代码如下:

C++ 复制代码
void string::insert(size_t pos, const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		size_t newCapacity = 2 * _capacity;
        //如果2倍还不够,就有多少扩容多少
		if (newCapacity < _size + len)
			newCapacity = _size + len;
		reserve(newCapacity);
	}
	size_t end = _size + len;
	while (end > pos + len - 1)//这里很关键
	{
		_str[end] = _str[end - len];
		--end;
	}
	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}
	_size += len;
}

拿hello world来举例子:

pos+len-1减去1是为了把h,也就是开头的字符顶掉

erase消除字符串

代码:

C++ 复制代码
void string::erase(size_t pos, size_t len)
{
	assert(pos < len);
	if (len >= _size - pos)
	{
        //用0赋值过去
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		//从后往前挪
		size_t end = pos + len;
		while (end<=_size)
		{
			_str[end - len] = _str[end];
			++end;
		}
		_size -= len;
	}
}

find查找字符

代码:

C++ 复制代码
size_t string::find(char ch, size_t pos = 0)
{
	assert(pos < _size);
	for (size_t i = pos; i < _size; i++)
	{
        //返回对应的下标,找到的情况下
		if (ch == _str[i])
			return i;
	}
    //找不到就返回无
	return npos;
}

这里对npos再进行进一步说明:

C++ 复制代码
public:
	//static const size_t npos=-1;//声明和定义分离
	//const修饰的静态成员变量可以给缺省值,对于整型的特例
	//不建议这样做,还是建议正常的声明和定义分离
	static const size_t npos;

定义的时候进行初始化,const修饰的对象只能修改一次值,就是初始化的时候才能修改

C++ 复制代码
const size_t string::npos= -1;//不能定义两次npos,一个文件里面
//const修饰的对象,定义只有一次才能初始化

find查找字符串

代码:

C++ 复制代码
size_t string::find(const char* str, size_t pos)
{
	assert(pos < _size);
	const char* ptr = strstr(_str + pos, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}

substr提取字符串

代码如下:

string.h

C++ 复制代码
	string substr(size_t pos, size_t len = npos);
private:
	char* _str = nullptr;
	size_t _size = 0;
	size_t _capacity = 0;

string.cpp

C++ 复制代码
string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);
	//大于后面串的长度,则直接取完
	if (len > (_size - pos))
	{
		len = _size - pos;
	}
	Tzuyu::string sub;
	sub.reserve(len);
	for (size_t i = 0; i < len; i++)
	{
		sub += _str[pos + i];
	}
	return sub;
}

和上面find查找字符串综合起来的应用如:

提取网址的部分数据:

C++ 复制代码
void test_string01()
{
	Tzuyu::string s3("hello world");
	cout << s3.find(' ') << endl;
	cout << s3.find("wo") << endl;

	Tzuyu::string s4 = "https://legacy.cplusplus.com/reference/cstring/strstr/?kw=strstr";
	size_t pos1 = s4.find(':');
	size_t pos2 = s4.find('/', pos1 + 3);
	if (pos1 != string::npos && pos2 != string::npos)
	{
		Tzuyu::string domain = s4.substr(pos1 + 3, pos2 - (pos1 + 3));
		cout << domain.c_str() << endl;
		Tzuyu::string uri = s4.substr(pos2 + 1);
		cout << uri.c_str() << endl;
	}
}

浅拷贝和深拷贝

具体来说,String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认 的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一 不想分享就你争我夺,玩具损坏。

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父 母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。

深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给 出。一般情况都是按照深拷贝方式提供。

下面是一个赋值拷贝

代码如下:

C++ 复制代码
string& string::operator=(const string& s)
{
	if (this != &s)
	{
		delete[] _str;
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;//出了作用域,自己返回自己
}

这也可以自己给自己赋值,有点像汽车窗子的防夹手功能

比较大小

代码如下:

string.h

C++ 复制代码
//比较大小这是相对于私有的来说的,用类域里面,不是私有的可以考虑函数重载比较大小
bool operator== (const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string& lhs, const string& rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator>= (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);

string.cpp

C++ 复制代码
bool operator!= (const string& lhs, const string& rhs)
{
	return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
	return !(lhs <= rhs);
}
bool operator< (const string& lhs, const string& rhs)
{
	return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
bool operator>= (const string& lhs, const string& rhs)
{
	return !(lhs < rhs);
}
bool operator<= (const string& lhs, const string& rhs)
{
	return lhs < rhs || lhs == rhs;
}

test.cpp

C++ 复制代码
cout << (s1 == s2) << endl;
	cout << (s1 < s2) << endl;
	cout << (s1 > s2) << endl;
	cout << (s1 == "hello world") << endl;
	cout << ("hello world" == s1) << endl;

流插入和流提取

流插入

string.h

C++ 复制代码
ostream& operator<<(ostream& os, const string& str);

string.cpp

C++ 复制代码
ostream& operator<<(ostream& os, const string& str)
	{
		//os<<'"';
		//os << "xx\"xx";//这不可以是因为无法判断结束的位置
		for (size_t i = 0; i < str.size(); i++)
		{
			//os << str[i];
			os << str[i];
		}
		//os << '"';

		return os;
	}

流提取

string.h

C++ 复制代码
istream& operator>>(istream& is, string& str);
void clear()
{
	_str[0] = '\0';
	_size = 0;
}

string.cpp

C++ 复制代码
istream& operator>>(istream& is, string& str)
{
	str.clear();

	char ch;
	//is >> ch;//遇到空格会停止输入,进入缓冲区
	ch = is.get();//get会解决is>>的问题
	while (ch != ' ' && ch != '\n')
	{
		str += ch;
		ch = is.get();
	}

	return is;
}

完整代码实现

string.h

C++ 复制代码
#pragma once
#include <iostream>
#include <assert.h>
#include <string>
using namespace std;
namespace Tzuyu
{
	class string
	{
	public:
		string(const char* str = " ");
		~string();
		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);//不建议过多使用,因为是全局变量
		string& operator+=(const char* str);
	public:
		//static const size_t npos=-1;//声明和定义分离
		//const修饰的静态成员变量可以给缺省值,对于整型的特例
		//不建议这样做,还是建议正常的声明和定义分离
		static const size_t npos;
		char& operator[](size_t i)
		{
			assert(i < _size);
			return _str[i];
		}

		const char& operator[](size_t i) const
		{
			assert(i < _size);
			return _str[i];
		}
		using iterator = char*;
		using const_iterator = const char*;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
		size_t size() const
		{
			return _size;
		}
		const char* c_str() const
		{
			return _str;
		}
		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos);
		string substr(size_t pos, size_t len = npos);
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	public :
		/*char* _str;
		size_t _size;
		size_t _capacity;*/
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
	};
	//比较大小这是相对于私有的来说的,用类域里面,不是私有的可以考虑函数重载比较大小
	bool operator== (const string& lhs, const string& rhs);
	bool operator!= (const string& lhs, const string& rhs);
	bool operator> (const string& lhs, const string& rhs);
	bool operator< (const string& lhs, const string& rhs);
	bool operator>= (const string& lhs, const string& rhs);
	bool operator<= (const string& lhs, const string& rhs);
	
	ostream& operator<<(ostream& os, const string& str);
	istream& operator>>(istream& is, string& str);
}

string.cpp

C++ 复制代码
#include "string.h"
namespace Tzuyu
{
	const size_t string::npos= -1;//不能定义两次npos,一个文件里面
	//const修饰的对象,定义只有一次才能初始化
	string::string(const char* str)
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_size + 1];
		strcpy(_str, str);
	}
	string::~string()
	{
		delete[]_str;
		_str = nullptr;
		_size = 0;
		_capacity = 0;
	}
	void string::push_back(char ch)
	{
		//if (_size == _capacity)
		//{
		//	reserve(_capacity == 0 ? 4 : _capacity * 2);
		//}
		//_str[_size] = ch;
		//_size++;
		insert(_size, ch);
	}
	void string::append(const char* str)
	{
		//size_t len = strlen(str);
		//if (_size + len > _capacity)
		//{
		//	size_t newCapacity = 2 * _capacity;
			//扩2倍不够,则需多少扩多少
		//	if (newCapacity < _size + len)
		//		newCapacity = _size + len;
		//	reserve(newCapacity);
		//}
		//strcpy(_str + _size, str);
		//_size += len;
		insert(_size, str);
		//代码的复用
	}
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[]_str;
			_str = tmp;
			_capacity = n;
		}
		
	}
	string&string:: operator+=(char ch)
	{
		push_back(ch);
		return*this;
	}
	string& string:: operator+=(const char* str)
	{
		append(str);
		return*this;
	}
	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//这里举个例子,相当于放在hello world\0的'\0'后面
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		_size++;
	}
	void string::insert(size_t pos, const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t newCapacity = 2 * _capacity;
			if (newCapacity < _size + len)
				newCapacity = _size + len;
			reserve(newCapacity);
		}
		size_t end = _size + len;
		while (end > pos + len - 1)//这里很关键
		{
			_str[end] = _str[end - len];
			--end;
		}
		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = str[i];
		}
		_size += len;
	}
	void string::erase(size_t pos, size_t len)
	{
		assert(pos < len);
		if (len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			//从后往前挪
			size_t end = pos + len;
			while (end<=_size)
			{
				_str[end - len] = _str[end];
				++end;
			}
			_size -= len;
		}
	}
	size_t string::find(char ch, size_t pos) //声明的时候可以给缺省值,实现的时候不能给
	{
		assert(pos < _size);
		for (size_t i = pos; i < _size; i++)
		{
			if (ch == _str[i])
				return i;
		}
		return npos;
	}
	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);
		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;
		}
	}
	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);
		//大于后面串的长度,则直接取完
		if (len > (_size - pos))
		{
			len = _size - pos;
		}
		Tzuyu::string sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}
		return sub;
	}
	//s1(s2)
	string::string(const string& s) //尽量用命名空间抱起来,否则string会被认为是库里面的
	{
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	//s1=s2=s3
	//s1=s1
	string& string::operator=(const string& s)
	{
		if (this != &s)
		{
			delete[] _str;
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		return *this;
	}
	bool operator== (const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) == 0;
	}
	bool operator!= (const string& lhs, const string& rhs)
	{
		return !(lhs == rhs);
	}
	bool operator> (const string& lhs, const string& rhs)
	{
		return !(lhs <= rhs);
	}
	bool operator< (const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) < 0;
	}
	bool operator>= (const string& lhs, const string& rhs)
	{
		return !(lhs < rhs);
	}
	bool operator<= (const string& lhs, const string& rhs)
	{
		return lhs < rhs || lhs == rhs;
	}

	ostream& operator<<(ostream& os, const string& str)
	{
		//os<<'"';
		//os << "xx\"xx";
		for (size_t i = 0; i < str.size(); i++)
		{
			//os << str[i];
			os << str[i];
		}
		//os << '"';

		return os;
	}

	istream& operator>>(istream& is, string& str)
	{
		str.clear();

		char ch;
		//is >> ch;
		ch = is.get();
		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			ch = is.get();
		}

		return is;
	}
}

test.cpp

C++ 复制代码
#include "string.h"
void test_string01()
{
	Tzuyu::string s3("hello world");
	cout << s3.find(' ') << endl;
	//cout << s3.find("wo") << endl; 没有实现find(const char*)

	Tzuyu::string s4 = "https://legacy.cplusplus.com/reference/cstring/strstr/?kw=strstr";
	size_t pos1 = s4.find(':');
	size_t pos2 = s4.find('/', pos1 + 3);
	if (pos1 != string::npos && pos2 != string::npos)
	{
		Tzuyu::string domain = s4.substr(pos1 + 3, pos2 - (pos1 + 3));
		cout << domain.c_str() << endl;
		Tzuyu::string uri = s4.substr(pos2 + 1);
		cout << uri.c_str() << endl;
	}
}
void test_string06()
{
	Tzuyu::string s1("hello world");
	Tzuyu::string s2(s1);//拷贝构造
	Tzuyu::string s3 = s1;//这里也是拷贝构造,不能单独看=就认为是赋值,赋值的前提是
	//两个对象都创立了
	Tzuyu::string s4 = "hello world";
	//隐式类型转换,构造+拷贝构造->直接构造
	//operator<<(cout, s1); 
	cout << s1 << endl;

	cin >> s1;
	cout << s1 << endl;

	std::string ss1("hello world");
	cin >> ss1;
	cout << ss1 << endl;
}
int main()
{
	Tzuyu::string s2;
	cout << s2.c_str() << endl;
	Tzuyu::string s1("hello world");
	cout << s1.c_str() << endl;
	s1[0] = 'x';
	cout << s1.c_str() << endl;
	Tzuyu::string::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		(*it1)--;
		++it1;
	}
	cout << endl;
	it1 = s1.begin();
	while (it1 != s1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;
	for (auto& ch : s1)
	{
		ch++;
	}
	for (auto ch : s1) 
	{
		cout << ch << " ";
	}
	cout << endl;
	const string s3("xxxxxxxx");
		for(auto& ch : s3)
		{
			//ch++;//不可以这样进行操作,因为auto自动推导的时候发现的是const修饰的,不能修改
			cout << s3 << " ";
		}
		cout << endl;
	return 0;
}
相关推荐
wjs20242 分钟前
MongoDB 更新集合名
开发语言
monkey_meng5 分钟前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
一只小小汤圆9 分钟前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
草莓base18 分钟前
【手写一个spring】spring源码的简单实现--bean对象的创建
java·spring·rpc
Estar.Lee20 分钟前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
legend_jz30 分钟前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE40 分钟前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
drebander42 分钟前
使用 Java Stream 优雅实现List 转化为Map<key,Map<key,value>>
java·python·list
乌啼霜满天2491 小时前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc
tangliang_cn1 小时前
java入门 自定义springboot starter
java·开发语言·spring boot