【十】【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>头文件。

结尾

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/374742.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入解析Elasticsearch的内部数据结构和机制:行存储、列存储与倒排索引之行存(一)

在当今的大数据时代&#xff0c;高效的数据检索和分析能力已成为许多应用程序的核心需求。Elasticsearch&#xff0c;作为一款强大的分布式搜索和分析引擎&#xff0c;正是为了满足这些需求而诞生的。它之所以能够在海量数据中实现毫秒级的搜索响应&#xff0c;以及灵活的数据分…

2024/02/06

画出TCP三次握手和四次挥手的示意图 三次握手 四次挥手 并且总结TCP和UDP的区别 TCP: TCP提供面向连接的&#xff0c;可靠的数据传输服务传输过程中&#xff0c;数据无误、数据无丢失、数据无失序、数据无重复 TCP会给每个数据包编上编号&#xff0c;该编号称之为序列号每个序…

Minio Linux 安装

1.下载地址 MinIO | Code and downloads to create high performance object storage 2.创建文件相关文件夹 mkdir 3.赋权 chomd x minio 4.创建用户组和租户 groupadd minio ### useradd minio -g minio ### 验证 cat /etc/passwd5.创建配置文件 vi minio.confMINIO_VOLU…

带着问题阅读源码——Spring MVC是如何在Spring Boot中注入的?

Spring MVC简介 Spring Web的MVC框架旨在全面处理HTTP请求&#xff0c;它具备以下特性&#xff1a; 可配置的处理程序映射&#xff1a;允许开发者根据需要将特定的URL路径映射到相应的处理程序上。 视图解析&#xff1a;框架能够根据处理程序返回的逻辑视图名&#xff0c;解析…

微软.NET6开发中解决方案和项目的概念

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;看到不少初学者在学习编程语言的过程中如此的痛苦&#xff0c;我决定做点什么&#xff0c;下面我就重点讲讲微软.NET6开发中解决方案和项目的概念。我将介绍 Visual Studio 2022的解决方案和项目的结构及组成…

UE5 获得UE内的音频组件与频谱

打开Audio Synesthesia插件 在Sounds内Analysis选择Synesthesia NRT 创建好后选择Settings,下面的sound选择你要选择的音乐 在Actor里添加Constant QNRT变量,选择刚刚创建的NRT 添加Audio组件 这里需要先运行SetSound,然后获得当前音频的总长度,Get Duration并设置变量 绑定时间…

zabbix-监控应用程序(Mysql、Nginx)

基础环境&#xff1a;zabbix服务端、两台zabbix被监控端、分别安装Mysql和Nginx环境拓扑图&#xff1a; 实验目标&#xff1a;可以通过zabbix监控到被监控端上安装的mysql与nginx&#xff0c;通过zabbix实时检测mysql和nginx的数据。实验步骤&#xff1a;1.在8.7服务器上安装My…

Asp .Net Core 系列:Asp .Net Core 集成 NLog

简介 NLog是一个基于.NET平台编写的日志记录类库&#xff0c;它可以在应用程序中添加跟踪调试代码&#xff0c;以便在开发、测试和生产环境中对程序进行监控和故障排除。NLog具有简单、灵活和易于配置的特点&#xff0c;支持在任何一种.NET语言中输出带有上下文的调试诊断信息…

Linux 命令基础

Shell概述 Linux操作系统的Shell作为操作系统的外壳&#xff0c;为用户提供使用操作系统的接口。它是命令语言、命令解释程序及程序设计语言的统称。 Shell是用户和Linux内核之间的接口程序&#xff0c;如果把硬件想象成一个球体的中心&#xff0c;内核围绕在硬件的外层管理着…

基于Python机器学习算法农业数据可视化分析预测系统(完整系统源码+数据库+详细文档+论文+部署教程)

文章目录 基于Python机器学习算法农业数据可视化分析预测系统完整源码获取方式在文章末尾一、项目简介二、开发环境三、项目技术四、功能结构五、功能实现模型构建封装类用于网格调参训练模型系统可视化数据请求接口模型评分 0.5*mse 六、系统实现七、总结完整源码获取 基于Pyt…

Qt程序设计-自定义QLineEdit控件添加鼠标单击事件

本文讲解Qt自定义QLineEdit控件添加鼠标单击事件。 QLineEdit控件默认没有单击事件,但是项目开发中有时需要单击事件,比如单击QLineEdit控件弹出软键盘。具体实现过程如下: 创建项目,在项目中添加一个类,命名为MyLineEdit 输入继承QLineEdit #ifndef MYLINEEDIT_H #defi…

Redis(十二)Bigkey

文章目录 游标案例生成100万测试数据key生产上限制keys */flushdb/flushall等危险命令不使用keys *&#xff1a;scan Biigkey案例多大算大发现bigkey渐进式删除生产调优示例问题 游标案例 生成100万测试数据key shell: for((i1;i<100*10000;i)); do echo "set k$i v…

分享76个节日PPT,总有一款适合您

分享76个节日PPT&#xff0c;总有一款适合您 76个节日PPT下载链接&#xff1a;https://pan.baidu.com/s/1-j7toLaBUBAJbkd85xe4VQ?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易…

HiveSQL——设计一张最近180天的注册、活跃留存表

0 问题描述 现有一个用户活跃表user_active(user_id,active_date)、 用户注册表user_regist(user_id,regist_date)&#xff0c;表中分区字段都为dt(yyyy-MM-dd)&#xff0c;用户字段均为user_id; 设计一张 1-180天的注册活跃留存表&#xff1b;表结构如下&#xff1a; 1 数据分…

【知识整理】一文理解系统服务高可用

一、如何理解高可用 1、什么是高可用 高可用性&#xff08;英语&#xff1a; High Availability&#xff0c;缩写为 HA&#xff09;&#xff0c;指系统无中断地执行其功能的能力&#xff0c;代表系统的可用性程度&#xff0c;是进行系统设计时的准则之一。 2、决定可用性的两…

【Go】一、Go语言基本语法与常用方法容器

GO基础 Go语言是由Google于2006年开源的静态语言 1972&#xff1a;&#xff08;C语言&#xff09; — 1983&#xff08;C&#xff09;—1991&#xff08;python&#xff09;—1995&#xff08;java、PHP、js&#xff09;—2005&#xff08;amd双核技术 web端新技术飞速发展&…

前端学习第四天

目录 一、复合选择器 1.后代选择器 2.子代选择器 3.并集选择器 4.交集选择器 5.伪类选择器 1.伪类-超链接&#xff08;拓展&#xff09; 二、CSS特性 1.继承性 2.层叠性 3.优先级 1.优先级-叠加计算规则 2.emmet写法 三、背景属性 1.背景图 ​编辑2.背景图平铺方…

JDK和CGLIB动态代理原理

动态代理会在程序运行时&#xff0c;自动的为原对象生成一个代理对象。该代理对象的方法会有逻辑上的增强&#xff0c;其一方面会执行增强的逻辑&#xff0c;另一方面其实就是通过反射调用被代理类的方法&#xff0c;这个调用过程跟静态代理就很像了。 JDK动态代理Demo如下&…

day04.C++库函数(常用)

目录 一.常用数学函数 #include / #include 二.常用字符串处理函数 #include / #include 2.1常见的内存函数&#xff1a; &#xff08;1&#xff09;memcpy库函数 (2)memcmp库函数 (3)memset 2.2字符串常见库函数 &#xff08;1&#xff09;strlen (2)strcpy 三、其他常…

【Transformer-Hugging Face 06/10】 数据预处理实例

目录 一、说明二、自然语言处理2.1 Pad2.2 截断2.3 构建张量 三、TensorFlow四、处理语音五、计算机视觉六、填充七、Multimodal 一、说明 在数据集上训练模型之前&#xff0c;需要将其预处理为预期的模型输入格式。无论您的数据是文本、图像还是音频&#xff0c;都需要将它们…