C++ string类的模拟实现

目录

0.前言

1.string类的常用接口说明(续)

c_str

find

rfind

find_first_of

find_last_of

find_first_not_of

find_last_not_of

substr

compare

2.常见拷贝辨析

2.1浅拷贝

2.2深拷贝

2.3写时拷贝

3.string的模拟实现

3.1构造与析构函数

3.1.1构造函数

3.1.2析构函数

3.2容量操作函数

3.2.1 size 和 length

3.2.2 capacity

3.2.3 reserve

3.2.4 resize

3.2.5 clear

3.3访问及遍历操作

3.3.1迭代器类型定义

3.3.2 begin 和 end 方法

3.4类对象修改操作

3.4.1添加操作

3.4.2插入操作

3.4.3删除操作

3.4.4交换操作

3.5其他字符串操作

3.5.1 substr

3.5.2 find

3.5.3 c_str

3.6运算符重载

3.6.1比较运算符

3.6.2流插入和提取运算符

4.完整代码

4.1 mystring.h

4.2 mystring.cpp

5.结语


(图像由AI生成) 

0.前言

在上一篇博客C++ string类-CSDN博客中,我们详细介绍了C++标准库中的string类,探讨了从构造函数到容量操作函数,以及访问及遍历操作和类对象的修改操作。本篇博客将继续深入探讨string类的成员函数,并进行简化版的string类模拟实现,以帮助大家更好地理解string类的内部工作机制。

1.string类的常用接口说明(续)

声明:该部分示例代码来自string - C++ Reference (cplusplus.com)

c_str

  • 功能:返回一个指向正规C字符串的指针, 内容与本string相同。这是连接C++和C语言标准库函数的桥梁。
  • 示例代码:
    // strings and c-strings
    #include <iostream>
    #include <cstring>
    #include <string>
    
    int main ()
    {
      std::string str ("Please split this sentence into tokens");
    
      char * cstr = new char [str.length()+1];
      std::strcpy (cstr, str.c_str());
    
      // cstr now contains a c-string copy of str
    
      char * p = std::strtok (cstr," ");
      while (p!=0)
      {
        std::cout << p << '\n';
        p = std::strtok(NULL," ");
      }
    
      delete[] cstr;
      return 0;
    }
  • 输出结果:
    Please
    split
    this
    sentence
    into
    tokens

find

  • 功能: 查找字符串中首次出现子串的位置。如果找不到子串,则返回string::npos。
  • 示例代码:
    // string::find
    #include <iostream>       // std::cout
    #include <string>         // std::string
    
    int main ()
    {
      std::string str ("There are two needles in this haystack with needles.");
      std::string str2 ("needle");
    
      // different member versions of find in the same order as above:
      std::size_t found = str.find(str2);
      if (found!=std::string::npos)
        std::cout << "first 'needle' found at: " << found << '\n';
    
      found=str.find("needles are small",found+1,6);
      if (found!=std::string::npos)
        std::cout << "second 'needle' found at: " << found << '\n';
    
      found=str.find("haystack");
      if (found!=std::string::npos)
        std::cout << "'haystack' also found at: " << found << '\n';
    
      found=str.find('.');
      if (found!=std::string::npos)
        std::cout << "Period found at: " << found << '\n';
    
      // let's replace the first needle:
      str.replace(str.find(str2),str2.length(),"preposition");
      std::cout << str << '\n';
    
      return 0;
    }
  • 输出结果:

    first 'needle' found at: 14
    second 'needle' found at: 44
    'haystack' also found at: 30
    Period found at: 51
    There are two prepositions in this haystack with needles.

rfind

  • 功能:查找字符串中最后一次出现子串的位置。类似find,但从字符串的末尾开始查找。
  • 示例代码:
    // string::rfind
    #include <iostream>
    #include <string>
    #include <cstddef>
    
    int main ()
    {
      std::string str ("The sixth sick sheik's sixth sheep's sick.");
      std::string key ("sixth");
    
      std::size_t found = str.rfind(key);
      if (found!=std::string::npos)
        str.replace (found,key.length(),"seventh");
    
      std::cout << str << '\n';
    
      return 0;
    }
  • 输出结果:

    The sixth sick sheik's seventh sheep's sick.
    

find_first_of

  • 功能: 查找字符串中第一次出现指定字符集中任一字符的位置。
  • 示例代码:
    // string::find_first_of
    #include <iostream>       // std::cout
    #include <string>         // std::string
    #include <cstddef>        // std::size_t
    
    int main ()
    {
      std::string str ("Please, replace the vowels in this sentence by asterisks.");
      std::size_t found = str.find_first_of("aeiou");
      while (found!=std::string::npos)
      {
        str[found]='*';
        found=str.find_first_of("aeiou",found+1);
      }
    
      std::cout << str << '\n';
    
      return 0;
    }
  • 输出结果:

    Pl**s*, r*pl*c* th* v*w*ls *n th*s s*nt*nc* by *st*r*sks.
    

find_last_of

  • 功能: 查找字符串中最后一次出现指定字符集中任一字符的位置。
  • 示例代码:
    // string::find_last_of
    #include <iostream>       // std::cout
    #include <string>         // std::string
    #include <cstddef>         // std::size_t
    
    void SplitFilename (const std::string& str)
    {
      std::cout << "Splitting: " << str << '\n';
      std::size_t found = str.find_last_of("/\\");
      std::cout << " path: " << str.substr(0,found) << '\n';
      std::cout << " file: " << str.substr(found+1) << '\n';
    }
    
    int main ()
    {
      std::string str1 ("/usr/bin/man");
      std::string str2 ("c:\\windows\\winhelp.exe");
    
      SplitFilename (str1);
      SplitFilename (str2);
    
      return 0;
    }
  • 输出结果:

    Splitting: /usr/bin/man
     path: /usr/bin
     file: man
    Splitting: c:\windows\winhelp.exe
     path: c:\windows
     file: winhelp.exe

find_first_not_of

  • 功能:查找字符串中第一次出现不属于指定字符集的字符位置。
  • 示例代码:
    // string::find_first_not_of
    #include <iostream>       // std::cout
    #include <string>         // std::string
    #include <cstddef>        // std::size_t
    
    int main ()
    {
      std::string str ("look for non-alphabetic characters...");
    
      std::size_t found = str.find_first_not_of("abcdefghijklmnopqrstuvwxyz ");
    
      if (found!=std::string::npos)
      {
        std::cout << "The first non-alphabetic character is " << str[found];
        std::cout << " at position " << found << '\n';
      }
    
      return 0;
    }
  • 输出结果:

    The first non-alphabetic character is - at position 12
    

find_last_not_of

  • 功能:查找字符串中最后一次出现不属于指定字符集的字符位置。
  • 示例代码:
    // string::find_last_not_of
    #include <iostream>       // std::cout
    #include <string>         // std::string
    #include <cstddef>        // std::size_t
    
    int main ()
    {
      std::string str ("Please, erase trailing white-spaces   \n");
      std::string whitespaces (" \t\f\v\n\r");
    
      std::size_t found = str.find_last_not_of(whitespaces);
      if (found!=std::string::npos)
        str.erase(found+1);
      else
        str.clear();            // str is all whitespace
    
      std::cout << '[' << str << "]\n";
    
      return 0;
    }
  • 输出结果:

    [Please, erase trailing white-spaces]
    

substr

  • 功能:从指定位置开始,生成一个新的长度为n的子字符串。
  • 示例代码:
    // string::substr
    #include <iostream>
    #include <string>
    
    int main ()
    {
      std::string str="We think in generalities, but we live in details.";
                                               // (quoting Alfred N. Whitehead)
    
      std::string str2 = str.substr (3,5);     // "think"
    
      std::size_t pos = str.find("live");      // position of "live" in str
    
      std::string str3 = str.substr (pos);     // get from "live" to the end
    
      std::cout << str2 << ' ' << str3 << '\n';
    
      return 0;
    }
  • 输出结果:

    think live in details.
    

compare

  • 功能:比较两个字符串。返回值 < 0 表示第一个字符串小于第二个字符串,0 表示两者相等,> 0 表示第一个字符串大于第二个字符串。
  • 示例代码:
    // comparing apples with apples
    #include <iostream>
    #include <string>
    
    int main ()
    {
      std::string str1 ("green apple");
      std::string str2 ("red apple");
    
      if (str1.compare(str2) != 0)
        std::cout << str1 << " is not " << str2 << '\n';
    
      if (str1.compare(6,5,"apple") == 0)
        std::cout << "still, " << str1 << " is an apple\n";
    
      if (str2.compare(str2.size()-5,5,"apple") == 0)
        std::cout << "and " << str2 << " is also an apple\n";
    
      if (str1.compare(6,5,str2,4,5) == 0)
        std::cout << "therefore, both are apples\n";
    
      return 0;
    }
  • 输出结果:

    green apple is not red apple
    still, green apple is an apple
    and red apple is also an apple
    therefore, both are apples

2.常见拷贝辨析

在C++中,拷贝对象是一个常见的操作,但如果处理不当,可能会导致资源管理上的问题,如内存泄漏或多次释放同一资源。这些问题通常源于浅拷贝和深拷贝的不当使用。

2.1浅拷贝

浅拷贝指的是对象的拷贝操作只复制对象的非静态数据成员的值。对于包含指针的对象,浅拷贝只复制指针的值(即内存地址),而不复制指针所指向的数据。这意味着原始对象和拷贝对象的指针成员将指向相同的内存地址。因此,当原始对象或拷贝对象被销毁时,同一内存地址可能会被释放多次,导致运行时错误。

考虑到std::string通常管理一个动态分配的字符数组,如果使用浅拷贝来复制std::string对象,两个对象将共享相同的内存。这样不仅违背了std::string安全和封装的设计原则,还可能导致多种并发问题和内存泄漏。

以下是一个浅拷贝的例子,其中我们模拟一个简化版的MyString类,只使用浅拷贝来复制数据:

#include <iostream>
#include <cstring>

class MyString {
private:
    char* data;

public:
    MyString(const char* pData = nullptr) {
        if (pData) {
            data = new char[strlen(pData) + 1];
            strcpy(data, pData);
        } else {
            data = nullptr;
        }
    }

    // 浅拷贝构造函数
    MyString(const MyString& other) {
        data = other.data;
    }

    ~MyString() {
        delete[] data;
    }

    void print() {
        std::cout << (data ? data : "") << std::endl;
    }
};

int main() {
    MyString str1("Hello, World!");
    MyString str2 = str1; // 使用浅拷贝构造函数

    str1.print(); // 正确输出
    str2.print(); // 正确输出,但这是不安全的

    // 程序结束时,str1和str2都会尝试删除同一内存区域
    return 0;
}

在这个示例中,当str1str2的析构函数被调用时,它们都试图删除同一个data指针指向的内存,这会导致双重删除的问题,会引发程序崩溃。

2.2深拷贝

深拷贝是指在拷贝一个对象时,不仅仅复制对象的基本类型数据,还包括对象所引用的所有资源都被复制。这种方法通常涉及为每一个被引用的数据分配新的内存,然后将原始对象中的数据复制到新分配的内存中。这样,原始对象和其副本将指向不同的内存地址,因此一个对象的修改不会影响到另一个对象。

对于std::string这样的类,深拷贝特别重要,因为它需要管理动态分配的内存来存储字符串数据。使用深拷贝可以确保每个字符串对象都有自己的独立数据副本,从而避免了多个对象共享同一内存块可能导致的问题。

以下是一个实现深拷贝的MyString类示例:

#include <iostream>
#include <cstring>

class MyString {
private:
    char* data;

public:
    MyString(const char* pData = nullptr) {
        if (pData) {
            data = new char[strlen(pData) + 1];
            strcpy(data, pData);
        } else {
            data = nullptr;
        }
    }

    // 深拷贝构造函数
    MyString(const MyString& other) {
        if (other.data) {
            data = new char[strlen(other.data) + 1];
            strcpy(data, other.data);
        } else {
            data = nullptr;
        }
    }

    ~MyString() {
        delete[] data;
    }

    void print() const {
        std::cout << (data ? data : "") << std::endl;
    }
};

int main() {
    MyString str1("Hello, World!");
    MyString str2 = str1; // 使用深拷贝构造函数

    str1.print(); // 输出: Hello, World!
    str2.print(); // 输出: Hello, World!

    // 程序结束时,str1和str2各自独立删除各自的内存区域
    return 0;
}

在这个示例中,MyString的拷贝构造函数被重新设计为深拷贝版本。当创建str2时,不仅复制了str1的数据成员data的值,还在堆上为str2.data分配了新的内存并复制了字符串内容。因此,str1str2指向了不同的内存块,它们的互相独立性得到了保障,避免了析构时的双重删除问题。

2.3写时拷贝

概念

写时拷贝(Copy-On-Write, COW)是一种优化技术,用于减少数据复制的需要,从而提高程序的效率和性能。在这种策略中,对象间共享相同的数据副本,只有在某个对象需要修改这些数据时,才会真正进行拷贝操作。这意味着,只有在数据被写入时,才会创建数据的独立副本。

优势

  • 内存效率:COW可以减少不必要的数据复制,节省内存。
  • 性能提升:减少内存分配和释放操作,可以提升程序性能。
  • 延迟拷贝:只在必要时进行数据拷贝,延迟了拷贝操作的开销。

在string类中的应用

在早期的std::string实现中,COW被用作一种优化手段,以避免在每次复制字符串时都进行数据的深拷贝。但是,由于多线程环境下的同步问题,现代的std::string实现已经逐渐放弃使用COW,改用始终保证每个字符串对象拥有自己独立数据的方法。

以下是一个模拟COW技术的简化的MyString类示例,为了简化,不考虑多线程安全问题:

#include <iostream>
#include <cstring>
#include <atomic>

class MyString {
private:
    struct StringData {
        char* data;
        std::atomic<int> ref_count;

        StringData(const char* pData = nullptr) : ref_count(1) {
            if (pData) {
                data = new char[strlen(pData) + 1];
                strcpy(data, pData);
            } else {
                data = nullptr;
            }
        }

        ~StringData() {
            delete[] data;
        }
    };

    StringData* pData;

    void detach() {
        if (pData && pData->ref_count > 1) {
            pData->ref_count--;
            pData = new StringData(pData->data);
        }
    }

public:
    MyString(const char* data = nullptr) {
        pData = new StringData(data);
    }

    // 使用引用计数实现写时拷贝
    MyString(const MyString& other) : pData(other.pData) {
        pData->ref_count++;
    }

    MyString& operator=(const MyString& other) {
        if (this != &other) {
            if (--pData->ref_count == 0) {
                delete pData;
            }
            pData = other.pData;
            pData->ref_count++;
        }
        return *this;
    }

    ~MyString() {
        if (--pData->ref_count == 0) {
            delete pData;
        }
    }

    void edit(const char* newData) {
        detach();
        delete[] pData->data;
        pData->data = new char[strlen(newData) + 1];
        strcpy(pData->data, newData);
    }

    void print() const {
        std::cout << (pData->data ? pData->data : "") << std::endl;
    }
};

int main() {
    MyString str1("Hello, World!");
    MyString str2 = str1; // 未触发拷贝,共享数据

    str2.edit("Hello, Universe!"); // 触发拷贝,现在str2有独立的数据
    str1.print(); // 输出: Hello, World!
    str2.print(); // 输出: Hello, Universe!

    return 0;
}

在这个示例中,MyString类通过使用引用计数和延迟分离策略(detach方法)来实现COW。当对象被修改(通过edit方法)时,如果引用计数大于1,detach方法会减少原数据的引用计数并为修改操作创建新的数据副本。这样,只有在实际需要时才进行数据的拷贝。

虽然COW在某些情况下可以极大地优化性能和内存使用,但在并发环境下需要谨慎使用,因为它引入了额外的复杂性和潜在的线程安全问题。 

3.string的模拟实现

在模拟实现string的各个成员函数之前,我们需要定义string类的成员变量,并将自己实现的string类封装在MyString命名空间中,以防与std::string冲突。

//mystring.h
#pragma once
#include<iostream>
#include<cstring>
#include<cassert>
#include<algorithm>
using namespace std;

namespace MyString
{
	class string
	{
	private:
		char* _str;
		size_t _size;//有效长度
		size_t _capacity;//能够容纳的最大有效长度

	public:
		//待实现的成员函数
	};
	
}

3.1构造与析构函数

MyString::string 类中,构造函数和析构函数是管理动态内存的核心。下面详细介绍这些函数:

3.1.1构造函数

实现了两种类型的构造函数:默认构造函数和拷贝构造函数。

  1. 默认构造函数

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

    这个构造函数接受一个 const char* 类型的参数,用于初始化新字符串。首先,计算传入字符串的长度来确定 _size_capacity,然后分配足够的内存,并使用 strcpy 函数复制字符串内容到成员变量 _str

  2. 拷贝构造函数

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

    拷贝构造函数用于创建一个新的字符串对象,作为另一个已存在的字符串对象的副本。通过创建一个临时字符串对象 tmp,然后交换临时对象和当前对象的内部状态,实现了高效的深拷贝。

3.1.2析构函数

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

析构函数的职责是释放字符串对象在生命周期内分配的内存资源。当一个 string 对象被销毁时(例如,当它超出作用域或被显式删除时),析构函数会被调用,确保不会有内存泄漏发生。通过检查 _str 是否为 nullptr,只有在 _str 真正指向动态分配内存时,才执行 delete[] 操作。

3.2容量操作函数

容量操作函数是用于管理字符串容量和大小的关键成员函数。下面是对这些函数的详细介绍:

3.2.1 size 和 length

size()length() 函数提供了获取字符串当前长度(有效字符数)的方法。在 MyString::string 类中,这两个函数实现是相同的,都返回 _size 成员变量的值。

size_t string::size()const
{
    return _size;
}

size_t string::length()const
{
    return _size;
}

这两个函数通常返回相同的结果,因为在大多数字符串实现中,字符串的“大小”和“长度”是相同的概念。

3.2.2 capacity

capacity() 函数返回字符串可以容纳的最大字符数,不需要重新分配内存。

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

这个函数对于理解字符串内部如何管理内存非常有用,它表明了在需要进行下一次内存分配之前,还可以向字符串中添加多少字符。

3.2.3 reserve

reserve() 函数用于请求改变字符串的容量。如果传递的参数 n 大于当前容量,函数将分配足够的内存以满足请求的容量。

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

这个函数只增加容量,不影响字符串的当前大小,也不初始化新增的内存空间。

3.2.4 resize

resize() 函数用于改变字符串的大小(有效长度),可以增加或减少长度。如果增加长度,并且新长度超过了当前容量,函数会先使用 reserve() 增加容量。新添加的字符将被初始化为参数 ch 指定的字符。

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

resize() 减少字符串的长度时,它将字符串的终结符放在新的长度后,有效地丢弃超出部分的字符。

3.2.5 clear

clear() 函数将字符串的长度设置为0,但并不减少容量,也不修改已分配的内存内容。它相当于调用 resize(0)

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

3.3访问及遍历操作

MyString::string 类中,提供了基本的迭代器支持,使得可以使用迭代器访问和遍历字符串中的字符。这些迭代器使得 MyString::string 类能以类似于标准容器的方式进行操作。以下是具体实现的详细介绍:

3.3.1迭代器类型定义

为了支持迭代访问,定义了两种类型的迭代器:

typedef char* iterator; // 迭代器
typedef const char* const_iterator; // 常迭代器

这里使用原生指针作为迭代器的类型。iterator 类型允许修改通过迭代器访问的元素,而 const_iterator 只允许读取元素,不允许修改,提供了对字符串内容的只读访问。

3.3.2 begin 和 end 方法

  • 非常量版本: 提供了 begin()end() 方法来返回指向字符串第一个字符和末尾空字符后一个位置的迭代器。

    iterator begin() {
        return _str; // 返回指向第一个字符的指针
    }
    
    iterator end() {
        return _str + _size; // 返回指向字符串结束后的位置的指针
    }
    

    这两个方法使得可以使用范围基的for循环和其他算法直接在 MyString::string 对象上操作,类似于操作标准容器。

  • 常量版本: 同样提供了 begin() constend() const 方法,这些方法返回 const_iterator 类型的迭代器,保证在遍历时不修改字符串内容。

    const_iterator begin() const {
        return _str; // 返回指向第一个字符的常量指针
    }
    
    const_iterator end() const {
        return _str + _size; // 返回指向字符串结束后的位置的常量指针
    }
    

这些方法的实现确保了能够以统一的方式遍历字符串,无论字符串内容是否允许修改。为了简化实现和使用,MyString::string 类中没有提供反向迭代器(如 reverse_iterator),这限制了一些从字符串末尾向开始进行的遍历操作。

3.4类对象修改操作

MyString::string 类提供了一系列修改字符串内容的成员函数,这些功能包括添加、插入、删除和交换操作。以下是对这些操作的详细介绍:

3.4.1添加操作

push_back

void string::push_back(char ch) {
    if (_size == _capacity) {
        reserve((_capacity == 0) ? 15 : 2 * _capacity);
    }
    _str[_size++] = ch;
    _str[_size] = '\0';
}
当容量不足以容纳新字符时,会自动进行扩容。

append

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

void string::append(const string& s) {
    append(s.c_str());
}
  • append(const char* str)append(const string& s) 方法用于在字符串末尾添加另一个字符串或C字符串。

operator+=

  • 运算符 += 重载提供了一种简便的方式来添加字符或字符串。
void string::operator+=(char ch) {
    push_back(ch);
}

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

void string::operator+=(const string& s) {
    append(s);
}

这些操作本质上是 push_backappend 方法的语法糖,使得字符串连接操作更自然和易于理解。

3.4.2插入操作

  • insert 方法允许在字符串的指定位置插入一个字符或字符串。
string& string::insert(size_t pos, char ch) {
    assert(pos <= _size);
    if (_size == _capacity) {
        reserve((_capacity == 0) ? 15 : 2 * _capacity);
    }
    for (size_t i = _size; i > pos; --i) {
        _str[i] = _str[i - 1];
    }
    _str[pos] = ch;
    ++_size;
    _str[_size] = '\0';
    return *this;
}

string& string::insert(size_t pos, const char* str) {
    size_t len = strlen(str);
    if (_size + len > _capacity) {
        reserve(_size + len);
    }
    for (size_t i = _size + len; i >= pos + len; --i) {
        _str[i] = _str[i - len];
    }
    memcpy(_str + pos, str, len);
    _size += len;
    return *this;
}

string& string::insert(size_t pos, const string& s) {
    return insert(pos, s.c_str());
}

这些函数通过移动元素和复制数据来实现插入操作。

3.4.3删除操作

  • erase 方法用于从字符串中删除从指定位置开始的一定数量的字符。
string& string::erase(size_t pos, size_t len) {
    if (pos >= _size) return *this;
    if (pos + len > _size) {
        _size = pos;
    } else {
        for (size_t i = pos + len; i < _size; ++i) {
            _str[i - len] = _str[i];
        }
        _size -= len;
    }
    _str[_size] = '\0';
    return *this;
}

这个方法调整 _size 并移动字符以填补被删除的空间。

3.4.4交换操作

  • swap 方法用于交换两个字符串的内容。
void string::swap(string& s) {
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}

这是一种高效的方式,用于在不进行数据复制的情况下交换两个字符串的内部状态。

3.5其他字符串操作

MyString::string 类提供了一些额外的字符串操作,这些操作对于字符串处理非常常见且有用。以下是这些操作的详细介绍:

3.5.1 substr

  • substr 方法用于生成原字符串的一个子字符串,从指定位置 pos 开始,长度为 len
string string::substr(size_t pos, size_t len) const {
    if (pos > _size) {
        throw std::out_of_range("Position is out of range");
    }
    if (len == npos || pos + len > _size) {
        len = _size - pos;  // Adjust length if it exceeds the string's end
    }

    string tmp;
    tmp.reserve(len);  // Optimize memory allocation
    for (size_t i = 0; i < len; ++i) {
        tmp.push_back(_str[pos + i]);
    }
    return tmp;
}

此函数检查位置是否有效,调整长度以防止越界,然后创建并返回新的字符串对象。

3.5.2 find

  • find 方法提供了查找子字符串或字符的功能。
    • 查找字符:

      size_t string::find(char ch, size_t pos) const {
          for (size_t i = pos; i < _size; ++i) {
              if (_str[i] == ch) {
                  return i;  // 返回字符所在位置
              }
          }
          return npos;  // 如果找不到,返回 npos
      }
      

      从指定位置 pos 开始搜索字符 ch,如果找到,则返回字符的位置。

    • 查找字符串:

      size_t string::find(const char* str, size_t pos) const {
          char* substr = strstr(_str + pos, str);  // 使用标准库函数进行搜索
          if (substr == nullptr) {
              return npos;  // 如果找不到,返回 npos
          }
          return substr - _str;  // 返回子字符串的起始位置
      }
      

      使用 strstr 函数从指定位置 pos 开始搜索子字符串 str,并返回其起始位置。

3.5.3 c_str

  • c_str 方法用于获取一个以 null 结尾的 C 字符串版本,这对于与需要 C 风格字符串的函数接口兼容非常有用。
const char* string::c_str() const {
    return _str;  // 直接返回内部数组的指针
}

这个方法保证字符串以 '\0' 结尾,使得 MyString::string 类的对象可以安全地用在标准 C 函数中。

3.6运算符重载

MyString::string 类中,运算符重载提供了一种便捷的方式来执行常见的字符串比较和输入/输出操作。这使得字符串对象可以直接参与到比较和流操作中,类似于基本数据类型或标准库中的字符串类。

3.6.1比较运算符

比较运算符允许字符串对象之间进行各种关系比较。这些运算符基于字符串的字典序(通过 strcmp 函数实现)来比较两个字符串:

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

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

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

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

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

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

这些重载确保了可以使用标准比较操作符来直接比较两个 string 对象,便于进行条件判断、排序等操作。

3.6.2流插入和提取运算符

流插入(<<)和提取(>>)运算符重载使得 string 对象可以直接与标准输入输出流 (std::ostreamstd::istream) 交互,这对于从标准输入读取数据或向标准输出打印字符串非常方便。

  • 插入运算符 (<<) 用于将字符串内容输出到输出流:
friend ostream& operator<<(ostream& _cout, const string& s) {
    _cout << s.c_str();
    return _cout;
}

这个函数简单地将字符串的 C 风格表示(通过 c_str() 获取)插入到给定的输出流中。

  • 提取运算符 (>>) 用于从输入流读取数据到字符串中:
friend istream& operator>>(istream& _cin, string& s) {
    char temp[1000];  // 注意:这里假定输入的最大长度为999
    _cin >> temp;
    s = temp;  // 使用赋值运算符重载
    return _cin;
}

这个函数读取输入流中的数据到一个临时字符数组中,然后使用赋值运算符将其赋值给字符串对象。需要注意的是,这种实现方式没有处理超长输入的情况,可能会导致缓冲区溢出。

4.完整代码

4.1 mystring.h

#pragma once
#include<iostream>
#include<cstring>
#include<cassert>
#include<algorithm>
using namespace std;
namespace MyString
{
	class string
	{
	private:
		char* _str;
		size_t _size;//有效长度
		size_t _capacity;//能够容纳的最大有效长度

	public:
		//构造函数、析构函数与赋值运算符重载
		string(const char* str = " ");
		string(const string& s);
		~string();
		string& operator=(string s);
		string& operator=(const char* str);

		//访问及遍历操作函数
		typedef char* iterator;//迭代器
		iterator begin();
		iterator end();
		typedef const char* const_iterator;//常迭代器
		const_iterator begin()const;
		const_iterator end()const;
		const char& operator[](size_t index)const;
		char& operator[](size_t index);

		//容量操作函数
		size_t size()const;
		size_t length()const;
		size_t capacity()const;
		void reserve(size_t n);
		void resize(size_t n, char ch = '\0');
		void clear();

		//对象修改操作函数
		void push_back(char ch);
		void append(const char* str);
		void append(const string& s);
		void operator+=(char ch);
		void operator+=(const char* str);
		void operator+=(const string& s);
		string operator+(char ch);
		string operator+(const char* str);
		string operator+(const string& s);
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		string& insert(size_t pos, const string& s);
		string& erase(size_t pos, size_t len = npos - 1);
		void swap(string& s);

		//其他操作函数
		string substr(size_t pos = 0, size_t len = npos - 1)const;
		size_t find(char ch, size_t pos = 0)const;
		size_t find(const char* str, size_t pos = 0)const;
		const char* c_str()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;
		bool operator>=(const string& s)const;
		friend ostream& operator<<(ostream& _cout, const string& s);
		friend istream& operator>>(istream& _cin, string& s);
		
		//静态成员
		const static size_t npos;
	};
}

4.2 mystring.cpp

 

#define _CRT_SECURE_NO_WARNINGS 1

#include"mystring.h"//自定义的string类

namespace MyString
{
	//构造函数、析构函数与赋值运算符重载
	string::string(const char* str)
	{
		_size = strlen(str);
		_capacity = _size;
		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}

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

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

	string& string::operator=(string s)
	{
		//现代写法
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
		return *this;
	}

	string& string::operator=(const char* str)
	{
		string tmp(str);
		std::swap(_str, tmp._str);
		std::swap(_size, tmp._size);
		std::swap(_capacity, tmp._capacity);
		return *this;
	}

	//访问及遍历操作函数
	string::iterator string::begin()
	{
		return _str;
	}
	
	string::iterator string::end()
	{
		return _str + _size;
	}

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

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

	const char& string::operator[](size_t index)const
	{
		assert(index < _size);
		return _str[index];
	}

	char& string::operator[](size_t index)
	{
		assert(index < _size);
		return _str[index];
	}

	//容量操作函数
	size_t string::size()const
	{
		return _size;
	}

	size_t string::length()const
	{
		return _size;
	}

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

	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::resize(size_t n, char ch)
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		if (n < _size)
		{
			_str[n] = '\0';
		}
		else
		{
			for (size_t i = _size; i < n; ++i)
			{
				_str[i] = ch;
			}
			_str[n] = '\0';
		}
		_size = n;
	}

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

	//对象修改操作函数
	void string::push_back(char ch)
	{
		//如果有效长度等于容量,进行扩容
		if (_size == _capacity)
		{
			size_t newcapacity = (_capacity == 0) ? 15 : 2 * _capacity;
			reserve(newcapacity);
		}
		_str[_size++] = ch;
		_str[_size] = '\0';
	}

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

	void string::append(const string& s)
	{
		const char* str = s.c_str();
		append(str);
	}

	void string::operator+=(char ch)
	{
		push_back(ch);
	}
	void string::operator+=(const char* str)
	{
		append(str);
	}
	void string::operator+=(const string& s)
	{
		append(s);
	}
	string string::operator+(char ch)
	{
		string tmp(*this);
		tmp.push_back(ch);
		return tmp;
	}
	string string::operator+(const char* str)
	{
		string tmp(*this);
		tmp.append(str);
		return tmp;
	}
	string string::operator+(const string& s)
	{
		string tmp(*this);
		tmp.append(s);
		return tmp;
	}
	string& string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			size_t newcapacity = (_capacity == 0) ? 15 : 2 * _capacity;
			reserve(newcapacity);
		}
		for (size_t i = _size; i > (int)pos; --i)
		{
			_str[i] = _str[i - 1];
		}
		_str[pos] = ch;
		++_size;
		_str[_size] = '\0';
		return *this;
	}
	string& string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len);
		}
		for (int i = _size; i >= (int)pos; --i)
		{
			_str[i + len] = _str[i];
		}
		strncpy(_str + pos, str, len);
		_size += len;
		return *this;
	}
	string& string::insert(size_t pos, const string& s)
	{
		insert(pos, s.c_str());
		return *this;
	}

	string& string::erase(size_t pos, size_t len)
	{
		if (pos >= _size)
		{
			return *this;
		}
		if (pos + len > _size)
		{
			_size = pos;
			_str[_size] = '\0';
			return *this;
		}
		for (size_t i = pos + len; i < _size; ++i)
		{
			_str[i - len] = _str[i];
		}

		_size -= len;
		_str[_size] = '\0';
		return *this;
	}

	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) const {
		if (pos > _size) {
			throw std::out_of_range("Position is out of range");
		}
		if (len == npos || pos + len > _size) {
			len = _size - pos;
		}

		string tmp;
		tmp.reserve(len);
		for (size_t i = 0; i < len; ++i) {
			tmp.push_back(_str[pos + i]);
		}
		return tmp;
	}

	size_t string::find(char ch, size_t pos)const
	{
		for (size_t i = pos; i < _size; ++i)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}

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

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

	//运算符重载
	bool string::operator<(const string& s)const
	{
		return strcmp(_str, s.c_str()) < 0;
	}
	bool string::operator>(const string& s)const
	{
		return strcmp(_str, s.c_str()) > 0;
	}
	bool string::operator==(const string& s)const
	{
		return strcmp(_str, s.c_str()) == 0;
	}
	bool string::operator!=(const string& s)const
	{
		return strcmp(_str, s.c_str()) != 0;
	}
	bool string::operator<=(const string& s)const
	{
		return strcmp(_str, s.c_str()) <= 0;
	}
	bool string::operator>=(const string& s)const
	{
		return strcmp(_str, s.c_str()) >= 0;
	}
	
	ostream& operator<<(ostream& _cout, const string& s) {
		_cout << s.c_str();
		return _cout;
	}

	istream& operator>>(istream& _cin, string& s) {
		char temp[1000];
		_cin >> temp;
		string tmp(temp);
		std::swap(s._str, tmp._str);
		std::swap(s._size, tmp._size);
		std::swap(s._capacity, tmp._capacity);
		return _cin;
	}

	//静态成员
	const size_t string::npos = -1;
}

5.结语

本文通过深入探讨 MyString::string 类的设计和实现,我们了解了如何构建一个功能齐全的字符串处理类。从基础的构造和析构函数到复杂的操作如插入、删除和运算符重载,这个类展示了在 C++ 中管理和操作字符串数据的有效方法。该实现支持常见的字符串操作,还通过运算符重载增强了与标准 I/O 流的交互能力,提供了一种直观和灵活的方式来处理文本数据。string的介绍到这里就告一段落,我们vector再见!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值