【C++】模拟string类的实现

 

目录

 

前言

string的成员变量

如何创建string对象 

构造函数

拷贝构造

字符串输出

改变容量

 插入

插入字符串

+=操作

插入insert

 删除

查找

流插入(<<)和流提取(>>)重载


前言

  string类是C++标准库中一个重要的组件,它提供了一种方便的字符串操作方式。与C语言中字符数组相比,string类更加灵活和易用,可以方便地处理动态大小的字符串,同时也提供了许多重要字符串操作方式,如拼接,分割,替换,查找等。

    string类本质上是一个封装了一个字符串的数组,可以在运行时根据需要调整字符串的大小。其设计的重要优点是内存自动管理,即它负责调整内存大小,避免了内存溢出或空间浪费的问题。接下来我们来模拟实现string的常用接口。

如有错误,欢迎大家指正。

string的成员变量

private:
        char* _str;//指向字符串
        size_t _size;//有效数据
        size_t _capacity;//字符数组空间的大小 

1._size表示有效字符的个数不包括' \0 '的空间。

2._capacity表示有效空间大小同样不包括' \0 '的空间。

3.在最初构造一个string对象时_size和_capacity的大小一样。

4.向内存申请空间存放字符串时,开辟的空间大小比_capacity大1用于存放 ' \0 '。

如何创建string对象 

    string s1;//无参构造
	string s2 = "hello world";//有参构造
	string s3(s2);//拷贝构造
	string s4 = s3;//赋值重载构造

无参构造可以在有参构造函数中给一个缺省值,缺省值为“ ”字符串自带' \0 '

构造函数

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

拷贝构造

        我们在堆区申请了内存,如果不显示定义拷贝构造函数,编译器默认生成的拷贝构造函数完成的是浅拷贝,当我们调用编译器默认生成的拷贝构造函数去构造一个对象时,这个对象的字符串指针也指向参数对象的字符串指针指向的空间,在对象生命周期结束时,编译器会自动调用析构函数,从而对同一块空间二次析构。

浅拷贝

//浅拷贝
		string(const string& s)
		{
			_size = s._size;
			_capacity = s._capacity;
			_str = s._str;
		}

深拷贝 

        在堆区重新申请一块空间,将内容拷贝进去,让字符指针变量指针向该空间; 

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

赋值重载

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

析构函数

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

字符串输出

        调用该函数得到对象指向字符串的指针,进行打印;

const char* c_str()
		{
			return _str;
		}

[ ]操作符重载

        在某些情况我们需要从某个位置开始输出字符串,如对字符串"hello world"子打印world,这时我们就需要[ ]操作符了,对于自定义类型,我们要自己对[ ]操作符进行重载。

	//只能读取 不能改变字符串
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		//可以读取也可以改变
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

         从输出可以看出字符串的ASCII码加了1。

迭代器

        行为像指针一样,VS环境下的迭代器不是用指针实现的,但我们可以封装一个指针去模拟。

typedef  char* iterator;

//begin() 指向字符串的第一个元素
iterator begin()
{
return _str;
}



//end() 指向 \0
iterator end()
{

return _str+_size;

        接下来我们就可以用迭代器去遍历字符串了。范围for只是简单替换迭代器。

        string s1 = "1234567890";
		clastr::string::iterator it1;

		for (it1 = s1.begin(); it1 != s1.end(); ++it1)
		{
			cout << (*it1);
		}
		cout << endl;

        //范围for
        for (auto ch: s1)
		{
			cout << ch<<" ";
		}
		cout << endl;

改变容量

        当我们对字符串进行增删时,字符串的容量是改变的,所以我们要去扩容。向堆区申请一定容量的内存,拷贝原内存空间的内容,释放原空间。以下是扩容的实现步骤。

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

        上面是内存空间大小的变化,接下来我们来分析有效字符串大小的变化,变化有三种情况。

有效字符串下标n小于等于原本的_size时,只需要将_size的值改为n,并且将下标为n的位置处放一个’\0’。
有效字符串下标n大于原本的_size,并小于原本的_capacity,此时不需要进行扩容只需要将原本的_size设置成n即可,并且将相比于原本字符多出来的位置用形参ch来填充,如果没有传ch,就使用缺省值’\0’。
有效字符串下标n大于原本的_capacity,此时需要进行扩容,之后的操作和上面的一样。

        void resize(size_t n, char ch = '\0')
		{
			if (n <= _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				if (n > _capacity)
				{
					reserve(n);
				}
				size_t i = _size;
				while (i<n)
				{
					_str[i++] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

 插入

插入字符:在原有的字符串的最后插入一个字符。

        string& push_back(char ch)
		{
			if (_size + 1 > _capacity)
			{
				reserve(_capacity*2);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
			return *this;

		}

插入字符串

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

		}

+=操作

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

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

 

插入insert

在指定位置插入字符。

 在指定位置插入字符串。


		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 end = _size + len;
			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];//挪动数据
				--end;
			}
			strncpy(_str + pos, str, len);
			_size = _size + len;
			return *this;
			/*size_t len = strlen(str);
			if (_size + len>_capacity)
			{
				reserve(_size + len);
			}
			int end = _size + len - 1;
			int gap = _size - pos + 1;

			while (gap--)
			{
				_str[end] = _str[end - len];
				--end;

			}
			strncpy(_str + pos, str, len);
			_str[_size + len] = '\0';
			_size = _size + len;*/
			
		}

 删除

在这里我们定义一下npos,这是一个静态成员变量,在类内进行声明的时候,给它一个缺省值-1,在定义的时候就会使用到这个缺省值。
静态成员变量的声明在类内,定义必须在类外,但是有一个特殊类型,整型家族的静态成员就可以在声明的时候给一个缺省值。
由于是size_t类型的-1,所以它的实际大小就是32个1的二进制,转换成十进制大致是42亿9千万。

const static size_t npos=-1;
        string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

查找

    size_t find(char ch,size_t pos=0)
		{
			assert(pos < _size);

			for (size_t i = 0; i < _size; ++i)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			char* p = strstr(_str + pos,str);
			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				return p - _str;
			}
		}


流插入(<<)和流提取(>>)重载

流插入(<<)

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

流提取(>>)

istream& operator>>(istream& in, string& s)
{
    s.clear();
    char buff[128] = { '\0' };
    size_t i = 0;
    char ch = in.get();
    while (ch != ' ' && ch != '\n')
    {
        if (i == 127)
        {
            s += buff;
            i = 0;
        }
        buff[i++] = ch;
        ch = in.get();
    }
    if (i >= 0)
    {
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值