前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >string的模拟全实现

string的模拟全实现

作者头像
学习起来吧
发布2024-05-16 15:21:21
960
发布2024-05-16 15:21:21
举报
文章被收录于专栏:学习C/++学习C/++

📝前言

前面我们学习了string的用法,本节我们将实现string的模拟实现,话不多说,直接上手,因此我们先了解我们是多文件进行编写,因此需要注意命名空间的控制,这是文件分布图:OK,我们开始~

注:标注声明的是加在类的声明(string.h),定义在类的定义(string.cpp)如果漏写,那就是小标题就是声明🥰:

🌠 string的基本要素

我们看 成员变量三部分:

  • char* _str: 指向存储字符串内容的动态内存空间的指针。
  • size_t _size: 记录字符串的长度(不包括结尾的空字符'\0')。
  • size_t _capacity: 记录当前动态内存空间的容量。

因为模拟实现,我们避免跟库里的std::string冲突,我们需要定义在自己的命名空间,这样的好处除了避免冲突,在多个文件中,命名空间的内容是可以合并的,

代码语言:javascript
复制
# define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;

namespace self
{
	class string
	{
	public:
	--------
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
}

🌉构造函数和析构函数

string有多种构造函数,默认构造函数,拷贝构造函数,我们可以实现string(),或者可以实现全缺省构造函数string(const char* str = "");我们只需要实现一个就可以了,防止调用错误:

🌠string()

声明:string();

空字符串构建:

代码语言:javascript
复制
string::string()
{
	_str = new char[1]{'\0'};
	_size = 0;
	_capacity = 0;
}
🌉string(const char* str = “”);

声明:string(const char* str = ""); 定义:

第一种实现:

代码语言:javascript
复制
string(const char* str)
    : _str(new char[strlen(str)+1])
    , _size(strlen(str))
    , _capacity(strlen(str))
{
    strcpy(_str, str);
}

缺点:

  1. 在初始化列表中调用了strlen(str)两次,这可能会造成不必要的性能损耗。
  2. 在初始化列表中分配内存空间时,使用了strlen(str) + 1的大小,这可能会造成内存浪费。

优化后的代码:

代码语言:javascript
复制
string::string(const char* str)
    : _size(strlen(str))
{
    _capacity = _size;
    _str = new char[_size + 1];
    strcpy(_str, str);
}

优点:

  1. 在初始化列表中只调用了一次strlen(str),减少了不必要的性能损耗。
  2. 在构造函数体内分配内存空间时,使用了_size + 1的大小,更加精确地满足了字符串的需求,避免了内存浪费。

🌠~string()

代码语言:javascript
复制
string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
🌉深拷贝string(const string& s);

String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构 造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块 空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

既然这样,开多一块空间存储分开存储s1,s2不就好了。

声明:string(const string& s); 定义实现:

代码语言:javascript
复制
string::string(const string& s)
{
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
}
  1. 在初始化列表中,使用new char[s._capacity + 1]分配了一块新的内存空间,大小为参数s的容量加1(用于存储字符串结尾的空字符)。
  2. 使用strcpy()函数将参数s的字符串内容复制到新分配的内存空间中。
  3. 将新创建的字符串长度(s._size)和容量(s._capacity)赋值给当前对象的成员变量_size_capacity

🌠三个成员函数的实现

🌉const size_t size() const;

返回当前string对象中存储的字符串的长度。

代码语言:javascript
复制
const size_t string::size() const
	{
		return _size;
	}
🌉const size_t capacity() const;

返回当前string对象的容量,即可以存储的最大字符数。

代码语言:javascript
复制
const size_t string::capacity() const
	{
		return _capacity;
	}
🌉 const char* c_str() const;

返回一个指向string对象内部存储的 C 风格字符串的指针。

代码语言:javascript
复制
const char* string::c_str() const
	{
		return _str;
	}
代码语言:javascript
复制
const size_t string::size() const
	{
		return _size;
	}

🌠迭代器

迭代器是一种抽象的概念,用于遍历容器中的元素。在string类中,迭代器用于遍历字符串中的字符。

代码语言:javascript
复制
typedef char* iterator;
typedef const char* const_iterator;

iterator begin();
iterator end();

iterator begin() const;
iterator end() const;
  1. 类型定义:
    • typedef char* iterator;: 定义了一个名为iterator的类型,它是一个指向char类型的指针。这个迭代器可以用于修改字符串中的字符。
    • typedef const char* const_iterator;: 定义了一个名为const_iterator的类型,它是一个指向const char类型的指针。这个迭代器只能用于读取字符串中的字符,不能修改它们。
  2. 成员函数:
    • iterator begin();: 返回一个指向字符串首字符的迭代器。
    • iterator end();: 返回一个指向字符串末尾字符的下一个位置的迭代器。
    • iterator begin() const;: 返回一个指向字符串首字符的只读迭代器。
    • iterator end() const;: 返回一个指向字符串末尾字符的下一个位置的只读迭代器。 定义实现:
代码语言:javascript
复制
string::iterator string::begin()
{
	return _str;
}

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

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

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

这些迭代器可以用于遍历string对象中的字符,例如使用for循环或标准库算法。非常量版本的迭代器可以用于修改字符串中的字符,而常量版本的迭代器只能用于读取字符串中的字符。这种设计提供了灵活性,使用户可以选择合适的迭代器来满足不同的需求。

🌉两个遍历下标实现

下标运算符[]允许用户通过下标访问字符串中的字符。

声明:

代码语言:javascript
复制
char& operator[](size_t pos);
const char& operator[](size_t pos) const;

成员函数声明:

  • char& operator[](size_t pos);: 重载了下标运算符[],接受一个size_t类型的下标作为参数,返回一个对字符串中对应位置的字符的引用。这个版本的下标运算符可以用于修改字符串中的字符。
  • const char& operator[](size_t pos) const;: 重载了下标运算符[],接受一个size_t类型的下标作为参数,返回一个对字符串中对应位置的字符的常量引用。这个版本的下标运算符只能用于读取字符串中的字符,不能修改它们。
代码语言:javascript
复制
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];
}

成员函数实现:

  • char& string::operator[](size_t pos): 首先使用assert断言检查下标是否在字符串的范围内,然后返回_str[pos]的引用。这允许用户通过下标修改字符串中的字符。
  • const char& string::operator[](size_t pos) const: 同样使用assert断言检查下标是否在字符串的范围内,然后返回_str[pos]的常量引用。这只允许用户通过下标读取字符串中的字符,不能修改它们。

🌠总代码

string的接口函数远远不止我介绍的这几种,阿森也会加更其他接口函数,以下是全代码的的接口和测试:

🌉string.h

代码语言:javascript
复制
// 禁用安全警告,这通常用于使用某些可能被认为是不安全的函数时
#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream> // 引入输入输出流库
#include <assert.h> // 引入断言库
using namespace std; // 使用标准命名空间

// 自定义命名空间self
namespace self {

    // 自定义字符串类
    class string {
    public:
        // 定义迭代器类型
        typedef char* iterator;
        typedef const char* const_iterator;

        // 构造函数
        string(const char* str = ""); // 使用C风格字符串初始化
        string(const string& s); // 拷贝构造函数

        // 赋值运算符重载
        string& operator=(const string& s);

        // 析构函数
        ~string();

        // 获取字符串大小
        const size_t size() const;

        // 获取C风格字符串
        const char* c_str() const;

        // 获取容量
        const size_t capacity() const;

        // 索引运算符重载
        char& operator[](size_t pos);
        const char& operator[](size_t pos) const;

        // 获取迭代器指向字符串开始和结束位置
        iterator begin();
        iterator end();

        // 获取常量迭代器指向字符串开始和结束位置
        iterator begin() const;
        iterator end() const;

        // 预留空间
        void reserve(size_t n);

        // 在字符串末尾添加一个字符
        void push_back(char ch);

        // 追加一个C风格字符串
        void append(const char* tmp);

        // 加法赋值运算符重载
        string& operator+=(char ch);
        string& operator+=(const char* str);

        // 插入字符或字符串
        void insert(size_t pos, char ch);
        void insert(size_t pos, const char* str);

        // 删除指定位置的字符或字符串
        void erase(size_t pos = 0, size_t len = npos);

        // 查找字符或字符串
        size_t find(char c, size_t pos = 0);
        size_t find(const char* s, size_t pos = 0);

        // 交换两个字符串
        void swap(string& str);

        // 获取子字符串
        string substr(size_t pos = 0, size_t len = npos);

        // 比较运算符重载
        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 _size;
        // 字符串容量
        size_t _capacity;

        // 静态常量npos,表示位置无效
        const static size_t npos;
        		//特例
		//const static size_t npos = -1;
		
		//只有size_t能支持,其他类型都不可以
		//const static double npos = -1;

    };

    // 输入运算符重载
    istream& operator>>(istream& is, string& str);

    // 输出运算符重载
    ostream& operator<<(ostream& os, const string& str);
}

// 注意:这里只是声明了npos常量,并未初始化。它应该在类的实现文件(.cpp)中进行初始化,
// 初始化为一个表示无效位置的常量值,通常是size_t的最大值或-1。

🌉string.cpp

代码语言:javascript
复制
# define _CRT_SECURE_NO_WARNINGS 1
#include "string.h"
namespace self
{
	const  size_t string::npos = -1;


	//string::string()
	//{
	//	_str = new char[1]{'\0'};
	//	_size = 0;
	//	_capacity = 0;
	//}
	string::string(const char* str)
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_size + 1];
		strcpy(_str, str);
	}

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

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

		return *this;
	}

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

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

	const size_t string::capacity() const
	{
		return _capacity;
	}

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

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

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

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

	const 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];
	}


	 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 (_size == _capacity)
		 {
			 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 (_size + len > _capacity)
		 {
			 reserve(_size + len);
		 }

		 //for (size_t i = 0; i <= len; i++)
		 //{
			// _str[_size + i] = tmp[i];
		 //}

		 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 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;
	 }

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

		 memmove(_str + pos, str, lenth);
		 _size += lenth;
	 }

	 void string::erase(size_t pos , size_t len )
	 {
		 assert(pos < _size);
		 //要删除的长度比pos位置长,有多少删多少
		 if (len > _size - pos)
		 {
			 _str[pos] = '\0';
			 _size = len;
		 }
		 else
		 {
			 strcpy(_str + pos, _str + pos + len);
			 _size -= len;
		 }
	 }

	 //找到返回下标
	 size_t string::find(char c, size_t pos)
	 {
		 assert(pos < _size);
		 for (size_t i = pos; i < _size; i++)
		 {
			 if (_str[i] == c)
			 {
				 return i;
			 }
		 }

		 return npos;

	 }

	 size_t string::find(const char* str, size_t pos)
	 {
		 assert(pos < _size);
		 const char* pch = strstr(_str + pos , str);
		 if (pch == nullptr)
		 {
			 return npos;
		 }
		 else
		 {
			 return pch - _str;
		 }
	 }

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


	 string string::substr(size_t pos, size_t len)
	 {
		 len = (len == npos) ? (_size - pos) : len;// 如果 len 为 npos,则设置为字符串剩余长度
		 
		 if (pos >= _size)// 如果 pos 超出字符串长度,则返回空字符串
			 return string::string();
		 
		 len = (pos + len >= _size) ? (_size - pos) : len;// 如果子字符串超出原始字符串的末尾,则调整 len
		 
		 char* new_str = new char[len + 1];
		 strncpy(new_str, _str + pos, len);
		 new_str[len] = '\0';

		 return string(new_str); //返回子字符串的 bit::string 对象
	}


		//这个sub为局部对象,返回会析构,
	 //string string::substr(size_t pos, size_t len)
	 //{
		// // len大于后面剩余字符,有多少取多少
		// if (len > _size - pos)
		// {
		//	 string sub(_str + pos);
		//	 return sub;
		// }
		// else
		// {
		//	 string sub;
		//	 sub.reserve(len);
		//	 for (size_t i = 0; i < len; i++)
		//	 {
		//		 sub += _str[pos + 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) || (*this < s);
	 }

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

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

	 bool string::operator!=(const string& s) const
	 {
		 return !(*this == s);
	 }
	
	 void string::clear()
	 {
		 _str[0] = '\0';
		 _size = 0;
	 }

	 //istream& operator>>(istream& is, string& str)
	 //{
		// str.clear();
		// char ch ='a';
		// scanf("%c", &ch);
		// while (ch != ' ' && ch != '\n')
		// {
		//	 str += ch;
		//	 scanf("%c", &ch);

		// }

		// return is;
	 //}

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

		 }

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

		 return os;
	 }

}

🌉test.cpp

代码语言:javascript
复制
# define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include "string.h"
namespace self
{
	void test_string01()
	{
		self::string s1("hello world");
		cout << s1.c_str() << endl;

		string::iterator it = s1.begin();
		while (it != s1.end())
		{
	        //it = 'y';
			cout << *it << " ";
			it++;
		}
		cout << endl;

		string::const_iterator it2 = s1.begin();
		while(it2 != s1.end())
		{ 
			//*it2 = 'y';
			cout << *it2;
			it2++;
		}
		cout << endl;

		for (auto e : s1)
		{
			cout << e << endl;
			e++;
		}
		cout << endl;

		const self::string s2("xxxxxxx");
		for (size_t i = 0; i < s2.size(); i++)
		{
			//s2[i] = 'y';
		}
		for (size_t i = 0; i < s2.size(); i++)
		{
			cout << s2[i] << ' ';
		}
		for (auto& e : s2)
		{
			cout << e << endl;
			e++;
		}
		cout << endl;
	}

	void test_string02()
	{
		self::string s3("hello world");
		cout << s3.c_str() << endl;
		
		s3.push_back('x');
		cout << s3.c_str() << endl;

		s3.push_back('y');
		cout << s3.c_str() << endl;

		s3.push_back('z');
		cout << s3.c_str() << endl;

		self::string s4("hello world");
		cout << s4.c_str() << endl;
		
		s4.append("xxxxxx");
		cout << s4.c_str() << endl;

		s4.append("yyyyyy");
		cout << s4.c_str() << endl;
	}

	void test_string03()
	{
		self::string s5("hello world");
		cout << s5.c_str() << endl;
		
		s5 += 'x';
		cout << s5.c_str() << endl;
	
		s5 += "yyyyyy";
		cout << s5.c_str() << endl;

	}

	void test_string04()
	{
		self::string s6("hello world");
		cout << s6.c_str() << endl;

		s6.insert(2, 'x');
		cout << s6.c_str() << endl;

		s6.insert(3, "yyy");
		cout << s6.c_str() << endl;
		
		s6.erase(2, 1);
		cout << s6.c_str() << endl;
		
		s6.erase(2, 3);
		cout << s6.c_str() << endl;
	}

	void test_string05()
	{
		self::string s7("hello world");
		cout << s7.c_str() << endl;

		size_t location = s7.find('h', 1);
		cout << location<< endl;

		size_t location02 = s7.find("he", 1);
		cout << location02 << endl;
	}

	void test_string06()
	{
		self::string buyer("money");
		self::string seller("goods");

		cout << "Before the swap, buyer has " << buyer.c_str();
		cout << " and seller has " << seller.c_str() << '\n';

		buyer.swap(seller);

		cout << " After the swap, buyer has " << buyer.c_str();
		cout << " and seller has " << seller.c_str() << '\n';
	}

	void test_string07()
	{
		self::string str = "We think in generalities, but we live in details.";

		string str2 = str.substr(3, 5);     // "think"

		cout << str2.c_str() << ' ' ;

	}

	void test_string08()
	{
		self::string url("https://legacy.cplusplus.com/reference/string/string/");
		size_t pos1 = url.find(':', 1);
		self::string url1 = url.substr(0, pos1 - 0);
		cout << url1.c_str() << endl << endl;

		cout << pos1 << endl;
		size_t pos2 = url.find('/', pos1 + 3);
		cout << pos2 << endl;

		self::string url2 = url.substr(pos1 + 3, pos2 - (pos1 + 3));
		cout << url2.c_str() << endl << endl;

		self::string url3 = url.substr(pos2 + 1);
		cout << url3.c_str() << endl;
	}

	void test_string09() 
	{
		self::string s1("hello world");
		cout << s1 << endl;

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

	void test_string10()
	{
		self::string s8("hello world");
		cout << s8.c_str() << endl;

		string s9(s8);
		cout << s9.c_str() << endl;

		string s10 = s8;
		cout << s10.c_str() << endl;


	}
}
int main()
{
	//self::test_string01();
	//self::test_string02();
	//self::test_string03();
	//self::test_string04();
	//self::test_string05();
	//self::test_string06();
	//self::test_string07();
	//self::test_string08();
	//self::test_string09();
	//self::test_string10();

	string s1("hello world");
	return 0;
}

测试一个部分:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-15,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 📝前言
  • 🌠 string的基本要素
    • 🌉构造函数和析构函数
      • 🌠string()
        • 🌉string(const char* str = “”);
      • 🌠~string()
        • 🌉深拷贝string(const string& s);
        • 🌉const size_t size() const;
        • 🌉const size_t capacity() const;
        • 🌉 const char* c_str() const;
    • 🌠三个成员函数的实现
    • 🌠迭代器
      • 🌉两个遍历下标实现
      • 🌠总代码
        • 🌉string.h
          • 🌉string.cpp
            • 🌉test.cpp
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档