【C++】详解string类

目录

简介

框架

构造

全缺省构造函数

​编辑

传对象构造函数

拷贝构造

析构函数

容量

size()

capacity()

empty()

clear()

reserve()

​编辑

resize()

遍历

检引用符号"[ ]"的重载

迭代器

begin()

end()

rbegin()

rend()

修改

push_back()

"+="运算符重载

c_str

find()

其他

赋值运算符重载

现在式写法

比较运算符重载

"<"运算符重载

"="运算符重载

复用


简介

因为世界上有很多种语言,所以在STL库里有一个管理字符的模板类:basic_string模板类,而本篇要讲的string类是basic_string模板类的一个实例化,类型为char

string类的接口设计是比较冗余的,是因为string类有一些历史包袱。在STL出现之前,就已经有string类了。在STL被惠普实验室开源之后,string类既要符合STL的标准又要向前兼容,所以string类的接口比较冗余。

string类的底层是顺序表,它在堆上开辟连续的空间,并将代码段中的常量字符串进行拷贝。通过顺序表实现对字符串的增删查改。可参考C++的动态内存管理一文:http://t.csdnimg.cn/qHEzj

小编会介绍一些常见的接口,解释它们的功能,参数。然后模拟实现一下。为了方便,小编会调用C语言库中的一些字符串操作函数。头文件string.h

模拟实现不是造一个更好的轮子,而是为了帮助我们更好的理解string类。模拟实现的代码是禁不住在各种场景下测试的,一定会出现各种bug。小编会指出一些能想的到的bug。庖丁解牛,恢恢乎游刃有余

C++是一门面向对象的语言,大家不必对每一个接口都有细致的了解。小编写本篇的目的是为了让大家能对string类的主体框架有一定的了解。让大家在查文档时游刃有余。


框架

为了让类名和接口的写法和库里的保持一致,需要封一层命名空间,名字为MyString

数据设计:

顺序表的有效数据:size_t _size

顺序表的空间大小:size_t _capacity

指向顺序表的指针:char* _str

static const size_t npos = -1; 

nposstring类中的一个静态成员变量,它表示一个无效的位置或者未找到的位置。在string类中,当查找某个子字符串或字符时,如果未找到,则返回npos

namespace MyString //命名空间
{

class string  //string类
{

public:


//接口(方法)


private:

	size_t _size; //数据有效个数
	size_t _capacity;  //空间大小
	char* _str;  //指向的空间 
	static const size_t npos = -1; 

};

}

构造

可参考详解构造函数一文:http://t.csdnimg.cn/8Hl5K

功能:构造一个string类对象,并将其初始化

在C++11的标准中,有非常多的构造函数,如下图

模拟实现:

小编先问大家一个问题,当我们用"x\0xxx"字符串构造对象时,有效数据_size应该给成多少呢,1还是5?

可以参考一下标准库里的实现

标准库的逻辑是:在初始化对象时,以第一个"\0'为准。我们就可以调用库中的strlen(),帮我们计算一下要初始化_size的大小。strlen()计算长度时也是以第一个"\0"为准。

后面会有"x\0xxx",但_size是5的情况。因为标准库中的string类的有效数据是以_size为准,而不是以"\0"为准。这是C++的string类和C语言中字符串函数不一样的地方。

全缺省构造函数

代码如下

//全缺省构造
string(const char* str = "")  
	:_size(strlen(str))                  
	,_capacity(_size + 1)          
	,_str(new char[_capacity])  
{
	strcpy(_str, str);
}

传对象构造函数

代码如下

	
           string(const string& str)
			:_size(str._size)
			, _capacity(str._capacity)    
			, _str(new char[str._capacity])                       
		{
			memcpy(_str, str._str, str._capacity); //这里用strcpy()拷贝会有很坑的bug
		}

为什么小编会说用strcpy()拷贝数据会有一个很坑的bug呢?

因为string()是以"\0"为基准作为结束条件的。而string类的顺序表可能会存放"x\0xxx"这样的字符串,数据可能会拷贝不全。

拷贝构造

可参考详解拷贝构造一文:http://t.csdnimg.cn/oJCAU

代码如下

	//拷贝构造
	string(const string& str) 
	{
		_size = str._size;  //有效数据个数
		_capacity = str._capacity;  //空间
		 _str = new char[str._capacity]; //开空间,深拷贝    
		memcpy(_str, str._str, str._capacity);  //这里建议用memcpy()防止中间有"\0"的问题

	}

现在式写法,代码如下

void swap(string& s) //交换数据函数
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
string(const string& s) 
	: _str(nullptr) //初始化数据
	,_size(0)
	,_capacity(0)
{
	string Tmp(s._str);  //调用构造函数构造一个与s对象一样的临时对象

	swap(Tmp);     //调用交换函数交换两个对象
}

传统写法:需要自己开空间,自己拷贝数据

现在写法:把事情交给别的函数或方法去做,具有面向对象思维

析构函数

//析构函数
~string()
{
	delete[] _str;
	_size = _capacity = 0;
}

容量

size()

功能:

返回字符串有效长度

模拟实现:

size_t size() const
{
	return _size;    
}

capacity()

功能:

返回string类的顺序表的空间

模拟实现:

size_t capacity() const
{
	return _capacity;
}

empty()

功能:

判断字符串是否为空

模拟实现:

bool empty() const
{
	if (_size == 0)
		return true;
	else
		return false;
}

clear()

功能:

清空有效字符

模拟实现:

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

_str[0] = '\0';这句代码是为了兼容C语言的一些接口

reserve()

功能:

将容量调整至n
此函数不会影响到字符串
n大于_capacity,编译器会将容量调整n(可能更大,不同版本的STL具体实现不同)
n小于或等于_capacity,编译器可能会缩容,也可能不会,不具有强制性

参数:

无符号整型n,代表要扩容的大小

模拟实现:

void reserve(size_t n = 0)
{
	if (n <= _capacity)
		return;

	char* ptr = new char[n + 1]; //开辟新空间, 多开一个是为了存放‘\0’                       
	memcpy(ptr, _str, _size + 1); //把旧空间的值拷贝给新空间  
	delete[] _str; //释放旧空间   
	_str = ptr; //_str指向新空间          
	_capacity = n + 1;                                         
	ptr = nullptr;      
}

resize()

功能:

将字符串长度调整为n个字符的长度
n < _size 时,删除多余字符(删掉_size - n个字符)
n > _size 时,会先扩容,如果指定了c,会将其初始化为字符c,没有指定,会将其初始化为"\0"

参数:

无符号整型n,代表要调整字符的长度
字符型c,代表要初始化的字符

模拟实现:库中给了我们两个接口,但我们只需要实现一个全缺省即可

思路也很简单,只需要处理n的不同情况即可。代码如下,配有详细的注释

void resize(size_t n, char c = '\0')//c为全缺省参数,不指定c时会将新开辟的空间初始化为"\0"
{
	if ( n < 0) //n小于为非法值,直接返回
		return;

	if (n > _size) //n大于有效字符时,会扩容
	{  
		reserve(n); //扩容,复用reserve,后面凡是扩容都会复用reserve

		for (size_t i = _size; i < _capacity - 1; i++) //对开辟的空间初始化
		{
			_str[i] = c;
		}

		_str[_capacity - 1] = '\0';  //给最后一块空间赋值为\0

		_size = n; //不要忘了调整有效数据

		return;
	}

	_str[n] = '\0'; //当n小于或等于0时删数据
	_size = n;
}

遍历

检引用符号"[ ]"的重载

功能:

检索第pos位置的下标

参数:

无符号整型pos,代表下标

返回值:

char类型,返回string类中顺序表的的第pos位置的字符

模拟实现:库中给了两个,一个非成员函数,一个成员函数。代码如下

char& operator[] (size_t pos)
{
	assert(pos >= 0);
	assert(pos <= _size);  //检查下标的合法性

	return _str[pos];  //返回顺序表中该下标的值

}
const char& operator[] (size_t pos) const
{
	assert(pos >= 0); 
	assert(pos <= _size); 

	return _str[pos]; 
}

迭代器

需要把返回值的类型取别名为迭代器的通用类型,代码如下

typedef char* iterator; //正向迭代器,可修改

typedef char* reverse_iterator; //反向迭代器,可修改

typedef const char* const_iterator; //正向迭代器,不可修改

typedef const char* const_reverse_iterator; //反向迭代器,不可修改

因为string类型的底层是基于顺序表实现的,所以string类型的迭代器是指针

begin()

获取有效字符的第一个位置
iterator begin() //获取第一个字符的位置
{
	return _str;
}
const_iterator begin() const
{
	return _str;
	}

end()

获取最后一个有效字符的下一个位置
iterator end() //最后一个字符的下一个位置
{
	return _str + _size; 
}
const_iterator end() const
{
	return _str + _size;

	}

rbegin()

获取最后一个有效字符的位置
reverse_iterator rbegin() //获取最后一个字符的位置
{
	return _str + _size - 1;

}
const_reverse_iterator rbegin() const
{
	return _str + _size - 1;

	}

rend()

获取第一个有效字符的前一个位置
reverse_iterator rend()//获取第一个字符位置的前一个位置
{
	return _str - 1;
}
const_reverse_iterator rend() const
{
	return _str - 1;

}

修改

push_back()

功能:

将字符c尾插至string类的字符串后面

参数:

字符型c,代表要尾插的字符

模拟实现:

void push_back(char c)
{
	if ((_size + 1) == _capacity || _size == _capacity) //判断是否要扩容
	{
		reserve(2 * _capacity);
	}

	_str[_size] = c;  //尾插
	_str[_size + 1] = '\0'; //插入\0
	++_size;  //更改有效数组的值

}

"+="运算符重载

功能:

尾插一个类的字符串
尾插一个字符串
尾插一个字符

返回值:

string类的引用

这三类运算符重载互相构成函数重载

实现代码如下

string& operator+= (const string& str)         
{
	size_t len = str._size + 1;  
	if ((_size + len) >= _capacity)  //扩容
	{
		reserve(2 * (_capacity + _size + len));
	}

	strcpy(_str + _size, str._str);   //拷贝数据

	return *this; 
	}

上述代码中拷贝数据用了strcpy(),在一般场景下是没问题的。但禁不住测试,大家能调试出来吗?这个bug前文已经提过很多次了。

string& operator+= (const char* s)
{
	while (*s)
	{
		push_back(*s);
		s++;

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

	return *this;
	}

小编这里复用了push_back(),小编懒得再造轮子了(害羞)

c_str

功能:

返回C形式的字符串

模拟实现:

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

find()

功能:

查找字符串序列在string类的顺序表中第一次出现的位置

参数:

string类类型的引用,表示要查找的字符串序列为string类型的顺序表
无符号整型pos,表示从哪一个下标开始找
字符型c,表示要查找的字符
字符型指针s,表示要查找的字符串

返回值:

找到:返回该位置的下标
没找到:返回npos

模拟实现:小编只实现其中的一种

size_t find(const string& str, size_t pos = 0) const
{
	if (pos < 0 || pos > _size) //检查坐标合法性
		return npos;

char* ptr = strstr(_str + pos, str._str); //strstr()是一种暴力匹配,
if (ptr == NULL)
{
	return npos;
}
else
{
	return ptr - _str;  
}
}

我们不是为了造更好的轮子,用strtstr()即可。


其他

赋值运算符重载

现在式写法

string& operator=(string tmp)
{
	swap(tmp);

		return *this;
}

比较运算符重载

只需实现"<"和"="即可,其他可以复用

"<"运算符重载

bool operator<(const string& s) 
{
	size_t i = 0;
	size_t j = 0;

	while (i < _size && j < s._size)//按长度小的字符串比较
	{
		if (_str[i] < s._str[j])
		{
			return true;
		}
		else if (_str[i] > s._str[j])
		{
			return false;
		}
		else
		{
			++i;
			++j;
		}
	}

	return _size < s._size;//如果相等长度下相等,长度小的字符串就小
}

"="运算符重载

bool operator==(const string& s)
{
	size_t i = 0;
	size_t j = 0;

	while (i < _size && j < s._size)
	{
		if (_str[i] < s._str[j] || _str[i] > s._str[j])
		{
			return false;
		}
		else
		{
			++i;
			++j;
		}
	}

	return _size == s._size ? true : false;
}

复用

	bool operator<=(const string& s)
	{
		return *this <= s;      
	}

	bool operator>(const string& s)
	{
		return !(*this <= s);  
	}
	
	bool operator>=(const string& s) 
	{
		return *this >= s; 
	}
	
	bool operator!=(const string& s)
	{
		return !(*this == s); 
	}

本文到这里就结束啦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/587466.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Data JPA数据批量插入、批量更新真的用对了吗

Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 前言 在前两篇文章已经介绍过&#xff0c;在使用Spring Data JPA时&#xff0c;DAO层的Respository通过继承J…

链表面试题1.

1&#xff0c;反转一个单链表 采用头插法即可 class Solution {public ListNode reverseList(ListNode head) {if(head null){return head;}ListNode cur head.next;head.next null;while(cur ! null){ListNode curN cur.next;cur.next head;head cur ;cur curN;}return …

websocket 单点通信,广播通信

Websocket协议是对http的改进&#xff0c;可以实现client 与 server之间的双向通信&#xff1b; websocket连接一旦建立就始终保持&#xff0c;直到client或server 中断连接&#xff0c;弥补了http无法保持长连接的不足&#xff0c;方便了客户端应用与服务器之间实时通信。 参…

ElasticSearch自动补全

一、拼音分词器&#xff1a; 当用户在搜索框输入字符时&#xff0c;我们应该提示出与该字符有关的搜索项&#xff0c;如图&#xff1a; 这种根据用户输入的字母&#xff0c;提示完整词条的功能&#xff0c;就是自动补全了。 GET /_analyze {"text":"我爱螺蛳粉…

C++类和对象上篇

&#x1f407; &#x1f525;博客主页&#xff1a; 云曦 &#x1f4cb;系列专栏&#xff1a;[C] &#x1f4a8;路漫漫其修远兮 吾将而求索 &#x1f49b; 感谢大家&#x1f44d;点赞 &#x1f60b;关注&#x1f4dd;评论 文章目录 &#x1f4d9;前言&#x1f4d9;1、面向过程…

高扬程消防水泵在火灾中的关键作用/恒峰智慧科技

在火灾这一无情的灾难面前&#xff0c;每一秒都至关重要。而在这一分一秒的较量中&#xff0c;高扬程消防水泵无疑扮演着举足轻重的角色。它不仅是灭火战斗的得力助手&#xff0c;更是保障人民生命财产安全的守护神。 高扬程消防水泵&#xff0c;顾名思义&#xff0c;其扬程远超…

VOS系统录音文件REC转MP3,REC录音转WAV与WAV转MP3具体项目中实现方式,以及占用空间变化!

前面讲解了VOS3000系统的录音REC转MP3的安装步骤&#xff0c;不清楚的&#xff0c;可以移步这里查看&#xff1a;http://t.csdnimg.cn/OXNT9 或者 点击这里查看安装步骤&#xff0c;下面继续讲下他们的使用方法&#xff0c;以及实际项目中的运用&#xff0c;首先我们需要明确我…

vue本地打包预览

vue官网介绍 serve官网 1、项目打包 npm run build2、安装serve npm install -g serve3、在项目的 dist 文件运行命令行 serve5、运行结果如下 6、在浏览器打开 在浏览器打开 http://localhost:3000 或者 http://192.168.0.198:3000 就可以看到打包的东西了

【高质量】2024五一数学建模C题保奖思路+代码(后续会更新)

你的点赞收藏是我后续更新的最大动力&#xff01; 一定要点击文末的卡片&#xff0c;那是获取资料的入口&#xff01; 你是否在寻找数学建模比赛的突破点&#xff1f; 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024 年五一数学建模&#xff08;C题&#xff09;…

展会资讯 | 现场精彩回顾 阿尔泰科技参展2024第23届中国国际(西部)光电产业!

2024第23届中国国际&#xff08;西部&#xff09;光电产业博览会&#xff0c;在成都世纪城新国际会展中心圆满落幕&#xff01;来自各地的光电领域设备及材料厂商汇聚一堂&#xff0c;展示前沿技术及创新成果。 展会现场&#xff0c;来自全国各地的500余家企业就精密光学、信息…

电商技术揭秘文章合集(3)

相关系列文章 电商技术揭秘相关系列文章合集&#xff08;1&#xff09; 电商技术揭秘相关系列文章合集&#xff08;2&#xff09; 电商技术揭秘文章合集&#xff08;3&#xff09; 电商技术揭秘二十八&#xff1a;安全与合规性保障 电商技术揭秘二十九&#xff1a;电商法律…

esp32s3使用psram后音频播报不了的问题解决记录

idf.py menuconfig开启psram后会报错 提示需要打补丁&#xff1a; 根据提示切换到IDF_PATH目录&#xff0c;然后执行git apply %ADF_PATH%/ida_patches/idf5.0_freertos.patch打补丁。 再次编译提示如下错误&#xff1a; assert failed: spi_flash_disable_interrupts_cach…

电脑装了两个Win10系统,怎么修改其名称方便识别?

前言 有小伙伴在上一期的双系统教程上留言说怎么修改双系统引导时候显示的名称 不然看起来两个系统好像都没啥分别&#xff0c;如果是Windows10Windows11的方案还好说&#xff0c;但如果是两个Windows10或者是两个Windows11&#xff0c;有时候还真的很不好分辨。 万一想要启动…

OpenHarmony语言基础类库【@ohos.util (util工具函数)】

ohos.util (util工具函数) 该模块主要提供常用的工具函数&#xff0c;实现字符串编解码&#xff08;[TextEncoder])&#xff0c;[TextDecoder]&#xff09;、有理数运算&#xff08;[RationalNumber8]&#xff09;、缓冲区管理&#xff08;[LRUCache9]&#xff09;、范围判断&…

hdc不是内部或外部命令,也不是可运行的程序或批处理文件。【鸿蒙报错已解决】

文章目录 项目场景:问题描述原因分析:解决方案:此Bug解决方案总结Bug解决方案寄语项目场景: 最近遇到了这个问题,看到网上也有人在询问这个问题,实操了很多网上的解决方案发现并不能解决这个Bug,所以我在解决这个问题后,总结了自己和其他人的解决经验,进行了整理,写…

全网最强JavaWeb笔记 | 万字长文爆肝JavaWeb开发——day08数据库Mybatis入门

万字长文爆肝黑马程序员2023最新版JavaWeb教程。这套教程打破常规&#xff0c;不再局限于过时的老套JavaWeb技术&#xff0c;而是与时俱进&#xff0c;运用的都是企业中流行的前沿技术。笔者认真跟着这个教程&#xff0c;再一次认真学习一遍JavaWeb教程&#xff0c;温故而知新&…

latex+vscode一直报错,配置文件json和环境变脸配置

1、json配置文件 {"latex-workshop.latex.tools": [{"name": "xelatex","command": "xelatex","args": ["-synctex1","-interactionnonstopmode","-file-line-error","%DOCF…

日期类的实现,const成员

目录 一&#xff1a;日期类实现 二&#xff1a;const成员 三&#xff1a;取地址及const取地址操作符重载 一&#xff1a;日期类实现 //头文件#include <iostream> using namespace std;class Date {friend ostream& operator<<(ostream& out, const Dat…

mysql定时执行语句

一、前提 #确保事件调度为开放(ON) SHOW VARIABLES LIKE event_scheduler;二、场景 1、创建test01 表&#xff0c;表中存储1000条数据&#xff1b; 2、创建空表test02&#xff0c;表结构与 test01相同&#xff1b; 3、将test01中的数据以每分钟10条的形式转移到test02中去三、…

基于Spring Boot的校园博客系统设计与实现

基于Spring Boot的校园博客系统设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统功能界面图&#xff0c;在系统首页可以查看首页、文…