C++ String类(带你一篇文章搞定C++中的string类)

感谢大佬的光临各位,希望和大家一起进步,望得到你的三连,互三支持,一起进步

数据结构习题_LaNzikinh篮子的博客-CSDN博客

初阶数据结构_LaNzikinh篮子的博客-CSDN博客

收入专栏:C++_LaNzikinh篮子的博客-CSDN博客

其他专栏:c语言基础_LaNzikinh篮子的博客-CSDN博客

个人主页LaNzikinh-CSDN博客

文章目录

  • 前言
  • 一.auto和范围for
  • 二.sting类的常用接口
  • 三.string类的模拟实现
  • 总结

前言

我们前面对C++的泛型编程做了了解,然后我们继续来说,C++标准库里string类


一.auto和范围for

1.1迭代器

如果我们要便利一串数字,我们在C语言中遍历东西,我们都会想到利用这个循环来遍历,而在C++中,我们有一个全新的语法来遍历东西,那就是迭代器。每种数据结构都有一种对应的迭代器,迭代器一般实现为容器的嵌套类型,在容器内部提供具体的实现。但是容器不同,底层元素遍历的方式也不同,那么为什么说迭代器遍历所有容器的方式是一样的呢?那是因为迭代器提供了常用的operator!=,operator++,operator*等运算符的重载函数,把迭代容器的细节全部隐藏在这些通用的运算符重载函数里面,因此用户侧表现出来的就是,迭代器遍历所有容器的方式都是一样的,其实底层都是不一样的。这里不做过多的解释。我们来看下面的例子

string s1;
string s2("hello world");
//s2[0] = 'x';
//cout << s2 << endl;
//下标遍历
for (size_t i = 0; i < s2.size(); i++)
{
	cout << s2[i];
}
cout << endl;
//迭代器遍历,it看作一个指针
string::iterator it = s2.begin();
while (it != s2.end())
{
	cout << *it;
	it++;
}

第一个,我们就是正常以前C语言当中的下标遍历循环遍历,下面那一种就是我们的这个迭代器遍历。

迭代器除了正向迭代器,还有反向迭代器,反向迭代器字面意思就是从后往前遍历,迭代器一共有四种,正向iterator,反向reverse_iterator,常量迭代器const_iterator,反向常量迭代器reverse_const_iterator,这四种。

string::rever_iterator rit=s1.begin();
while(rit!=si.end())
{
rit++;
}

1.2auto和范围for

为什么要讲这么多迭代器的东西呢?就是因为auto和范围for的底层就是迭代器.

//auto,范围for,字符赋值,自动迭代,自动判断结束
// 底层就是迭代器
for (auto& ch : s2)
{
	cout << ch;
}

auto会自动推到类型,s2自动赋值给ch。

二.sting类的常用接口

2.1size()和capacity()

这两个就是来看一下你这个里面的容量是多少空间是多少,size():返回字符串有效字符长度,capacity():返回空间总大小,补充:length(),也可以返回字符串有效字符长度

	string s1("hello world");
	cout << s1.size()<<"  "<< s1.capacity();

max_size()就是容量的最大值

2.2reserve()和resize(),注意:reverse()

这两个函数是经常容易搞混的两个函数,一个是开辟空间,你给一个值,我直接给你开通那么大,还有一个就是逆置函数,可以将一串字符给逆置

reserve():为字符串预留空间

void test_string2()
{
	string s1("hello world");
	cout << s1.size() << "  " << s1.capacity();
	cout << endl;
	s1.reserve(100);
	cout << s1.size() << "  " << s1.capacity();

}

由实验可知,在string中,reserve()是不会缩容的,开了多少就是多少

注意在string中没有逆置

resize()调整字符串大小,将有效字符的个数该成n个,多出的空间用字符c填充

2.3插入删除字符函数

s.inset(插入),s.erase(删除),s.append(尾插字符串),s.push_back(尾插一个字符),operator+=(尾插入)

	string s("hello world");
	s += "io";
	cout << s << endl;

	s.append("yyy");
	cout << s << endl;
	//下标控制
	s.insert(0, "end");
	cout << s << endl;
	//下标控制
	s.erase(6, 10);
	cout << s << endl;

2.4replace(),clear(),empty()

replace()是替换字符的意思可以将字符串中的字符替换

string s2("hello world");
s2.replace(5,1,"**");
cout << s2 << endl;

通过下标控制的

empty():检测字符串释放为空串,是返回true,否则返回false,clear():清空有效字符

2.5substr(),find(),operator+

substr():在str中从pos位置开始,截取n个字符,然后将其返回

find():从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

string s("hello world");
int a = s.find("h");
cout << a << endl;

注意非成员函数operator+:尽量少用,因为传值返回,导致深拷贝效率低

2.6输入输出流

他们都属于string类非成员函数

operator>>输入流,operator<<输出流

但是如果输入一个空格,就无法接收到了,该怎么办呢?

getline()获取一行字符串,特别的输入,遇到空格不会结束会继续输入数据

2.7特殊函数c_str()返回C格式字符串

他的本质是返回底层空间的指针,比如想用printf输出string,printf接收的是指针,没办法直接输出string,所以就可以用string.c str()进行输出

三.string类的模拟实现

在这里只会实现在string中重要的成员函数,要了解他们的底层逻辑,其他的会使用的可以了

我们先来看他的头文件

using namespace std;
namespace lanzi
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			return _str + _size;
		}
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str+_size;
		}
		
		string(const char* str = " ")
		{
			_size = strlen(str);
	
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		//深拷贝
		//s1(s2);
		string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;

		}
		// s2 = s1
		// s1 = s1
		//赋值
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				// s1 = s1
				delete _str;
				_str = new char[s._capacity + 1];
				strcpy(_str, s._str);
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}
		const char* c_str() const
		{
			return _str;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_capacity = _size = 0;
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		size_t size() const
		{
			return _size;
		}

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

			return _str[pos];
		}
		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* 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 = 0);
		string substr(size_t pos = 0, size_t len = npos);
	private:
		//char _buff[16];
		char* _str;
		size_t _size;
		size_t _capacity;
		//static const size_t npos = -1;
		static const size_t npos;
	};
	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);

	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
}

要实现的就是这样,现在我们一一来实现

3.1构造与析构

我们先来完成最简单的构造与析构函数,注意:在string中是存在\0的,所以自己模拟实现的时候一定要注意这个细节。

初始化

用new来开辟一个空间,但是要存放\0,然后把size,capacity都赋为0

	string() :_str(new char[1]{'\0'}), _size(0), _capacity(0)
	{}

构造

其实和初始化是可以进行合并的,但是要注意_capacity不包含\0,所以在开空间的时候要多开一个,用来存放\0,用缺省值来实现,如果你不传东西,我就给你初始化,反之构造

string(const char* str = " ")
{
	_size = strlen(str);
	// _capacity不包含\0,所以在开空间的时候要多开一个,用来存放\0
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

析构

把开辟的空间全部释放即可

~string()
{
	delete[] _str;
	_str = nullptr;
	_capacity = _size = 0;
}

3.2赋值与拷贝构造

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

拷贝构造

//深拷贝
//s1(s2);
string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;

}

赋值:存在特殊情况,自己给自己赋值

// s2 = s1
// s1 = s1
//赋值
string& operator=(const string& s)
{
	if (this != &s)
	{
		// s1 = s1
		delete _str;
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

3.3容易的成员函数

不做讲解直接代码

迭代器相关的成员函数

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

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

clear函数,size,capacity函数,

	void clear()
	{
		_str[0] = '\0';
		_size = 0;
	}
	size_t size() const
	{
		return _size;
	}

	size_t capacity() const
	{
		return _capacity;
	}

[]引用函数

	char& operator[](size_t pos)
	{
		assert(pos < _size);

		return _str[pos];
	}

以上这些函数都是作为内联函数,在类里面实现的,因为他们经常反复调用,所以在类里用内联函数去实现,节省效率

3.4reserve()扩容函数

动态开辟一个指定空间,然后利用strcpy去实现复制

void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

3.5尾插函数push_back

如果满了,就扩容

	void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}

3.6尾插字符串append()

因为是字符串,所以和字符有区别,要注意实现

void string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size+len > _capacity)
	{
		reserve(_size + len>2* _capacity? _size + len:2* _capacity);
	}
	strcpy(_str + _size, str);
	_size += len;
}

3.7+=实现尾插

直接复用

	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}

3.8插入函数insert()

插入一个字符

往后移动,效率极差

	void string::insert(size_t pos, char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//存在\0
		int end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			end--;
		}
		_str[pos] = ch;
		_size++;
	}

插入一个字符串

先将原来的字符移动位置,然后在指定位置上插入

	void string::insert(size_t pos, const char* s)
	{
		size_t len = strlen(s);
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		int 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] = s[i];
			}
		_size += len;
	}

3.9删除函数erase()

pos表示起始位置,len为要删除的长度,如果长度高于后面大小就全部删除,如果没有,就删指定大小

void string::erase(size_t pos, size_t len = npos)
{
	assert(pos > _size);
		if (len > _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
			else
			{
				for (size_t i = pos + len; i <= _size; i++)
				{
					_str[i - len] = _str[i];
				}

				_size -= len;
				}
	
}

3.11查找函数find()

查找一个字符

根据所给的位置,一一遍历,找到就返回,没有就返回空

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

查找一个字符串

直接利用函数strstr去找

	size_t string::find(const char* str, size_t pos)
	{
		assert(pos > _size);
		{
			char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				//因为在string中,都是返回下标所以要减去_str
				return ptr - _str;
			}
		}
	}

3.21生成子串函数substr()

 len大于剩余字符长度,更新一下len,然后赋值

string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);
	// len大于剩余字符长度,更新一下len
	if (len > _size - pos)
	{
		len = _size - pos;
	}

	string sub;
	sub.reserve(len);
	for (size_t i = 0; i < len; i++)
	{
		sub += _str[pos + i];
	}
	return sub;
}

3.31比较函数

只要实现两个,其他都可以复用

	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}
	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}
	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);

	}
	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 <s2);
	}
	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}
	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

3.41流插入提取函数

流提取,利用auto,for来遍历

ostream& operator<<(ostream& out, const string& s)
{
	for (auto& ch : s)
	{
		out << ch;
	}
	return out;
}

流插入

注意:流插入遇到空格就停止接受,所以要用get()来接受空格,先创建一个buff数组,提高效率,利用get来接收字符,如果不是空和/0的话,就把它放到数组里,如果数组满了的话,就直接结束,将buff数组的值全部放进string的类中,没有的话就继续获取数据

	//很难
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		const int N = 256;
		char buff[N];
		int i = 0;
		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\0')
		{
			buff[i++] = ch;
			if (i==N-1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}
}

总结

string类是C++标准库里的一个类,为什么学习string类,因为C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列 的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户 自己管理,稍不留神可能还会越界访问。其实在标准库还有很多这里内容,下次在一一讲解

  • 51
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 34
    评论
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LaNzikinh篮子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值