C++——模拟实现string

news2024/9/22 18:46:15

1.再谈string

string为什么要被设计成模板?日常使用string好像都是char*,char*不够使用吗,为什么要设计成模板呢?

1.1 关于编码

//计算机的存储如何区分呢?

int main()
{
	//比如在C语言中,有整型
	//如果是有符号的话,设计了原、反、补码
	//负数要存储它的补码,方便运算
	//浮点数要设计到存精度等问题

	int a = 0;

	return 0;
}

那么如何去更好地表示其它的各种文字呢?

1.2 ASCII编码

ASCII_百度百科 (baidu.com)

ASCII编码表示美国信息技术的编码,在一开始设计时,只考虑了存储美国的文字信息

内存中存储字母时,不会把字母存储进去,内存中都是0、1组合的二进制代码,所以,要在二进制代码代表的值符号间,建立一个一一映射对应的关系,这个关系表,一般称为编码表


ASCII表就是美国的文字所建立的拥有一一映射对应的关系的编码表

int main()
{	
    char str[] = "hello world";
	return 0;
}

//要显式时,发现它是字符串,会去查它的编码表

1.3 unicode

统一码_百度百科 (baidu.com)

计算机向全世界推广后,不单单需要能够表示汉字,还需要能够表示其他国家的文字符号,有些国家的文字比较简单,有些可能比汉字还要复杂。因此,设计出了万国码。

所以要表示汉字,也要使得每个汉字都对应一个数字
那如何表示汉字呢?也是用一个byte表示一个汉字吗?
要知道1个byte才8个bit位,这样的话才只能表示256个汉字,显然不能很好地表示

方案有三种

1.3.1 UTF-8

int main()
{
	//UTF-8   主流使用,一个值可能在多个字节上面存储
	//但windows喜欢使用gbk,linux喜欢使用UTF-8

	char str1[] = "hello world";//12byte
	char str2[] = "工大";//5byte
	char str3[] = "工大 hello";//可以混着使用,11byte

	cout << sizeof(str1) << endl;
	cout << sizeof(str2) << endl;
	cout << sizeof(str3) << endl;

	char* a = str3;
	cout << *a << endl;

    return 0;
}

UTF-8的缺点:变长,意味着识别比较复杂,太不统一,比如做屏蔽时;
或是有些情况下文字不需要兼容ASCII,这时就要使用其它方式了

1.3.2 UTF-16

1.3.3 UTF-32

1.4设计成模板的原因

int main()
{
	//为了更好地兼容这两种编码,类型进行了延申
	//C++11之前,设计出了宽字节,一个char占据2byte
    //用来更好地表示其它的编码,比如UTF-16
	
    wchar_t ch1;	//宽字节
	cout << sizeof(ch1) << endl;//2byte

	char16_t ch2;
	char32_t ch3;

	cout << sizeof(ch2) << endl;//2byte
	cout << sizeof(ch3) << endl;//4byte

    return 0;
}

这也是string要设计成模板的原因,它可以传不同的模板参数,比如char16_t、char32_t等,可以适用不同的编码,表示更多国家的不同的文字。

1.5何为乱码

比如某文字默认使用UTF-8来存储,但显示时没有使用对于的编码表来查找,就会出现乱码。
存储格式和解释方式没有对应起来

1.6GBK

GBK字库_百度百科 (baidu.com)

虽然unicode是全世界的编码,但它未必非常适合汉字的表达。GBK是中国创造的、更适合汉字表达的编码表。

int main()
{
	char s1[] = "你好!!";

	s1[0]++;//陪
	s1[0]++;
	s1[0]++;
	s1[0]++;

	s1[3]++;//耗
	s1[3]++;
	s1[3]++;//号

	//在净网行动中非常有用
	//黑名单词库

    return 0;
}

2.模拟实现string

2.1无参的构造和析构

//string.h

#pragma once

namespace jxy
{
	class string
	{
	public:

//初始化的顺序是按照声明的顺序,而不是初始化列表出现的顺序

		string(const char* str)
			:_size(strlen(str))
			,_capacity(_size)//capacity一般不算'\0'
		{
			_str = new char[_capacity+1];//开空间时要多开一个
			strcpy(_str, str);//strcpy会把'\0'也拷贝过去
		}

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

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};

	void test_string1()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

	}
}


//Test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
//这里间接包了string的相关函数

//#include"string.h"
//注意,如果写在这里可能会报错,因为std的展开在下面
//编译器为了追求编译速度,只会向上查找

using namespace std;
#include"string.h"//编译器实际编译时是没有.h的,在预处理阶段就在.c或者.cpp展开了

int main()
{
	jxy::test_string1();

	return 0;
}

2.2单参数的构造

错误示范:

namespace jxy
{
	class string
	{
	public:

		string()
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{}

	};

	void test_string1()
	{
		string s2;
		cout << s2.c_str() << endl;
		
        //这样会崩溃
		//char*有个特点,自定义类型识别会以字符串去识别,会直接去解引用,遇到'\0'才终止
		//这里空指针解引用就会出错

		std::string s1;
		cout << s1.c_str() << endl;//库里面的是没问题的

	}
}

正确写法1:

namespace jxy
{
	class string
	{
	public:

		string()
			:_str(new char[1]{'\0'})//这里需要开空间
			,_size(0)
			,_capacity(0)
		{
			//_str[0] = '\0';//或者在这里初始化
		}


	};

	void test_string1()
	{
		string s2;
		cout << s2.c_str() << endl;

	}
}

正确写法2:在无参构造处给上缺省参数

namespace jxy
{
	class string
	{
	public:

		//const char* str="\0" 这样写不够规范
		 
		string(const char* str="")//C语言规定常量字符串后面默认有'\0'
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity+1];
			strcpy(_str, str);
		}

	};

	void test_string1()
	{
		string s2;
		cout << s2.c_str() << endl;
	}
}

2.3 size、capacity和c_str

namespace jxy
{
	class string
	{
	public:
		size_t capacity() const
		{
			return _capacity;
		}

		size_t size() const
		{
			return _size;
		}

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

	};

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
}

2.4遍历数组

2.4.1[]

namespace jxy
{
	class string
	{
	public:

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

		const char& operator[](size_t pos) const//给const对象使用
		{
			assert(pos < _size);
			return _str[pos];
		}

	};
//...
}

2.4.2迭代器

namespace jxy
{
	class string
	{
	public:

		typedef char* iterator;//迭代器可以实现成指针,也可以不
		//可以使用原生指针来代替iterator

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str+_size;
		}

	};
//...
}

2.4.3范围for

namespace jxy
{
	class string
	{
	//...
	};

	void test_string2()
	{

//1. []
		string s1("hello world");
		for (size_t i = 0; i <s1.size() ; i++)
		{
			cout << s1[i] << " ";
		}
		cout << endl;

//2.迭代器
		string::iterator it=s1.begin();//在里面typedef的或者是内部类

		while (it != s1.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;

//3.范围for
		//这里还支持范围for
		for (auto ch : s1)//底层代码和上面的迭代器类似
		{
			//auto ch = *it;
			cout << ch << " ";
		}
		cout << endl;

		//范围for的本质是替换成迭代器,编译时直接替换过去
		//而且有很严格的规范,名字变化一下都是不可以的
	}
}

2.5复用reserve实现尾插

实现尾插的三种方式:

1.push_back

2.append

3.+=

namespace jxy
{
	class string
	{
	public:

		void reserve(size_t n)
		{
			assert(n > _capacity);

			char* str1 = new char[n+1];//失败会抛异常
			strcpy(str1,_str);//会把'\0'拷贝过去

			delete[] _str;//越界会在这里崩溃
			_str = str1;
			_capacity = n;
		}

		void push_back(char ch)
		{//扩容问题
			if (_size == _capacity)
			{
				reserve(_capacity==0 ? 4 :_capacity * 2);

			}
			_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;
		}
//...
	};

	void test_string3()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.push_back('0');
		cout << s1.c_str() << endl;

		s1.append("hello lxy");
		cout << s1.c_str() << endl;

		s1 += '$';
		cout << s1.c_str() << endl;

		s1+="hellox";
		cout << s1.c_str() << endl;

	}
}

2.6 insert和erase

insert错误写法:

namespace jxy
{
	class string
	{
	public:
		void insert(size_t pos,char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);

			}

			size_t end = _size;//头插会出问题,这里是无符号数

			//int end = _size; //改为有符号数也是行不通的
			//在操作符,如 >= 两边,类型不同时,会发生类型提升
			//这里end虽然是有符号数,但会被提升成无符号数
			while (end >= pos)
			{
				_str[end + 1] = _str[end];
				end--;
			}
			_str[pos] = ch;
			_size++;

		}
//...
	};

	void test_string4()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.insert(5, '#');
		cout << s1.c_str() << endl;

		s1.insert(s1.size(), '#');
		cout << s1.c_str() << endl;

		s1.insert(0, '#');//头插
		cout << s1.c_str() << endl;
	}

}

正确写法1:

namespace jxy
{
	class string
	{
	public:
		void 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)
			{
				_str[end] = _str[end-1];
				end--;
			}
			_str[pos] = ch;
			_size++;

		}
//...
	};
}

正确写法2:

namespace jxy
{
	class string
	{
	public:
		void insert(size_t pos,char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);

			}

			int end = _size;
			while (end>=(int)pos)//或者把这里强转一下,避免出现类型提升
			{
				_str[end] = _str[end-1];
				end--;
			}
			_str[pos] = ch;
			_size++;

		}
//...
	};
}

namespace jxy
{
	class string
	{
	public:

		void insert(size_t pos,size_t len=npos)
		{

		}

		void insert(size_t pos,const char* str)
		{

		}

		void erase(size_t pos,size_t len=npos)
		{

		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

//注意
		//const static size_t npos =-1;  //特例
		//这里是特殊情况,按理说不能在这里初始化
		//这里给值给的是缺省值,缺省值是给初始化列表使用的
		//静态成员变量不会去执行初始化列表,它不属于对象,它属于整个类,按理说要在类外面初始化
		//但是const修饰的静态的整型可以,所以这里既是定义,又是定义初始化
		
		//const static double npos = 1.2;//这样都不会去支持

		const static size_t npos;
	};

	const size_t string::npos = -1;
}

2.7运算符重载

namespace jxy
{
	class string
	{
	public:

//运算符重载
//设计成非成员函数更好,便于模板的使用
		bool operator<(const string& str1)
		{
			return strcmp(_str, str1._str)<0;
		}

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

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

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

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

		bool operator!=(const string& str1)
		{
			return !(*this == str1);
		}
//...
	};
}

2.8流插入

namespace jxy
{
	class string
	{
    public:

		typedef const char* const_iterator;//const迭代器指向的内容不能修改
		//指针本身可以修改,指向的内容不能修改

		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			return _str + _size;
		}

    //...
	};

	//没有必要定义为友元,想访问私有成员变量才需要定义为友元
	ostream& operator<<(ostream& out, const string& s)
	{
		1.
		//for (size_t i = 0; i < s.size(); i++)
		//{
		//	out << s[i];
		//}
		//return out;

		//2.可以使用范围for,但要替换成const迭代器
		for (auto ch : s)
			out << ch;

		return out;
	}
}

2.9流提取和clear

namespace jxy
{
	class string
	{
    public:
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

    //...
	};

	//没有必要定义为友元,想访问私有成员变量才需要定义为友元

	istream& operator>>(istream& in,string& s)
	{//流提取要从缓冲区中取一个个的字符
		s.clear();

		char ch;
		//in >> ch; //这样写获取不到空格
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			//in >> ch;
			ch = in.get();

		}
		return in;
	}

	void test_string6()
	{
		string s1("hello world");
		cout << s1 << endl;

		cin >> s1;
		cout << s1 << endl;

	}
}

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

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

相关文章

Linux网络——HTTP协议详解(2)

文章目录 HTTP方法GET方法POST方法 状态码与报头状态码报头 会话 HTTP方法 HTTP方法有这些 但是怎么说呢&#xff0c;这些方法只有GET和POST方法是99%情况用到的 剩下的几乎不太用&#xff0c;如果有兴趣可以找《图解HTTP》&#xff0c;是处于了解的范畴 大家肯定一看就明白…

Qt Creator项目模板介绍

在Qt Creator中创建项目时&#xff0c;用户可以从多个模板类别中进行选择&#xff0c;以满足不同的开发需求。 Application(Qt) 在Application(Qt)类别下&#xff0c;Qt Creator提供了多种用于创建不同类型Qt应用程序的模板。这些模板主要包括&#xff1a; Qt Widgets Applic…

专业解析:移动硬盘“要求格式化”背后的真相与数据救援策略

引言&#xff1a;格式化预警下的数据危机 在日常的数字生活中&#xff0c;移动硬盘作为数据存储与传输的重要工具&#xff0c;其稳定性与安全性直接关系到用户资料的安全。然而&#xff0c;不少用户遭遇过这样一个令人头疼的问题——移动硬盘在接入电脑后&#xff0c;系统突然…

Tcping:一款实用的端口存活检测工具

简介 tcping 是一个基于TCP协议的网络诊断工具,通过发送 TCP SYN/ACK包来检测目标主机的端口状态。 官网:tcping.exe - ping over a tcp connection 优点: (1)监听服务器端口状态:tcping 可以检测指定端口的状态,默认是80端口,也可以指定其他端口。 (2)显示ping返…

MATLAB系列08:输入/输入函数

MATLAB系列08&#xff1a;输入/输入函数 8. 输入/输入函数8.1 函数textread8.2 关于load和save命令的进一步说明8.3 MATLAB文件过程简介8.4 文件的打开和关闭8.4.1 fopen函数8.4.2 fclose函数 8.5 二进制 I/O 函数8.5.1 fwrite 函数8.5.2 fread函数 8.6 格式化 I/O 函数8.6.1 f…

【漏洞复现】金斗云 HKMP download 任意文件读取漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

[Meachines] [Medium] Sniper RFI包含远程SMB+ powershell用户横向+CHM武器化权限提升

信息收集 IP AddressOpening Ports10.10.10.151TCP:80,135,139,445,49667 $ nmap -p- 10.10.10.151 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 80/tcp open http Microsoft IIS httpd 10.0 |_http-server-header: Microsoft-IIS/10.…

【算法业务】互联网风控业务中的拒绝推断场景算法应用分享(涉及半监督算法、异常检测、变分自编码、样本权重自适应调整、迁移学习等)

1. 业务目标和任务描述 该项目是很早期的一个工作&#xff0c;属于互联网信贷风控场景&#xff0c;研究并应用信贷中的拒绝推断任务&#xff0c;处理方式也许对于目前的一些业务还有参考意义&#xff0c;因此这里做下分享。拒绝推断是指在信贷业务中&#xff0c;利用已知的接受…

植物大战僵尸【源代码分享+核心思路讲解】

植物大战僵尸已经正式完结&#xff0c;今天和大家分享一下&#xff0c;话不多说&#xff0c;直接上链接&#xff01;&#xff01;&#xff01;&#xff08;如果大家在运行这个游戏遇到了问题或者bug&#xff0c;那么请私我谢谢&#xff09; 大家写的时候可以参考一下我的代码思…

网络安全-shire写任务计划、反弹shell、写私钥、反序列化

目录 一、环境 二、 介绍 三、开始做题 四、写公钥 一、环境 网上自己找 二、 介绍 我们经过前面文章很清楚知道&#xff0c;shiro是将数据存储在内存当中&#xff0c;内存落盘实现一个数据存储&#xff0c;而当其结合python&#xff0c;python将登录的session存储到shiro里…

矩阵范数介绍

这里写目录标题 理论1 诱导范数 (induced norm)2 “元素形式”范数(“entrywise" norm)3 Schatten 范数 论文中常用范数的书写 理论 参考张贤达矩阵分析page 34 矩阵范数主要有三种类型&#xff1a;诱导范数&#xff0c;元素形式范数和Schatten范数 1 诱导范数 (induce…

AiAutoPrediction足球网与泊松分布足球预测比赛模型介绍

AiAutoPrediction足球软件上线于2020年9月&#xff0c;是国内首家将泊松分布概率公式应用于足球比赛比分预测的软件。 AiAutoPrediction足球系列软件如下&#xff1a; AIAutoPrediction SoccerPredictor |走地大小球|走地让球|走地角球|数据分析 AiScorePredictor 泊松分布…

这可能又是一款 Java 程序员的必备插件了,无需解压直接编辑修改 jar 包内文件,神器!(附源码)

作为一名 Java 程序员&#xff0c;在维护一些古老的程序时&#xff0c;可能会遇到这种情况&#xff1a;项目依赖的 jar 包过于久远&#xff0c;已经没有源码了&#xff0c;但是有不得不修改的 bug 要处理。这时候就得想办法反编译 jar 包进行修改&#xff0c;并且重新打包&…

基于51单片机的可燃性气体报警器设计

文章目录 前言资料获取设计介绍设计程序具体实现截图设计获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们…

《财富之眼:用经济思维看清世界》pdf电子书下载

《财富之眼&#xff1a;用经济思维看清世界》pdf电子书下载 内容简介 一切社会现象都是经济现象&#xff0c;我们只能赚到自己认知范围内的 钱。我国社会主要矛盾已经转化为人民日益增长的美好生活需要和不 平衡不充分的发展之间的矛盾&#xff0c;其中“不平衡不充分”很大程…

无法删除选定的端口,不支持请求【笔记】

场景&#xff1a;在删除打印机端口时&#xff0c;提示&#xff1a;“无法删除选定的端口&#xff0c;不支持请求”&#xff0c;如下图所示。 以下以删除USB036端口为示例&#xff0c;操作步骤如下&#xff1a; 在注册表编辑器中&#xff0c;从以下注册表项中“计算机\HKEY_LO…

C++_22_异常

文章目录 异常概念&#xff1a;**抛出异常&#xff1a;**关键字&#xff1a; **捕获异常&#xff1a;****栈解旋&#xff1a;****异常的接口声明&#xff1a;****异常对象的生命周期&#xff1a;**1 传递异常对象【不使用】2 传递异常对象指针【不使用】3 传递异常对象引用【**…

Xilinx系FPGA学习笔记(八)FPGA与红外遥控

系列文章目录 文章目录 系列文章目录红外通信简单介绍红外协议分析 FPGA实现红外通信 红外通信 简单介绍 利用红外线来传送&#xff0c;不干扰其他电器设备工作&#xff0c;也不会影响周边环境&#xff0c;若对发射信号进行编码&#xff0c;可实现多路红外遥控功能。 红外遥控…

全面介绍 CSS 属性值计算 —— 掌握它就了解大部分 CSS

CSS 的核心之一就在此&#xff0c;直接影响我们开发中的调试和布局&#xff01;&#xff01;&#xff01; 举个 &#x1f330;&#xff1a;页面上存在一个 h1 元素&#xff0c;不设置任何样式&#xff0c;但是当我们点开 computed 查看&#xff0c;几乎 MDN 上的 CSS 属性都存…

C++高精度计时方法总结(测试函数运行时间)

文章目录 一、clock()函数——毫妙级二、GetTickCount()函数&#xff08;精度16ms左右&#xff09;——毫妙级三、高精度时控函数QueryPerformanceCounter()——微妙级四、高精度计时chrono函数——纳妙级五、几种计时比较六、linux下的计时函数gettimeofday()-未测试参考文献 …