【C++】STL_ string的使用 + 模拟实现

前言

1. STL简介

(1)什么是STL

STL是(standard template libaray-标准模板库)的首字母缩写,是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

(2)STL的版本

  • 原始版本:
    由HP实验室完成的原始版本,主要是给Visual studio使用。
  • P.J. 版本:
    由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
  • RW版本:
  • SGR版本:
    由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。
    我们后面学习STL要阅读部分源代码,主要参考的就是这个SGR版本。

(3)STL的六大组件

  • 空间配置器–内存池
  • 迭代器
  • 配接器
  • 容器(其实是数据结构)
  • 仿函数
  • 算法
    在这里插入图片描述
    六大组件之间的关系:
    在这里插入图片描述
    迭代器 主要是用来访问容器的
    在这里插入图片描述
    Iterators特性:
  • 迭代器提供了通用的方法来访问容器。
  • iterator像指针一样的类型,有可能就是指针(比如vector、String),也有可能不是指针(比如Map、set、 list是类来封装List迭代器),其用法像指针一样的东西。
  • **iterator是个类型,**用这个类型可以定义一个对象

2. string的使用

String是类模板,string是被typedef 出来的,比STL产生的早,遵循STL的那一套.

  • 使用string的时候,要包含头文件 #include< string >
  • typedef basic_string string;
  • 由于string这个类中有上百个成员函数的接口,我们要会用其中比较常见的接口,string的学习文档:链接: 传送门
  • string是默认带\0 的,和C语言字符串一样。即:string s1;//只有一个\0

Operator at 和Operator [ ]的区别在于,Operator [ ]越界以后会抛异常。

2.1 npos

string 中的npos是静态成员变量,初始值是-1.
在这里插入图片描述

2.2 遍历字符串string的每一个字符

遍历字符串有三种方法:

  • 第一种方式,下标 + [] – []是C++重载的运算符。
  • 第二种方式,迭代器 – 迭代器是用来访问数据结构的。
  • 第三种方式,范围for – 前提是:C++11才支持的语法。
void test_string3()
{
	//遍历string的每一个字符
	string s1("hello");
	cout << s1[0] << endl;
	s1[0] = 'x';
	cout << s1.size() << endl;

	//遍历一共有三种方式

//·第一种方式,下标 + []  -- []是C++重载的运算符:

	//size就是返回它有多少个字符,是不包含\0的
	for (size_t i = 0; i < s1.size(); i++)
	{
		//s1.operator[](i);相当于调用这个函数
		cout << s1[i] << " ";//[]相当于函数调用
	}
	cout << endl;

	//编译器是看类型的,下面的运用和上面的类中调用成员函数完全不同
	//const char* s2 = "world";
	//s2[i];//*(s2 + i)

//·第二种方式,迭代器   --   迭代器是用来访问数据结构的:

	//sting::iterator是个类型,用这个类型可以定义一个对象

	string::iterator it = s1.begin();//或者叫iter
	//begin是指向第一个位置
	//end不是结束位置,而是最后一个位置的下一个位置
	//如果end是最后一个位置的下一个的话就会访问不到最后一个位置
	//[    ) -- 左闭右开的结构 -- 方便遍历

	//写成小于 < 也是可以的,但是不建议,标准的地方就是写的不等于 != 
	//统一用不等于 != 
	while (it != s1.end()) 
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	//现阶段理解的迭代器:像指针一样的东西或者就是指针

//·第三种方式,范围for  --  前提是:C++11才支持的语法
    //范围for的原理:替换成迭代器
	//自动取元素,赋值给ch,自动判断结束,自动++
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

现阶段理解的迭代器:像指针一样的东西或者指针。
iterator是个类型,用这个类型可以定义一个对象

  • begin是指向第一个位置
  • end不是结束位置,而是最后一个数据的下一个位置
  • 如果end不是最后一个数据的下一个位置的话,循环条件中就会访问不到最后一个位置
  • [ ) – 左闭右开的结构 – 方便遍历

范围for:

  • 范围for又叫语法糖,因为它用起来很舒服很好用,省略了大量的代码
  • 其实在底层编译器替代成了迭代器,只是上层个看起来厉害
  • 大家可以通过看汇编代码来看底层实现的逻辑
  • 范围for和迭代器底层并没有太大差异

2.3 迭代器:

四种迭代器分类:

  • 第一种,普通的正向迭代器:const_iterator
  • 第二种,反向迭代器:reverse_iterator
  • 第三种,正向迭代器,能读不能写const_iterator
  • 第四种,反向迭代器,能读不能写const_reverse_iterator
//不改变就加const保护
//普通迭代器是可读可写的
void Func(const string& rs)
{
//第三种,正向迭代器,能读不能写
	string::const_iterator it = rs.begin();
	while (it != rs.end())
	{
		//(*it) += 1;
		cout << *it << " ";
		it++;
	}
	cout << endl;

//第四种,反向迭代器,能读不能写
	//string::const_reverse_iterator rit = rs.rbegin();
	//auto自动推导:
	auto rit = rs.rbegin();
	while (rit != rs.rend())
	{
		//(*rit) -= 1;
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
}

//四种迭代器:
void test_string5()
{
//第一种,普通的正向迭代器:
	string s("hello world");
	string::iterator it = s.begin();
	while (it != s.end())
	{
		(*it) += 1;
		cout << *it << " ";
		it++;
	}
	cout << endl;
	cout << s << endl;

//第二种,反向迭代器:
	string::reverse_iterator rit = s.rbegin();
	while (rit != s.rend())
	{
		(*rit) -= 1;
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
	cout << s << endl;

	Func(s);

}
//iterator是终极方式,[] + 下标,是附带方式

2.4 string的内存管理

string的容量大小:Capacity
string的长度,既可以用length(),也可以用size();
reserve和resize都不会缩容量capacity,但是resize会让size降下来,只留size个。

Reserve只修改capacity大小,不修改size大小.
Resize既修改capacity大小,也修改size大小,会进行初始化
reserve和resize都不会缩容量capacity,但是resize会让size降下来,只留size个。

void test_string6()
{
	string s("hello world");
	//length产生的比size早
	cout << s.length() << endl;
	cout << s.size() << endl;
	cout << s.max_size() << endl;
	//容量要扩容
	cout << s.capacity() << endl;
}

2.5 string模拟实现

2.5.1 深拷贝:

浅拷贝的问题: 如果是栈的类,普通的浅拷贝(按字节拷贝),会出现问题,两个栈的str指针指向同一个地方,两个栈相互影响,我们并不希望这样,所以我们要学习一下深拷贝。

浅拷贝的效果:
在这里插入图片描述
深拷贝的效果:
在这里插入图片描述

3 .具体代码实现(string.hpp):

主要是增删查改拷贝构造

3.1 拷贝构造代码实现:
#pragma once

#include<iostream>
#include<assert.h>
#include<string>

using namespace std;

//class mystring
//class String

//自己实现的封装在命名空间中防止冲突
namespace Joker
{
	//实现一个简单的string,只考虑资源管理深浅拷贝的问题
	//暂且不考虑增删查改
	//string需要考虑完善的增删查改和使用的string
	class string
	{
	public:
		//const对象遍历访问是不可以被修改的,应该实现两种
		//两种迭代器的参数不同,函数重载
		typedef char* iterator;
		typedef const char* const_iterator;

		const_iterator begin() const
		{
			return _str;
		}

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

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		//无参的构造函数
		/*string()
			:_size(0)
			,_capacity(0)
		{
			_str = new char[1];
			_str[0] = '\0';
		}*/

		//全缺省
		//1、"\0"这是有两个\0  
		//2、""这是有一个\0  
		//3、'\0'这里是**把\0的assic码值0**给了指针,实际是空指针**(注意:‘\0'的ascII为0 | ’0‘的ASCII值为48)**
		//而且如果支持c_str,cout的时候,解引用空指针会报错
		string(const char* str = "")     //""是C语言默认常量字符串,后面有\0
			:_size(strlen(str))          //strlen()是不会判空指针的
			, _capacity(_size)

		{
			_str = new char[_capacity + 1];//给'\0'多开一个
			strcpy(_str, str);
		}

//·构造函数:
//·传统的写法:本分,老实,老老实实干活,该开空间开空间,该拷贝数据就自己拷贝数据
		//s2(s1); - 深拷贝
		//在类里面只要用string对象访问成员都是不受限制的
		//私有是限制在类外面使用对象去访问成员
		/*string(const string& s)
			:_size(strlen(s._str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}*/

//·现代写法:剥削,要完成深拷贝,自己不想干活,安排别人干活,然后窃取劳动成果
		//要初始化一下,不然有可能释放野指针
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);--调用构造函数
			swap(tmp);
		}

拷贝构造的现代写法:

  • 先构造函数一个string类型的 temp 对象:使用传过来指针实例化一个所需一样的对象
  • 再将tmp对象的内容和所要拷贝构造的对象的成员变量进行交换
  • 在将这个拷贝函数结束之后,tmp对象的生命周期结束,自动调用其析构函数,释放掉空间

注意:

和temp交换的this 代表的_str指针是随机值,交换给temp之后,析构对随机指针释放是会报错的。

//赋值重载:
//传统写法:
		//s1 = s3,如果不传引用返回,用传值返的话会深拷贝,代价太大了
		//new失败了之后会抛异常,用try捕获
		//string& operator = (const string& s)
		//{
		//	if (this != &s)
		//	{
		//		//1、先释放:如果s1开空间失败了,之前的空间也被释放了
		//			/*delete[] _str;
		//			_str = new char[strlen(s._str) + 1];
		//			strcpy(_str, s._str);*/

		//		//2、先开空间:下面写法可以避免上述问题
		//		char* tmp = new char[s._capacity + 1];
		//		strcpy(tmp, s._str);
		//		delete[] _str;
		//		_str = tmp;
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	return *this;
		//}

//现代写法:
//现代方法一:
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s._str);--调用构造函数
				swap(tmp);
			}
			return *this;
		}*/

//现代方法二:-- 更简单,一行代码搞定,适用于所有深拷贝
		//s 就是 s1的深拷贝,先传参,传参就是拷贝构造
		string& operator=(string s) --调用拷贝构造
		{
			swap(s);
			return *this;
		}

赋值重载的现代写法:

  • 赋值函数中,形参是string类型的对象,调用函数是值传参
  • S对象已经是拷贝构造出来的对象,直接将s对象和所需要拷贝的对象交换就好。
~string()
{
	if (_str != nullptr)
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
}

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

//普通对象调用这个
char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

//const修饰过的对象调用这个.(const引用中的const只是限定了不能通过此引用去修改变量的值)
const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

运算符[ ]的分析:

  • 如果是传值返回的话,要是想对其进行修改的话,就不行
  • 所以这里的话,必须是传的引用
3.2 增删查改的代码实现:
//s.size(&s)
//size_t size(cosnt string* const this)
size_t size() const
{
	return _size;
}
//加上const普通对象可以调用,const对象也可以调用
//不加的话const对象调用会有权限放大的风险

size_t capacity() const
{
	return _capacity;
}

//reserve扩容
void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

//1、大于capacity
//2、小于capacity,大于size
//3、小于size
//resize的作用
//扩空间 + 初始化
//删除部分数据,保留前n个
		void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				if (n > _capacity)
				{
					reserve(n);
				}

				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

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

//添加一个字符
		void push_back(char ch)
		{
			//if (_size == _capacity)
			//{
			//	reserve(_capacity == 0 ? 4 : _capacity * 2);
			//}

			//_str[_size] = ch;
			//_size++;
			//_str[_size] = '\0';
			insert(_size, ch);
		}

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

//添加一个字符串
		void append(const char* str)
		{
			/*size_t len = _size + strlen(str);
			if (len > _capacity)
			{   
				reserve(len);
			}

			strcpy(_str + _size, str);
			_size = len;*/

			insert(_size, str);
		}
//Insert一个字符
	string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}  

			/*size_t end = _size;
			while (end >= pos)
			{
				_str[end + 1] = _str[end];
				end--;
			}*/

			//最好的一种方式
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = ch;
			_size += 1;

			return *this;
		}

//Insert一个字符串
	string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (len == 0)
			{
				return *this;
			}
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			//往后挪动len个位置
			size_t end = _size + len;
			//while(end >= pos + len) //-- 最好别这样写,怕别人给极端场景,pos,end都是0
			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;

			return *this;
		}

//erase一个数值
string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t begin = pos + len;
				while (begin <= _size)
				{
					_str[begin - len] = _str[begin];
					begin++;
				}

				_size -= len;
			}
			return *this;
		}
//求字串的函数
string substr(size_t pos,size_t len=npos)const
{
   assert(pos<_size);
   size_t reallen=len;
   if(len==npos||pos+len >_size)
   {
      reallen=_size-pos;
   }
   string sub;
   for(size_t i=0;i<reallen;++i)
   {  
      sub+=_str[pos+i];
   }
   return sub;
}

size_t find(char ch, size_t pos = 0)
		{
			for (; pos < _size; pos++)
			{
				if (_str[pos] == ch)
				{
					return pos;
				}
			}
			return npos;
		}

size_t find(const char* str, size_t pos = 0)
		{
			const char* p = strstr(_str + pos, str);
			//KMP,BM只做了解

			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				return p - _str;
			}
		}

全局Swap函数和 string::Swap( )成员函数的区别:

  • 上述逻辑和之前C语言实现的逻辑并无二异
  • 只是用了C++的语法
//交换 -- 用库里的交换还要调用拷贝构造
void swap(string& s)
{
	//调用库里的 -- 库中的(类模板)是全局的,也可以写std
	::swap(_str, s._str);
	::swap(_size, s._size);
	::swap(_capacity, s._capacity);
}
3.3 String中的成员变量和全局重载
private:
	char* _str;
	size_t _size;		//有效字符个数 -- 不包含'\0'
	size_t _capacity;	//存储有效字符的空间

	//静态不能给缺省值,强制要求在类外面定义
	const static size_t npos;
};

//定义
const size_t string::npos = -1;

//流插入
ostream& operator<<(ostream& out, const string& s)
{
	//中间有'\0'就不能将整个字符串打印完
	//out << s.c_str() << endl;
	
	//趋向于这样写 -- 这样能保证每个字符都能打印出来
	for (auto ch : s)
	{
		out << ch;
	}

	return out;
}

//流提取
	istream& operator>>(istream& in, string& s)
	{
		//方法一:
		//char ch;
		//in >> ch;
		//ch = in.get();
		//while (ch != ' ' && ch != '\n')
		//{
		//	s += ch;
		//	//in >> ch;
		//	ch = in.get();
		//}

		//要清理掉之前的空间
		s.clear();
		char ch;
		ch = in.get();
		char buff[128] = { '\0' };
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				s += buff;
				memset(buff, 0, sizeof(char) * 128);
				i = 0;
			}
			ch = in.get();
		}

		s += buff;
		return in;
	}


void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
3.4 String中的运算符重载

运算符重载不一定是成员函数,可以写成全局

	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}

	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}

	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}

	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}

	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}
}
3.5 Windows上库里面对String的实现

总共有28个字节,成员变量如下:

private//好处就是对小字符操作更快,以空间换时间
		//<16 字符串存在buff数组中
		//>=16 存在_str指向的堆空间上
		  char   _buff[16];
          size_t _capacity;
		  size_t _size;
		  char* _str;

尾声
看到这里,相信大家对这个C++有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦

  • 28
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
键盘输入在计算机编程中起着非常重要的作用,为了更好地处理键盘输入,面向对象编程和STL可以提供很多有用的工具和方法。 在面向对象编程中,可以定义一个Keyboard类来处理键盘输入。这个类可以包含一个方法来读取键盘的输入,并将其保存到一个字符串中。该方法可以利用输入流来读取键盘输入,并使用STL中的string类来保存输入的内容。 另外,我们可以使用STL中的容器来存储键盘输入的多个字符。例如,使用vector类来存储按键的顺序,每当用户按下一个键,就将其添加到vector中。通过这种方式,我们可以轻松地管理和处理所有的键盘输入。 除了存储按键的顺序外,我们还可以使用STL中的map类来存储每个按键的状态。例如,使用map<char, bool>来保存每个按键是否被按下。当用户按下或释放一个键时,更新相应的按键状态。通过这种方式,我们可以方便地检查每个按键的当前状态。 另一个应用STL的例子是模拟键盘输入的延迟效果。我们可以使用STL中的chrono库来实现按键之间的延迟。通过设置适当的延迟时间,可以模拟真实键盘输入的速度和交互效果。 综上所述,通过面向对象编程和STL的应用,可以很方便地处理和模拟键盘输入。从读取键盘输入到存储按键顺序,再到检查按键状态,STL提供了各种有用的容器和方法供我们使用,使得键盘输入的处理更加简单和高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值