【C++】STL之string功能及模拟实现

news2024/11/24 17:54:55

目录

前沿

一、标准库中的string类

二、string类的常用接口说明

 1、string类对象的常见构造

 2、string类对象的容量操作

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

 4、string类对象的修改操作

 5、string类非成员函数

 6、vs下string结构的说明

三、string类的模拟实现

 1、构造函数

 2、析构函数

 3、拷贝构造函数

 4、赋值运算符重载

 5、比较运算符重载

 6、push_back ,append,+=

 7、容量(resize,reserve)(重点)

 8、插入和删除(insert,erase)

 9、迭代器


前沿

STL (standard template libaray-标准模板库)是 C++ 标准库的重要组成部分,由六大部分构成:仿函数,空间配置器,算法,容器,迭代器和配接器,其中包含了各式各样的容器,方便我们以后编写程序,比如今天要总结的 string 容器。

一、标准库中的string类

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作 单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信 息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits 和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结:

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

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

二、string类的常用接口说明

 1、string类对象的常见构造

(constructor)函数名称功能说明
string()(重点)构造一个空字符串,长度为0个字符。
string (const char* s)(重点)通过C字符串构造string类对象
string (const string& str)(重点)拷贝构造
string (const string& str, size_t pos, size_t len = npos)复制str从pos位置开始往后npos个字符
string (const char* s, size_t n)复制C字符串前n个字符
string (size_t n, char c)string类对象包含n个字符c
template < class InputIterator > string (InputIterator first, InputIterator last)通过一个字符串区间构造对象
void Teststring()
{
	string s1;              // 构造空的string类对象s1
	string s2("hello bit"); // 用C格式字符串构造string类对象s2
	string s3(s2);          // 拷贝构造s3
}

 2、string类对象的容量操作

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty(重点)检测字符串释放为空串,是返回true,否则返回false
clear(重点)清空有效字符
reserve(重点)为字符串预留空间**(只改变capacity的值)
resize(重点)将有效字符的个数该成n个,多出的空间用字符c填充(改变size和capacity的值)

注意:

  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不会改变容量大小。
#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world");
	s1.reserve(10);
	s1.resize(6, '0');
	return 0;
}

当运行到 reserve 时:

我们发现当参数小于size时,改操作对参数几乎没有影响。

当运行resize时:

我们发现这里参数 size 发生了改变,但是没有改变容量的大小。

注意:当 reserve 参数大于 size 时就会开出大于参数的空间,具体是多少由编译器决定,resize 的第一个参数大于 size 时也会扩大空间,同时还将 size 扩大到 n,在原始数据上将后面数据初始化为ch.

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

函数名称功能说明
operator[]返回pos位置的字符,const string类对象调用
begin+ endbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rendbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
范围forC++11支持更简洁的范围for的新遍历方式

 (1)可以通过[]+下标的方式来获取该字符串某个位置的字符。

void Test()
{
	string s1("hello World!");
	cout << s1[0] << " " << s2[0] << endl;

	s1[0] = 'H';
	cout << s1 << endl;
}
int main()
{
	Test();
	return 0;
}

运行结果:

(2)还可以用迭代器的方式访问。

void Test()
{
	string s("hello world!");
	//begin代表字符串第一个位置
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;
	// string::reverse_iterator rit = s.rbegin();
	// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
	auto rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit;
		rit++;
	}
	cout << endl;
}
int main()
{
	Test();
	return 0;
}

运行结果:

(3) 还可以用范围 for 的方式访问。

void Test()
{
	string s("hello world!");
	for (auto ch : s)
		cout << ch;
	cout << endl;
}
int main()
{
	Test();
	return 0;
}

运行结果:

 4、string类对象的修改操作

函数名称功能说明
push_back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+=在字符串后追加字符串str
c_str返回C格式字符串
find + npos从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
insert在源字符串某一位置插入字符串str
erase在某一位置开始进行删除
swap字符串交换
find_first_of搜索字符串中与参数中指定的任何字符匹配的第一个字符
find_last_of在字符串中搜索与参数中指定的任何字符匹配的最后一个字符
find_first_not_of搜索字符串中与参数中指定的任何字符不匹配的第一个字符
find_last_not_of在字符串中搜索不匹配其参数中指定的任何字符的最后一个字符
void Test()
{
	string str;
	str.push_back(' ');   // 在str后插入空格
	str.append("hello");  // 在str后追加一个字符"hello"
	str += 'w';           // 在str后追加一个字符'w'   
	str += "orld";          // 在str后追加一个字符串"orld"
	cout << str << endl;
	cout << str.c_str() << endl;   // 以C语言的方式打印字符串

	string s("world");
	s.insert(0, "hello");
	cout << s << endl;


	s.insert(2, "abcdef", 3);//插入该字符串的三个字符在二号位置
	cout << s << endl;

	string s("abcd");
	s.insert(s.find('b'), "abcdef");//找到字符b的位置然后插入"abcdef"
	cout << s << endl;

	s.replace(s.find('f'), 1, "20%");//找到f的位置用20%代替
	cout << s << endl;

	string s("hello world");
	size_t pos = s.find_first_of("eo", 0);//从位置0处找到字符e或字符o第一次出现的位置
	while (pos != string::npos)//如果没有找到匹配项,函数返回string::npos。
	{
		s[pos] = '*';
		pos = s.find_first_of("eo", pos + 1);//将整个字符串中的字符e或字符o换成*
	}

	//将不是e或o的字符都替换成*
	string s("hello world");
	size_t pos = s.find_first_not_of("eo", 0);
	while (pos != string::npos)
	{
		s[pos] = '*';
		pos = s.find_first_not_of("eo", pos + 1);
	}
	cout << s << endl;

	string s("hello world");
	size_t pos = s.find_last_of("eo", s.size() - 1);//从后开始找
	while (pos != string::npos)
	{
		s[pos] = '*';
		pos = s.find_last_of("eo", pos - 1);
	}
	cout << s << endl;
}

注意:

  1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

 5、string类非成员函数

函数功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率低
operator>>输入运算符重载
operator<<输出运算符重载
getline(重要)获取一行字符串
relational operators大小比较

   这里面的 getline 函数是获取一行字符串的,和单纯的流插入是不一样的,因为字符串中很可能会包含空格的,而 cin 会认为这是结束的标志,进而不再读取字符,因此才有了这个 getline 函数。

具体用法如下:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s;
	getline(cin, s);
	cout << s << endl;
	return 0;
}

运行结果: 

 6、vs下string结构的说明

 注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

   关于string对象应该占多少字节这个问题,我们先大胆猜一下为12个字节(一个指向字符串的指针,一个是整形的 size,一个整形的 capacity),但明显不是这样的,string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string 中字符串的存储空间:
  (1)当字符串长度小于16时,使用内部固定的字符数组来存放;
  (2)当字符串长度大于等于16时,从堆上开辟空间。

union _Bxty
{ 
    // storage for small buffer or pointer to larger one
	value_type _Buf[_BUF_SIZE];
	pointer _Ptr;
    // to permit aliasing
	char _Alias[_BUF_SIZE]; 
}_Bx;

   因为大多数情况下字符串的长度都小于16,那 string 对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高;还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量;还有一个指针做一些其他事情。因此总共占16+4+4+4=28个字节

三、string类的模拟实现

 1、构造函数


class string
{
public:
	string(const char* str = "")
		:_size(strlen(str))
	{
		_capcity = _size == 0 ? 4 : _size;
		_str = new char[_capcity + 1];
		strcpy(_str, str);
	}
private:
	char* _str;
	size_t _size;
	size_t _capcity;
};

 2、析构函数

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

 3、 拷贝构造函数

//传统版写法
string(const string& s)
	:_size(s._size)
	, _capcity(s._capcity)
{
	_str = new char[_capcity + 1];
	strcmp(_str, s._str);
}

//现代版写法
String(const string& s)
	:_str(nullptr)
{
	string str(s._str);
	swap(_str, str);
}

注意:第二种现代版写法, 一定要先构造一份对象然后再交换,要不然直接交换的话就把传过来的对象变空了。

 4、 赋值运算符重载

//传统版写法
string& operator=(const string& s)
{
	_size = s._size;
	_capcity = s._capcity;
    // 防止new失败,我们先创建一个临时变量开辟空间
	// 拷贝完后,在给_str
	char* tmp = new char[_capcity + 1];
	strcpy(tmp, s._str);
	delete[] _str;
	_str = tmp;
	return *this;
}

//现代版写法
String& operator=(String s)
{
	swap(_str, s._str);
	return *this;
}

 5、比较运算符重载

bool operator==(const string& s)
{
	return strcmp(_str, s._str) == 0;
}

bool operator>(const string& s)
{
	return strcmp(_str, s._str) > 0;
}

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

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

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

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

比较运算符的重载可以先写一个大于和等于的函数,其他的比较可以直接用这两个进行复用。 

 6、push_back ,append,+=

//增加一个字符
void push_back(char c)
{
	if (_size == _capacity)
	{
		reserve(_capacity * 2);
	}
	_str[_size++] = c;
	_str[_size] = '\0';
}
//增加一个字符串
void append(const char* str)
{
	int len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
}

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

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

 7、容量(resize,reserve)(重点)

void resize(size_t n, char c = '\0')
{
	if (n > _size)
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		memset(_str + _size, c, n - _size);
	}
	_size = n;
	_str[n] = '\0';
}
void reserve(size_t n)
{
	if (n > _capcity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capcity = n;
	}
}

 8、插入和删除(insert,erase)

//插入字符
string& insert(size_t pos, char ch)
{
	assert(pos <= _size);
	//插入前检查是否需要扩容
	if (_size + 1 > _capcity)
		reserve(_size * 2);
	//挪动数据
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	_size++;
	return *this;
}
//插入字符串
string& insert(size_t pos, const char* s)
{
	assert(pos <= _size);
	size_t lens = strlen(s);
	if (_size + lens > _capcity)
		reserve(_size + lens);
	//挪动数据
	size_t end = _size + lens;
	while (end > pos + lens - 1)
	{
		_str[end] = _str[end - lens];
		--end;
	}
	strncpy(_str, s, lens);
	_size += lens;
	return *this;
}
string& erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	//挪动数据
	if (len == npos || _size - pos <= len)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		size_t begin = pos + len;
		while (begin <= _size)
		{
			_str[begin - len] = _str[begin];
			++begin;
		}
		_size -= len;
	}
	return *this;
}

 9、迭代器

typedef char* iterator;
typedef const char* const_iterator;

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


本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

老铁们,记着点赞加关注!!! 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL(备份还原索引视图入门)

文章目录 第一节 备份和还原1、题目2、题目作答 第二节 索引1.题目2.题目作答 第三节 视图1 题目2 题目作答 第一节 备份和还原 1、题目 CREATE DATABASE beifen;use beifen;CREATE TABLE books(bk_id INT NOT NULL PRIMARY KEY,bk_title VARCHAR(50) NOT NULL,copyright YEA…

django框架向DRF框架演变过程详解

一、Django框架实现项目查询接口 主要知识点&#xff1a; Django框架视图函数 1、在 Django 项目中创建一个应用&#xff08;如果还没有创建&#xff09;&#xff1a; python manage.py startapp projects 2、在项目的 models.py 文件中定义项目模型 from django.db impor…

视频融合平台EasyCVR登录后通道数据及菜单栏页面显示异常的排查与解决

EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等&#xff0c;能对外分发RTSP、RTMP、FLV、HLS、WebRTC等格式的视频流。 有用…

只需三步实现Gateway结合Sentinel实现无侵入网关限流,注意避坑!

前言&#xff1a;本文基于您已有基础的可运行的微服务系统&#xff0c;使用了Sping Cloud Alibaba&#xff0c;Gateway,Nacos等&#xff1b;目标实现网关流控类型的限流。 顾名思义限流用于在高并发场景下限制请求流量的进入&#xff0c;保护系统不被冲垮。阿里巴巴的开源senti…

数据库慢查询优化

数据库慢查询优化 1.分析慢查询原因 分析导致慢查询的原因是数据库性能优化的关键步骤之一。下面是一些常见的方法和工具&#xff0c;可以帮助你确定慢查询的原因&#xff1a; 慢查询日志&#xff1a; 开启慢查询日志&#xff0c;允许数据库记录执行时间超过阈值的查询语句。…

百度ERNIE 3.0——中文情感分析实战

目录 前言一、百度ERNIE 3.0二、使用ERNIE 3.0中文预训练模型进行句子级别的情感分析2-1、环境2-2、数据集加载2-3、加载预训练模型和分词器2-4、基于预训练模型的数据处理2-5、数据训练和评估2-6、模型验证2-7、情感分析结果的预测以及保存 三、自定义个人案例3-1、如何自定义…

数据库语句

文章目录 数据库语句SQL语言分类MySQL中6种常见的约束1.DDL1.1 创建新的数据库1.2 创建新的表1.3 删除指定的数据表 2.DML管理表2.1 插入数据2.2 修改&#xff08;更新数据&#xff09;2.3 在数据表中删除指定的数据 3.DQL查询数据记录4.DCL4.1 修改表名和表结构4.2 扩展表结构…

Node.js详解(四):连接MongoDB

文章目录 一、安装MongoDB访问驱动二、连接数据库三、添加数据四、添加多条数据五、修改数据六、查询数据1、查询单条记录2、查询多条记录 七、删除数据八、完整示例代码1、路由 Api 接口&#xff1a;2、运行结果&#xff1a; MongoDB 对许多平台都提供驱动可以访问数据库&…

前端vue入门(纯代码)31_route-link的repalce属性

如果夜里十二点我还回你消息&#xff0c;那么意味着什么&#xff0c;意味着我是真的很喜欢玩手机。 【29.Vue Router--router-link的replace属性】 <router-link>的replace属性 replace属性的作用是&#xff1a;控制路由跳转时操作浏览器历史记录的模式。【当我们从一个…

城市内涝监测设备-内涝监测终端

随着我国城市化发展迅速、全球极端天气现象频发带来的暴雨天气增多&#xff0c;汛期暴雨引发道路低洼处、立交桥底、隧道、涵洞等城市 内涝时有发生&#xff0c;甚至开启城市看海模式&#xff0c;对交通、电力、通讯等造成了严重的影响和破坏&#xff0c;严重时造成人民生命、财…

放弃使用Merge,开心拥抱Rebase!

1. 引言 大家好&#xff0c;我是比特桃。Git 作为现在最流行的版本管理工具&#xff0c;想必大家在开发过程中都会使用。由于 Git 中很多操作默认是采用 Merge 进行的&#xff0c;并且相对也不容易出错&#xff0c;所以很多人都会使用 Merge 来进行合并代码。但Rebase 作为 Gi…

官宣!菁英实习生计划启动,百度大模型团队诚邀你的加入

大模型风起&#xff0c;人才需求涌 在这个充满变革的时代&#xff0c;我们见证了AI的快速发展。从“阿尔法狗”击败世界围棋冠军&#xff0c;到生成式大模型以势不可挡的浪潮席卷全球&#xff0c;掀起人类社会一场眩晕式变革。新技术、新工具、新的生产力正在改变经济活动各环…

小红书运营推广

大家好&#xff0c;我是权知星球&#xff0c;今天给大家分享一下小红手运营推广的一些经验&#xff0c;希望能给大家运营小红书带来一些帮助。 这篇文章虽然是基于小红书的运营写的&#xff0c;但新媒体的东西都是相通的&#xff0c;相信这篇文章对运营其他媒体的同学也会有所…

抓包工具Fiddler:fiddler的介绍及安装

Fiddler简介 Fiddler是比较好用的web代理调试工具之一&#xff0c;它能记录并检查所有客户端与服务端的HTTP/HTTPS请求&#xff0c;能够设置断点&#xff0c;篡改及伪造Request/Response的数据&#xff0c;修改hosts&#xff0c;限制网速&#xff0c;http请求性能统计&#xff…

MyBatis源码分析_Executor组件及3个火枪手(6)

目录 1. 前提 2. Executor执行器 3. 总结 4. 三个火枪手 5. StatementHandler生成Statement 6. ParameterHandler 参数解析 7. BoundSql的数据结构 8. 总结 1. 前提 在Mybatis源码分析_事务管理器 &#xff08;5&#xff09;_chen_yao_kerr的博客-CSDN博客一文中&…

网关微服务简单配置

导入一下网关的基本依赖 <dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>com.alibaba.cloud<…

直播商城系统源码的威力:开启直播购物新时代

随着科技的不断进步和人们对互动性购物体验的追求&#xff0c;直播购物正成为电商行业的热门趋势。直播商城系统源码的威力在这一潮流中愈发显现&#xff0c;为商家和消费者提供了无限的机会和便利。 下面是一个简单的示例&#xff0c;展示了如何利用直播商城系统源码创建一个…

深度强化学习:深度解析 MADDPG

深度强化学习:深度解析 MADDPG 学习强化学习,码代码的能力必须要出众,要快速入门强化学习 搞清楚其中真正的原理,读源码是一个最简单的最直接的方式。最近创建了一系列该类型文章,希望对大家有多帮助。 另外,我会将所有的文章及所做的一些简单项目,放在 1.MADDPG 原理…

JS脚本 - 批量给所有指定标签追加Class属性

JS脚本 - 批量给所有指定标签追加Class属性 前言一. 脚本二. 测试运行 前言 公司里我们有个应用引入了UBT埋点&#xff0c;记录了页面上所有的点击操作以及对应的点击按钮。但是我们看下来发现&#xff0c;我们需要给每个按钮加一个唯一标识做区分&#xff0c;并且这个ID是给U…

选读SQL经典实例笔记07_日期处理(下)

1. 一个季度的开始日期和结束日期 1.1. 以yyyyq格式&#xff08;前面4位是年份&#xff0c;最后1位是季度序号&#xff09;给出了年份和季度序号 1.2. DB2 1.2.1. sql select (q_end-2 month) q_start,(q_end1 month)-1 day q_endfrom (select date(substr(cast(yrq as c…