C++string类的模拟实现以及经验分享

1. 为什么学习string类?

1.1 C语言中的字符串

C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些string系列的库函数,例如strcpy、strstr、strcmp、memcpy、memmove等等
,但是这些库函数与字符串是分离开的,不太符合面向对象程序设计的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

1.2 两个面试题

字符串转整形数字
字符串相加
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
string类的文档介绍
这里有大量详细有关string类函数的介绍以及使用方法、返回值等等,读者可自行阅读。

2. string类的实现

首先为了不与库中的string类冲突,这里我们要把所有自己写的string类要放在自己的一个命名空间里。这里就拿自己的名字作为命名空间的名字吧。

namespace HB

{
	class string
	{
		public:
		...
		//各种成员函数
		
		private:
		//各种成员变量
        char* _str;
        size_t _capacity;
        size_t _size;

        static const size_t npos = -1;//模仿库里面的npos
	}
}

这里C++专门给int留了绿灯,static的成员变量原本不可以直接在这里赋值,静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。
对于静态成员变量有不理解的小伙伴可以看这篇文章C++类和对象(下)

构造函数:

这里构造函数参数用的是缺省值,为的是当你定义一个string类对象的时候,不给参数这里能默认提供一个空字符串放到string里,为后续的操作提供方便。

string(const char* str = "")//构造函数
{
      int len = strlen(str);
      _str = new char[len + 1];//开空间

      strcpy(_str, str);//字符串拷贝

      _size = len;
      _capacity = len;
}

拷贝构造函数

一般我们这里传统的写法是自己去根据要拷贝的对象先开一块和它一样大的空间,其次把字符串整体拷贝过去,然后把_size以及_capacity的信息都赋上对应的值。

//传统写法
        //string(const string& s)//拷贝构造函数
        //{
        //    int len = strlen(s._str);
        //    _str = new char[len + 1];

        //    strcpy(_str, s._str);

        //    _size = len;
        //    _capacity = s._capacity;
        //}

大佬研究出来的写法是直接用刚才写好的构造函数去定义好一个temp对象,然后需要我们自己再去针对这个类写一个对象与对象之间的swap函数(最好不要直接用库里面的,因为库里面的swap函数为了适应泛型而进行了相当于3次拷贝构造)
在这里插入图片描述

void swap(string& s)
        {
        //这里我们直接交换成员变量的值即可
            std::swap(_str, s._str);
            std::swap(_size, s._size);
            std::swap(_capacity, s._capacity);
        }

但是这里会有一个问题,假如说string s2(s1); 这里的s2的三个成员变量都还是随机值,强行和temp交换之后,temp随着拷贝构造函数的完成编译器要对他进行销毁,这时调用析构函数的时候发现temp对象的_str中的值还是一些随机值,好!那么这里就有野指针的问题了,在VS2013的编译器之下会报错,那么就要去在拷贝构造的初始化列表对于_str进行初始化一下为nullptr,下面析构函数的delete[],对于这个空指针是不会报错的,最后和这个temp对象进行交换即可。

//大佬研究出来的写法
        string(const string& s)//拷贝构造函数
            :_str(nullptr)
            , _size(0)
            , _capacity(0)
        {
            string temp(s._str);
            //this->swap(temp);
            swap(temp);
        }

赋值运算符重载:

传统写法类似于拷贝构造,同样是开空间、赋值等一系列操作,这里不多赘述。

传统写法
        //string& operator=(const string& s)//赋值运算符重载
        //{
        //    if (this != &s)
        //    {
        //        _size = strlen(s._str);
        //        _str = new char[_size + 1];

        //        strcpy(_str, s._str);
        //        _capacity = s._capacity;
        //    }
        //    return *this;
        //}

下面是大佬的写法,既然上面拷贝构造都写好了,那直接就使用拷贝构造来帮我们完成开空间、赋值等操作,然后还是将*this和temp交换,得到的就是我们想要的值了。

        //大佬研究出来的写法
        string& operator=(const string& s)//赋值运算符重载
        {
            if (&s != this)
            {
                string temp(s);//复用拷贝构造
                swap(temp);
            }
            
            return *this;
        }

析构函数

注意上面构造函数new的是一个数组,那么下面delete的时候也要以数组的方式。

 ~string()//析构函数
        {
            delete[] _str;
            _str = nullptr;
            _size = 0;
            _capacity = 0;
        }

流提取运算符重载

老规矩了,像流插入和流提取运算符的重载是不能直接写到成员函数里的,原因:在类里实现的话左操作数是this对象,并且这个this对象只能是类类型,正常我们cout<<d1;但是我们想在类里面实现的话,最后在类里实现完之后使用的时候要d1<<cout,所以说在类里流插入和流提取不能进行重载。这里干脆就只能把它放在类外面,弄成一个全局函数。而且为了能满足链式访问(cout<<s1<<s2<<endl;),我们这里的函数的返回值也得是istream类和ostream类的。

而提取的方法也是有两种,一种是把输入的字符串一个字符一个字符的把它们读到对象s里,而且还用到了标准输入流中的get函数std::istream::get;另外一种方法是为了减少数组频繁扩容,所以找一个数组(大小自己定)把要输入的字符先存放到这个数组中,当这个数组已经满了的时候,或者没有满已经到了输入字符串的末尾了,就把这个数组中的值再整体尾插到string类对象中。
具体实现代码:

istream& operator>>(istream& _cin, string& s)
    {
        方法一:
        //s.clear();
        //char ch = _cin.get();//这种方法如果说输入的字符串比较长的情况下,会进行频繁的扩容,不好
        //while (1)
        //{
        //    if (ch == ' ' || ch == '\n')
        //        break;
        //    s += ch;
        //    ch = _cin.get();
        //}

        //方法二:
        //用一个数组来当作一个缓冲,到达你设置的临界值之后才会向string对象里添加一整个字符串
        s.clear();
        char ch = _cin.get();
        char arr[100] = { '\0' };//初始化为全\0非常重要
        int arrnum = 0;
        while (1)
        {
            if (ch == ' ' || ch == '\n')
                break;
            if (arrnum == 99)
            {
                s += arr;

                arrnum = 0;
            }

            arr[arrnum++] = ch;

            ch = _cin.get();

        }
        arr[arrnum] = '\0';
        s += arr;

        return _cin;
    }

其他成员函数以及完整代码链接
好了今天的分享就到此为止了
最后:如果你觉得对你有用就一键三连吧,哪里有没看懂的地方或者哪里有错误可以在评论区留言欢迎批评指正,作者看到的话会第一时间回复。
end

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有效的放假者

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

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

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

打赏作者

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

抵扣说明:

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

余额充值