Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >string类的模拟实现

string类的模拟实现

作者头像
用户11316056
发布于 2024-10-16 01:24:56
发布于 2024-10-16 01:24:56
21800
举报
运行总次数:0

一、构造函数(有难度)

我们首先看看几个典型的错误。

错误一:

这里使用初始化列表进行构造函数的初始化,str本身是const类型,而初始化列表又将str赋值给了_str,所以此时就无法对str进行接下来string类的增删查改操作(只有查可以)。

错误二:

这里错误原因是初始化顺序跟声明顺序有关,先声明_str,所以第一步先初始化_str,而我们为了不用每次都调用strlen函数,而是调用了_size,而我们先调用了_str,此时的_size还没有初始化,所以会报错!这是一个大坑!!!改个顺序就能报错的大坑!

综上我们可以看出string的构造不适合用初始化列表,因此我们改用普通构造函数,大不了我们定义的时候不初始化,其实对于string这个类是没有问题的。

一般内置类型我们使用初始化列表和构造函数没有多少差别。

如何对空的string进行初始化?

典型错误:

这里在打印空字符串时,会报错,原因不是析构函数中delete/free对空指针的解引用,因为delete或者free函数内部会有对空指针的特殊检查,如果是空指针,delete和free不做处理。

原因是cout函数会自动识别s2.c_str,,因为c_str是一个char*类型,cout就会自动识别他是字符串类型,所以此时会造成经典的cout对空指针的解引用!

正确方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        string(const char* str = "\0")//构造函数 全缺省
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_size + 1];
			//strcpy(_str, str);
		}

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

对于空的string要多开辟一个空间,用来存储' /0 '。

我们没有必要写两个构造函数,我们使用缺省参数来接受,注意这里是"\0",而不是’\0‘,也可以直接"",空字符串默认含有斜杠0.第二种参数是char类型,与char*类型的参数都不匹配。这里用"",也是可以的,因为常量字符串默认含有\0。

二、自定义实现 [ ] 操作符:

注意这里必须要使用引用返回!!!

引用有两大作用:

1、减少拷贝

2、修改返回值

这里我们想要 [] 来修改字符串的值,返回值不用引用,就无法实现修改,因此引用在某些特定条件下不可替代!!!  

这里有两种 [ ] 实现方式,第一种就是普通版本(可读可写),第二种就是const版本(只准读)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	char& operator[](size_t pos)
	{
		assert(pos < _size);
		return _str[pos];
	}

	const char& operator[](size_t pos) const
	{
		assert(pos < _size);
		return _str[pos];
	}

注意:const修饰迭代器,迭代器本身可以修改,但是指向的内容不能修改

三、string+=运算符的重载

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	void reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];//多开辟一个留给'\0'
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

	void push_back(char ch)
	{
		if (_size == _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(newcapacity);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}

	void append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len);
		}
		strcpy(_str + _size, str);
		_size += len;
	}

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

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

四、insert函数实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			int end = _size;
			while (end >= (int)pos)//注意隐式类型转换,容易造成死循环!
			{
				_str[end + 1] = _str[end];
				--end;
			}
			_str[pos] = ch;
			_size++;
		}

注意while循环的判断条件,如果不强制类型转换,容易造成隐式类型转换,因为在C语言比较大小有一个规则小的会向大的转换,end是int类型为-1会转换成无符号整形的pos,因此需要强制类型转换!

五、erase函数实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || pos + len >= _size)//条件必须要有len == npos
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}

分为两种情况,

第一种就是需要erase的长度大于等于本身字符串的长度,那么直接在pos位置放置' \0 ' 即可完成。

假如我们if判断条件只写pos+len >= _size,是会报错的!因为假设len == npos,注意这里的npos( size_t npos = -1)表示无符号整型的最大值,再加上len会造成数据的溢出!

第二种就是erase的长度小于字符串本身的长度,这时候直接用strcpy,注意这里strcpy也将字符串最后一个位置的\0拷贝过来!

六、拷贝构造函数和赋值重载函数的 传统写法和现代写法的区别

举个生动形象的例子:

假如我们要搬砖去挣钱买显卡爪刀,我们有两种方式:

第一种传统方法:自己去搬砖,用自己搬砖挣的钱去买。

第二种现代方法:我们可以推荐老乡去搬砖挣钱,我们自己不用搬砖,我们在其中抽取老乡搬砖的钱。

上面是帮助我们理解,但到了真正代码这块,我们就发现传统写法的拷贝构造函数和复制重载函数都是需要我们自己去创建一块新的空间,然后赋值。

但是到了现代写法,我们用构造函数创建一个新的对象(老乡),然后将内容交给老乡去做。我们最后自己使用swap函数将新创建的对象和自身交换即可!

但是这个写法有一个问题,就是tmp是临时变量,出函数作用域,会调用析构函数,假如s2不是空,是随机值,接着与tmp交换,然后析构tmp,就会导致内存泄漏,因此我们在使用这个方法的时候,要将构造函数有缺省值为空!

赋值重载函数:

原理与上一个类似。都是不用自己干活,交给别人干。

注意这里参数不能引用传参!!!

我们不用引用传参的目的就是去调用拷贝构造函数,然后让拷贝构造产生的s和我们的*this进行交换!

下面是原码实例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	//传统写法
	//string::string(const string& s)
	//{
	//	_str = new char[s._capacity];
	//	_size = s._size;
	//	_capacity = s._capacity;
	//	strcpy(_str, s._str);
	//}

	//现代写法
	string::string(const string& s)
	{
		string tmp(s._str);
		swap(tmp);
	}


	//传统写法
	//string& string::operator=(const string& s)
	//{
	//	_str = new char[s._capacity];
	//	_size = s._size;
	//	_capacity = s._capacity;
	//	strcpy(_str, s._str);
	//	return *this;
	//}

	//现代写法	
	string& string::operator=(string s)
	{
		swap(s);
		return *this;
	}

七、流插入和流提取操作符的重载

我们默认都是将这两个函数重载在类的外部,所以不是类的成员函数,因为使用上的方便。

问题:流插入和流提取的重载必须要用友元函数吗 答案是不一定,因为是否用到友元,看我们是否调用到类的私有成员,如果没有,那就不用友元函数!

注意流提取不能这么书写:

因为in >> ch 这段代码不能识别出 ’ ‘ 和 ’\0‘,cin和scanf都是默认将他们当作分隔符!!!

原码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char tmp = in.get();
		while (tmp != ' ' && tmp != '\n')//注意换行是'\n'
		{
			s += tmp;
			tmp = in.get();
		}
		return in;
	}

	ostream& operator<<(ostream& out, string& s)
	{
		for (auto i : s)
		{
			out << i;
		}
		return out;
	}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-07-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++】C++STL 揭秘:Strng背后的底层逻辑
在模拟实现string过程中,为了避免跟库中string发生冲突,需要创建个命名空间,在命名空间中实现string。
是店小二呀
2024/08/17
2030
【C++】C++STL 揭秘:Strng背后的底层逻辑
【C++进阶】深入STL之string:模拟实现走进C++字符串的世界
前言:在C++中,string是一个极其重要且常用的类,它为我们提供了丰富的字符串操作功能。然而,了解其背后的实现原理,不仅可以帮助我们更好地使用它,还能让我们对C++的内存管理、模板编程等有更深入的理解。本文将带你走进C++字符串的世界,通过模拟实现一个简单的string类,来探索其内部机制
Eternity._
2024/06/14
2610
【C++进阶】深入STL之string:模拟实现走进C++字符串的世界
string类(下)(模拟实现string类,深度剖析其底层)
断言(Assertion)是编程中一种常用的调试辅助手段,用于在代码执行期间验证某个条件是否为真。如果条件为真(即满足预期),则程序继续执行;如果条件为假(即不满足预期),则断言失败,通常会导致程序抛出一个错误、输出一条错误信息,甚至直接终止程序。断言主要用于开发和测试阶段,以确保代码的正确性和健壮性。
suye
2024/10/16
2710
【C++】string类的模拟实现
​ 该函数的作用:在 pos 位置上插入 字符c 或者 字符串str ,并返回该字符的位置!
利刃大大
2025/02/07
1710
【C++】string类模拟实现:探索其内部机制
通过对string类的学习,我们知道string类的模拟实现最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数,以下是对模拟实现string类变量以及函数的封装:
大耳朵土土垚
2024/05/24
2100
【C++】string类模拟实现:探索其内部机制
C++-手把手教你模拟实现string
模拟实现string只需要三个成员变量,capacity,size,_str,也就是容量,数据大小,指向字符串的指针。
用户10923087
2024/02/19
2110
C++-手把手教你模拟实现string
C++【string类,模拟实现string类】
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
逆向-落叶
2024/11/10
3060
C++【string类,模拟实现string类】
手搓string类
这里的_capacity是给有效字符预留的空间,为了给’\0’留位置在开空间的时候要多开一个。
始终学不会
2023/03/28
4060
手搓string类
【C++指南】string(三):basic_string底层原理与模拟实现详解
传统写法需要手动分配内存并拷贝数据,而现代C++写法通过“构造临时对象 + 交换资源”简化代码:
倔强的石头_
2025/03/23
3390
【C++指南】string(三):basic_string底层原理与模拟实现详解
初识C++ · 模拟实现string
继上文介绍了string的函数实现之后,本文介绍模拟实现string的大部分函数。
_lazy
2024/10/16
1410
初识C++ · 模拟实现string
C++之模拟实现string
因为学习了string的相关知识,了解了string大部分接口的底层实现原理,所以我决定自己模拟实现一个mini版的string类,用来加深对string各方面知识的理解。 如果有错误或不足之处,还望各位读者小伙伴们指出。
摘星
2023/04/28
3290
C++:String的模拟实现
模拟实现的节奏比较快,大家可以先去看看博主的关于string的使用,然后再来看这里的模拟实现过程
小陈在拼命
2024/03/08
1700
C++:String的模拟实现
模拟实现C++中的string类(详细解析)
学习C++,特别是C++中的STL部分,重点不是学习如何去使用STL,而是知道其底层原理是怎么样的,是怎么去实现的。因此,本篇文章带来的是对C++中的string的模拟实现。废话不多说,让我们去了解string是如何实现的吧!
二肥是只大懒蓝猫
2023/03/30
1.1K0
模拟实现C++中的string类(详细解析)
【c++】string类模拟实现
我们stl库中的string类实在std命名空间的,这里我们自定义一个命名空间own,包含string类和简单的成员变量:
用户11029103
2024/04/20
1680
【c++】string类模拟实现
【C++】—掌握STL string类:string的模拟实现
浅拷贝也称之为位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就导致多个对象公用同一份资源,当一个对象销毁时就会导致该资源释放掉,而此时的其他对象不知道该资源已经被释放掉,所以继续对资源进行操作时,就会导致访问违规。 可以使用深拷贝解决浅拷贝的问题即:每个对象都有一份独立的资源,不需要和其他对象共享。
_孙同学
2024/11/13
2030
【C++】模拟实现string类
https://blog.csdn.net/weixin_72357342/article/details/136852268?spm=1001.2014.3001.5502 而在本次项目中我们的目标是模拟实现一个string类: 该string包含四个成员变量,分别是:
修修修也
2024/05/30
3910
【C++】模拟实现string类
string的模拟全实现
前面我们学习了string的用法,本节我们将实现string的模拟实现,话不多说,直接上手,因此我们先了解我们是多文件进行编写,因此需要注意命名空间的控制,这是文件分布图:OK,我们开始~
学习起来吧
2024/05/16
2020
string的模拟全实现
【C++修炼之路】9. string类的模拟实现
本篇文章是衔接上一篇string,进行string的模拟实现,其中包含了众多重载函数,以及一些实现的细节,由于上篇已经知道具体函数的含义,这一篇就以纯代码的方式进行叙述。此外,这篇还对内置类型的知识进行了进一步的扩展。
每天都要进步呀
2023/03/28
3200
【C++修炼之路】9. string类的模拟实现
C++ ——string的模拟实现
https://blog.csdn.net/hedhjd/article/details/142023625?spm=1001.2014.3001.5501
迷迭所归处
2024/11/19
1430
【C++】string类的模拟实现
1. 库里面的构造函数实现了多个版本,我们这里就实现最常用的参数为const char *的版本,为了同时支持无参的默认构造,这里就不在多写一个无参的默认构造,而是用全缺省的const char *参数来替代无参和const char *参数的两个构造函数版本。
举杯邀明月
2023/04/12
7820
【C++】string类的模拟实现
相关推荐
【C++】C++STL 揭秘:Strng背后的底层逻辑
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档