• C++的string你可能会用,但是你模拟实现过了吗?带你实现以下string的重要接口!


    模拟实现string


    ​ 为了不与库里面的string相冲突,我们可以将自己的string写在自己的命名空间(namespace)里面。

    以下是程序需要要的头文件。

    #pragma once
    #include
    #include
    #include
    using namespace std;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    #pragma once是防止头文件被再次引用,是一个C语言的知识点,开始实现。


    注意以下写的函数,如果没有特殊表明,都是成员函数的意思,成员函数里面是默认有this指针的

    string类的是写在一个文件,测试函数写在另一个文件(main()函数)

    一.四大默认成员函数

    ​ 首先我们得想想,string类的成员变量都有谁,string是一个字符数组。

    1. 维护动态内存的指针(_str)
    2. 指向最后位置的下标(_size)
    3. 字符数组容量(_capacity)。
    namespace kcc//kcc是博主的名字
    {
    public:
        //成员函数…………
        
    private:
        char* _str;
        size_t _size;
        size_t _capacity;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    构造函数
    string(const char* str = "")//动态内存--要解决深拷贝的问题
    {
    	_size = strlen(str);
    	_capacity = _size;
        _str = new char[_capacity + 1];
        strcpy(_str, str);//这里就很好的复用了c库里面的库函数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ​ 要申请_capacity+1长的空间的原因是,我们要将\0也拷贝进来。

    析构函数
    ~string()
    {
        delete[] _str;
        _str = nullptr;
        _size = 0;
    	_capacity = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ​ 析构函数主要解决对象的清理工作,1.要完成对空间的释放 2.将维护空间的指针置为空指针,避免野指针的存在

    3._size_capacity也要变为0。


    拷贝构造函数
    传统写法–自己生火,自己做饭

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TckUWpDR-1663384266179)(D:\gitee仓库\博客使用的表情包\自己做饭.jpg)]

    ​ 还记得我们之前说的,编译器默认生成的拷贝构造函数,只能完成简单的值拷贝,而像这种动态内存的,需要深拷贝,这次可不能靠编译器来帮我们了。

    ​ 所以我们要实现的功能是,重新申请一块动态空间,再将其内容拷贝过来。

    string(const string& s)
    {
        _str = new char[strlen(s._str) + 1];
        strcpy(_str, s._str);
        _size = s._size;
        _capacity = s._capacity;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    现代写法(别人烧火做饭,再给我)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HpF4FgO6-1663384266181)(D:\gitee仓库\博客使用的表情包\点外卖.jpg)]

    ​ 为了实现别人的东西给我们,我们先实现一个交换函数。(也是我们的string的成员函数)

    void swap(string& s)
    {
        ::swap(_str, s._str);//加了两个点是因为swap是用库里面的交换函数
        ::swap(_size, s._size);
        ::swap(_capacity, s._capacity);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ​ 库里面的交换函数是这样的,是一个模板函数。如果我们用库里面的,将会多次调用构造函数和拷贝构造函数,这样子效率其实比较低,所以我们可以自己实现一个对string的交换函数。

    string(const string& s)
        :_str(nullptr)
        {			
            string tmp(s._str);
    		swap(tmp);//在类中是有this指针的
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6


    赋值运算符重载operaor=
    传统写法
    string& operator= (const string& s)
    {
        //考虑自己给自己赋值的问题
        if (this != &s)
        {
            delete[]_str;
            _str = new char[strlen(s._str) + 1];
            strcpy(_str, s._str);
            _size = s._size;
            _capacity = s._capacity;
        }
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ​ 如果不考虑自赋值,那么delete[]_str;,这句代码就会让后面的程序崩掉了。

    现代写法
    string& operator=(string s)
    {    
        swap(s);
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ​ 虽然现代写法的拷贝构造觉得没有什么太大的优势,但是现代写法的赋值运算符重载,显得代码的妙处很高!很灵活。

    但是现代写法没有考虑自赋值的问题,自赋值会使地址发生该改变。我的理解是:自赋值本来就是一个错误的写法,现代的编程素养不断提高,也不会在程序中写出自赋值的情况。当然了,如果你非要去实现自赋值,那还是用传统写法吧。或者把现代写法改一改。

    其实面试时,面试官叫你实现一个string主要也是想看看,你会如何去实现这几个默认成员函数。


    二、三种遍历方式

    下标遍历法

    ​ 下标遍历法首先我们要实现一个成员函数size(),其可以返回成员变量_size。

    size()
    size_t size() const
    {
        return _size;
    }
    
    • 1
    • 2
    • 3
    • 4

    温馨建议:如果一个成员函数不会改变对象内容,实现时加上const修饰,不然const对象是无法调用该成员函数的(权限放大)

    关于权限的放大,缩小大家可以参考这篇文章:

    (337条消息) 类与对象(下)_龟龟不断向前的博客-CSDN博客


    ​ 我们想像内置类型一样,欢快的使用,我们还得实现一个运算符[]重载

    operator[]
     char& operator[]( size_t pos) //返回引用才可修改
     {
         return _str[pos];//访问对应下表的内容
     }
    
    • 1
    • 2
    • 3
    • 4

    温馨提示:如果一个成员函数既可以修改对象的内容,又可以不修改对象的内容。即:可读可写,与仅可读,那么我们要实现两个成员函数

    const char& operator[](size_t pos) const
    {
        return _str[pos];
    }
    
    • 1
    • 2
    • 3
    • 4

    这样子,const与非const对象均可调用。

    下标遍历的代码实现:

    int main()
    {
        kcc::string s = "hello world";
        for(int i = 0;i< s.size();++i)
        {
            cout<<s[i]<<" "
        }
        cout<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    迭代器遍历法

    ​ string的迭代器其实就是一个char*指针。

    iterator与const_iterator

    ​ 大家可能会说:不是说string迭代器是指针吗,那iterator是啥呀,我也不认识啊,其实iterator的原理很简单,就是使用了typedef

    typedef char* iterator;
    typedef const char* const_iterator
    
    • 1
    • 2
    begin()
    char* begin() 
    {
        return _str;
    }
    
    • 1
    • 2
    • 3
    • 4

    返回首元素的地址,根据咱们的温馨提示,如果有可读可写和仅可读功能的,成员函数要实现两个。

    const char* begin() const//const迭代器
    {
    	return _str;
    }
    
    • 1
    • 2
    • 3
    • 4
    end()
    char* end()
    {
        return (_str + _size);
    }
    
    • 1
    • 2
    • 3
    • 4
    const char* end() const//const迭代器
    {
        return (_str + _size);
    }
    
    • 1
    • 2
    • 3
    • 4

    迭代器遍历法的代码实现:

    int main()
    {
        kcc::string s1 = "hello world";
        iterator it1 = s1.begin();
        while(it1 != s1.end())
        {
            *it1+=1;
            cout<<*it1<<" ";//可读可写
            ++it1;
        }
        
        const kcc::string s2 = "hello world";
        iterator it2 = s2.begin();
        while(it2 != s2.end())
        {
            cout<<*it2<<" ";//仅可读
            ++it2;
        }
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    范围for遍历法

    代码实现:

    int main()
    {
        string s = "hello world";
        for(auto& e: s)
        {
            e+=1;
            cout<<e<<" ";
        }
        cout<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其实范围for的原理也很简单,就是将代码替换成了迭代器的代码,不信你可以将上面的begin()的成员函数写成Begin(),范围for就遍历不起来的。


    三、string的增删查改

    ​ 由于增要考虑到增容的问题,为了增强代码的复用,所以咱们先要实现一个reserve(),它可以改变_capacity的值。

    reserve–改变容量(异地增容)
    void reserve(size_t n)
    {
        if (n > _capacity)
        {
            char* tmp = new char[n + 1];
            strcpy(tmp, _str);
            delete[]_str;     
            _capacity = n; 
            _str = tmp;//让_str来维护这块空间
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们顺便也把resize给实现了把

    resize()–改变_size的值
    void resize(size_t n,char c = '\0')
    {
        if (n < _size)
        {
            _str[n] = '\0';
            _size = n;
        }
        else
        {
            if (_size + n > _capacity)
            {
               
                reserve(_capacity + n);       
            }
    
            for (int i = _size; i < n; i++)       
            {
                _str[i] = c;
            }
            _str[n] = '\0';              
            _size = n;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    push_back()–尾插字符
    void push_back(char c)
    {
        //增容情况
        //这里没有考虑如果_capacity是0话还是会增容失败
        if (_size == _capacity)
        {
            size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
            reserve(newcapacity);
        }
        _str[_size] = c;
        _str[_size+1] = '\0';
        _size++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;

    这句代码的意义是很重要的,因为我们实现的string的空串的_capacity是0,如果_capacity再怎么2倍增容,还是会增容失败。


    append–尾插字符串
    void append(const char* str)
    {
        //这个增容情况就不像push_back,因为增容2倍可能也不够
        //所以建议增容一个字符串的长度
        size_t len = strlen(str);
        if (_size == _capacity)
        {
            reserve(len + _capacity);
        }
        strcpy(_str + _size, str );//将str的内容拷贝进来
        _size += len;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    operator+=–既可以尾插字符,又可以尾插字符串
    string& operator+=(const char ch)
    {
        push_back(ch);
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    string& operator+=(const char* str)
    {
        append(str);
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    string& operator+=(const char* str)
    {
        append(str);
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ​ 很多同学可能会说,库里面为什么还要设计push_back和append啊,明明一个operator+=就够了,但是我们从实现的角度理解,

    operator是复用了push_back和append。


    insert–中间插,头插字符或字符串
    string& insert(size_t pos,const char c)//在pos位置,插入字符
    {
        assert(pos <= _size);
        if (_size == _capacity)//增容问题
        {
            size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
            reserve(newcapacity);
        }
        size_t end = _size + 1;//防止头插时,end越界了,因为是无符号数
        //1.挪动数据
        while (end > pos)
        {
            _str[end] = _str[end - 1];
            --end;
        }
        //2.插入字符
        _str[pos] = c;
        _size++;//不要忘记调正_size的值了
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    特别注意,上面的end是无符号数,end如果减到-1了,其实是一个很大的数,并不是一个小于0的数。

    string& insert(size_t pos, const char* str)
    {
        assert(pos <= _size);
        int len = strlen(str);
        int newcapacity = (_size + len >= _capacity) ? _capacity + len : _capacity;
        reserve(newcapacity);
        //2.挪动数据-这次我们使用指针
        char* end = _str + _size;
        while (end >= _str + pos)//pos的位置上的数据也要挪
        {
            *(end + len) = *end;
            --end;
        }
        //3.填数据
        strncpy(_str + pos, str, len);
        _size += len;
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    strncpy(_str + pos, str, len);这句代码我们得讨论以下,可千万不敢用strcpy,因为strcpy会把\0也拷贝过来,与我们的需要不一致,所以我们要设置一个拷贝的最大长度,避免\0被拷贝过来了。


    string的超大专属数npos
    namespace kcc//kcc是博主的名字
    {
    public:
        //成员函数…………
        
    private:
        char* _str;
        size_t _size;
        size_t _capacity;
        static const size_t npos;
    }
    
    const size_t npos = -1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    erase–从指定位置pos开始,删除n个字符
    string& erase(size_t pos = 0,size_t n = npos)
    {
        //将数据向前挪动即可
        assert(pos <= _size);
        if (n < _size)
        {
            char* start = _str + pos + n;
            while (start <= _str + _size)
            {
                *(start - n) = *start;
                ++start;
            }
            _size -= n;
        }   
        else
        {
            _str[0] = '\0';
            _size = 0;   
        }
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    删除数据并不是说是真的将数据删除,我们上面的代码,仅仅只是挪动后面的数据,将前面的数据覆盖了。

    温馨提示:insert和erase都需要挪动数据,极端情况下的时间复杂度是O[N^2],所以尽量少用insert和erase


    find–从指定位置,找指定字符和字符串的下标(从前向后找)

    找到了返回下标,找不到返回npos

    size_t find(const char ch, size_t pos = 0) const
    {
        assert(pos < _size);  
        for (size_t i = pos; i < _size; ++i)
        {
            if (_str[i] == ch)
            {
                return i;
            }
        }   
        return npos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    size_t find(const char* str,size_t pos = 0)const
    {
        assert(pos < _size);  
        char* ret = strstr(_str, str);//在_str中找str,返回第一个找到的下标
        if (ret)
        {
            return (ret - _str);
        }
        else
        {
            return npos;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    reverse–逆置字符串

    想必大家在c语言时期已经写过很多遍了吧。这个逆置是使用迭代器逆置的

    什么是迭代器逆置,c语言时期我只学过用指针逆置啊,或者下标逆置啊。

    不要慌张,上面不是讲了string的迭代器本质不就是指针嘛)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QRlGYQfx-1663384266182)(D:\gitee仓库\博客使用的表情包\冷静.jpg)]

    string& reverse(iterator left,iterator right)
    {
        while (left < right)
        {
            char tmp = *left;
            *left = *right;
            *right = tmp;
            ++left;
            --right;
        }
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    rfind–从后向前找
    size_t rfind(const char ch, size_t pos = npos)const
    {
        char* end = nullptr;
        if (pos < _size)
        {
            end = _str + pos;
        }
        else
        {
            end = _str + _size;
        }
        while (end >= _str)
        {
            if (*end == ch)
            {
                return end - _str;
            }
            --end;
        }
        return npos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    size_t rfind(const char* ch, size_t pos = npos)const//ch是c串的首元素地址
    {
        size_t len = strlen(ch);
        char* end = nullptr;
        if (pos > _size)
        {
            end = _str + _size;
        }
        else
        {
            end = _str + pos;
        }
        while (end >= _str)
        {
            int ret = strncmp(end, ch, len);
            if (ret == 0)//找到了就会返回0
            {
                return (end - _str);
            }
            --end;
        }
        return npos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    不过这样子实现rfind的复用性很差,大家可以思考一下,如果用find和reverse来实现rfind。

    欢迎在评论区留下你的代码。


    改其实咱们遍历的使用就讲了,三种遍历里面可以修改对象的数据。

    四、字符串比较

    为了可以更好地体现c++的字符串和c串的兼容性,我们可以写一个c_str()函数,库里面也写了

    const char* c_str(const string& s)
    {
        return s._str;
    }
    
    • 1
    • 2
    • 3
    • 4

    ####> , < ,>= ,<= ,== ,!=(可以不设置为成员函数)

    bool operator>(const string& s1, const string& s2)//我们可以直接用库里面的来实现
    {
        return strcmp(s1.c_str(),s2.c_str()) > 0;
    }
    
    bool operator==(const string& s1, const string& s2)
    {
        return strcmp(s1.c_str(), s2.c_str()) == 0;
    }
    
    bool operator>=(const string& s1, const string& s2)
    {
        return (s1 > s2) || (s1 == s2);
    }
    
    bool operator<(const string& s1, const string& s2)
    {
        return !(s1 >= s2);
    }
    
    bool operator<=(const string& s1, const string& s2)
    {
        return !(s1 > s2);
    }
    
    bool operator!=(const string& s1, const string& s2)
    {
        return !(s1 == s2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    如果以上函数在未写声明的时候定义,编译器找对应的函数只会从下向上找,所以先后顺序要清楚。

    五、operator<<,operator>>,getline

    以前说过,operator<<,operator>>为了占位是要定义在类的外面的,getline也定义在类外面

    关于运算符重载的占位可以看看博主的另一篇文章:

    (337条消息) 类与对象(下)_龟龟不断向前的博客-CSDN博客

    由于我们已经可以通过成员函数得到串的内容,所以不再需要将这些函数定义成类的友元了


    operator<<
    ostream& operator<<(ostream& out, string& s)//注意字符串输出可不是遇到\0结束,而是输出所有内容
    {
        for (int i = 0; i < s.size(); ++i)
        {
            out << s[i];
        }
        return out;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    看到什么差别了吗,有些同学总是会说,C语言的字符串跟C++的字符串有什么区别啊,不都是字符数组吗,但是你输出一个C的串,它遇到\0就会结束不再输出,而C++的string则是输出至_size结束。

    图解:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XjPmhfb6-1663384266183)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220917104257023.png)]

    大家思考一下,C语言的输出上述串,输出的结果是hello

    而c++输出的是helloworld


    operator>>

    如果思考一下:如果对一个已初始化的对象进行输出,那么他原先的内容将被覆盖,所以咱们还需要一个成员函数clear()来清空其内容

    clear()–成员函数
    void clear()
    {
        _size = 0;
    }
    
    • 1
    • 2
    • 3
    • 4

    不要不可思议这段代码,还是那个思想,删除并不一定就是删除

    istream& operator>>(istream& in, string& s)//这个是遇到空格或者回车就会结束了
    {
        s.clear();//我们得先将s的内容清空
        char ch;
        ch = getchar();
        while ((ch != ' ') && (ch != '\n'))//用in会自动省略\n,所以咱们用scanf或者getchar
        {        
            s += ch;//用+=就不用考虑增容了
            ch = getchar();
        }
        return in;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    getline()
    istream& getline(istream& in, string& s)
    {
        s.clear();//我们得先将s的内容清空
        char ch;
        ch = getchar();
        while (ch != '\n')//用in会自动省略\n,所以咱们用scanf或者getchar
        {
            s += ch;//用+=就不用考虑增容了
            ch = getchar();
        }
        return in;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    六、相对应的string的作业

    1. 917. 仅仅反转字母 - 力扣(LeetCode)
    2. 387. 字符串中的第一个唯一字符 - 力扣(LeetCode)
    3. 字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com)
    4. 125. 验证回文串 - 力扣(LeetCode)
    5. 415. 字符串相加 - 力扣(LeetCode)
    6. 541. 反转字符串 II - 力扣(LeetCode)
    7. 557. 反转字符串中的单词 III - 力扣(LeetCode)
    8. 43. 字符串相乘 - 力扣(LeetCode)
    9. 找出字符串中第一个只出现一次的字符_牛客题霸_牛客网 (nowcoder.com)
    10. 剑指 Offer 05. 替换空格 - 力扣(LeetCode)

    七、聊一聊心得吧

    ​ 其实这个模拟实现string的程序写的时候真的很痛苦,写代码一时爽,测试起来真的要了我的老命,各种BUG层出不穷。

    说来这些BUG也很哭笑不得。

    1. new int[4]我把[]写成(),导致一调用析构函数就报错
    2. const关键词不知道什么时候加,导致对象调用成员函数老报错(总结在上面文章的温馨提示)
    3. 输出字符串显示很多乱码,因为后面的\0没有处理
    4. …………

    BUG之大,一锅炖不下。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jvk8PxP6-1663384266184)(D:\gitee仓库\博客使用的表情包\一锅炖不下.jpg)]

    ​ 但是编程不就是这样的吗,程序员的大部分时间都在调式代码,微软搞了这么多年的操作系统不也还是会蓝屏

    还记得我的一个导师说过:编程带给你的快乐往往是让你经理了多重痛苦,给你一份敞开心扉的爽快,在我实操完了string

    的模拟实现,测试完了一般程序,真的是很欣慰开心,当然了上面的代码还有很多的不足之处,还有很多可以改良的地方。

    欢迎大家在评论区留言指出,多谢了。

    string的模拟实现的的代码也已经上传到了gitee上面:
    5.2模拟实现string/5.2模拟实现string · small_sheep/cplusplus0study - 码云 - 开源中国 (gitee.com)


    如果这篇文章有帮到你,请点歌免费的赞再走吧,这是对我最大的鼓励!

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RMfY5qNE-1663384266184)(D:\gitee仓库\博客使用的表情包\给点赞吧.jpg)]
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SCEzktGt-1663384266185)(D:\gitee仓库\博客使用的表情包\要赞.jpg)]

  • 相关阅读:
    node cnpm npm 的坑
    css 对号 叉号
    Git命令
    说一说设备综合效率OEE
    Linux- pipe()系统调用
    Python_面向对象
    微信小程序学习
    python实战故障诊断之CWRU数据集(五):线性判别模型及二次判别模型的应用
    【计算机毕业设计】75.教师工作考核绩效管理系统源码
    自定义通知栏显示不全、heads-up通知栏的应用
  • 原文地址:https://blog.csdn.net/m0_64361907/article/details/126902937