string类模拟实现

news2024/11/15 18:42:17

 了解过string常用接口后,接下来的任务就是模拟实现string类。


目录

VS下的string结构

默认成员函数和简单接口

string结构

c_str()、size()、capacity()、clear()、swap()

构造函数

 拷贝构造函数

赋值重载

析构函数

 访问及遍历

容量操作

reserve

resize

修改操作

push_back

append

operator+=

insert

earse

输入/输出运算符重载


VS下的string结构

■首先测试下string结构的大小(注:下述测试是在32位平台上进行的,指针占4个字节)

int main()
{
	string s;
	cout << sizeof(s) << endl;
	return 0;
}

 通过调试窗口理解string的结构:

int main()
{
	string s("Hello");

	return 0;
}

通过测试知道string总共占28个字节,调试发现结构中有一个联合体,用来定义string中字符串的存储空间: 1.当字符串长度小于16时,使用内部固定的字符数组来存放 。2.当字符串长度大于等于16时,从堆上开辟空间。

union _Bxty
{ 
 char _Buf[16];
 char* _Ptr;
 char _Alias[16]; 
} _Bx;

 除了联合体,还有一个size_t字段保存字符串长度,还有一个size_t字段记录容量!此外,还有一个做其它事情的指针。总大小:16+4+4+4 = 28 !!


默认成员函数和简单接口

下述接口的模拟实现仅仅是一种写法,可能不是最优的,也可能存在一些BUG,仅供参考!

string结构

c_str()、size()、capacity()、clear()、swap()

为了测试方便,先将上述几个常用且简单的接口进行实现:

        //返回指向_str的指针
        const char* c_str()
 		{
			return _str;
		}
        //返回_size
		size_t size() const
		{
			return _size;
		}
        //返回_capacity
		size_t capacity() const
		{
			return _capacity;
		}
		//清空有效字符
		void clear()
		{
			_size = 0;
			_str[0] = '\0';
		}

复习时间:const修饰返回值的作用是避免返回值被修改,const写在成员函数后面修饰的是隐藏的this指针,不能对类的成员进行修改!

swap:string类中提供一个swap接口用来交换两个string对象。

        void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size,s._size);
			std::swap(_capacity, s._capacity);
		}

构造函数

分析:对于string s1(“Hello”)/ string s2 都能处理;

逻辑思路:通过strlen计算str的字符个数,确定size和capacity的值,空间一般多开一个,因为要算上\0。在将str 拷贝到开辟好的空间中。

//构造
string(const char* str = "")
{
     _size = strlen(str);
	 _capacity = _size;
	 _str = new char[_capacity+1];

	 strcpy(_str,str);
	 cout << "调用构造函数!" << endl;
}

测试代码:

    //构造函数测试
	void TestString1()
	{
		string s1("zhang");
		string s2;
		cout << s1.c_str() << endl;
	}

 拷贝构造函数

分析:string s1(s2);

按照拷贝对象的容量申请空间,对拷贝对象进行拷贝。

传统写法:

        //拷贝构造
		string(const string& s)
		{
			_str = new char[s._capacity];
			_size = s._size;
			_capacity = s._capacity;

			strcpy(_str,s._str);
			cout << "调用拷贝构造!" << endl;
		}

测试代码:

    //拷贝构造测试
	void TestString2()
	{
		string s2("Hi zxy!");
		cout << s2.c_str() << endl;
		string s3(s2);
		cout << s3.c_str() << endl;
	}

现代写法:这里注意在初始化列表对s进行初始化,否则当tmp对象析构的时候,会出现析构野指针的问题。

        //现代写法
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
            //this->swap(tmp);
			swap(tmp);
			cout << "拷贝构造" << endl;
		}

赋值重载

传统写法:

注意:不要见到&就是引用,在下述判断代码中,&s取出的s的地址,和this指针进行比较,如果地址不相同则进行赋值工作!

赋值的过程需要将右操作数赋值给做左操作数,这时从新根据右操作数的容量申请一块空间,将左操作数空间释放。_str指向刚刚开好的空间,将右操作数中的字符串拷贝到_str中。

        //赋值重载
		string& operator=(const string& s)
		{
			//两操作数地址不相等
			if (this != &s)
			{
				//申请一块能容纳右操作数的空间
				char* tmp = new char[s._capacity];
				delete[] _str;
				_str = tmp;
				strcpy(_str,s._str);
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}

 测试代码:

//赋值重载测试
	void TestString3()
	{
		string str1("Hello World!");
		string str2("Hi!");
		cout <<"赋值前str1:"<< str1.c_str() << " str2:" << str2.c_str() << endl;
		str1 = str2;
		cout <<"赋值后str1:"<< str1.c_str() << " " << str2.c_str() << endl;
	}

测试结果:

现代写法:

第二种相比较第一种写法省去了一次拷贝构造,传值调用形参是实参的临时拷贝,所以第二种写法更加的简洁,第一种写法更容易理解。

       //赋值重载现代写法1
		string& operator=(string& s)
		{
			if (this != &s)
			{
				//string tmp(s._str);
				string tmp(s);
				swap(tmp);
			}
			return *this;
		}
		//赋值重载现代写法2
        string& operator=(string s)
		{
			swap(s);
			return *this;
		}

析构函数

		//析构
		~string()
		{
			_capacity = _size = 0;
			delete[] _str;
			_str = nullptr;
			cout << "调用析构函数!" << endl;
		}

测试代码:

    //析构函数测试
	void TestString4()
	{
		string str("zxy");
		cout << str.c_str() << endl;
	}

 访问及遍历

operator[ ]

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

begin/end

       typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

迭代器:访问string对象、vector等对象元素的一种通用机制。迭代器类似于指针类型(不一定是指针),使用迭代器可以访问某个元素。范围for的支持就和迭代器有关:

	void TestString2()
	{
		string s2("My name is zxy!");
		string::iterator it1 = s2.begin();

		while (it1 != s2.end())
		{
			cout << *it1;
			it1++;
		}
		cout << endl;
		string::iterator it2 = s2.begin();
		while (it2 != s2.end())
		{
			(*it2)++;
			cout << *it2;
			it2++;
		}
	}

证明范围for是依赖于迭代器实现的:

1.屏蔽掉string中的begin或者end.

 2.放开屏蔽,更改begin的命名为Begin。一样的报错

 3.提供小写的begin和end

 综上所述:便捷神秘的范围for,运用迭代器这种通用的机制来实现元素的遍历和访问。

容量操作

reserve

扩容:申请一块n大小的空间,将_str指向的字符串拷贝到tmp指向的空间中,清理_str指向的字符串并将_str指向新空间,更新容量的大小。

		//扩容
		void reserve(size_t n)
		{
			//新开一块空间
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;

			_capacity = n;
		}

    void TestString6()
	{
		string ss("Hello World!");
		cout << "size:" << ss.size() << endl;
		cout << "capacity:" << ss.capacity() << endl;
		cout << "str:" << ss.c_str() << endl;
		cout << "--------------reserve--------------" << endl;
		ss.reserve(100);
		cout << "size:" << ss.size() << endl;
		cout << "capacity:" << ss.capacity() << endl;
		cout << "str:" << ss.c_str() << endl;
	}

 

resize

改变字符串中有效字符的个数,当n>_size时,其余位置用ch占位。当n<_size时只有前n个字符是有效字符。

//resize,更改有效元素个数的大小
		void resize(size_t n,char ch = '!')
		{
			if (n > _size)
			{
				reserve(n);
				for (size_t i = _size;i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[n] = '\0';
			}
			else
			{
				_size = n;
				_str[n] = '\0';
			}
		}

 上述代码中,当n小于_size时,在n位置填入\0,有效字符的个数正好是n,因为字符数组的下标是从0开始的。

    void TestString7()
	{
		string ss("Hello");
		cout << "str:" << ss.c_str() << endl;
		cout << "size:" << ss.size() << endl;
		//字符串有效字符个数增多
		ss.resize(10,'x');
		cout << "str:" << ss.c_str() << endl; 
		cout << "size:" << ss.size() << endl;
		//字符串有效字符个数变少
		ss.resize(3);
		cout << "str:" << ss.c_str() << endl;
		cout << "size:" << ss.size() << endl;
	}

 

修改操作

push_back

尾插操作,也就是在字符串末尾追加一个字符:

 

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

append

在_str末尾追加一个字符串:

 

		//追加字符串
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size+len+1);
			}
			strcpy(_str+_size,str);
			_size += len;
		}

operator+=

重载+=运算符,使+=既可以追加字符右可以追加字符串,实现思路和上述基本一样,这里直接调用上面的接口。

        //运算符重载
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

测试push_back/append/+=性能

	void TestString3()
	{
		string ss("Hi");
		cout << "str:" <<ss.c_str() << endl;
		ss.append("zxy"); 
		cout << "append(zxy):" <<ss.c_str() << endl;
		ss.push_back('!');
		cout << "push_back(!):" <<ss.c_str()<< endl;
		ss += '!';
		cout << "+=字符(!):" <<ss.c_str() << endl;
		ss += "bd";
		cout << "+=字符串(bd):" <<ss.c_str() << endl;
	}

insert

insert这里重载了两个接口,分别是用来插入字符和插入字符串:

●在pos位置插入字符

 

		//插入字符
		string& insert(size_t pos,char ch)
		{
			//检查pos是否越界
			assert(pos <= _size);
			if (_capacity == _size)
			{
				//扩容
				size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
				reserve(newcapacity);
			}
			//挪动数据
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end-1];
				end--;
			}
			_str[pos] = ch;
			_size++;

			return *this;
		}
	void TestString9()
	{
		string ss("Helloworld!");
		ss.insert(6, ' ');
		cout << ss.c_str() << endl;
	}

 ●在pos位置插入字符串:

 


		//插入字符串		
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _size)
			{
				//扩容
				size_t newcapacity = _capacity == 0 ? 4 : _size+len;
				reserve(newcapacity);
			}
			//挪动数据
			size_t end = _size + len;
			while (end > pos+len-1)
			{
				_str[end] = _str[end - len];
				end--;
			}

			strncpy(_str+pos,str,len);
			_size += len;
			return *this;
		}
    void TestString10()
	{
		string ss("Hi Lisi!");
		ss.insert(3, "zxy I am ");
		cout << ss.c_str() << endl;
	}

earse

删除pos后的len个字符,如果len == npos,将pos后的数据全部删除!

		//删除字符
		string& earse(size_t pos,size_t len = npos)
		{
			assert(pos <= _size);
			//如果pos后面的数据不够删或者=npos,pos后数据全部删除
			if (len == npos || len + pos >= _size-pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

输入/输出运算符重载

小提问:输入和输出的重载为了和我们的使用习惯保持一致一般会定义在全局,那么一定要在对应自定义类型中声明友元吗?

答:不一定,声明友元的原因是要访问类中的私有成员,如果在重载的过程中不涉及私有成员的访问,就不用写友元声明!

    //流插入
	ostream& operator<<(ostream& out,string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i];
		}
		return out;
	}

流提取的实现使用了一个字符数组,in.get()用于读取字符,当数组满时追加到_str中,没满时将数据存放到buff数组中,最后输入结束时如果buff中还有数据的话将剩余数据追加到_str中,不过要注意在buff的数据末尾添加一个\0。

	//流提取
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		size_t i = 0;
		char buff[128] = {'\0'};
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)
			{
				s += buff;
				i = 0;
			}
			
			buff[i++] = ch;
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

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

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

相关文章

C语言(typedef,函数和指针)

目录 一.typedef 二.函数和指针 一.typedef typedef是一种高级数据特性&#xff0c;利用typedef可以为某一类型自定义名称。typedef创建的符号名只受限于类型 typedef unsigned char BYTE; BYTE x 这里的BYTE就相当于unsigned char x typedef unsigned char *BYTE; BYTE x,…

[oeasy]python0082_VT100_演化_颜色设置_VT选项_基础色_高亮色_索引色_RGB总结

更多颜色 回忆上次内容 上次 了解了控制序列 背后的故事 一切标准 都是 从无到有 的就连 负责标准的组织 也是 从无到有 的 VT-05 奠定了 基础颜色 黑底 绿字隔行 扫描 但 多颜色设置 是如何出现 的呢&#xff1f;&#xff1f;&#x1f914; 控制字符 1974年 产品 从VT05…

语音识别系列之脉冲神经网络特征工程

人工神经网络&#xff08;Artificial Neural Network, ANN&#xff09;中的单个人工神经元是对生物神经元的高度抽象、提炼和简化&#xff0c;模拟了后者的若干基本性质。得益于误差反向传播算法&#xff0c;网络权重可根据设定的目标函数得到有效地调整&#xff0c;ANN在视觉、…

LeetCode初级算法题(Java):反转链表+统计N以内的素数+删除排序数组中的重复项

文章目录1 反转链表1.1 题目1.2 解题思路解法1&#xff1a;迭代解法2&#xff1a;递归1.3 题解代码2 统计N以内的素数2.1 题目2.2 解题思路与题解代码解法1&#xff1a;暴力算法代码展示解法1&#xff1a;埃氏筛代码展示3 删除排序数组中的重复项3.1 题目3.2 解题思路3.3 题解代…

近红外染料标记小分子1628790-37-3,Cyanine5.5 alkyne,花青素CY5.5炔基

试剂基团反应特点&#xff1a;Cyanine5.5 alkyne用于点击化学标记的远红外/近红外染料炔烃。氰基5.5是Cy5.5的类似物&#xff0c;一种流行的荧光团&#xff0c;已广泛用于各种应用&#xff0c;包括完整生物体成像。在温和的铜催化化学条件下&#xff0c;该试剂可与叠氮基共轭&a…

构建RFID系统的重要组成部分

RFID读写设备&#xff0c;通常被用来扫描读取安装了RFID电子标签的目标物品&#xff0c;能实现快速批量无接触读写&#xff0c;是构建RFID系统的重要组成部分。RFID读写设备&#xff0c;通常有固定式读写设备和可移动读写设备两种。下面来了解一下RFID的特点&#xff0c;RFID系…

EZ-Cube简易款下载器烧写使用方法

一、硬件连接 跟目标芯片接4根线 VCC、GND、TOOL、REST 四根线&#xff0c;如果板子芯片自己外接电源的&#xff0c;VCC 线可以不接。 二、 安装烧写软件和驱动 烧写软件&#xff1a;https://download.csdn.net/download/Stark_/87444744?spm1001.2014.3001.5503 驱动程序&a…

java微信小程序的在线学习平台

本文以实际运用为开发背景,运用软件工程原理和开发方法,它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个开发过程首先对在线学习平台进行需求分析,得出在线学习平台主要功能。接着对在线学习平台进行总体设计和详细设计。总体设计主要包括小程序功能设计、小程…

Apache JMeter 5.5 下载安装以及设置中文教程

Apache JMeter 5.5 下载安装以及设置中文教程JMeter下载Apache JMeter 5.5配置环境变量查看配置JDK配置JMeter环境变量运行JMeter配置中文版一次性永久设置正文JMeter 下载Apache JMeter 5.5 官方网站&#xff1a;Apache JMeter 官网 版本介绍&#xff1a; 版本中一个是Bina…

TCP协议 ---可靠传输的各种机制

目录 一、可靠 确认应答机制&#xff1a;保证数据可靠、有序的到达对端 超时重传机制 二、效率 2.1 提高自身发送数据量 滑动窗口机制&#xff1a; 滑动窗口的发送缓冲区是环形队列 滑动窗口的大小会变化吗&#xff1f; 滑动窗口的nb之处 滑动窗口丢包问题 2.2 对方…

锻炼管理器wger的安装

本文是 2021 年 2 月完成的&#xff0c;最近因为工作比较忙&#xff0c;就把这些老文章翻出来&#xff0c;但为了发表&#xff0c;老苏差不多又重写了一遍。 因为当时跑的是 wger/apache &#xff0c;现在新的 wger/apache 版本在老苏的机器上&#xff0c;会遇到 AH00141: Coul…

Windows Git Bash 配置

Windows Git Bash 配置 本文参考的文章&#xff1a; 在 Windows 的 Git Bash 中使用包管理器 - iris (ginshio.org)Git bash 安装 pacman & Windows 解压 zst 文件 | 伪斜杠青年 (lckiss.com) 一、Git的安装 Git 的安装应该是都会的&#xff0c;但还是应该说以下&#…

前端常见基础面试题css篇

目录 1.css3有哪些新特性&#xff1f; 2.CSS有哪些基本选择器&#xff1f;它们的权重是如何表示的&#xff1f; 3.css 选择器的类型优先级排序 4.写出几种CSS实现元素两个盒子垂直水平居中的代码 5.CSS 常见的伪类和伪元素有哪些? 6.CSS的引入方式有哪些&#xff1f;ink…

CMMI-外包与采购管理

外包与采购管理&#xff08;Outsourcing and Procurement Management, OPM&#xff09;是指外包管理和采购管理&#xff0c;目的是选择合适的承包商和供应商&#xff0c;并依据合同进行有效的管理。外包与采购管理过程域是SPP模型的重要组成部分。本规范阐述了外包与采购管理过…

自定义认证器

自定义认证器工作流程 1、设备向AWS IoT发送http请求&#xff0c;包含token和token签名 2、IoT判断这是一个自定义认证请求&#xff0c;然后确认token和token签名是否匹配 3、Lambda函数验证token之后&#xff0c;将输出5项数据&#xff0c; isAuthenticated&#xff1a;求是…

Zookeeper入门与应用

Zookeeper入门与应用1.简介1.1.应用场景1.2.Zookeeper的设计目标1.3数据模型2. 单机安装3.常用shell命令3.1.查询3.2.创建3.3.更新3.4.删除4.zookeeper的Acl权限控制案例/远程登录acl 超级管理员5.zookeeper的 JavaAPI连接到Zookeeper5.1.新增节点5.2.修改节点5.3.删除节点5.4.…

4、数组、切片、map、channel

目录一、数组二、切片三、map四、channel五、引用类型一、数组 数组&#xff1a; 数组是块连续的内存空间&#xff0c;在声明的时候必须指定长度&#xff0c;且长度不能改变所以数组在声明的时候就可以把内存空间分配好&#xff0c;并赋上默认值&#xff0c;即完成了初始化数组…

Spring反射内置工具类ReflectionUtils

Spring反射内置工具类ReflectionUtils前言反射1&#xff0c;什么是反射2&#xff0c;反射的实现2.1获取class对象的三种实现2.1.1Object ——> getClass();2.1.2 任何数据对象&#xff08;包括数据基本类型&#xff09;都有一个静态的class属性通过Class类的静态方法&#x…

TX Text Control .NET 31.0 SP1 for WPF 企业版Crack

将文档编辑、创建和 PDF 生成添加到 WPF 应用程序。 TX Text Control for WPF Enterprise 是一个免版税、完全可编程的丰富编辑控件&#xff0c;它在专为 Visual Studio 设计的可重用组件中为开发人员提供了广泛的文字处理功能。它提供全面的文本格式&#xff0c;强大的邮件合并…

golang fmt.Sprintf(“%.2f“) 的舍入问题

首先&#xff0c;fmt.Sprintf("%.2f")使用的是banker rounding 而不是四舍五入&#xff0c;banker rounding 的定义如下&#xff08;来自百度百科&#xff09;&#xff1a;1.要求保留位数的后一位如果是4&#xff0c;则舍去。例如5.214保留两位小数为5.21。2.如果保留…