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

都比较简单易懂。

相关文章
|
1月前
|
C++ 容器
|
30天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
C++ 容器
|
1月前
|
C++ 容器
|
1月前
|
存储 C++ 容器
|
1月前
|
安全 C语言 C++
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
34 4
|
1月前
|
存储 编译器 程序员
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
63 2
|
1月前
|
编译器 C语言 C++
【C++】C++ STL 探索:String的使用与理解(三)
【C++】C++ STL 探索:String的使用与理解
|
1月前
|
存储 编译器 C++
【C++】C++ STL 探索:String的使用与理解(二)
【C++】C++ STL 探索:String的使用与理解
|
1月前
|
编译器 C语言 C++
【C++】C++ STL 探索:String的使用与理解(一)
【C++】C++ STL 探索:String的使用与理解