C++实现String类的方法详解

 更新时间:2022年8月21日 16:36  点击:68 作者:。菀枯。

前言

在C语言中,没有专门用来表示字符串的类型。C语言的字符串是一系列以’\0’为结尾的字符的集合。虽然C语言为这样的字符串提供了一系列的库函数如strcpy, strcmp等等,但这些函数与字符串这个类型是分开的,这不太符合C++中面试对象的思想,所以在C++中封装了一个string类,来帮助我们操作字符串。string该如何使用,我这里就不做赘述了,大家可以去看看官方文档呀

string - C++ Reference (cplusplus.com)

string模拟实现

string简单实现

首先我们不考虑string类的增删查改,只是先给string类搭建一个最简单的框架出来。

和C语言中相同,为了存储一个字符串,我们的string类需要一个char*的指针来指向字符像这个对象。作为一个对象,string还需要有构造函数,析构函数和拷贝构造。

class string
{
private:
	char *_str;
public:
	string(const char *str)
		: _str(new char[strlen(str) + 1]) // +1 是给'\0'留出位置
	{
		strcpy(_str, str);
	}

	string(const string &str)
		: _str(new char[strlen(str._str) + 1])
	{
		strcpy(_str, str._str);
	}
	~string()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
};

有的朋友可能会疑惑,这里的构造函数和拷贝构造函数为什么不用编译器自动生成的,直接将_str指向原本的字符串就可以了,为什么还要开辟空间呢?

这是因为我们在日常使用中,假如有两个string类 a 和 b,b是由a拷贝构造而来,一般情况下我们在修改b的同时不希望a也被改。此外,如果直接将_str指向原本的字符串会导致的问题是当 a 和 b用完被销毁时,会对同一片空间调用两次析构函数,对同一片空间释放两次。所以在这里,我们需要重新开辟一片空间来给这个string。这也就是所谓的深拷贝。

然后,为了访问string类中的元素,我们需要对运算符[]进行重载。

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

这样我们就实现了一个简单的string类。

string完整实现

构造函数,析构函数,拷贝构造

之前我们实现的一个string类是一个最简单的string类,它没有办法进行增删查改,接下来我们就来一点一点完善它。

要实现增删查改,我们还需要两个变量,一个记录string类当前长度,一个记录string类的容量大小。加入这两个变量后,我们原本的构造函数,拷贝构造和析构函数需要发生一点点变化。

class string
{
private:
	char *_str;
	size_t _size;
	size_t _capacity;

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

    string(const string &str)
        : _size(str._size), _capacity(str._capacity)
    {
        _str = new char[_size + 1];
        strcpy(_str, str._str);
    }
    
	~string()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
	}
};

运算符重载

接下来我们来实现一下,string类的运算符。在实现运算符重载时,我们需要做的只是实现少数几个运算符即可,其他的运算符可复用前面实现的运算符来达到我们想要的效果。

//关系运算符的重载
bool operator>(const string &s)
{
    return strcmp(_str, s.c_str());
}

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

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

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);
}
//操作运算符的重载
string &operator=(string& str)
{
    if(*this != str)
    {
        char *tmp = new char[str._capacity + 1];
        strcpy(tmp,str._str);
        delete[] _str;
        _str = tmp;
        _size = str._size;
        _capacity = str._capacity;
    }
    return *this;
}

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

    return *(_str + pos);
}

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

string接口实现

首先是比较简单的size(),empty(),capacity(),clear()。这些接口大部分直接访问string类的成员变量就可以得到结果。

size_t size() const
{
    return _size;
}

size_t capacity() const
{
    return _capacity;
}

bool empty() const
{
    return 0 == _size;
}
//后面添加const的目的是为了让所有对象都可以进行访问
void clear()
{
    _str[0] = '\0';
    _size = 0;
    _capacity = 0;
}

因为后面的接口大部分都需要进行空间的调整,所以首先我们将调整空间的接口,reserve和resize实现。

void reserve(size_t n)
{
    if (n > _capacity) //判断是否需要扩容
    {
        char *tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}

//resize和reserve的区别在于,reserve只是开空间,而resize还要进行初始化
void resize(size_t n, char c = '\0')
{
    if (n > _capacity)
    {
        reserve(n); //开空间复用reserve
    }
    for (size_t i = _size; i < n; ++i)
    {
        _str[i] = c;
    }
    _size = n;
    _str[_size] = '\0';
}

接下来是插入的实现,首先是push_back,这个比较简单,找到尾部进行插入即可。

void push_back(char n)
{
    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : _capacity * 2); //开空间复用reserve
    }
    _str[_size++] = n;
    _str[_size] = '\0';
}

接下来是insert,这个较push_back而言要麻烦一些,因为除了尾插,其他地方去插入数据你都需要挪动后面数据的位置。

string &insert(size_t pos, const char *str)
{
    //检查空间是否足够
    assert(pos <= _size);
    size_t len = strlen(str);
    if (len + _size > _capacity)
    {
        reserve(len + _size);
    }

   	//挪动后面的数据
    size_t end = _size + len;
    while (end != pos + len - 1)
    {
        _str[end] = _str[end - len];
        --end;
    }

    //数据插入
    strncpy(_str + pos, str, len);
    _size += len;
    return *this;
}

写完了插入,接下来当然就是删除接口:eraser

string &eraser(size_t pos, size_t len = npos) //npos为静态变量,值为-1
{
    assert(pos < _size);
    
    if (len == npos || pos + len >= _size) //将位置后的元素全部删除
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else //删除位置后的部分元素
    {
        size_t begin = pos + len;
        while (begin <= _size)
        {
            _str[begin - len] = _str[begin];
            begin++;
        }
        _size = _size - len;
    }
    return *this;
}

迭代器的实现

C++中的迭代器和指针类似。为什么要有迭代器呢?因为C++中有各种各样的容器,每个容器它背后的存储方式不同,访问方式也不同,为了让使用者的使用成本降低,使大部分容器可以以相同的方式去访问,就有了迭代器的产生。

接下来我们来实现string的迭代器,其实string的迭代器就是一个指针。并不用去封装特别的东西。

typedef char *iterator;
typedef const char *const_iterator;

const_iterator begin() const
{
    return _str;
}

const_iterator end() const
{
    return _str + _size;
}

iterator begin()
{
    return _str;
}

iterator end()
{
    return _str + _size;
}

部分函数优化和完善

前面在写运算符重载时,还有部分运算符未重载在此加上

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

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

同时增加拷贝构造和operator=的现代写法,之前我们写拷贝构造和operator=时都需要自己去重新开空间,那么这个活可不可以让其他人帮我做呢?

我们来看看下面这一段代码

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

string(const string &s)
    : _str(nullptr), _size(0), _capacity(0)
{
    string tmp(s._str);
    swap(tmp);
}

string &operator=(string s)
{
    swap(s);
    return *this;
}

上述代码同样可以帮我们完成拷贝构造和operator= ,原理如下:

1.首先是拷贝构造,我们在拷贝构造中使用构造函数去创建一个临时对象,这个临时对象在创建时,就帮我们开辟了空间。然后我们将临时对象和此对象的所有成员进行一个交换,这样此对象就可以接管临时对象创建的那块空间,我们的拷贝构造也就成功了

2.在operator=这,我们使用的是传值传参。好处在于由于我们的string类是自定义对象,所以在传参时会去调用拷贝构造,这样传过来的str参数也拥有了自己的空间,此时我们和拷贝构造一样,将str所开辟的那块空间接管,同时由于str是函数参数,当函数结束时,str会去调用析构函数进行一个空间释放。

完整代码

class string
{
public:
    typedef char *iterator;
    typedef const char *const_iterator;

    const_iterator begin() const
    {
        return _str;
    }

    const_iterator end() const
    {
        return _str + _size;
    }

    iterator begin()
    {
        return _str;
    }

    iterator end()
    {
        return _str + _size;
    }

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

    string(const string &s)
        : _str(nullptr),
          _size(0),
          _capacity(0)
    {
        string tmp(s._str);
        swap(tmp);
    }

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

    string &operator=(string s)
    {
        swap(s);
        return *this;
    }

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

        return *(_str + pos);
    }

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

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

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

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

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

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

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

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

    size_t size() const
    {
        return _size;
    }

    size_t capacity() const
    {
        return _capacity;
    }

    bool empty()
    {
        return 0 == _size;
    }

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

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

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

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

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

        size_t end = _size + len;
        while (end != pos + len - 1)
        {
            _str[end] = _str[end - len];
            --end;
        }

        strncpy(_str + pos, str, len);
        _size += len;
        return *this;
    }

    string &eraser(size_t pos, size_t len = npos)
    {
        assert(pos < _size);

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

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

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

    size_t find(char c, size_t pos = 0) const
    {
        while (pos < _size)
        {
            if (_str[pos] == c)
            {
                return pos;
            }
            ++pos;
        }
        return npos;
    }

    size_t find(char *s, size_t pos = 0) const
    {
        const char *p = strstr(_str + pos, s);
        if (p == nullptr)
        {
            return npos;
        }
        else
        {
            return p - _str;
        }
    }

private:
    char *_str;
    size_t _size;
    size_t _capacity;
    const static size_t npos;
};

const size_t string::npos = -1;

到此这篇关于C++实现String类的方法详解的文章就介绍到这了,更多相关C++ String类内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://blog.csdn.net/m0_60447315/article/details/126448202

相关文章

  • C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • C++万能库头文件在vs中的安装步骤(图文)

    这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ bitset用法

    这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++递归删除一个目录实例

    这篇文章主要介绍了C++递归删除一个目录的实现方法,涉及到目录的操作及递归算法的应用,需要的朋友可以参考下...2020-04-25
  • C++实现的O(n)复杂度内查找第K大数算法示例

    这篇文章主要介绍了C++实现的O(n)复杂度内查找第K大数算法,结合实例形式分析了算法的原理以及具体实现方法,需要的朋友可以参考下...2020-04-25
  • VSCode搭建C/C++编译环境的详细教程

    Visual Studio Code是一款免费开源的现代化轻量级代码编辑器,支持几乎所有主流的开发语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比 Diff、GIT 等特性,这篇文章主要介绍了VSCode搭建C/C++编译环境,需要的朋友可以参考下...2020-05-15
  • C++ 将数据转为字符串的几种方法

    这篇文章主要介绍了C++ 将数据转为字符串的几种方法,十分的实用,有需要的小伙伴可以参考下。...2020-04-25
  • Windows配置VSCode+CMake+Ninja+Boost.Test的C++开发环境(教程详解)

    这篇文章主要介绍了Windows配置VSCode+CMake+Ninja+Boost.Test的C++开发环境,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-05-12
  • 详解Dev C++使用教程(使用Dev C++编写C语言程序)

    这篇文章主要介绍了详解Dev C++使用教程(使用Dev C++编写C语言程序),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-11
  • C++ 约瑟夫环的实例代码

    这篇文章主要介绍了C++ 约瑟夫环的实例代码的相关资料,希望通过本文能帮助到大家,实现这样的功能,需要的朋友可以参考下...2020-04-25
  • C++循环队列实现模型

    这篇文章主要介绍了C++循环队列实现模型,较为详细的分析了循环队列算法的原理与实现方法,具有一定的参考借鉴价值,需要的朋友可以参考下...2020-04-25
  • VC++中HTControl控制类使用之CHTDlgBase对话框基类实例

    这篇文章主要介绍了VC++中HTControl控制类使用之CHTDlgBase对话框基类,是比较丰富而实用的功能,需要的朋友可以参考下...2020-04-25
  • C++实现大数乘法算法代码

    这篇文章主要介绍了C++实现大数乘法算法代码的相关资料,需要的朋友可以参考下...2020-04-25
  • C++ STL关联式容器自定义排序规则的2种方法

    这篇文章主要介绍了C++ STL关联式容器自定义排序规则的2种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-04
  • C语言/C++如何生成随机数

    这篇文章主要介绍了C语言/C++如何生成随机数,C语言/C++产生随机数主要用到的是rand()函数, srand()函数,C语言/C++里没有自带的random(int number)函数,如何解决?感兴趣的小伙伴们可以参考一下...2020-04-25
  • C++实现俄罗斯方块(linux版本)

    这篇文章主要为大家详细介绍了linux版本C++实现俄罗斯方块,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-07-22
  • C++之异常处理详解

    C++中处理异常的过程是这样的:在执行程序发生异常,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级的函数来解决,上一级解决不了,再传给其上一级,由其上一级处理...2020-04-25
  • c++11新增的便利算法实例分析

    这篇文章主要介绍了c++11新增的便利算法,主要有用于判断、查找、数组、序列等的操作算法,非常具有实用价值,需要的朋友可以参考下...2020-04-25
  • 利用rapidjson实现解析嵌套的json的方法示例

    今天小编就为大家分享一篇关于利用rapidjson实现解析嵌套的json的方法示例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...2020-04-25
  • C++ 学习之旅二 说一说C++头文件

    作为一个二手的.net程序员,你看到了C++头文件一定就犯迷糊了,这到底是个啥玩意。再我纠结了24个小时, google20次,度娘10下,看过10来骗文章以后,我可能稍微开窍了。我对C++头文件总结,与.net比较如下...2020-04-25