【十】【C++】string类的模拟实现

浅拷贝

浅拷贝(Shallow Copy)是对象复制的一种方式,其中复制对象的过程仅仅复制对象的值,而不复制引用所指向的实际对象或数据。这意味着原始对象和拷贝对象会共享相同的引用或指针指向的数据。

浅拷贝的特点:

共享内存:拷贝对象和原始对象共享相同的内存地址指向的数据。

快速复制:由于不需要复制引用指向的实际数据,浅拷贝的过程通常比深拷贝更快。

潜在风险:如果原始对象或拷贝对象修改了共享的数据,这种变化会影响到另一个对象。这可能导致数据不一致、意外的副作用或内存泄漏等问题。

 
class SimpleClass {
public:
    int* data;

    SimpleClass(int value) {
        data = new int(value);
    }

    // 浅拷贝构造函数
    SimpleClass(const SimpleClass& other) {
        data = other.data; // 只复制指针,不复制指针指向的数据
    }

    ~SimpleClass() {
        delete data;
    }
};

在这个例子中,拷贝构造函数执行浅拷贝,仅复制data指针而不复制指针所指向的int值。这意味着拷贝后的对象和原始对象共享同一个int值。如果一个对象被销毁(调用析构函数),它也会释放共享的内存,这可能导致另一个对象访问已释放的内存,从而引发未定义行为。

浅拷贝可以理解为简单的等号赋值,如果对指针或者引用进行浅拷贝,此时指针和引用会与原指针和引用共用同一地址空间,即存储的地址空间是相同的,也就是只是简单的把值进行复制,而指向的空间,空间上的值不会复制。。

深拷贝

深拷贝(Deep Copy)是对象复制的一种方式,它不仅复制对象本身的值,还包括对象引用的所有内容到新的内存地址中。这意味着原始对象和拷贝对象在物理上完全独立,它们不会共享任何内存地址指向的数据。

深拷贝的特点:

独立性:拷贝对象和原始对象不会共享任何数据。对拷贝对象的修改不会影响原始对象,反之亦然。

资源复制:深拷贝会递归地复制所有对象,包括对象中的所有引用指向的数据。

更高的开销:由于需要复制所有的数据到新的内存地址,深拷贝通常比浅拷贝有更高的时间和空间开销。

 
class DeepCopyClass {
public:
    int* data;

    DeepCopyClass(int value) {
        data = new int(value);
    }

    // 深拷贝构造函数
    DeepCopyClass(const DeepCopyClass& other) {
        data = new int(*other.data); // 复制指针所指向的数据到新的内存地址
    }

    ~DeepCopyClass() {
        delete data; // 释放分配的内存
    }
};

在这个例子中,拷贝构造函数通过new操作符分配新的内存,并复制原始对象data指针所指向的值到这块新的内存。这确保了拷贝对象和原始对象在物理上是完全独立的。

深拷贝不仅会复制指针和引用的值,还会把对象的所有内容放到一个新的内存地址上。

初始化列表的初始化器---浅拷贝

 
/*初始化列表初始化器---浅拷贝
 浅拷贝---简单的“=”赋值*/
#include <iostream>
using namespace std;
class A {
    private:
        int _a;
        int* _p;
        int& _b;
    public:
        A(int a, int* p, int &b)
            : _a(a)
            , _p(p)
            , _b(b)
        {}
        void Show() {
            cout << "&_a:" << &_a << endl;
            cout << "_p:" << _p << endl;
            cout << "&_b:" << &_b << endl;
        }
 };
int main() {
    int a = 10;
    int b = 30;
    int* p = new int(20);
    A x(a, p, b);
    x.Show();
    cout << "&a:"<<&a << endl;
    cout << "p:"<<p << endl;
    cout << "&b:"<<&b << endl;
 }

浅拷贝,用a,p,b对_a,_p,_b进行浅拷贝,可以理解为简单的“=”赋值,即_a=a,_p=p,_b=b

此时_p指针指向p的地址,_b引用是b的别名。

string类(简易版)传统写法简单实现

 
/*string类传统写法简单实现*/
#include <iostream>
using namespace std;
#include <string.h>
class String {
    public:
        String(const char* str = "") {
            if (nullptr == str)
                str = "";

            _str = new char[strlen(str) + 1];
            strcpy(_str, str);
        }

        String(const String& s)
            : _str(new char[strlen(s._str) + 1]) {
            strcpy(_str, s._str);
        }

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

        ~String() {
            if (_str) {
                delete[] _str;
                _str = nullptr;
            }
        }

    private:
        char* _str;
 };
int main() {


    return 0;
 }

构造函数

 
String(const char* str = "") {
    if (nullptr == str)
        str = "";

    _str = new char[strlen(str) + 1];
    strcpy(_str, str);
}

这是String类的构造函数,它接受一个C风格字符串str作为参数,默认为空字符串""

""表示一个字符大小的空间上,只存储'\0'

如果传入的strnullptr,为了防止访问空指针,将str设置为空字符串。

使用new为成员变量_str分配足够的内存来存储传入的字符串(包括结尾的空字符'\0')。

strlen()从头扫描字符串,直到遇到'\0'停止,计数,不包括'\0'

使用strcpy将传入的字符串复制到_str指向的内存中。

strcpy复制字符串,同时会把'\0'复制过去。

拷贝构造函数

 
String(const String& s)
    : _str(new char[strlen(s._str) + 1]) {
    strcpy(_str, s._str);
}

这是String类的拷贝构造函数,它接受另一个String对象s作为参数。

使用new_str分配足够的内存,以存储s._str指向的字符串。

使用strcpy复制字符串,同时会把'\0'复制过去。

赋值操作符

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

这是String类的赋值操作符重载,允许将一个String对象赋值给另一个String对象。

首先检查自赋值的情况,如果不是自赋值,则继续。

创建一个新的临时字符数组temp,并复制源字符串s._strtemp

释放_str当前指向的内存,避免内存泄漏。

_str指向新分配并已复制的内存。

返回当前对象的引用,以支持链式赋值。

析构函数

 
~String() {
    if (_str) {
        delete[] _str;
        _str = nullptr;
    }
}

这是String类的析构函数,负责释放_str指向的动态分配的内存,防止内存泄漏。

检查_str是否非空,如果是,则使用delete[]释放内存,并将_str设置为nullptr

string类(简易版)现代写法简单实现

 
/*string类现代写法简单实现*/
#include <iostream>
using namespace std;
#include <string.h>
// 深拷贝实现方式二:简洁版/现代版
class String
{
public:
    String(const char* str = "")
    {
        if (nullptr == str)
            str = "";

        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }

    String(const String& s)
    : _str(nullptr)
    {
        // 调用构造函数创建一个新对象
        String strTemp(s._str);
        swap(_str, strTemp._str);
    }

    String& operator=(String s)
    {
        swap(_str, s._str);
        return *this;
    }

    ~String()
    {
        if (_str)
        {
            delete[] _str;
            _str = nullptr;
        }
    }

private:
    char* _str;
 };

构造函数

 
String(const char* str = "") {
    if (nullptr == str)
        str = "";
    
    _str = new char[strlen(str) + 1];
    strcpy(_str, str);
}

这是String类的构造函数,它接受一个C风格字符串str作为参数,默认为空字符串""

如果传入的strnullptr,为了防止访问空指针,将str设置为空字符串。

使用new为成员变量_str分配足够的内存来存储传入的字符串(包括结尾的空字符'\0')。

使用strcpy将传入的字符串复制到_str指向的内存中。

拷贝构造函数

 
String(const String& s)
: _str(nullptr) {
    String strTemp(s._str);
    swap(_str, strTemp._str);
}

这是String类的拷贝构造函数。首先,它初始化_strnullptr

然后,它创建一个临时String对象strTemp,使用传入的对象s的字符串数据初始化。

通过swap函数交换当前对象的_str成员和strTemp_str成员。这样,当前对象获得了一个新的字符串副本,而strTemp将在构造函数结束时自动销毁,并释放原来的字符串内存。

赋值操作符

 
String& operator=(String s) {
    swap(_str, s._str);
    return *this;
}

这是String类的赋值操作符重载。它接受一个String对象作为参数,但是这里的参数不是引用,而是按值传递,这意味着调用这个操作符时,会自动创建参数s的副本(通过拷贝构造函数)。

然后,它通过swap函数交换当前对象的_str和参数s_str。由于s是一个副本,当赋值操作完成后,s会被销毁,同时负责释放之前当前对象所持有的字符串内存。

这种方法被称为**拷贝-交换(Copy-and-Swap)**技术,它简化了代码,同时自然地处理了自赋值情况,并提供了异常安全保证。

析构函数

 
~String() {
    if (_str) {
        delete[] _str;
        _str = nullptr;
    }
}

这是String类的析构函数,负责释放_str指向的动态分配的内存,防止内存泄漏。

检查_str是否非空,如果是,则使用delete[]释放内存,并将_str设置为nullptr

拷贝构造函数与赋值操作符的异同

拷贝构造函数中s.str必须是引用,出了作用域不会调用析构函数。所以必须自己创建一个临时对象,这样出作用域之后这个临时对象就会调用析构函数,此时只要临时对象是s.str的深拷贝即可,只需要利用拷贝构造创建深拷贝的临时对象即可,然后交换指针,出作用域后自动调用析构处理原_str的空间,此时_str完成深拷贝。

赋值操作符函数中s.str可以不是引用,我们直接传值传参即可,这样s.str就是临时对象,出作用域之后自动调用析构函数,此时只需要交换指针即可,出作用域后自动调用析构处理原_str的空间,此时_str完成深拷贝。

迭代器

迭代器是一种访问容器(如数组、链表、树等数据结构)中元素的对象,它提供了一种方法来顺序访问容器内的元素而不需要暴露容器的内部表示。迭代器抽象了容器元素的访问机制,使得对元素的访问独立于容器的实现方式。

迭代器通常通过容器提供的方法获得,如begin()end()方法。begin()方法返回指向容器第一个元素的迭代器,而end()方法返回指向容器最后一个元素之后位置的迭代器,用于标示容器的末端。

 
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 使用迭代器遍历vector
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用C++11范围基于for循环和auto关键字简化遍历
    for (auto& value : vec) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    return 0;
}

sting类(升级版)简单实现

 
#include <crtdbg.h>
#include <assert.h>
using namespace std;
#include <iostream>
#include <string.h>
namespace Mystring {
    class string {
        public:
            typedef char* iterator;
            typedef char* reverse_iterator;
            /
            // 构造函数
            string(const char* str = "") {
                if (nullptr == str)
                    str = "";

                _size = strlen(str);
                _str = new char[_size + 1];
                strcpy(_str, str);
                _capacity = _size;
            }
            //拷贝构造
            string(const string& s)
                : _str(nullptr)
                , _size(0)
                , _capacity(0) {
                string strTemp(s._str);
                this->swap(strTemp);
            }
            //构造函数重载,n个ch字符
            string(size_t n, char ch) {
                _size = n;
                _str = new char[n + 1];
                memset(_str, ch, n);
                _str[n] = '\0';
                _capacity = n;
            }
            //等号运算符重载
            string& operator=(string s) {
                this->swap(s);
                return *this;
            }
            //析构函数
            ~string() {
                if (_str) {
                    delete[] _str;
                    _str = nullptr;
                    _size = 0;
                    _capacity = 0;
                }
            }

            ///
            // 迭代器
            iterator begin() {
                return _str;
            }

            iterator end() {
                return _str + _size;
            }

            reverse_iterator rbegin() {
                return end();
            }

            reverse_iterator rend() {
                return begin();
            }

            
            // 容量
            size_t size()const {
                return _size;
            }

            size_t capacity()const {
                return _capacity;
            }

            bool empty()const {
                return 0 == _size;
            }

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

            void resize(size_t newsize, char ch) {
                size_t oldsize = size();
                if (newsize > oldsize) {
                    size_t oldcap = capacity();
                    if (newsize > oldcap) {
                        reserve(newsize);
                    }

                    memset(_str + _size, ch, newsize - oldsize);
                }

                _size = newsize;
                _str[_size] = '\0';
            }

            void resize(size_t newsize) {
                resize(newsize, 0);
            }

            void reserve(size_t newcapacity) {
                size_t oldcapacity = capacity();
                if (newcapacity > oldcapacity) {
                    char* temp = new char[newcapacity + 1];
                    strcpy(temp, _str);
                    delete[] _str;
                    _str = temp;
                    _capacity = newcapacity;
                }
            }

            /
            // 元素访问
            char& operator[](size_t index) {
//                assert(index < _size);
                return _str[index];
            }

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

            
            // 修改
            void push_back(char ch) {
                if (_size == _capacity)
                    reserve(_capacity * 2);

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

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

            string& operator+=(const string& s) {
                *this += s._str;
                return *this;
            }

            string& operator+=(const char* s) {
                size_t len = strlen(s);
                char* temp = new char[_size + len + 1];
                strcpy(temp, _str);
                strcat(temp, s);

                _size += len;
                delete[] _str;
                _str = temp;
                _capacity = _size;
                return *this;
            }

            
            // 特殊操作
            const char* c_str()const {
                return _str;
            }

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

                return npos;
            }

            size_t rfind(char ch, size_t pos = npos) {
                pos = pos < _size ? pos : _size - 1;
                for (int i = pos; i >= 0; --i) {
                    if (_str[i] == ch)
                        return i;
                }

                return npos;
            }

            string substr(size_t pos = 0, size_t n = npos) {
                if (n == npos)
                    n = _size;

                if (pos + n >= _size) {
                    n = _size - pos;
                }

                char* temp = new char[n + 1];
                strncpy(temp, _str + pos, n);
                temp[n] = '\0';

                string strRet(temp);
                delete[] temp;
                return strRet;
            }

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

            friend ostream& operator<<(ostream& _cout, const string& s) {
                _cout << s._str;
                return _cout;
            }
        private:
            char* _str;
            size_t _size;
            size_t _capacity;

        public:
            static size_t npos;
    };

    size_t string::npos = -1;
 }
void TestString1() {
    Mystring::string s1;
    Mystring::string s2("hello");
    Mystring::string s3(s2);
    Mystring::string s4(10, 'A');

    cout << s2 << endl;

    for (size_t i = 0; i < s3.size(); ++i) {
        cout << s3[i];
    }
    cout << endl;

    for (auto e : s4)
        cout << e;
    cout << endl;

    auto it = s4.begin();
    while (it != s4.end()) {
        cout << *it;
        ++it;
    }
    cout << endl;
 }

void TestString2() {
    Mystring::string s("hello");


    s.clear();
    cout << s.size() << endl;
    cout << s.capacity() << endl;

    if (s.empty()) {
        cout << "ok" << endl;
    } else {
        cout << "error" << endl;
    }
 }

void TestString3() {
    Mystring::string s("hello");
    s.reserve(10);
    cout << s.size() << endl;
    cout << s.capacity() << endl;

    s.reserve(20);
    cout << s.size() << endl;
    cout << s.capacity() << endl;

    s.reserve(15);
    cout << s.size() << endl;
    cout << s.capacity() << endl;

    s.reserve(5);
    cout << s.size() << endl;
    cout << s.capacity() << endl;
 }

void TestString4() {
    Mystring::string s("hello");
    s.resize(10, '!');
    cout << s.size() << endl;
    cout << s.capacity() << endl;
    cout << s << endl;

    s.resize(20, 'A');
    cout << s.size() << endl;
    cout << s.capacity() << endl;
    cout << s << endl;

    s.resize(15);
    cout << s.size() << endl;
    cout << s.capacity() << endl;
    cout << s << endl;

    s.resize(5);
    cout << s.size() << endl;
    cout << s.capacity() << endl;
    cout << s << endl;
 }

void TestString5() {
    Mystring::string s1("hello");
    s1[0] = 'H';

    const Mystring::string s2(s1);
    cout << s2[0] << endl;
 }

void TestString6() {
    Mystring::string s("hello");
    s.push_back('!');
    cout << s << endl;

    s += "world";
    cout << s << endl;


    Mystring::string ss("!!!");
    s += ss;
    cout << s << endl;
 }

void TestString7() {
    Mystring::string s("hellohellohello");
    size_t pos = 0;
    while (true) {
        pos = s.find('h', pos);
        if (pos == Mystring::string::npos)
            break;

        cout << pos << endl;
        pos++;
    }
 }

void TestString8() {
    Mystring::string s("aaabbbbbccc.txt");
    int start = s.find('b');
    int end = s.rfind('b');
    Mystring::string ret = s.substr(start, end - start + 1);
    cout << ret << endl;

    cout << s.substr(s.rfind('.') + 1) << endl;
 }

int main() {
    // TestString1();
    // TestString2();
    // TestString3();
    // TestString4();
    // TestString5();
    // TestString6();
    // TestString7();
    TestString8();
    _CrtDumpMemoryLeaks();
    return 0;
 }

成员变量

_str:指向动态分配的字符数组,用于存储字符串数据。

_size:当前字符串的长度。

_capacity:分配的存储容量,即_str可以容纳的最大字符数。

构造函数

有参与无参构造函数

默认构造函数接受一个C风格字符串,默认为空字符串""。如果传入的是nullptr,则使用空字符串来初始化_str

 
string(const char* str = "") {
    if (nullptr == str)
        str = "";

    _size = strlen(str);
    _str = new char[_size + 1];
    strcpy(_str, str);
    _capacity = _size;
}

无参构造函数和有参构造函数结合,无参构造函数等价于全缺省,把有参构造函数的所有参数写成全缺省的形式,这样就把无参构造函数和有参构造函数结合起来一起写了。

无参构造函数表示创建一个空字符串,空字符串表示一个字符大小的空间,里面仅仅存放'\0'。注意空字符串与nullptr是不一样的,如果使用nullptr充当空字符串,此时调用strlen函数程序会崩溃,因为strlen遇到'\0'停止,如果对nullptr对象调用strlen系统就会崩溃。

strlen计算的是字符串字符的个数,并不包括'\0',因此申请空间的时候需要多申请一个空间存放'\0'

之后利用strcpy函数拷贝里面的数据即可,注意strcpy会把'\0'也拷贝过去。

最后修改_capacity即可。

拷贝构造函数

拷贝构造函数使用"拷贝-交换"技术,先创建一个临时string对象strTemp,然后通过swap函数交换临时对象和当前对象的成员变量,实现深拷贝。

 
string(const string& s)
    : _str(nullptr)
    , _size(0)
    , _capacity(0) {
    string strTemp(s._str);
    this->swap(strTemp);
}

swap函数是string编写的一个交换函数,用来交换string的所有成员变量。

swap作用的对象是两个string类,系统默认的swap只能交换内置类型,我们想要交换自定义类型的成员变量就必须自己定义swap函数。

拷贝构造函数,利用已经存在的string类,进行深拷贝,由于传入的参数必须是引用,所以我们选择创建一个临时对象,再交换他们的成员变量。我们编写好的有参无参构造函数就是一个深拷贝。

临时对象出作用域之后会自动调用析构函数,此时临时对象的指针指向的空间被消灭,指针本身也被消灭。

构造函数重载,n个ch字符

另一个构造函数接受字符的数量和字符本身,用于创建包含重复字符的字符串。

 
//构造函数重载,n个ch字符
string(size_t n, char ch) {
    _size = n;
    _str = new char[n + 1];
    memset(_str, ch, n);
    _str[n] = '\0';
    _capacity = n;
}

memset函数逐字节赋值,对于_str的前n个字节用字符ch的字节代替,_str中每一个元素占用一个字节,因此_str中前n个元素就是ch字符。

注意不要忘记对最后一个位置添上'\0',并修改_capacity的值。

赋值操作符

使用"拷贝-交换"技术,参数按值传递,这意味着自动调用拷贝构造函数来创建s的副本。然后,使用swap函数交换副本和当前对象的成员变量。

 
//等号运算符重载
string& operator=(string s) {
    this->swap(s);
    return *this;
}

传入的参数s可以不传引用类型,说明可以直接值传参,传入的参数本身就是临时对象,此时我们就不需要再创建临时对象,直接交换所有成员变量即可。

s是临时对象,出作用域之后会自动调用析构函数,此时s成员变量中的指针指向的空间会被消灭,指针本身也会被消灭。

析构函数

负责释放_str指向的动态分配的内存,避免内存泄露。

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

迭代器支持

beginend方法返回指向字符串起始和结束位置的迭代器。

rbeginrend方法提供了逆序遍历的支持,但实现可能有误,因为它们应该返回逆序迭代器的类型,而不是直接返回endbegin的结果。

 
typedef char* iterator;
typedef char* reverse_iterator;
iterator begin() {
    return _str;
}

iterator end() {
    return _str + _size;
}

reverse_iterator rbegin() {
    return end();
}

reverse_iterator rend() {
    return begin();
}

容量和大小管理

提供了sizecapacityemptyclearresizereserve等方法,用于管理和查询字符串的大小和容量。

resize指定字符

 
void resize(size_t newsize, char ch) {
    size_t oldsize = size();
    if (newsize > oldsize) {
        size_t oldcap = capacity();
        if (newsize > oldcap) {
            reserve(newsize);
        }

        memset(_str + _size, ch, newsize - oldsize);
    }

    _size = newsize;
    _str[_size] = '\0';
}

resize重新设定_size成员变量,_capacity一定比_size大,重新设定的newsize的值有三种可能,比_size小,介于_size_capacity之间,或者比_capacity大。

resize操作不会影响_capacity

如果newsize_size小,那么只需要把_size设置为newsize,再把最后一个位置赋值为'\0'即可。

如果newsize介于_size_capacity之间,只需要把多出来的部分赋值成ch字符即可,利用memset快速赋值。

如果newsize_capacity大,还需要进行增容操作,之后再把多出来的部分赋值成ch字符即可。

resize不指定字符

 
void resize(size_t newsize) {
    resize(newsize, 0);
}

复用指定字符的函数,把指定字符改为'\0'即可。

reserve

 

void reserve(size_t newcapacity) {
    size_t oldcapacity = capacity();
    if (newcapacity > oldcapacity) {
        char* temp = new char[newcapacity + 1];
        strcpy(temp, _str);
        delete[] _str;
        _str = temp;
        _capacity = newcapacity;
    }
}

重新设定_capacity成员变量。newcapacity如果比_capacity小或者相等,那么不会有任何变化,也就是使用reserve重新设定_capacity只允许增大。

如果newcapacity_capacity大,申请一个新的空间大小,把旧的空间消灭然后指向新的空间大小即可。

元素访问

重载了下标操作符[],允许访问和修改指定位置的字符。

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

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

字符串修改

提供了push_backoperator+=方法,用于向字符串末尾添加字符或另一个字符串。

 
string& operator+=(const char* s) {
    size_t len = strlen(s);
    char* temp = new char[_size + len + 1];
    strcpy(temp, _str);
    strcat(temp, s);

    _size += len;
    delete[] _str;
    _str = temp;
    _capacity = _size;
    return *this;
}

string类型对象+=一个字符数组,strcpy拷贝原数据,strcat在后面添加新数据。

 
void push_back(char ch) {
    if (_size == _capacity)
        reserve(_capacity * 2);

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

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

string& operator+=(const string& s) {
    *this += s._str;
    return *this;
}

特殊操作

c_str方法返回一个C风格字符串,即以'\0'终止的字符数组。

findrfind方法用于在字符串中查找给定字符的第一次或最后一次出现的位置。

substr方法提取字符串的一个子串。

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

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

    return npos;
}

size_t rfind(char ch, size_t pos = npos) {
    pos = pos < _size ? pos : _size - 1;
    for (int i = pos; i >= 0; --i) {
        if (_str[i] == ch)
            return i;
    }

    return npos;
}
string substr(size_t pos = 0, size_t n = npos) {
    if (n == npos)
        n = _size;

    if (pos + n >= _size) {
        n = _size - pos;
    }

    char* temp = new char[n + 1];
    strncpy(temp, _str + pos, n);
    temp[n] = '\0';

    string strRet(temp);
    delete[] temp;
    return strRet;
}

findrfind遍历所有元素,找指定字符。

find是从pos位置往后遍历,而rfind是从pos位置往前遍历。

strencpy_str+pos位置开始,后面的n个字符依次复制给temp,并且不会把'\0'复制过去,因此复制完需要手动添加'\0'

需要返回string类对象,利用构造函数创建对象。

交换

swap方法交换两个string对象的成员变量。

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

友元函数

重载了插入运算符<<,允许将string对象直接输出到标准输出流。

 
friend ostream& operator<<(ostream& _cout, const string& s) {
    _cout << s._str;
    return _cout;
}

友元函数是独立于string类的函数,不属于string类,即使友元函数在string类内部。

内存泄漏检测

_CrtDumpMemoryLeaks()在程序结束时检查内存泄漏,这是特定于Visual Studio的调试功能。

使用_CrtDumpMemoryLeaks()需要引用#include <crtdbg.h>头文件。

反向迭代器探究

 
#include<iostream>
using namespace std;
#include<string>
int main(){
    string s={"abcdefg"};
    auto it=s.rbegin();
    while(it!=s.rend()){
        cout<<*it<<endl;
        it++;
    }
 }

string类中的反向迭代器rbegin指向的是最后一个元素后一个位置,但是解引用却会输出最后一个元素的值,我认为这是编译器自动优化的操作,在反向迭代器中,解引用会自动调用迭代器++后的解引用。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

  • 45
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值