【C++】string类

string是表示字符串的字符串类,该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。. string在底层实际是:basic_string模板类的别名,typedef basic_string string,在使用string类时,必须包含#include头文件以及using namespace std;

本篇文章将重点讲解string类的使用和模拟实现


1.string类常用接口说明

1.1string类对象的常见构造

string() 构造空的string类对象,即空字符串
string(const char*s)用C-string来构造string类对象
string(size_t n,char c)string类对象中包含n个字符C
string(const string& s)拷贝构造
string(const char* s,size_t n)用字符串s的前n个字符来构造

1.2string类对象的容量操作

size()        返回字符串的有效字符长度
length()返回字符串的有效字符长度
capacity()返回空间总大小
empty()检测字符串是否为空,是返回true,否则返回false
clear()清空有效字符
reserve(int)为字符串预留空间
resize(int,char)将有效字符的个数该成n个,多出的空间用字符c填充,如果只给了一个参数,默认初始化为'\0'

注意:

1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。

2. clear()只是将string中有效字符清空,不改变底层空间大小。

3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。

4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。

1.3string类对象的访问及遍历操作

1.operator[]

string str = "hello word";
	int size = str.size();
	for (int i = 0; i < size; i++)
	{
		//str.operator[](i);
		cout << str[i] << endl;
	}

2.迭代器

正向迭代器

string str = "hello word";
	string::iterator it = str.begin();//得到一个指向首元素的指针
	while (it != str.end())//str.end()得到一个指向'\0'的指针
	{
		cout << *it << endl;
		it++;
	}

反向迭代器

	string str = "hello word";
	string::reverse_iterator it = str.rbegin();//得到一个指向最后一个有效元素的指针
	while (it != str.rend())//str.rend()得到一个指向首元素前一个的指针
	{
		cout << *it << endl;
		it++;
	}

 

 string::iterator得到的是可读可写的指针,可以通过该指针修改原来的字符串

如果想保护原来的字符数,只读不让写string::const_iterator

为了方便,我们可以用auto的方式来自动识别类型

 3.范围for

string str = "hello word";
	for (auto e : str)
	{
		cout << e << endl;
	}
	return 0;

范围for的底层原理还是迭代器

1.4string类对象的修改操作

push_back在字符串后尾插字符c
append在字符后追加一个字符串
operator+=(最常用的)在字符串后追加字符串str
c_str返回C格式字符串
find+npos从字符串pos位置开始往后找字符c,返回该字符在字符串的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符,然后将其返回

 注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

1.5 string类非成员函数

operator+尽量少用,因为船只返回,导致深拷贝效率低
operator>>流输入运算符重载
operator<<流提起运算符重载
getline获取一行字符串
relational operators比较运算符重载

2.string类模拟实现

2.1构造函数

 在标准库中没有对string变量赋初始值的话,会默认是空字符串,开15个空间

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

2.2 reserve

reserve是string类中更改容量的一个成员函数,准确的来说是一个扩容函数,如果n<capacity那么这个函数将不会做任何改变。

		void reserve(size_t n = 0)
		{
			if (n > _capacity)
			{
				char* temp = new char[n + 1];//需要多一个空间给'\0'
				strcpy(temp, _str);//将原数据拷贝
				delete[] _str;
				_str = temp;
				_capacity = n;
			}
		}

 2.3拷贝构造函数

写拷贝构造函数前需要讲讲深浅拷贝问题

如果没有写拷贝构造函数,那么编译器就会自动生成一个浅拷贝的拷贝构造函数

 

 如果是浅拷贝的话,str1和str2指向的字符串中间是同一片空间,会造成以下几个问题

1.如果str1修改,会影响str2的值,同样str2修改会影响str1的值

2.结束调用析构函数的时候会对同一片空间析构两次

所以需要深拷贝,深拷贝会重新开辟一个空间给str2

		//s1(s2)
		//传统写法
		string(const string& s)
			:_size(s._size)
			,_capacity(s._capacity)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}

上面这个拷贝构造函数是传统的写法,现在的拷贝构造函数会复用构造函数

		void swap(string& s)
		{
			
			std::swap(_str, s._str);//用全局的swap函数对string底层的字符串进行交换
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//现在写法
		string(const string& s)
			:_size(s._size)
			,_capacity(s._capacity)
			,_str(nullptr)//需要赋一个空指针,交换后这个指针将会给temp,temp回收后会调用析构函数,没有给初始指针会报错
		{
			string temp(s._str);
			//swap(temp,this)  使用全局的swap函数会调用深拷贝效率低
			swap(temp);
		}

2.4 operator=()

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

 和拷贝构造函数一样,

2.4 c_str()

获得c语言形式的字符串

		const char* c_str() const
		{
			return _str;
		}

2.5 size()

 获得字符串长度

		size_t size()
		{
			return _size;
		}

 2.6 capacity()

		size_t capacity(const string& s)const
		{
			return s._capacity;
		}

2.7 operator[]()

[]运算符重载,获取字符串中i位置的字符

		char& operator[](size_t i)
		{
			return _str[i];
		}

同时需要考虑到只读的情况

		const char& operator[](size_t i)const
		{
			return _str[i];
		}

2.8 operator=()

		//s1 = s2
		//传统版本
		string& operator=(const string& s)
		{
			if (&s != this)
			{

				char* temp = new char[s._capacity + 1];
				strcpy(temp, s._str);
				delete[] _str;
				_str = temp;
				_size = s._size;//最后放在后面,防止temp没申请到空间
				_capacity = s._capacity;
			}
			return *this;
		}

 同样赋值运算符重载也可以复用构造函数

		//现在写法
		string& operator=(string s)
		{
			swap(s);//用临时变量和*this交换
			return *this;
		}

2.9relational operator

比较函数的实现

比较原则:比较第一个不匹配的字符的大小,如果都匹配就比较字符串的长度

	int compare(const string& s)const
	{
		return std::strcmp(_str, s._str);
	}
    bool operator<(string& str1, string& str2)
	{
		if (str1.compare(str2) < 0)
		{
			return true;
		}
		return false;
	}
	bool operator==(string& str1, string& str2)
	{
		if (str1.compare(str2) == 0)
		{
			return true;
		}
		return false;
	}
	bool operator<=(string& str1, string& str2)
	{
		if (str1<str2||str1==str2)
		{
			return true;
		}
		return false;
	}
	bool operator>(string& str1, string& str2)
	{
		return !(str1 <= str2);
	}
	bool operator>=(string& str1, string& str2)
	{
		return !(str1 < str2);
	}
	bool operator!= (string & str1, string & str2)
	{
		return !(str1 == str2);
	}

 2.10 insert()

 我们就实现两个重载,一个是从pos位置插入一个字符,一个是从pos位置插入字符串

insert函数与顺序表的插入一样,可以参考下面这篇文章

(4条消息) 【数据结构】顺序表_Patrick star`的博客-CSDN博客

		//插入一个字符
		void insert(size_t pos,char c)
		{
			assert(pos <= _size);
			size_t end = _size+1;
			while (end!=pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = c;
		}
		//插入一个字符串
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if ((_size + len) > _capacity)
			{
				reserve(_size + len);
			}
			size_t begin1 = _size + 1;
			size_t begin2 = begin1 + len;
			while (begin1 != pos)
			{
				_str[begin2-1] = _str[begin1-1];
				begin1--;
				begin2--;
			}
			strncpy(_str+begin1, str, len);
		}

2.11push_back()

尾插一个字符,可以复用insert

		void push_back(const char c)
		{
			insert(_size, c);
		}

2.12 append()

尾插一个字符串,同样复用insert

		void append(const char* str)
		{
			insert(_size, str);
		}

2.13 operator+=()

复用push_back和append

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

2.14 迭代器

string类中的迭代器实际上就是char指针的别名

		typedef char* interator;
		typedef const char* const_interator;
		interator begin()
		{
			return _str;
		}
		interator end()
		{
			return _str+_size;
		}
		const_interator begin()const
		{
			return _str;
		}
		const_interator end()const
		{
			return _str+_size;
		}

当我们使用范围for进行遍历的时候,编译器会自动调用迭代器,所以这也要求我们命名必须规范,如果命名不规范就不能使用范围for进行遍历

2.15 流提取和流插入运算符重载

	std::ostream& operator<<(std::ostream& out, string str)
	{
		//out<<str.c_str   这种形式无法输出\0
		for (auto e : str)
		{
			out << e;
		}
	}
	std::istream& operator>>(std::istream in, string str)
	{
		str.clear();
		char ch = in.get();
		char buff[128] = { '\0' };
		size_t i = 0;
		while (ch != ' ' || ch != '\n')
		{
			buff[i++] = ch;

			if (i == 127)
			{
				str += buff;
				memset(buff, '\0', 128);
				i = 0;
			}
			ch = in.get();
		}
		str += buff;
		return in;
	}

3.string类实现总览

#pragma once
#include <iostream>
#include<assert.h>
#include<string.h>
using namespace std;

namespace ldx
{
	class string
	{
	public:
		typedef char* interator;
		typedef const char* const_interator;
		interator begin()
		{
			return _str;
		}
		interator end()
		{
			return _str+_size;
		}
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(15)
		{
			if (_size>_capacity)
			{
				_capacity = _size;
			}
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		s1(s2)
		传统写法
		//string(const string& s)
		//	:_size(s._size)
		//	,_capacity(s._capacity)
		//{
		//	_str = new char[_capacity + 1];
		//	strcpy(_str, s._str);
		//}
		void swap(string& s)
		{
			
			std::swap(_str, s._str);//用全局的swap函数对string底层的字符串进行交换
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//现在写法
		string(const string& s)
			:_size(s._size)
			,_capacity(s._capacity)
			,_str(nullptr)//需要赋一个空指针,交换后这个指针将会给temp,temp回收后会调用析构函数,没有给初始指针会报错
		{
			string temp(s._str);
			//swap(temp,this)  使用全局的swap函数会调用深拷贝效率低
			swap(temp);
		}
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
				_size = _capacity = 0;
			}
		}
		char& operator[](size_t i)
		{
			return _str[i];
		}
		const char& operator[](size_t i)const
		{
			return _str[i];
		}


		s1 = s2
		传统版本
		//string& operator=(const string& s)
		//{
		//	if (&s != this)
		//	{

		//		char* temp = new char[s._capacity + 1];
		//		strcpy(temp, s._str);
		//		delete[] _str;
		//		_str = temp;
		//		_size = s._size;//最后放在后面,防止temp没申请到空间
		//		_capacity = s._capacity;
		//	}
		//	return *this;
		//}
		//现在写法
		string& operator=(string s)
		{
			swap(s);//用临时变量和*this交换
			return *this;
		}
		//插入一个字符
		void insert(size_t pos,const char c)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity==0? 4:2 * _capacity;
				reserve(newCapacity);
			}
			size_t end = _size+1;
			while (end!=pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = c;
		}
		//插入一个字符串
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if ((_size + len) > _capacity)
			{
				reserve(_size + len);
			}
			size_t begin1 = _size + 1;
			size_t begin2 = begin1 + len;
			while (begin1 != pos)
			{
				_str[begin2-1] = _str[begin1-1];
				begin1--;
				begin2--;
			}
			strncpy(_str+begin1, str, len);
		}
		void reserve(size_t n = 0)
		{
			if (n > _capacity)
			{
				char* temp = new char[n + 1];//需要多一个空间给'\0'
				strcpy(temp, _str);//将原数据拷贝
				delete[] _str;
				_str = temp;
				_capacity = n;
			}
		}
		const char* c_str() const
		{
			return _str;
		}
		size_t size()
		{
			return _size;
		}
		int compare(const string& s)const
		{
			return std::strcmp(_str, s._str);
		}
		size_t capacity(const string& s)const
		{
			return s._capacity;
		}
		void push_back(const char c)
		{
			insert(_size, c);
		}
		void append(const char* str)
		{
			insert(_size, str);
		}
		string& operator+=(const char c)
		{
			push_back(c);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		void clear()
		{
			_size = 0;
			_str[0] ='\0';
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;//有效字符的容量
	};
	bool operator<(string& str1, string& str2)
	{
		if (str1.compare(str2) < 0)
		{
			return true;
		}
		return false;
	}
	bool operator==(string& str1, string& str2)
	{
		if (str1.compare(str2) == 0)
		{
			return true;
		}
		return false;
	}
	bool operator<=(string& str1, string& str2)
	{
		if (str1<str2||str1==str2)
		{
			return true;
		}
		return false;
	}
	bool operator>(string& str1, string& str2)
	{
		return !(str1 <= str2);
	}
	bool operator>=(string& str1, string& str2)
	{
		return !(str1 < str2);
	}
	bool operator!= (string & str1, string & str2)
	{
		return !(str1 == str2);
	}
	std::ostream& operator<<(std::ostream& out, string str)
	{
		//out<<str.c_str   这种形式无法输出\0
		for (auto e : str)
		{
			out << e;
		}
	}
	std::istream& operator>>(std::istream in, string str)
	{
		str.clear();
		char ch = in.get();
		char buff[128] = { '\0' };
		size_t i = 0;
		while (ch != ' ' || ch != '\n')
		{
			buff[i++] = ch;

			if (i == 127)
			{
				str += buff;
				memset(buff, '\0', 128);
				i = 0;
			}
			ch = in.get();
		}
		str += buff;
		return in;
	}
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值