【C++】string模拟实现

简介: 这篇博客探讨了自定义实现C++ `string` 类的关键功能,包括构造、拷贝构造、赋值运算符重载及析构函数。作者强调了理解并实现这些功能对于面试的重要性。博客介绍了`string` 类的头文件`string.h`,其中定义了迭代器、基本成员函数如`swap()`、`size()`、`c_str()`等,并提到了深拷贝概念。此外,还展示了构造函数、析构函数和赋值运算符的实现,以及迭代器的定义与使用。博客还包括对C语言字符串函数的引用,以辅助读者理解实现细节。

前言

本篇博客主要内容:实现string类的基本功能

string使用很快就讲完了,但是光会用string还不够,在面试中,面试官总喜欢让我们自己来模拟实现string类,包括string类的构造、拷贝构造、赋值运算符重载以及析构函数等等内容。所以,我认为string类的自实现还是有必要讲一下的。

🔥string类的接口函数

我们本次并不会将string类的所有接口函数逐一讲解,讲一些常用的和重点的。本次string的实现分成了两个文件,一份是string.h,一份string.cpp
看看string.h的内容:

# define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<cstring>
#include<cassert>
using std::cout;
using std::cin;
using std::endl;
namespace ForcibleBugMaker
{
   
   
    class string 
    {
   
   
    public:
        // 交换,非成员函数
        friend void swap(string& s1, string s2);

        // 定义迭代器
        typedef char* iterator;
        typedef const char* const_iterator;
        //迭代器获取
        iterator begin();
        iterator end();
        const_iterator begin() const;
        const_iterator end() const;

        // string默认成员函数
        string(const char* str = "");
        string(const string& s);
        string& operator=(string tmp);
        ~string();

        // 获取只读字符串
        const char* c_str() const;

        // 获取容量
        size_t size() const;
        size_t capaity() const;

        // []获取元素重载
        char& operator[](size_t pos);
        const char& operator[](size_t pos) const;

        // 开辟空间
        void reserve(size_t n);

        // 尾插字符或字符串
        void push_back(char ch);
        void append(const char* str);
        string& operator+=(char ch);
        string& operator+=(const char* str);

        // 插入字符或字符串
        void insert(size_t pos, char ch);
        void insert(size_t pos, const char* str);

        //删除字符串
        void erase(size_t pos = 0, size_t len = npos);

        // 查找字符或字串
        size_t find(char ch, size_t pos = 0);
        size_t find(const char* str, size_t pos = 0);

        // string对象比较
        bool operator>(const string& str) const;
        bool operator==(const string& str) const;
        bool operator>=(const string& str) const;
        bool operator<(const string& str) const;
        bool operator<=(const string& str) const;
        bool operator!=(const string& str) const;

        // 获取string对象字串
        string substr(size_t pos, size_t len);

        // 交换函数,成员函数
        void swap(string& str);

        // 清除串中内容
        void clear();

    private:
        char* _str = nullptr;
        size_t _size = 0;
        size_t _capacity = 0;
        // 常量成员
        const static size_t npos;
    };

    // 交换函数,非成员函数
    void swap(string& s1, string s2);

    // 流插入和流提取重载,非成员函数
    std::ostream& operator<<(std::ostream& so, const string& str);
    std::istream& operator>>(std::istream& is, string& str);
}

在实现string类添加和拷贝的一些函数中,使用了C语言中的一些库函数,以此方便实现,这些函数在C++中统一存放在<cstring>
如果对C语言的一些字符串函数不太了解,可以看看我之前写的一篇博客:C语言-字符串函数,相信会对你有所帮助。

🔥string类的模拟实现

接下来进入主要内容,按照string.h的接口开始实现。

以下接口都是放在命名空间里的,不同文件的相同命名空间在编译时会自动合并。

==swap交换==

如果你仔细观察,会发现存在两个swap交换函数,一个string中的成员函数,另一个是非成员函数。
在std的默认swap当中,是这样的:
在这里插入图片描述

template <class T> void swap ( T& a, T& b )
{
    
    
  T c(a); a=b; b=c;
}

当我们使用这个成员函数进行交换时,会造成拷贝消耗,我们提供对应的非成员函数重载是为了防止C++程序员掉坑。
成员函数的swap:

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

非成员函数的swap:

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

==默认成员函数==

默认成员函数,就是不提供编译器自动会生成的一些函数。我们这里实现构造函数,析构函数和赋值运算符重载

构造函数:

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

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

构造函数提供了两个,分别支持了字符串构造和string对象构造。_str指向通过new开辟空间,这个空间需要比实际的capacity大,因为需要在字符串末尾多存放一个'\0'

析构函数:

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

这个没什么好说,释放空间,指针置空,_size和_capacity置0。

赋值运算符重载:

string的赋值属于深拷贝。

拷贝分为深拷贝和浅拷贝:
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享
深拷贝:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

浅拷贝:
在这里插入图片描述
深拷贝:
在这里插入图片描述
如果中规中矩的来写(传统版赋值重载):

String& operator=(const String& s)
{
   
   
    if (this != &s)
    {
   
   
        char* pStr = new char[strlen(s._str) + 1];
        strcpy(pStr, s._str);
        delete[] _str;
        _str = pStr;
    }
    return *this;
}

但如果你能对之前实现的代码进行复用,你会发现这个过程可以简化非常多(现代版赋值重载)

string& string::operator=(string tmp)
{
   
   
    // 调用成员函数的swap,交换*this和tmp
    swap(tmp);
    return *this;
}

在C++的编写中,学会复很有必要。

==迭代器==

之前讲过,迭代器不一定是指针,但是你可以把它想象成指针,可以通过正常的运算操作来控制其指向的元素。在string中,我们使用指针来模拟实现迭代器。
迭代器在不同编译器下的实现方式有所不同,比如VS下的迭代器就不是一个指针,而是一个类模板。

在这里插入图片描述
在类中需要typedef一下,可以看到我们所实现的string迭代器的本质:

// 定义迭代器
typedef char* iterator;
typedef const char* const_iterator;

获取迭代器的接口:

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

这时,我们已经可以使用我们自己的迭代器了:

string str("hello world!");
string::iterator it = str.begin();
while (it != str.end()) {
   
   
    cout << *it << " ";
    ++it;
}
cout << endl;

在这里插入图片描述

==获取容量和内容信息==

这些都是返回str类型状态和内容的函数。

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

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

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

// 运算符重载
char& string::operator[](size_t pos)
{
   
   
    assert(pos < _size);
    return _str[pos];
}

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

都比较简单易懂。

相关文章
|
3天前
|
C语言 C++
【C++】string模拟实现(下)
本文档介绍了自定义`string`类的一些关键功能实现,包括`reserve()`用于内存管理,`push_back()`和`append()`添加字符或字符串,运算符`+=`的重载,以及`insert()`, `erase()`进行插入和删除操作。此外,还涵盖了`find()`查找函数,字符串的比较运算符重载,`substr()`获取子串,`clear()`清除内容,以及流插入和提取操作。常量`npos`用于表示未找到的标记。文档以代码示例和运行结果展示各功能的使用。
|
3天前
|
编译器 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类中的一个常量,表示找不到匹配项时的返回值。博客通过实例展示了这些函数的用法。
|
3天前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
10天前
|
C++ 容器
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
18 1
|
3天前
|
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` 是一个表示最大值的常量,用于标记未找到匹配的情况。示例代码展示了这些函数的实际应用,如替换元音、分割路径、查找非字母字符等。
|
3天前
|
C++
C++】string类的使用③(修改器Modifiers)
这篇博客探讨了C++ STL中`string`类的修改器和非成员函数重载。文章介绍了`operator+=`用于在字符串末尾追加内容,并展示了不同重载形式。`append`函数提供了更多追加选项,包括子串、字符数组、单个字符等。`push_back`和`pop_back`分别用于在末尾添加和移除一个字符。`assign`用于替换字符串内容,而`insert`允许在任意位置插入字符串或字符。最后,`erase`函数用于删除字符串中的部分内容。每个函数都配以代码示例和说明。
|
3天前
|
安全 编译器 C++
【C++】string类的使用②(元素获取Element access)
```markdown 探索C++ `string`方法:`clear()`保持容量不变使字符串变空;`empty()`检查长度是否为0;C++11的`shrink_to_fit()`尝试减少容量。`operator[]`和`at()`安全访问元素,越界时`at()`抛异常。`back()`和`front()`分别访问首尾元素。了解这些,轻松操作字符串!💡 ```
|
3天前
|
存储 编译器 Linux
【C++】string类的使用②(容量接口Capacity )
这篇博客探讨了C++ STL中string的容量接口和元素访问方法。`size()`和`length()`函数等价,返回字符串的长度;`capacity()`提供已分配的字节数,可能大于长度;`max_size()`给出理论最大长度;`reserve()`预分配空间,不改变内容;`resize()`改变字符串长度,可指定填充字符。这些接口用于优化内存管理和适应字符串操作需求。
|
3天前
|
C++ 容器
【C++】string类的使用①(迭代器接口begin,end,rbegin和rend)
迭代器接口是获取容器元素指针的成员函数。`begin()`返回首元素的正向迭代器,`end()`返回末元素之后的位置。`rbegin()`和`rend()`提供反向迭代器,分别指向尾元素和首元素之前。C++11增加了const版本以供只读访问。示例代码展示了如何使用这些迭代器遍历字符串。
|
3天前
|
存储 编译器 C语言
【C++】string类的使用①(默认成员函数
本文介绍了C++ STL中的`string`类,它是用于方便地操作和管理字符串的类,替代了C语言中不便的字符数组操作。`string`基于`basic_string`模板,提供类似容器的接口,但针对字符串特性进行了优化。学习资源推荐[cplusplus.com](https://cplusplus.com/)。`string`类提供了多种构造函数,如无参构造、拷贝构造、字符填充构造等,以及析构函数和赋值运算符重载。示例代码展示了不同构造函数和赋值运算符的用法。