c++:模拟实现string类

一、默认成员函数

1.成员变量

cpp 复制代码
private:
		char* _str;
		//string对象中字符串的长度
		size_t _size;
		//string对象中可容纳字符串长度
		size_t _capacity;
public:
		const static size_t npos;
//static类型的成员变量只能在类外初始化
	const size_t string::npos = -1;

注意:_capacity是可容纳的字符长度,不包含'\0',在为string类开空间时要多一个长度来放'\0',即默认构造时要开辟_capacity+1大小。

2.默认构造

cpp 复制代码
//构造函数
		string(const char* str = "")
		{
			size_t len = strlen(str);
			_capacity = len;
			_size = len;
			//开辟_capacity+1个空间是因为还有一个'\0'
			_str = new char[_capacity + 1];
			//把str的内容拷贝给_str,要注意最后的'\0'
			memcpy(_str, str, _capacity + 1);
		}

注意:这里的拷贝str内容要用memcpy,不能用strcpy,因为strcpy遇到'\0'就停止了,拷贝不到'\0'。

3.拷贝构造

cpp 复制代码
        string(const string& s)
		{
			_capacity = s._capacity;
			_size = s._size;
			//开辟_capacity+1个空间是因为还有一个'\0'
			_str = new char[_capacity + 1];
			//把str的内容拷贝给_str,要注意拷贝到'\0'
			memcpy(_str, s._str, _capacity + 1);
		}	

注意:拷贝构造是深拷贝。

4.operator=

cpp 复制代码
        //传进来一个临时拷贝来的局部变量s,出作用域销毁
		void swap(string& s)
		{
			std::swap(s._str, _str);
			std::swap(s._size, _size);
			std::swap(s._capacity, _capacity);

		}
        string& operator=(string s)
		{
			//v3,自己函数重载一个swap函数
			swap(s);
			return *this;
		}

注意:这里的operator=传的参数是 string s,在传参时会调用拷贝构造(深拷贝) 生成一个局部变量s,等函数结束时会自动调用string的析构函数释放掉,再通过自己写的swap来交换这两个string对象的成员,swap函数的实现调用的是c++库中的swap。这里巧妙地运用了局部自定义变量出作用域会自动调用析构函数的特性,使我们不用自己去开空间。

5.析构函数

cpp 复制代码
        ~string()
		{
			delete[] _str;
			_capacity = 0;
			_size = 0;
		}

注意:开辟空间时用的new [] ,释放空间就要用 delete []。

二、迭代器

1.begin和end

cpp 复制代码
        typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}
		const_iterator begin()const
		{
			return _str;
		}
		//end的位置是字符串末尾的下一个位置,所以要加_size
		iterator end()
		{
			return _str + _size;
		}
		const_iterator end()const
		{
			return _str + _size;
		}

注意:这里要生成const类型的迭代器和非const类型的迭代器以应对不同类型的使用情况。还要注意end的位置是字符串末尾的下一个位置。

rbegin和rend的实现类似。可以自己试一试。

2.c_str

cpp 复制代码
        const char* c_str()const
		{
			return _str;
		}

注意:c_str是为了兼容c语言的接口,返回的是一个char*的字符串。

可以这样用:string s("abcdef");printf("%s\n",s.c_str());

就会打印出abcdef。

3._capacity相关的接口

3.1 size,capacity,empty

cpp 复制代码
        size_t size()const
		{
			return _size;
		}
		size_t capacity()const
		{
			return _capacity;
		}

注意:_size和_capacity都是private的私有成员变量,用接口来获得相应数据。

//this指针本质是Type* const pointer ,不改变*this时,可以给*this加上const--->const Type* const pointer

cpp 复制代码
        bool empty()const
		{
			return _size == 0;
		}

注意:_size是表示string对象的有效字符个数,当它为0时表示string对象为空。

3.2 reserve

cpp 复制代码
//传的参数是你要开的有效空间,在内部会多开一个空间放'\0'
		void reserve(size_t n)
		{
			//先判断容量
			//不缩容
			if (n >= _capacity)
			{
				char* tmp = new char[n + 1];
				//要把'\0'拷贝到新的_str里
				memcpy(tmp, _str, _size + 1);
				delete[] _str;
				_str = tmp;
				_capacity = n;

			}
		}

reserve一般不缩容,只扩容,扩容时只需要开一个n+1长度的空间,保存_str的数据,释放_str的空间,刷新_capacity的数据即可。

3.3 resize

cpp 复制代码
		//有两种情况,扩大/缩小
		void resize(size_t size, char c = '\0')
		{
			//缩小
			if (size < _size)
			{
				_size = size;
				_str[_size] = c;
			}
			else
			{
				reserve(size);
				memset(_str + _size, c, (size + 1 - _size));
				_size = size;
				_str[_size] = '\0';

			}

		}

注意:缩小是简单,只要把_size刷新成你传的参数size大小,在把size位置的值改成'\0',就完成了resize的缩小。扩大时,要考虑扩容,即把它扩容到size大小,可以用reserve来扩容,然后再把新扩容的位置的值,刷新为'\0'即可,char c='\0'给的是缺省参数,你可以把扩容的部分初始化成其他值。

4.modify相关接口

4.1 push_back

cpp 复制代码
        void push_back(char ch)
		{
			//先判断容量
			if (_size >= _capacity)
			{
				//string用空串初始化(默认构造)时,_capacity和_size都为0,这里的_capacity不能直接*2
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size++] = ch;
			_str[_size] = '\0';
		}

注意:尾插时要先判断容量再尾插

4.2 append

cpp 复制代码
		void append(const char* str)
		{
			size_t len = strlen(str);
			//先判断容量
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			//这里要把str的'\0'也拷贝进来
			memcpy(_str + _size, str, len + 1);
			//_capaciry在reserve中处理,这里只要改_size即可
			_size += len;
		}

注意:尾插一段字符串,也要先扩容再尾插。

5.operator运算符

5.1 operator+= c/str

cpp 复制代码
        string& operator+=(char c)
		{
			push_back(c);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

这里可以复用push_back和append.

5.2 operator[] 非常好用

cpp 复制代码
		char& operator[](size_t index)
		{
			return _str[index];
		}
		const char& operator[](size_t index) const
		{
			return _str[index];
		}

5.3 operator>,>=,<,<=,==,!=

cpp 复制代码
        bool operator<(const string& s)
		{
			//比较较短的串,
			int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);

			// "hello" "hello"   false  ,ret==0------_size<s._size为假 返回false
			// "helloxx" "hello" false  ,ret==0------_size<s._size为假 返回false
			// "hello" "helloxx" true   ,ret==0------_size<s._size为真 返回true
			//普通情况 "elloxx" "helloxx",ret!=0------ret<0为真 返回true (e<h)
			//普通情况 "helloxx" "elloxx",ret!=0------ret<0为假 返回false  (e<h)
			return ret == 0 ? _size < s._size : ret < 0;
		}

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

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

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

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

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

注意:这里只要完成一个函数,其他的都可以复用。

5.4 opoerator>>和operator<<

cpp 复制代码
//先在类域中声明 友元函数
public:
        friend std::ostream& operator<<(std::ostream& _cout, const mystring::string& s);
		friend std::istream& operator>>(std::istream& _cin, mystring::string& s);
operator>>
cpp 复制代码
        //流插入本质是用新的值覆盖旧的数据,先用clear把它的数据清空
        void clear()
		{
			_size = 0;
			_str[0] = '\0';

		}

	std::istream& operator>>(std::istream& _cin, mystring::string& s)
	{
		s.clear();
		char ch = _cin.get();
		//处理缓冲区的换行和空格
		while (ch == ' ' || ch == '\n')
		{
			ch = _cin.get();
		}
        //这里为了防止它频繁扩容,为他加了一个数组,一段一段的拷贝到string 对象里,减少扩容次数
		char buf[128];
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buf[i++] = ch;
			if (i == 127)
			{
				//防止拷贝时,没有结束符
				buf[i] = '\0';
				s += buf;
				i = 0;
			}
			ch = _cin.get();
		}
		return _cin;
	}
operator<<
cpp 复制代码
	std::ostream& operator<<(std::ostream& _cout, const mystring::string& s)
	{
		for (auto ch : s)
		{
			_cout << ch;
		}
		return _cout;
	}

这里用的范围for本质是调用寄存器来实现的。

6.find相关接口

6.1 find

cpp 复制代码
        // 返回c在string中第一次出现的位置
        size_t find(char c, size_t pos = 0) const
		{
			assert(pos <= _size);
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == c)
				{
					return i;
				}
			}
			return npos;
		}

		// 返回子串s在string中第一次出现的位置
		size_t find(const char* s, size_t pos = 0) const
		{
			assert(pos <= _size);
			const char* tmp = strstr(_str + pos, s);
			if (tmp == nullptr)
			{
				return npos;
			}
			else
			{
				return tmp - _str;
			}

		}

注意:在查找之前要先判断pos是否合法。

6.2 substr

cpp 复制代码
        string substr(size_t pos = 0, size_t len = npos) const
		{
			assert(pos <= _size);
			size_t n = len;
			if (len == npos || len + pos > _size)
			{
				n = _size - pos;//这个n的长度是包含'\0'的
			}
			string tmp;
			//开空间
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}
			return tmp;
        }

返回一个字串。

6.3 insert

cpp 复制代码
        // 在pos位置上插入字符c/字符串str,并返回该字符的位置
		string& insert(size_t pos, char c)
		{
			assert(pos <= _size);
			if (_size + 1 >= _capacity)
			{
				reserve(_capacity + 1);
			}
			memmove(_str + pos + 1, _str + pos, _size - pos + 1);
			_str[pos] = c;
			return *this;
		}

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			int len = strlen(str);
			if (_size + len >= _capacity)
			{
				reserve(_size + len);
			}
			memmove(_str + pos + len, _str + pos, _size + 1 - pos);
			_size = _size + len;
			memcpy(_str + pos, str, len);

			return *this;
        }

注意:这里只能用memmove防止内存重叠。

6.4 erase

cpp 复制代码
        // 删除pos位置上的元素,并返回该元素的下一个位置
		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			if (len != npos && len + pos >= _size)
			{
				len = _size - pos;
			}
			memmove(_str + pos, _str + pos + len, _size + 1 - pos - len);
			_size = _size - len;
			return *this;
		}

注意:这里只能用memmove防止内存重叠。


总结

想要学好库中的string最好自己实现一个出来,能够加深对string的理解。

蟹蟹观看!点赞!关注!收藏!评论!一键三连!

相关推荐
小魏冬琅7 分钟前
探索面向对象的高级特性与设计模式(2/5)
java·开发语言
lihao lihao10 分钟前
C++stack和queue的模拟实现
开发语言·c++
TT哇21 分钟前
【Java】数组的定义与使用
java·开发语言·笔记
天天进步201526 分钟前
Lodash:现代 JavaScript 开发的瑞士军刀
开发语言·javascript·ecmascript
姆路34 分钟前
QT中使用图表之QChart概述
c++·qt
假装我不帅35 分钟前
js实现类似与jquery的find方法
开发语言·javascript·jquery
look_outs39 分钟前
JavaSE笔记2】面向对象
java·开发语言
萧鼎39 分钟前
【Python】高效数据处理:使用Dask处理大规模数据
开发语言·python
西几44 分钟前
代码训练营 day48|LeetCode 300,LeetCode 674,LeetCode 718
c++·算法·leetcode
风清扬_jd1 小时前
Chromium HTML5 新的 Input 类型week对应c++
前端·c++·html5