当前位置:首页 » 《资源分享》 » 正文

05 STL中的string类和模拟实现_精致的灰的博客

18 人参与  2022年01月08日 14:16  分类 : 《资源分享》  评论

点击全文阅读


string是STL中的类,相比于C语言的字符数组和字符指针,string对字符串的处理方便很多。


文章目录

  • 一、字符的一些补充
  • 二、string类
    • 2.1.string类的常见构造函数和析构函数
    • 2.2.三种遍历方式
    • 2.3.string常用的接口
  • 三、string模拟实现
    • 3.1.深浅拷贝的问题
    • 3.2.命名空间
    • 3.3.默认成员函数
      • 构造函数
      • 析构函数
      • 重载赋值运算符=
    • 3.4.元素访问函数
      • []重载
      • find和rfind
      • c_str
    • 3.5.容积函数
      • size()和capacity()
      • reserve
      • resize
      • empty
      • clear
    • 3.6.增删查改
      • 用来交换对象中成员变量的swap函数
      • insert
      • push_back和append
      • 重载+=
      • erase
    • 3.7.迭代器
      • begin()和end()迭代器
    • 3.8.非成员函数重载
      • 重载输出<<
      • 重载输入>>
      • getline
    • 3.9.关系运算符重载


一、字符的一些补充

众所周知英文字符等符号在计算机中采用ASCII码的方式储存。
但是对于中文等其他字符,ASCII码则无法表示(因为一个字节存不下),对于这些字符,则采用Unicode编码么标准:它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。当然Unicode标准下字符是两个字节,这样就能存下大部分字符了。
当然也有新的字符型wchar_t来存储两个字节的字符。
在这里插入图片描述


二、string类

相比于C语言,string类型更符合面向对象的思想,它将string以及成员函数放在了一个头文件<string>中,而库< iostream >已经隐式地包含了<string>头文件,所以无需显式引用该头文件也能够使用string类。这样更方便一些。

相比于C语言的字符数组或字符指针,string更安全一些,字符数组或字符指针很容易就会越界访问。

了解string类最好的方式就是查看官方文档。 string类

2.1.string类的常见构造函数和析构函数

在C++98中有以下七种构造函数:
在这里插入图片描述

其中比较重要的是默认构造、字符串构造和拷贝构造。

#include<iostream>
using namespace std;
int main()
{
	string s1;                    //默认构造
	string s2("hello");          //字符串构造
	string s3(s2);               //拷贝构造
	string s4("hello", 1);       //复制字符串前 n 个字符 
	string s5("hello", 1, 2);    //从字符串第一个位置往后拷贝2个字符
	string s6("hello", 1, string::npos); //npos是一个静态的全局变量为-1补码无穷大  超出长度则全部拷贝
	string s7("hello", 1, 20);    //同上
	string s8(5, 'a');            //用n个字符填充字符串
	string s9 = "hello";          //等价于字符串构造,编译器优化了隐式类型转换的过程

	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
	cout << s7 << endl;
	cout << s8 << endl;
	cout << s9 << endl;

	s1 = s8;                //重载=
	cout << s1 << endl;


	return 0;
}

在这里插入图片描述

析构函数则是在对象销毁时自动调用,销毁开辟的空间:
在这里插入图片描述

2.2.三种遍历方式

  1. 采用下标的方式
    在这里插入图片描述
    在这里插入图片描述

size()是一个公共成员函数,返回string的长度。和length()相同。
在这里插入图片描述

  1. 迭代器遍历
    迭代器iterator是string中的类,所以要加域作用限定符。
    在这里插入图片描述

begin()返回开始的下标位置的迭代器。
迭代器返回的是一个左闭右开的下标区间,所以end()返回的不是最后一个数据位置的迭代器,返回是最后一个位置下一个位置。
C++中凡是给迭代器一般都是给的左闭右开的区间。迭代器是类似指针一样的东西,它的用法就是模拟指针。

迭代器的意义是遍历更复杂的容器,比如list、map等。

反向迭代器:
在这里插入图片描述

反向迭代器++的时候是倒着往前走的。

const对象只能使用const迭代器遍历,const迭代器是只读:
在这里插入图片描述

iterator遍历普通对象是可读可写的,const_iterator 遍历const对象是只读的。

  1. 范围for
    在这里插入图片描述

依次取容器中的数据赋值给e,自动判断结束。
范围for实现的本质是迭代器,因此支持其他容器的遍历。

2.3.string常用的接口

几个常用的函数:

  1. push_back(ch)
    尾插一个字符ch
  2. append(str)
    在字符串后追加一个字符串str
  3. +=str
    在字符串后追加一个字符串str。重载了+=.。相比于append,实际中更常用+=
  4. insert(pos,str) insert(pos,n,ch)
    在下标为pos位置插入一个字符串str。或者插入n个字符ch。尽量少用insert,因为插入需要挪动数据。
  5. erase(pos,len)
    在下标为pos后删除长度为len的字符,如果不写pos和len则默认从开始的位置向后全删除。
  6. resize(n,ch)
    改变size的大小到n,capacity也会改变。多出来的size空间则用字符ch补全,如果不写ch则默认为\0
  7. reserve(n)
    改变容量到大于n的大小,size并不会改变。
  8. c_str()
    获取对象中字符串的首地址。
  9. find(ch/str,pos)和rfind(ch/str,pos)
    从pos位置开始找一个字符或者字符串,pos不写默认给0。返回字符或字符串在源字符串的下标位置。
    rfind从后往前找。
  10. substr(pos,len)
    返回字符串从pos位置开始,长度为len的部分。

通过find和substr两个函数可以查找网址的域名和协议:

#include<iostream>
using namespace std;
string GetDomain(const string& url)
{
	size_t pos = url.find("://");
	if (pos != string::npos)
	{
		size_t start = pos + 3;
		size_t end = url.find('/', start);
		if (end != string::npos)
		{
			return url.substr(start, end - start);
		}
		else
		{
			return string();
		}
	}
	else
	{
		return string();
	}

}
string GetProtocol(const string& url)
{
	size_t pos = url.find("://");
	if (pos != string::npos)
	{
		return url.substr(0, pos - 0);
	}
	else
	{
		return string();
	}
}

int main()
{
	string url = "https://blog.csdn.net/qq_44918090/article/details/118532221";
	cout << GetDomain(url) << endl;
	cout << GetProtocol(url) << endl;

	return 0;
}

在这里插入图片描述

  1. getline(cin,s)
    往string对象s中输入一行(包括空格),遇到换行终止。
  2. reverse(s.begin(),s.end())
    逆置s,传入开始和结束的迭代器。

三、string模拟实现

string本质上是一个管理字符串的顺序表(数组),字符串的结尾有\0,所以采用顺序表的写法即可。

3.1.深浅拷贝的问题

string类模拟实现需要注意的问题就是深浅拷贝,所以拷贝构造函数应该采用深拷贝的方式,因为新的对象也需要开辟空间,如果采用浅拷贝就会导致它们指向同一块空间,在析构时会释放两次该空间。
在这里插入图片描述

在这里插入图片描述

如果是深拷贝:
在这里插入图片描述

另外,赋值运算符=默认也是浅拷贝。

3.2.命名空间

如果不加命名空间和库实现隔离,则会和库里面的string重名导致无法调用,当然也可以给模拟实现的string改类名,这里采用命名空间的解决办法,命名空间起名为A,当然也可以起别的名字:

namespace A
{
	class string
	{  

   	private:
		char* _str;//字符指针,指向字符串
		size_t _size;//记录字符串的长度
		size_t _capacity;//记录字符串空间的大小
		static const size_t npos;//表示字符串的最大长度,设置成静态常量
	};
	const size_t string::npos = -1;//在类外给静态常量赋值
}

3.3.默认成员函数

构造函数

       //构造函数
		string(const char* str = "")
			::_str(new char[strlen(str) + 1])//要有\0,所以开辟的空间要比长度大
			, _size(strlen(str))
			, _capacity(_size + 1)//要有\0,所以空间要比长度大
		{
			strcpy(_str, str);//将str中的字符串拷到this中
		}
		//拷贝构造
		string(const string& s)
			:_str(new char[strlen(s._str) + 1])//因为有\0,所以开辟长度+1的空间
			,_size(strlen(s._str))
			,_capacity(_size+1)
		{
			strcpy(_str, s._str);//将s中的字符串拷到this中
		}

        //也可以采用这种写法
		//string(const string& s)
		//	:_str(nullptr)
		//	,_size(0)
		//	,_capacity(0)//将this的字符指针置为空,长度、空间大小置为0
		//{
		//	string tmp(s._str);//用默认构造创建一个临时对象,不能传s,否则会死循环
		//	swap(tmp);//将临时对象中的数据和this交换
		//}

析构函数

	 ~string()
		{
		//_size和_capacity不需要置零,因为对象生命周期结束这俩成员变量就没了
			delete[]_str;
			_str = nullptr;
		}

重载赋值运算符=

    //因为是值传递,所以会出现自己给自己赋值的情况,字符指针指向的字符串地址会发生改变 
    string& operator=(string s)//因为是值传递,在传参时会拷贝 构造一个临时对象s
		{
			swap(s);//交换s和this
			return *this;
		}
    //也可以采用这种写法
   /*string& operator=(const string& s)
		{
			if (this != &s) //排除自己给自己赋值的情况
			{
				delete[]_str;//销毁原来的字符指针_str指向的空间
				_str = new char[strlen(s._str) + 1];//重新给_str开辟一块空间
				strcpy(_str, s._str);//将s中字符串拷贝到_str指向的空间中
			}
			return *this;
		}*/

3.4.元素访问函数

[]重载

     //[]重载
     //重载两个,一个普通对象,一个const对象
		char& operator[](size_t i)//返回引用可以对成员变量进行修改
		{
			assert(i < _size);//防止越界
			return _str[i];
		}
		const char& operator[](size_t i)const
		{
			assert(i < _size);
			return _str[i];
		}

find和rfind

		//查找一个字符和字符串
		size_t find(char ch, size_t pos = 0)const
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		size_t find(const char*str, size_t pos = 0)const
		{
			assert(pos < _size);
			const char* ret = strstr(_str+pos, str);
			if (ret)
			{
				return ret - _str;
			}
			else
			{
				return npos;
			}
		}

    //反向查找字符或字符串
    //将原字符串逆置然后复用find函数
		size_t rfind(char ch, size_t pos = npos)
		{
			string tmp(*this);
			reverse(tmp.begin(), tmp.end()); //逆置tmp
			//如果pos大于字符串长度,则将pos改为字符串最后一个位置的下标
			if (pos >= _size)
			{
				pos = _size - 1;
			}
			//逆置以后,pos的位置要发生改变
			pos = _size - 1 - pos;
			size_t ret = tmp.find(ch, pos); //复用find函数
			if (ret != npos)
			{
				return _size - 1 - ret; //返回ret镜像对称后的位置
			}
			else
			{
				return npos;
			}
		}
	
		size_t rfind(const char* str, size_t pos = npos)
		{
			string tmp(*this); 
			size_t len = strlen(str); //待查找的字符串的长度
			//拷贝并逆置str
			char* arr = new char[len + 1]; 
			strcpy(arr, str); 
			int left = 0, right = len - 1; 
			while (left < right)
			{
				::swap(arr[left], arr[right]);
				left++;
				right--;
			}
			reverse(tmp.begin(), tmp.end()); //逆置tmp
			//如果pos大于字符串长度,则将pos改为字符串最后一个位置的下标
			if (pos >= _size)
			{
				pos = _size - 1;
			}
			//逆置以后,pos的位置要发生改变
			pos = _size - 1 - pos;
			size_t ret = tmp.find(arr, pos); //复用find函数
			delete[] arr; //销毁arr
			if (ret != npos)
			{
				return _size - ret - len; //返回ret镜像对称后再调整的位置
			}
			else
			{
				return npos; 
			}		
		}

c_str

		char* c_str()const
		{
			return _str;
		}

3.5.容积函数

size()和capacity()

	size_t size()const//加const的好处是普通对象和const对象都可以调用
		{
			return strlen(_str);
		}
	size_t capacity()    
		{
			return _capacity;
		}

reserve

		void reserve(size_t n)
		{
			if (n > _capacity)//如果需要开的空间比实际的小就不用开了
			{
				char* tmp = new char[n+1];
				strncpy(tmp, _str,_size+1);//将\0也要拷进去
				delete[]_str;//销毁原来的空间
				_str = tmp;//指向新开的空间
				_capacity = n+1;
			}
		}

resize

		//开空间+初始化,扩展capacity 并且初始化空间,size也要改变
		void resize(size_t n, char val = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';//n比实际的size小的时候直接将下标为n的地方改为\0即可
			}
			else
			{
				// 如果newSize大于底层空间大小,则需要重新开辟空间
				if (n > _capacity)
				{
					reserve(n);
				}
				memset(_str + _size, val, n - _size);
				_size = n;
				_str[n] = '\0';
			}
		}

empty


		bool empty()
		{
			return strcmp(_str, "") == 0;
		}

clear

		void clear()
		{
			_size = 0;
			_str[0] = '\0';
		}

3.6.增删查改

用来交换对象中成员变量的swap函数

   void swap(string& s)
		{
		//加域作用限定符::是为了调用std中的swap函数,如果不加则是自己调用自己死循环
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

这个函数的目的是为了交换对象中的成员变量,在接下来其他函数的实现中被调用。

insert

		//下标为pos位置插入字符串
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size +len > _capacity)
			{
				reserve(_size + len);
			}
			//挪字符串
			char* end = _str + _size;
			while (end >= _str + pos)
			{
				*(end + len) = *end;
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;

		}
		
		//下标为pos位置插入n个字符
		string& insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			if ((_size+n) >= _capacity)
			{
				reserve(_capacity == 0 ? (_size + n) : (_size + n) * 2);
			}
			char* end = _str+_size;
			while (end >= _str + pos)
			{
				*(end + n) = *end;
				end--;
			}
			memset(_str + pos, ch, n);
			_size+=n;
			return *this;
		}

push_back和append

		void push_back(char ch)
		{
		  //方法一
			/*if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);//空间不够扩到两倍
			}
			_str[_size] = ch;
			_str[_size + 1] = '\0';
			_size++;*/

       //方法二、直接调用insert
			insert(_size,1, ch);
		}
		void append(const char* str)
		{
		  //方法一
			/*size_t len = _size + strlen(str);//计算插入后的长度
			if (len >= _capacity)
			{
				reserve(len);
			}
			strcpy(_str + _size, str);//从_str结束位置开始拷贝
			_size = len; */
      
       //方法二、直接调用insert
			insert(_size, str);
		}

重载+=

        //复用尾插函数即可
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

erase

		//删除
		string& erase(size_t pos,size_t len=npos)
		{
			assert(pos < _size);
			size_t leftlen = _size - pos;
			if (len >= leftlen)//pos后的长度比要删的长度小
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);//删除后剩余的字符移到前面
				_size -= len;
			}
			return *this;
		}

3.7.迭代器

begin()和end()迭代器

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

3.8.非成员函数重载

重载输出<<

	ostream& operator<<(ostream& out,const string& s) 	
	{
	  //也可以使用下标遍历
		/*for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i];
		}*/
		//使用范围for遍历
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

重载输入>>

	istream& operator>>(istream& in, string& s)
	{
		s.clear();//清空s里的数据
		char ch;
		ch=in.get();//连续输入
		while (ch != ' ' && ch != '\n')//遇到空格或换行结束
		{
			s += ch;
			ch = in.get();
		}	
		return in;
	}

getline

	istream& getline(istream& in, string& s)
	{
		s.clear();//清空s里的数据
		char ch;
		ch = in.get();//连续输入
		while (ch != '\n')//遇到换行结束
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}

3.9.关系运算符重载

参考日期类的关系运算符重载,>、>=、<、<=、==、!=只需要实现出两个,其他直接复用这两个即可。

	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 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 !(s1 == s2);
	}

点击全文阅读


本文链接:http://zhangshiyu.com/post/32919.html

字符串  函数  字符  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1