【C++程序员的自我修炼】简单实现 string 库的常用接口函数

天接云涛连晓雾

星河欲转千帆舞


目录

string 类环境的搭建

实现 c_str() 函数

 实现 size() 函数

重载运算符operator[]

实现简单迭代器 begin()、end()

实现 reserve() 函数

实现 push_back() 函数

实现 append() 函数 

 重载运算符operator+=

实现 insert() 函数

实现 erase() 函数

 实现 find() 函数

实现 swap() 函数

 string 的深拷贝

 string 的赋值拷贝

实现 substr() 函数

实现 string 比较大小运算符重载

实现 clear() 函数

实现 string 中的流重载 

整体代码的实现

 契子

  前面我们已经大致了解到 string 库底层的许多接口函数,为了巩固理解以及快速上手今天我们就来实现一下简单 string 库的实现

  首先这里提前说明:只是简单的实现底层接口掌握其基本逻辑,并不会涉及太多,比如说模板以及迭代器的底层实现


string 类环境的搭建

首先我们练习的话可以声明与定义分开来写,然后再用一个单独的文件进行测试,像这样:

(这里顺便提一下)在C++ 标准 string 库里一般都是放在 .h文件 中且都是写成内联的形式 ~

首先,先把我们的构造、析构函数先搭建出来:

//string.h
#pragma once
#include<iostream>
#include<assert.h>
using std::cout;
using std::endl;
using std::cin;
using std::istream;
using std::ostream;

namespace Mack
{
	class string
	{
	public:
		string(const char* str = "");
		~string();
	private:
		char* _str;
		size_t _capacity;
		size_t _size;
		const static size_t npos;
	};
}
//string.cpp
#include"string.h"
namespace Mack
{
	const size_t string::npos = -1;
	string::string(const char* str)
		:_size(strlen(str))
	{
		_str = new char[_size + 1];
		_capacity = _size;
		strcpy(_str, str);
	}

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

 然后把在标准库中我们的要用的函数的命名空间展开 ~

实现 c_str() 函数

在 C++ 中 c_str() 库函数的作用就是获取一个字符串的首元素地址,所以我们直接返回 _str 即可

    const char* c_str() const
	{
		return _str;
	}
//测试代码
void TestString()
{
	string s1("hello world");
	cout << s1.c_str() << endl;
}


 实现 size() 函数

在 C++ 中 size() 库函数的作用就是获取一个字符串的长度(不包含 '\0' 哦)

所以我们直接返回  _size 即可

	size_t size() const
	{
		return _size;
	}

重载运算符operator[]

对于运用场景的不同我们重载了两个operator[],不过基础逻辑还是一样的,提取当前 pos 位置的字符并返回,注意要判断 pos 的合法范围哦 ~ 不要越界了还不知道

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

	const char& operator[](size_t pos) const
	{
		assert(pos < _size);
		return _str[pos];
	}
//测试代码
void TestString()
{
	string s1("hello world");
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
}

这里和上面 size() 一起测试了,都没有问题 


实现简单迭代器 begin()、end()

对于迭代器我们的印象是这样一长串东西(是一个模板)

我们简单来看,其实可以用 指针 浅浅的代替一下:

begin() 返回的是字符串的首元素位置,我们直接返回 _str 即可

end() 返回的是字符串的末尾位置,我们之间返回 _str + _size 即可

//string.h
    class string
    {
    public:
    typedef char* iterator;
	iterator begin();
	iterator end();
    //..
    };

//string.cpp
	string::iterator string::begin()
	{
		return _str;
	}

	string::iterator string::end()
	{
		return _str + _size;
	}
//代码测试
void TestString()
{
	string s1("hello world");
	for (auto i : s1)
	{
		cout << i << " ";
	}
	cout << endl;
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
}

我们知道我们 范围for 的底层逻辑就是迭代器,这里浅浅的实现了一下 


实现 reserve() 函数

根据我们前面所学,我们知道 resserve 是对空间的预留,我们可以开辟一个我们自己预定的空间tmp,再将已有数据拷贝到该空间,并释放原 _str ,最后将 _str 指向 tmp 即可

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


实现 push_back() 函数

push_back() 相当于我们顺序表的尾插,要先判断是否需要扩容,再考虑末尾插入,别忘了 '\0' 哦 

    void string::push_back(char ch)
	{
		if (_capacity == _size)
		{
			size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcapacity);
		}
		_str[_size] = ch;
		_str[_size + 1] = '\0';
		_size++;
	}
//代码测试
void TestString()
{
	string s1("hello world");
	s1.push_back('x');
	cout << s1.c_str() << endl;
	s1.push_back('y');
	cout << s1.c_str() << endl;
}


实现 append() 函数 

append() 的常见用法就是在末尾追加一个字符串,可以将目标字符串 str 拷贝到源字符串 _str 中

(拷贝时覆盖 _str 中 '\0' 的位置,但是 str 末尾的 '\0' 会保留,所以我们不需要补 '\0' )

    void append(const char* str)
	{
		size_t len = strlen(str);
		if (len + _size > _capacity)
		{
			reserve(len + _size);
		}
		strcpy(_str + _size, str);
		_size += len;
	}
//测试
void TestString()
{
	string s1("hello world");
	s1.append("xxxxx");
	cout << s1.c_str() << endl;
}


 重载运算符operator+=

在我们的印象中,operator+= 可以追加一个字符或者字符串,这样我们就可以复用一下

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

	string& operator+=(const char* str)
	{
		append(str);
		return *this;
	}
//测试
void TestString()
{
	string s1("hello world");
	s1 += 'x';
	cout << s1.c_str() << endl;
	s1 += "yyy";
	cout << s1.c_str() << endl;
}


实现 insert() 函数

insert() 函数常用于我们的头插,但是经过我们上次的学习我们知道 insert() 的功能不仅于此

insert() 不仅可以指定位置插入字符,还可以指定位置插入字符串

<1> 指定位置插入字符:跟我们顺序表的指定位置插入差不多,挪动数据在进行插入即可

<2> 指定位置插入字符串:我们可以挪动将指定位置后的数据挪动 len 个(目标字符串 str 长度)然后将 str 拷贝到指定位置即可

	void insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			size_t newcpacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcpacity);
		}
		size_t len = _size + 1;
		while (pos < len)
		{
			_str[len] = _str[len - 1];
			len--;
		}
		_str[pos] = ch;
		_size++;
	}

	void insert(size_t pos, const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len);
		}
		size_t end = len + _size;
		while (end > pos)
		{
			_str[end] = _str[end - len];
			end--;
		}
		memcpy(_str + pos, str, len);
		_size += len;
	}
//代码测试
void TestString()
{
	string s1("hello world");
	s1.insert(0, 'x');
	cout << s1.c_str() << endl;
	s1.insert(0, "yyy");
	cout << s1.c_str() << endl;
	s1.insert(6, 'z');
	cout << s1.c_str() << endl;
	s1.insert(9, "mmm");
	cout << s1.c_str() << endl;
}


实现 erase() 函数

根据我们之前对 string 库的理解,可以知道 erase() 可以做到 删除指定位置 pos 的任意字符 len

一开始我们可以分类讨论一下:

<1> len 超过了从 pos 位置开始的剩余字符长度 -- 全部删完(将该位置用 '\0' 覆盖)

<2> len 没有超过 pos 位置开始的剩余字符长度 -- 删中间(将剩余字符拼接到 pos 位置)

	void erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if(len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
	}
//代码测试
void TestString()
{
	string s1("hello world");
	s1.erase(8, 5);
	cout << s1.c_str() << endl;
	s1.erase(2, 3);
	cout << s1.c_str() << endl;
}


 实现 find() 函数

根据我们之前的学习,我们知道 find() 函数有两种常见的功能:
<1> 从 pos 位置开始在主串中查找单个字符

<2> 从 pos 位置开始在主串中查找与子串相匹配的位置:我们可以使用 BF 暴力算法, strstr 的底层就是 BF

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

	size_t find(const char* sub, size_t pos)
	{
		char* p = strstr(_str + pos, sub);
		return  p - _str;
	}


实现 swap() 函数

为什么 string 库里面要单独搞一个 swap 出来呢?用下面这个不香吗

虽然上面这个也能完成交换,但是造成了很大的代价!!!

简单来说明一下:

<1> 我们想交换 s1、s2 的数据首先要拷贝构造一个与 s1 一模一样的空间 c

<2> 然后开个与 s2 一样大的空间并将字符串拷贝过去,让 s1指向这块空间,并释放原空间

<3> 最后 s2 开一个与 c 一样大的空间并将字符串拷贝过去,让 s2 指向这块空间,并释放原空间

以上用了一个拷贝构造和两次赋值构造,都是深拷贝要开空间拷贝数据

所以 string 库里专门提供了一个内部的 swap :

这个 swap 只要借助一个变量交换一下指针指向即可

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

只是交换指针指向的话,我们可以借助 std 内部的

//代码测试
void TestString()
{
	string s1("hello");
	string s2("world");
	s1.swap(s2);
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
}


 string 的深拷贝

    string(const string& s)
    {
        _str = new char[s._capacity + 1];
        strcpy(_str, s._str);
        _size = s._size;
        _capacity = s._capacity;
    }

 string 的赋值拷贝

 string& operator=(const string& s)
    {
        if (this != &s)
        {
            char* tmp = new char[s._capacity + 1];
            strcpy(tmp, _str);
            delete[] _str;
            _str = tmp;
            _size = s._size;
            _capacity = s._capacity;
        }
        return *this;
    }
//代码测试
void TestString()
{
	string s1("hello world");
	string s3;
	s3 = s1;
	cout << s1.c_str() << endl;
}


实现 substr() 函数

substr 常被我们用来提取字符串

<1> 从 pos 位置开始剩余字符串小于 len (要提取的字符串长度)我们就直接提取完

<2> 从 pos 位置开始剩余字符串不小于 len 则从 pos 位置开始一个字符一个字符的拷贝 len 个字符

    string substr(size_t pos, size_t len)
	{
		if (_size - pos < len)
		{
			string sub(_str + pos);
			return sub;
		}
		else
		{
			string sub;
			sub.reserve(len);
			for (int i = pos; i < pos + len; i++)
			{
				sub += _str[i];
			}
			return sub;
		}
	}
//代码测试
void TestString()
{
	string s1("hello world");
	string s3 = s1.substr(6, 5);
	cout << s3.c_str() << endl;
}


 

实现 string 比较大小运算符重载

关于 string 的比较大小,我们可以借助 strcmp :

1.strcmp常用于比较字符串的大小

2.第一个字符串大于第二个字符串,则返回大于0的值

3.第一个字符串等于第二个字符串,则返回0

4.第一个字符串小于第二个字符串,则返回小于0的值

5.比较两个字符串对应位置上的字符ASCLL码值的大小来判断字符串的大小

完成一部分比较后,我们还可以对结果进行复用 

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

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

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

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

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

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

 


实现 clear() 函数

clear 在我们 Linux 中经常使用就是清空的意思,而这里就是清空整个字符串

我们只需要将 '\0' 塞在开头即可

    void clear()
	{
		_str[0] = '\0';
		_size = 0;
	}


 

实现 string 中的流重载 

我们之前写日期类的时候是将流重载写成友元函数,而这里我们可以不写成友元,因为我们可以不访问类中元素就可以实现相关操作

我们先来看我们的流提取

	istream& operator>> (istream& is, string& str)
	{
		str.clear();
		char ch;
		ch = is.get();
		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			ch = is.get();
		}
		return is;
	}

cin 进行提取会将原内容进行覆盖,所以我们要提前清空一下数据

这里我们使用了 get() -- 显示提取一个字符 

我们把取到的字符追加到对应的字符串即可,但我们输入 空格 或者 回撤 键就会退出提取

	ostream& operator<< (ostream& os, const string& str)
	{
		for (int i = 0; i < str.size(); i++)
		{
			os << str[i];
		}
		return os;
	}

流插入我们可以一个字符一个字符的输出到我们的显示屏上

//代码测试
void TestString()
{
	string s1;
	cin >> s1;
	cout << s1 << endl;
}

整体代码的实现

string.h

#pragma once
#include<iostream>
#include<assert.h>
using std::cin;
using std::cout;
using std::endl;
using std::istream;
using std::ostream;


namespace Mack
{
	class string
	{
	public:
		typedef char* iterator;
        string(const string& s);
		string& operator=(const string& s);
		string(const char* str = "");
		~string();

		const char* c_str() const;
		size_t size() const;

		char& operator[](size_t pos);
		const char& operator[](size_t pos) const;

		iterator begin();
		iterator end();

		void reserve(size_t n = 0);
		void push_back(char ch);
		void append(const char* str);

		string& operator+=(char ch);
		string& operator+=(const char* ch);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len);

		size_t find(char ch, size_t pos);
		size_t find(const char* sub, size_t pos);

		void swap(string& s);

		string substr(size_t pos, size_t len);

		bool operator<(const string& s) const;
		bool operator<=(const string& s) const;
		bool operator>(const string& s) const;
		bool operator>=(const string& s) const;
		bool operator==(const string& s) const;
		bool operator!=(const string& s) const;

		void clear();


	private:
		char* _str;
		size_t _capacity;
		size_t _size;
		const static size_t npos;
	};
	ostream& operator<< (ostream& os, const string& str);
	istream& operator>> (istream& is, string& str);
}

string.cpp

#include"string.h"

namespace Mack
{
    string::string(const string& s)
    {
        _str = new char[s._capacity + 1];
        strcpy(_str, s._str);
        _size = s._size;
        _capacity = s._capacity;
    }

    string& string::operator=(const string& s)
    {
        if (this != &s)
        {
            char* tmp = new char[s._capacity + 1];
            strcpy(tmp, _str);
            delete[] _str;
            _str = tmp;
            _size = s._size;
            _capacity = s._capacity;
        }
        return *this;
    }

	const size_t string::npos = -1;
	string::string(const char* str)
		:_size(strlen(str))
	{
		_str = new char[_size + 1];
		_capacity = _size;
		strcpy(_str, str);
	}

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

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

	size_t string::size() const
	{
		return _size;
	}

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

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

	string::iterator string::begin()
	{
		return _str;
	}

	string::iterator string::end()
	{
		return _str + _size;
	}

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

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

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

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

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

	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			size_t newcpacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcpacity);
		}
		size_t len = _size + 1;
		while (pos < len)
		{
			_str[len] = _str[len - 1];
			len--;
		}
		_str[pos] = ch;
		_size++;
	}

	void string::insert(size_t pos, const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len);
		}
		size_t end = len + _size;
		while (end > pos)
		{
			_str[end] = _str[end - len];
			end--;
		}
		memcpy(_str + pos, str, len);
		_size += len;
	}

	void string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if(len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
	}

	size_t string::find(char ch, size_t pos)
	{
		for (int i = pos; i < size(); i++)
		{
			if (_str[i] == ch)
				return i;
		}
		return npos;
	}

	size_t string::find(const char* sub, size_t pos)
	{
		char* p = strstr(_str + pos, sub);
		return  p - _str;
	}

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

	string string::substr(size_t pos, size_t len)
	{
		if (_size - pos < len)
		{
			string sub(_str + pos);
			return sub;
		}
		else
		{
			string sub;
			sub.reserve(len);
			for (int i = pos; i < pos + len; i++)
			{
				sub += _str[i];
			}
			return sub;
		}
	}

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

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

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

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

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

	istream& operator>> (istream& is, string& str)
	{
		str.clear();
		char ch;
		ch = is.get();
		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			ch = is.get();
		}
		return is;
	}

	ostream& operator<< (ostream& os, const string& str)
	{
		for (int i = 0; i < str.size(); i++)
		{
			os << str[i];
		}
		return os;
	}

}

先介绍到这里啦~

有不对的地方请指出💞

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烟雨长虹,孤鹜齐飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值