【C++初阶】9. string类的模拟实现

简介: 【C++初阶】9. string类的模拟实现

string类的完整实现放这里啦!快来看看吧

1. string类的成员

string类的作用就是将字符串类型实现更多功能,运算符重载,增删改查等等操作,所以其成员就包含char*的字符串

private:
        char* _str;
        size_t _capacity;
        size_t _size;

2. 构造函数

2.1 带参构造函数

在之前的学习过程中,我们了解到类中存在的六个默认函数,其中就包含默认构造函数,那么对于string类是否需要用户自己实现构造函数呢?
答案是需要的,我们需要根据字符串的长度开辟空间,也需要将字符串拷贝到开辟的空间当中

        // 带参的构造函数
        string(const char* str)
        {
   
   
            _capacity = strlen(str);
            _size = _capacity;
            _str = new char[_capacity + 1];

            strcpy(_str, str);
        }

==我们的代码实现中_capacity是计算到'\0'停止,所以在堆上new的时候要加1,给'\0'预留空间==

2.2 不带参构造函数

        string()
        {
   
   
            _str = new char[1];
            _str[0] = '\0';
            _capacity = _size = 0;
        }

不带参数就开辟1个字节的空间存放\0

2.3 默认缺省构造函数

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

            strcpy(_str, str);
        }

对于不传参的string类初始化,则是采用默认值的方式"" 该字符串自带斜杠0

这里需要区分清楚:'\0' / "\0" / "" 这几种情况的区别:

  • '\0'是字符\0 ASCII码值为0 相当于nullptr 拿nullptr作为默认值,后面strlen直接报错
  • 而"\0" 则是用两个\0初始化字符串
  • ""就是单纯的\0

    3. 拷贝构造函数

    那么拷贝构造是否也需要用户实现呢?
    答案是需要的,因为string类的构造会开辟新空间,那么要实现对空间的深拷贝就需要自己实现
    ```cpp
     // 深拷贝
     string(const string& s)
     {
         _capacity = s._size;
         _size = _capacity;
         _str = new char[_capacity + 1];
    
        strcpy(_str, s._str);
    }
# 4. 赋值重载函数
```cpp
// s1 = s3;
string& operator=(const string& s)
{
    if (this != &s)
    {
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._str);

        delete[] _str;
        _str = tmp;

        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}

首先要判断f赋值符号左右两边是否是同一对象,是则直接返回*this,否则再进一步操作

拓展:现代写法的拷贝构造和赋值重载

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5. 析构函数

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

6. c_str()

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

c_str 返回的是string类中的字符串数组的地址,对于流插入(<<) 会自动识别类型,==const char* 类型会以字符串的形式打印输出%s==
所以,string类通常用该接口打印字符串

7. size()

        size_t size() const
        {
   
   
            return _size;
        }

返回当前字符串长度(不包含\0) 最好设计成size_t 类型:因为size不会出现负数的情况

8. capacity()

size_t capacity() const
{
   
   
    return _capacity;
}

返回当前字符串容量

9. operator[]

        // 普通对象:可读可写
        char& operator[](size_t pos)
        {
   
   
            assert(pos < _size);
            return _str[pos];
        }

        // const对象:只读
        const char& operator[](size_t pos) const
        {
   
   
            assert(pos < _size);
            return _str[pos];
        }

对[]的重载,[]访问分为两种情况:普通对象(可修改数据)const对象(不可修改)
==传引用返回更加高效,也可修改数据==
记得assert断言,防止出现越界访问的情况

10. 迭代器的实现

迭代器的底层可能是指针,也可能不是(list等其他结构),在string类中迭代器的底层就是char* 类型的指针

        typedef char* iterator;

        iterator begin()
        {
   
   
            return _str;
        }

        iterator end()
        {
   
   
            return _str + _size;
        }

==范围for循环的本质就是迭代器== 范围for自动替换成迭代器中的begin和end,如果把begin替换成Begin也无法配对(报错)
在这里插入图片描述

11. reserve() -- 调整字符串容量(扩容)

当我们要进行增删改查等操作时,如果空间过小则无法继续进行,需要扩容

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

                _capacity = n;
            }
        }

实现接口的方式就是在堆上重新找一块n大小的空间,将原空间拷贝过来并释放原空间
将_str指向新空间,将_capacity置为n

12. 尾插单个字符

        void push_back(char ch)
        {
   
   
            if (_size == _capacity)
            {
   
   
                size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newCapacity);
            }

            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }

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

==在尾插单个字符的时候要注意,之前_size位置的元素是\0,所以在_size++之后要在将_size放入\0==

13. 尾插字符串

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

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

==而尾插字符串时因为str自带\0,strcpy会拷贝\0,所以_size+_len 位置就是\0,不需要单独处理==

14. 在任意位置插入单个字符

在这里插入图片描述

        string& insert(size_t pos,char ch)
        {
   
   
            assert(pos <= _size);
            if (_capacity == _size)
            {
   
   
                size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newCapacity);
            }

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

            _str[pos] = ch;
            ++_size;

            return *this;
        }

15. 在任意位置插入字符串

在这里插入图片描述

16. 删除len个字符

在这里插入图片描述

17. 查找字符/子串

在这里插入图片描述

18. 调整大小

在这里插入图片描述

19. 流插入、流提取

在之前的学习过程中,我们了解到流提取和流插入一般都不会实现成成员函数,因为istream / ostream 会抢占this指针的位置
提问:是不是流提取和流插入一定要实现成友元全局函数呢?
答:不是,全局函数是正确的(不能定义在类体内) 但是不一定要实现成友元,友元的作用是帮助我们去访问类内的成员。
在这里插入图片描述
流提取和流插入是为自定义类型而生的(C++),printf和scanf对内置类型非常友好但是无法识别自定义类型(C语言)
在这里插入图片描述
所以在打印string类时还是推荐使用流插入
在这里插入图片描述
经过get函数的优化已经可以基本实现流提取功能,但是针对众多字符的插入采用+=操作会导致频繁扩容的情况,那么如何优化呢?

优化1:临时空间减少扩容

在这里插入图片描述

优化2:覆盖数据

在这里插入图片描述
到这里string类就完整实现啦!撒花✿✿ヽ(°▽°)ノ✿

相关文章
|
3天前
|
设计模式 安全 编译器
【C++11】特殊类设计
【C++11】特殊类设计
22 10
|
8天前
|
C++
C++友元函数和友元类的使用
C++中的友元(friend)是一种机制,允许类或函数访问其他类的私有成员,以实现数据共享或特殊功能。友元分为两类:类友元和函数友元。类友元允许一个类访问另一个类的私有数据,而函数友元是非成员函数,可以直接访问类的私有成员。虽然提供了便利,但友元破坏了封装性,应谨慎使用。
39 9
|
4天前
|
存储 编译器 C语言
【C++基础 】类和对象(上)
【C++基础 】类和对象(上)
|
12天前
|
C语言 C++
【C++】string模拟实现(下)
本文档介绍了自定义`string`类的一些关键功能实现,包括`reserve()`用于内存管理,`push_back()`和`append()`添加字符或字符串,运算符`+=`的重载,以及`insert()`, `erase()`进行插入和删除操作。此外,还涵盖了`find()`查找函数,字符串的比较运算符重载,`substr()`获取子串,`clear()`清除内容,以及流插入和提取操作。常量`npos`用于表示未找到的标记。文档以代码示例和运行结果展示各功能的使用。
|
12天前
|
编译器 程序员 C语言
【C++】string模拟实现
这篇博客探讨了自定义实现C++ `string` 类的关键功能,包括构造、拷贝构造、赋值运算符重载及析构函数。作者强调了理解并实现这些功能对于面试的重要性。博客介绍了`string` 类的头文件`string.h`,其中定义了迭代器、基本成员函数如`swap()`、`size()`、`c_str()`等,并提到了深拷贝概念。此外,还展示了构造函数、析构函数和赋值运算符的实现,以及迭代器的定义与使用。博客还包括对C语言字符串函数的引用,以辅助读者理解实现细节。
|
12天前
|
编译器 C++
【C++】string类的使用④(字符串操作String operations )
这篇博客探讨了C++ STL中`std::string`的几个关键操作,如`c_str()`和`data()`,它们分别返回指向字符串的const char*指针,前者保证以&#39;\0&#39;结尾,后者不保证。`get_allocator()`返回内存分配器,通常不直接使用。`copy()`函数用于将字符串部分复制到字符数组,不添加&#39;\0&#39;。`find()`和`rfind()`用于向前和向后搜索子串或字符。`npos`是string类中的一个常量,表示找不到匹配项时的返回值。博客通过实例展示了这些函数的用法。
|
11天前
|
存储
数据存储之数组的特点,长度固定,适应变化需求,集合类特点是空间可变,ArrayList泛型,ArrayList<String> array = new ArrayList<String>()
数据存储之数组的特点,长度固定,适应变化需求,集合类特点是空间可变,ArrayList泛型,ArrayList<String> array = new ArrayList<String>()
|
12天前
|
C++
【C++】string类的使用④(常量成员Member constants)
C++ `std::string` 的 `find_first_of`, `find_last_of`, `find_first_not_of`, `find_last_not_of` 函数分别用于从不同方向查找目标字符或子串。它们都返回匹配位置,未找到则返回 `npos`。`substr` 用于提取子字符串,`compare` 则提供更灵活的字符串比较。`npos` 是一个表示最大值的常量,用于标记未找到匹配的情况。示例代码展示了这些函数的实际应用,如替换元音、分割路径、查找非字母字符等。
|
12天前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
12天前
|
C++
C++】string类的使用③(修改器Modifiers)
这篇博客探讨了C++ STL中`string`类的修改器和非成员函数重载。文章介绍了`operator+=`用于在字符串末尾追加内容,并展示了不同重载形式。`append`函数提供了更多追加选项,包括子串、字符数组、单个字符等。`push_back`和`pop_back`分别用于在末尾添加和移除一个字符。`assign`用于替换字符串内容,而`insert`允许在任意位置插入字符串或字符。最后,`erase`函数用于删除字符串中的部分内容。每个函数都配以代码示例和说明。