IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> STL——理解string -> 正文阅读

[Java知识库]STL——理解string


前言

这次我们将了解C++中的string。


1、string类的基本介绍

string类是C++用来管理字符串的,它是一种特殊的容器。与C中传统的字符串(只是在数组的一个字符序列,我们称之为字符数组)大有不同。C + +字符串对象属于一个类,这个类有很多内置的特点,与C中的字符串相比,我们不需要担心内存是否足够,字符串长度等诸多问题,因为类中的成员函数已经帮我们解决了这些不必要的麻烦,其中的功能也能满足使用者的大多数需求,并且不需要使用者了解底层的实现原理。但作为一名优秀的程序员,还是应该多多了解其中的细节。

2、string与basic_string的关系

basic_string是类模版,并且是容器类模版,basic_string类模版的对象管理的序列是标准的C++ 字符串,basic_string包括string、wstring、u16string和u32string)。 标准C++ 字符串类是一个容器,因此可以像操作其他普通类型一样,对C++ string类进行操作,例如比较,迭代,STL算法等。
在这里插入图片描述
在这里插入图片描述

string 的定义为:typedef basic_string string

在cplusplus中我们可以了解string的使用方法,以及各个接口——》https://www.cplusplus.com/reference/
在这里插入图片描述

3、string的常用接口

  • string构造函数
定义功能说明
string()构造空的string类对象,即空字符串 示例:string str()
string(const char *s)用常量字符串来构造string类对象 示例:string str(“feng”)
string(const string& s)拷贝构造:用一个已经存在的string对象去构造另一个对象 示例: string s1(s)
string(size_t n, char c)string类对象中包含n个字符c
  • string 类运算符重载
重载运算符功能说明
>>从标准输入流中提取字符
<<将字符串插入到标准输出流
=将右侧的字符串赋值给左侧的 string 对象
+=将右侧字符串的副本附加到左侧的 string 对象
+返回将两个字符串连接在一起所形成的字符串
[]使用数组下标,返回某个位置的字符的引用
关系运算符以下每个关系运算符都将被执行:< > <= >= = !=
  • string 类的成员函数
成员函数功能说明
iterator begin()返回指向 string 中第一个字符的迭代器
iterator end()返回指向 string 中最后一个字符的下一个位置的迭代器(即’\0’位置)
const_iterator begin()返回指向 string 中第一个字符的迭代器。(const)
const_iterator end()返回指向 string 中最后一个字符的下一个位置的迭代器(即’\0’位置)。(const)
reverse_iterator rbegin()返回指向 string 中最后一个字符的反向迭代器
reverse_iterator rend()返回指向 string 中第一个字符前一个位置的反向迭代器(即-1位置)
size_t _capacity()返回 string 被分配的容量的大小
size_t _size()返回 string 中字符串的长度
void clear()删除 string 中存储的所有字符
char* c_str()返回 string 对象的 C 字符串值(即以’\0’结束)
istream& getline(istream&, string& s)获取一行字符,包括空格
void swap(string& str)交换 string 和 str 的内容
string substr(size_t pos, size_t n)返回一个string 对象,_str为一个字串,子串长度为 n 个字符,从string 的 pos 位置开始
size_t find(char ch, size_t pos)返回在 string 中找到的 ch 字符的第一个位置。如果没有找到 ch,则返回 string::npos
size_t find(char*str, size_t pos)返回在 string 中找到的 str 字符串的第一个位置。如果没有找到 str,则返回 string 类的静态成员 string::npos
static const size_t nposstring 类中的静态成员,值为-1
string& erase(size_t pos, size_t n)从 string中_str字符串的pos位置 开始,删除 n 个字符
string& insert(size_t pos, char*str);
string& insert(size_t pos, char ch)
将 str 字符串或ch 字符 插入到 string 中指定位置,从位置 pos 开始
string& push_back(char ch)将ch字符插入string的末尾位置
string& append(const char* str)将str字符串插入string的末尾位置
void resize(size_t n)将string字符串长度扩大/ 缩小至n
void reserve(size_t n)将string容量扩大至n,如果n<_capacity 则保持不变
bool empty()判断字符串是否为空

4.string部分接口模拟实现

由于string类的接口太多,我们只实现一些比较常用的接口,毕竟我们不造轮子,但是我们可以通过部分接口的学习去了解整个string类

4.1结构定义

namespace feng//因为标准库里面已经有string了,为了防止命名污染
{             //我们自己定义一个命名空间,将我们自己实现的string放在里面,就不会和库里面的string冲突
	class string
	{
	public:

    private:
    	char* _str;//用来指向字符串
		size_t _size;//有效个数,不包含\0
		size_t _capacity;//有效空间,不包含\0
        static const size_t npos;//需要在类外初始化为-1
	};
	const size_t string:: npos = -1;
}

4.2默认成员函数

如果不写拷贝构造函数,编译器将进行浅拷贝,这样就会导致相同的空间进行两次析构(也就是两次delete),程序就会崩溃,所以需要进行深拷贝处理
在这里插入图片描述

        //用于现代写法——交换两个string对象的值
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

        //实现无参构造或者使用常量字符串构造
        //string s1() 或者 string s1("feng")
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		//传统写法
		//已经存在的string对象去构造另一个不存在的string对象
		//s2(s1)
		//string(const string& s)
		//	:_size(s._size)
		//	, _capacity(s._capacity)
		//{
		//	_str = new char[_capacity + 1];
		//	strcpy(_str, s._str);
		//}
		//现代写法
		string(const string& s)
			:_str(nullptr)//如果_str不置空,程序会崩溃
			,_size(0)     //因为tmp生命周期结束后会调用析构函数,取释放随机地址所指向的空间
			,_capacity(0)
		{                      //s2(s1)
			string tmp(s._str);//用s1中的_str(字符串)构造一个tmp(作为临时对象)
			swap(tmp);//交换tmp和s2中的每个成员变量
		}             //因为tmp是个局部对象,所以生命周期结束后会自动调用析构函数,又因为tmp中的_str是nullptr,所以不会做处理
		              //通过上述操作就很好的完成了s2的构造

        //传统写法
		//s2 = s1
		//string& operator=(const string& s)
		//{
		//	if (this != &s)//如果是自己给自己赋值,就不做处理
		//	{
		//		char* tmp = new char[s._capacity + 1];
		//		strcpy(tmp, s._str);
		//		delete[] _str;//delete应该放在new后面,防止new失败,导致原空间被释放
		//		_str = tmp;
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	return *this;
		//}
		//现代写法
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		string tmp(s);
		//		swap(s);//交换之后,tmp最后也会进行析构
		//	}
		//	return *this;
		//}
		//更绝的现代写法
		string& operator=(string s)//不需要判断是否是自己给自己赋值
		{
			swap(s);
			return *this;
		}
		
		//析构函数
		~string()
		{
			if (_str)//如果_str为nullptr,则不做处理
			{
				delete[] _str;
				_str = nullptr;
				_size = _capacity = 0;
			}
		}

4.3reserve和resize扩容函数

		void reserve(size_t n)
		{
			if (n > _capacity)//如果n<=_capacity就不做处理
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;//该语句放在new char[n+1]后面,是为了防止new失败,但原来的_str不会被delete
				_str = tmp;
				_capacity = n;//更新容量大小
			}
		}

		void resize(size_t n, char ch = '\0')
		{
			if (n <= _size)//因为是用_size来判断有效字符的个数,所以只需要在_size的位置添加'\0'即可
			{
				_str[_size] = '\0';
				_size = n;
			}
			else
			{
				if (n > _capacity)//表明容量不够,需要进行扩容
				{
					reserve(n);
				}
				memset(_str + _size, ch, n - _size);//将新增容的部分用字符ch初始化
				_size = n;
				_str[_size] = '\0';

			}
		}

4.4c_str、size()、find

        //返回对象中字符串的指针
		const char* c_str() const//_str不需要修改,所以加const是为了防止_str被修改
		{                        //返回值被const修饰,也是防止_str被修改
			return _str;
		}

        //返回有效字符的个数
		size_t size() const
		{
			return _size;
		}
		
		//查找字符,找到返回字符的所在位置的下标,找不到返回npos(-1)
		size_t find(char ch)
		{
			for (size_t i = 0; i < _size; i++)//字符串的遍历
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}

			return npos;
		}
		
		//查找子串,返回子串在主串中起始位置的下标
		size_t find(const char* str, size_t pos)
		{
			const char* p = strstr(_str + pos, str);
			if (p == nullptr)
			{
				return npos;
			}
			return (p - _str);
		}

4.5下标访问,[]的重载(operator[])

        //利用[]的重载可以对对象进行下标访问,好比数组的下标访问
        //非const对象调用,不仅可以查看,也可以修改,因为返回的是引用
		char& operator[](size_t pos)
		{
			assert(pos < _size);//不能越界访问
			//_str = nullptr;
			return _str[pos];
		}

        //const对象调用,只能查看,不能修改,因为返回的引用被const修饰
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

4.6随机插入或随机删除(insert、erase)

		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			size_t end = _size + 1;
			while (end > pos)//画图分析可知该过程可以顺便完成\0的挪动
			{
				_str[end] = _str[end - 1];//从前往后挪动数据
				end--;
			}
			_str[pos] = ch;//pos位置的数据已经被挪走,所以直接将ch放在pos的位置
			_size++;
			return *this;
		}

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (len + _size > _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);//为了防止空对象使用insert
			}

			size_t end = _size + len;
			//错误写法,挪动数据时,已经越界了,但编译器不会报错
			//while (end > pos)//画图分析可知该过程可以顺便完成\0的挪动
			//{
			//	_str[end] = _str[end - len];
			//	end--; 
			//}

			while (end >= pos + len)
			{
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;

		}

		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (pos == npos || pos + len >= _size)//如果位置为-1或者大于等于_size,就直接在有效数据的最后一个位置放\0即可
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

4.7追加字符或追加字符串函数(push_back、append、operator +=)

		void push_back(char ch)
		{
			//if (_size == _capacity)
			//{
			//	reserve(_capacity == 0 ? 4 : _capacity * 2);//为了解决空string
			//}
			//
			//	_str[_size++] = ch;
			//_str[_size] = '\0';
			insert(_size, ch);//上面等价于对insert的复用

		}

		void append(const char* str)
		{
			//size_t len = strlen(str);
			//if (_size + len > _capacity)//原本的字符串长度加上追加的字符串长度已经超过了现有的容量空间,此时需要扩容
			//{
			//	reserve(_size + len);
			//}
			//strcpy(_str + _size, str);//从原来的字符串(_str)末尾的\0开始拷贝,会导致\0被覆盖           
			                            //但是strcpy会将str的\0拷贝到_str中
			//_size += len;             
			insert(_size, str);//上面等价于对insert的复用
		}

        //s1 += ‘f’
		string& operator +=(char ch)
		{
			push_back(ch);//复用
			return *this;
		}
      
        //s1 += "feng"
		string& operator +=(const char* str)
		{
			append(str);//复用
			return *this;
		}

4.8比较运算符的重载

	bool operator<(const string& s1, const string& s2)
	{
		//size_t i1 = 0, i2 = 0;
		//while (i1 < s1.size() && i2 < s2.size())
		//{
		//	if (s1[i1] < s2[i2])
		//	{
		//		return true;
		//	}
		//	else if (s1[i1] > s2[i2])
		//	{
		//		return false;
		//	}
		//	else
		//	{
		//		i1++;
		//		i2++;
		//	}
		//}
		//return i2 < s2.size() ? true : false;
		//调用成员函数c_str()返回指针,再调用strcmp()比较
		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 strcmp(s1.c_str(), s2.c_str()) > 0;
		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);
	}

4.9流插入(<<)和流提取(>>)重载

	ostream& operator<<(ostream& out, string& s)
	{
	    //使用范围for遍历数据
		//for (auto ch : s)
		//{
		//	out << ch;
		//}
		for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i];
		}

		//不能这样写,如果整个字符串中有字符'\0'(不是结尾的\0),就会导致字符串打印不完整
		//out << s.c_str();

		return out;//返回ostream引用
	}

	istream& operator>>(istream& in, string& s)
	{
		char ch = in.get();
		while (ch != '\n')//除字符'\n'之外,其他的都为有效字符
		{
			s += ch;
			ch = in.get();
		}
		return in;//返回istream引用
	}

总结

  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string
  4. 不能操作多字节或者变长字符的序列

注意:使用string类时,必须包含#include头文件以及using namespace std;

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-11 21:59:56  更:2022-03-11 22:03:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/5 13:49:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码