首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【C++】C++STL 揭秘:Strng背后的底层逻辑

  • 25-03-07 11:29
  • 2984
  • 9195
blog.csdn.net

在这里插入图片描述

C++语法相关知识点可以通过点击以下链接进行学习一起加油!
命名空间缺省参数与函数重载C++相关特性类和对象-上篇类和对象-中篇
类和对象-下篇日期类C/C++内存管理模板初阶String使用

在上篇介绍string类的使用与理解,本篇将为大家来带关于string的底层实现逻辑,当然这不是一定库里面的实现逻辑。我们设计一个string类是为了在使用string类相关接口,是为了我们更好的使用string类相关接口,在使用过程中知道该接口效率高还是低,可以更好地去选择

请添加图片描述
Alt
?个人主页:是店小二呀
?C语言专栏:C语言
?C++专栏: C++
?初阶数据结构专栏: 初阶数据结构
?高阶数据结构专栏: 高阶数据结构
?Linux专栏: Linux

?喜欢的诗句:无人扶我青云志 我自踏雪至山巅 请添加图片描述

文章目录

  • 一、模拟现实string准备工作
  • 二、构造函数
    • 2.1 无参构造函数
    • 2.2 有参构造函数
    • 2.3 全缺省构造函数
  • 三、析构函数
  • 四、交换函数Swap
  • 五、拷贝构造函数
    • 5.1 传统写法
    • 5.2 现代写法
  • 六、operator[] (下标方括号)
  • 七、operator=(赋值运算符重载)
    • 7.1 传统写法
    • 7.2 现代写法
    • 7.3 现代写法优化
  • 八、Size(获得字符串长度)
  • 九、Capacity(获得容量大小)
  • 十、resever
  • 十一、resize
  • 十二、insert
    • 12.1 任意位置插入字符
    • 12.2 任意位置插入字符串
  • 十三、push_back
  • 十四、append
  • 十五、operator +=
  • 十六、find
    • 16.1 寻找字符
    • 16.2 寻找字符串
  • 十七、substr
  • 十八、clear
  • 十九、erase
  • 二十、运算符重载
  • 二十一、实现迭代器
    • 21.1 可读可修改
    • 22.2 可读不可修改
  • 二十三、模拟实现流插入和流提取
    • 23.1 operator<<(流插入)
    • 23.2 operator>>(流提取)
  • 头文件:string.h

一、模拟现实string准备工作

在模拟实现string过程中,为了避免跟库中string发生冲突,需要创建个命名空间,在命名空间中实现string。

namespace str
{
	class string
	{

	};
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

string底层是动态字符串顺序表,对此string中需要这个四个成员变量作为支架。

  1. _str:指向动态开辟的空间
  2. _size:有效数据个数
  3. _capacity:容量空间的大小(不包括\0,实际空间包括)
  4. npos:静态成员变量,表示最大值(属于整个类)
namespace str
{
	class string
	{
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static const size_t npos;
	};
		//在类外初始化
		const size_t string:: npos = -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

二、构造函数

2.1 无参构造函数

string()
    :_str(new char[1])//为'\0'开一块空间
        :_size(0)
            :_capacity(0)
            {
                _str[_size] = '\0';
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.2 有参构造函数

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

先确定str有效字符串长度,再决定申请多大空间,最后将str中内容拷贝给该对象

2.3 全缺省构造函数

【瑕疵版本】:

string(const char *str = nullptr)
    :_size(strlen(str))
        ,_capacity(strlen(str))
    {
        _str = new char[_capacity + 1];
        strcpy(_str, str);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

不足在于:当缺省值为nullptr时,strlen计算大小会报错,strlen会对参数部分解引用操作。同时这里strlen函数调用多次,效率降低。

【完善版本】:

string(const char *str="")
    :_size(strlen(str))
    {
        _capacity = _size;
        _str = new char[_capacity + 1];
        strcpy(_str, str);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

缺省值给""空字符串,编译器会自动填充\0,达到了无参的要求。无参调用时,调用strlen()函数计算大小为0。虽然缺省值可以直接给\0,但是没有必要,会导致有两个\0存在。

小结:在实践中推荐只保留一个全缺省构造函数。

三、析构函数

~string()
{
    delete[]_str;
    _size = _capacity = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5

清空_str指向动态空间中的资源,delete[] _str会调用析构函数和free释放空间。

四、交换函数Swap

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

//string类外
void swap(string& x,string &y)
{
    x.swap(y);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这里实现了在string类外和string类中的交换函数swap,但是类外的本质还是调用类中的swap。虽然算法库有swap函数模板,但是需要走深拷贝,代价很大,这里需要重现实现swap函数。

实现一个成员函数,通过交换属性(值拷贝),代价较少。虽然本质还是调用算法库中库函数,但是使用方式不同,付出的代价也不同。

小结:在使用swap函数时,需要根据自己的需求来使用,不然会弄巧成拙的

五、拷贝构造函数

5.1 传统写法

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

在这里插入图片描述

具体流程:_str指向空,该对象没有被实例化。先开辟一块跟被拷贝对象等大的空间,再将数据拷贝一份。拷贝构造是将其他对象数据,拷贝到新的对象中。这本身是不影响被拷贝对象。

5.2 现代写法

string(const string& str)
{
    string ss(str._str);
    swap(ss);
}
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

具体流程:_ str指向空,该对象没有被实例化。先调用string ss(str. _str)构造函数,得到一份str._str相同数据跟_str交换。相当于就是 _str和打工仔ss进行交互,完成拷贝操作。

这不采用string ss(s)拷贝构造,在拷贝构造中调用拷贝构造,本身就是闭环。

小结:

  • 无论是现代写法还是传统写法,在效率上是一样的,主要在于书写行数的关系
  • 现代写法和传统写法参数相同,不能构成函数重载,只能选择一个使用

六、operator[] (下标方括号)

char& operator[](size_t pos) 
{
    assert(pos < _size);

    return _str[pos];
}

const char& operator[](size_t pos) const
{
    assert(pos < _size);

    return _str[pos];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

具体分析:

  1. 首先operator[]是调用函数,并且有两个函数重载
  2. 其次operator[]是个大进步,对于检查越界的情况
  3. 如果是数组的话,很难检查是否有越界情况发生(越界检查是一种抽查,在容易越界的地方,放几个值,看这个值是否被修改)。越界读的话,很难发现越界,是很危险的
  4. operator[]实现中,有assert判断是否出现有越界的情况。
  5. 关于const char& operator[](size_t pos) const情况,是为了对于被const修饰对象,也可以访问其元素,并且仅限于读权限

七、operator=(赋值运算符重载)

7.1 传统写法

string& operator=(const string& str)
{
    char* tmp = new char[str._capacity + 1];
    strcpy(tmp, str._str);

    delete[]_str;
    _str = tmp;
    _size = str._size;
    _capacity = str._capacity;

    return *this
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述

具体说明:

赋值操作是发生于两个对象已经创建完毕,对此被赋值对象自然存在一块空间,需要等着被清除资源。这里赋值拷贝不能影响到赋值对象,可以采用使用一个临时变量进行中间过程交换,开辟跟被拷贝对象等大的空间,将数据拷贝,将_str指向旧空间释放,指向tmp指向空间完成赋值。

tmp是函数内部声明的自动变量(局部变量),出作用域会自动销毁,但是指向堆上分配的内存不会因为tmp的销毁而释放,导致_str指向那块空间不会被回收,顺便完成了tmp指向空间为空操作。

7.2 现代写法

string& operator=(const string& s)
{
    string ss(s);
    swap(ss);
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里插入图片描述

这里不用担心ss对象指向空间没有得到释放,ss对象属于局部对象,出了函数作用域就会调用析构函数和销毁。

7.3 现代写法优化

string& operator=(string ss)
{
    swap(ss);
    return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5

相对于上面的现代写法,这里在传参的过程中完成了拷贝构造。

小结:

  • 无论是现代写法还是传统写法,在效率上是一样的,主要在于书写行数的关系
  • 现代写法和传统写法参数相同,不能构成函数重载,只能选择一个使用

八、Size(获得字符串长度)

		size_t size(const string& s) const
		{
			return s._size;
		}
  • 1
  • 2
  • 3
  • 4

九、Capacity(获得容量大小)

    size_t capacity(const string& s) const
    {
        return s._capacity;
    }
  • 1
  • 2
  • 3
  • 4

十、resever

    void reserve(size_t n)
    {
        if (n > _capacity)
        {
            char* tmp = new char[n + 1];
            strcpy(tmp, _str);

            delete[]_str;
            _str = tmp;

            _capacity = n;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

说明:

  1. reserve只有在所需空间超过当前容量才起到扩容作用
  2. 让打工仔tmp开辟满足条件的空间,_str在进行拷贝、销毁、转化指向
  3. 这里需要改动_capacity就行, _size代表是有效个数

十一、resize

void resize(size_t n, char ch = '\0')
{
    if (n <= _size)
    {
        _str[n] = '\0';
        _size = n;
    }
    else
    {
        //提前开辟好空间
        reserve(n);
        for (size_t i = _size; i < n; i++)
        {
            _str[i] = ch;
        }
        _str[n] = '\0';			
        _size = n;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

说明:

  1. 使用resize需要考虑三种情况,但是模拟实现可以只考虑两种情况
  2. 提前使用reserve开辟好空间,避免扩容操作,将两种情况处理成一种情况
  3. 当n小于等于当前大小,需要使用\0的形式截断字符串
  4. 当n大于等当前容量,就是按照ch完成填充新开辟空间

十二、insert

12.1 任意位置插入字符

【瑕疵版本】:

void insert(size_t pos, char ch)
{
    assert(pos <= _size);

    // 扩容2倍
    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }

    size_t end = _size;
    while (end >= pos)
    {
        _str[end + 1] = _str[end];
        --end;
    }

    _str[pos] = ch;
    ++_size;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

不足之处:从代码的逻辑来看,感觉是没啥问题。如果选择在首位置插入数据,end进行–操作变成负数。由于end类型为size_t 无符号整数会导致end为最大值,循环不会停下。

【尝试调正】:那么将end设置为符号整型,就可以保证它可以为负数

int end = _size;
while (end >= pos)
{
    _str[end + 1] = _str[end];
    --end;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

但是end和pos是不同类型的变量,在进行比较时,编译器会执行整型提升,size__t隐式转化为int类型再比较。

【最终方案】:

size_t end = _size + 1;
while (end > pos)
{
    _str[end] = _str[end - 1];
    --end;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

让end指向结尾下一个位置。在数据移动的时候,解决了首位置插入end等于pos移动导致死循环的问题。

12.2 任意位置插入字符串

    void insert(size_t pos, const char* str)
    {
        assert(pos <= _size);

        size_t len = strlen(str);
        if (len + _size > _capacity)
        {
            reserve(_size + len);
        }

        size_t end = _size + len;
        while (end > pos + len)
        {
            _str[end] = _str[end - len];
            --end;
        }
		//通过strncpy将需要插入字符串覆盖,完成插入
        strncpy(_str + pos, str, len);
        _size += len;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在这里插入图片描述

具体流程:先提前判断插入字符串长度是否会超过当前容量,这里同上面一致将元素移动备份,将待插入元素进行覆盖操作。需要注意插入在首位置死循环的问题,同样采取上面的解决办法:向后移动N位预留空间。代码逻辑可以参考中两张图片理解。

十三、push_back

void push_back(char ch)
{
    // 扩容2倍
    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }

    _str[_size] = ch;
    ++_size;
    _str[_size] = '\0';
    //insert(_size, ch);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

说明:

  1. 插入单个字符时,需要判断空间是否充足
  2. 跟顺序表的尾插差不多
  3. 实现任意位置插入,直接内部调用insert也是可以的

十四、append

    void append(const char* str)
    {
        // 扩容
        size_t len = strlen(str);
        if (_size + len > _capacity)
        {
            reserve(_size + len);
        }

        strcpy(_str + _size, str);//在尾部拷贝完成添加操作
        _size += len;
        
        //insert(_size, str);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

说明:

  1. 当然可以选择一开始直接扩容
  2. 插入字符串时,需要判断空间是否充足
  3. 跟顺序表的尾插差不多
  4. 实现任意位置插入,直接内部调用insert也是可以的

十五、operator +=

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

    string& operator+=(const char* str)
    {
        append(str);
        return *this;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

十六、find

16.1 寻找字符

size_t find(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

说明:

  1. 从任意位置遍历,找到返回该位置索引
  2. 没有找到就返回npos(-1)
  3. const修饰函数,仅限于读权限

16.2 寻找字符串

size_t find(const char* sub, size_t pos = 0) const
{
    assert(pos < _size);

    const char* p = strstr(_str + pos, sub);
    if (p)
    {
        return p - _str;
    }
    else
    {
        return npos;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

说明:

  1. strstr匹配所需字符串,返回匹配到字符串第一个位置
  2. 如果为空,表示不存在
  3. 如果找到了,利用指针-指针,得到下标位置索引

十七、substr

string substr(size_t pos = 0, size_t len = npos)
{
    string sub;
    //if (len == npos || len >= _size-pos)
    if (len >= _size - pos)
    {
        for (size_t i = pos; i < _size; i++)
        {
            sub += _str[i];
        }
    }
    else
    {
        for (size_t i = pos; i < pos + len; i++)
        {
            sub += _str[i];
        }
    }
    return sub;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

说明:

  1. substr功能是返回从指定位置开始len长度的字符串
  2. 先创建string空对象用于接收截取字符串
  3. 当len == npos 或len >= _size - pos,代表了从pos位置到尾的字符串截取。
  4. 尽量书写len >= _size - pos,而不是len + pos >= _size这种,是为了防止len + pos超过类型最大值范围

十八、clear

void clear()
    {
        _size = 0;
        _str[_size] = '\0';
    }
  • 1
  • 2
  • 3
  • 4
  • 5

清空资源,不释放空间

十九、erase

void erase(size_t pos = 0, size_t len = npos)
{
    assert(pos <= _size);

    if (pos > _size - len || len==npos)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在这里插入图片描述

具体说明:如果长度大于从指定位置到结束位置的长度,只需要在pos位置设置结束标识符。不然的话,就是将需要清空位置被后面的内容覆盖就行。如果对于strcpy这块逻辑不明白,可以回顾下strcpy使用方法。

二十、运算符重载

bool operator==(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret == 0;
	}

	bool operator<(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret < 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
  • 30
  • 31

说明:跟类和对象实现日期Date是一样的,具体有需要点击该连接日期类

二十一、实现迭代器

21.1 可读可修改

    typedef char* iterator;

    iterator begin()
    {
        return _str;
    }


    iterator end()
    {
        return _str + _size;
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

22.2 可读不可修改

    typedef const char* iterator;

    const_iterator begin() const
    {
        return _str;
    }


    const_iterator end() const
    {
        return _str + _size;
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

说明:

  1. 迭代器是一个像指针的东西,可以是指针,也可以不是指针,具体还需要看底层实现
  2. 这里使用指针实现迭代器,返回第一个位置和最后一个位置的下一个位置。

二十三、模拟实现流插入和流提取

23.1 operator<<(流插入)

#include
using namespace std;
ostream& operator<<(ostream& out, const string& s)
    {
        for (auto ch : s)
        {
            out << ch;
        }

        return out;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

说明:

  • operator是C++标准库中的一个类,用于输出数据流,位于标准命名空间std中。对此需要引用std命名空间访问。
  • out << ch;重载的<<运算符用于将字符ch写入输出流out是C++标准库中重载

23.2 operator>>(流提取)

istream& operator>>(istream& in, string& s)
{
    s.clear();

    char ch;
    //in >> ch;
    ch = in.get();
    char buff[128];
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;
        // [0,126]
        if (i == 127)
        {
            buff[127] = '\0';
            s += buff;
            i = 0;
        }

        ch = in.get();
    }

    if (i > 0)
    {
        buff[i] = '\0';
        s += buff;
    }

    return in;
}
  • 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
  • 30
  • 31

具体流程:首先clear先清空对象中的资源,这里需要考虑char类型的最大值为128,创建个数组用于输入到类中,使用operator+=将得到的字符串输入到对象中。

流提取是将从键盘中读取的数据存储到s对象中,至于返回类型为istream&是为支持连续流提取操作cin >> s >> s2。string中getline接口模拟实现是一样的就不展开.

头文件:string.h

#pragma once
#include 
#include 
using namespace std;
//模拟实现string 4.4

//设置命名空间,防止跟库中string有冲突
namespace  bit
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _str;
		}

		const_iterator begin() const
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator end() const
		{
			return _str + _size;
		}

		//\0会占用一块空间
		
		//无参构造
		//string()
		//	:_str(new char[1])
		//	,_size(0)
		//	,_capacity(0)
		//{
		//	_str[_size] = '\0';

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

		//全缺省值构造
		string(const char *str="")
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		析构构造
		//~string()
		//{
		//	delete[]_str;
		//	_size = _capacity = 0;
		//}

		//构造函数s1(s2)
		//传统写法--c_str作用还是不明白
		//string(const string& s)
		//{
		//	_str = new char[s._capacity + 1];
		// strcpy(_str, s._str);
		//	_size = s._size;
		//	_capacity = s._capacity;
		//}

		//现代写法s1(s2)
		//string(const string& s)
		//{
		//	//string ss(s);//为什么需要做一份拷贝呢?不会递归死循环吗?
		//	 
		//	//不能在拷贝构造里面,调用拷贝构造,应该调用构造,大小和容量可以根据字符串来计算
		//	string ss(s._str);
		//	swap(ss);//这里完成的是交换,是s1拷贝s2一份,s2本身不跟s1交换
		//}


		//意思是在传参的时候,进行了拷贝构造,但是拷贝构造还没有实现
		//string(string ss)
		//{
		//	swap(ss);
		//}

		//修改string的接口
		//赋值operator=

		//传统写法 s1=s2;
		string& operator=(const string& s)
		{
			char* tmp = new char[s._capacity + 1];
			strcpy(tmp, s._str);

			delete[]_str;
			_str = tmp;
			_size = s._size;
			_capacity = s._capacity;

			return *this;
		}

		//现代写法
		string& operator=(const string& s)
		{
			//可以使用构造或者使用拷贝构造
			string ss(s._str);
			swap(ss);
			return *this;
		}

		//优化 有拷贝构造基础上
		string& operator=(string ss)
		{
			swap(ss);
			return *this;
		}


		//遍历

		size_t size(const string& s) const
		{
			return s._size;
		}

		size_t capacity(const string& s) const
		{
			return s._capacity;
		}

		char& operator[](size_t pos) 
		{
			assert(pos < _size);

			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);

			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);

				return _str[pos];
		}

		//扩容操作
		
		void reserve(size_t n)
			{
				if (n > _capacity)
				{
					char* tmp = new char[n + 1];

					strcpy(tmp, _str);
				
					delete[]_str;
					_str = tmp;
				
					_capacity = n;
				}
			}

		void resize(size_t n, char ch = '\0')
		{
			//如果小于该容量
			if (n <= _capacity)
			{
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				//提前开辟好空间
				reserve(n);
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}

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

		//插入操作
		void push_back(char ch)
		{
			//判断容量是否满了
			if (_size == _capacity)
			{
				//是否为一开始的状态,就是为空待插入
				reserve(_capacity == 0 ? 2 : 2 * _capacity);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}
		

		void append(const char* str)
		{
			//不用想这么多,先扩容先
			size_t len = strlen(str);
			//里面会给\0开辟一块空间

			reserve(_capacity+len);

			strncpy(_str + _size, str, len);

			_size = _size + len;
		}


		void swap(string& s)//做值拷贝,属性交换,避免深拷贝
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		//实现更加轻松的+=
		string& operator+=(const char ch)
		{
			push_back(ch);
			return *this;
		}

		string& operator+=(const char* str)	
		{
			append(str);
			return *this;
		}

		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			if (_size == _capacity)
			{	
				reserve(_capacity == 0 ? 2 : 2 * _capacity);
			}

			//size_t end = _size;
			0=0
			//while (end > pos)
			//{
			//	_str[end] = _str[end - 1];
			//	end--;
			//}

			//_str[pos] = ch;
			//_size++;
			//_str[_size] = '\0';
			
			//从\0开始移动
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}

			_str[pos] = ch;
			++_size;
		}

		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);

			size_t len = strlen(str);
			if (len + _size > _capacity)
			{
				reserve(_size + len);
			}

			size_t end = _size + len;
			while (end > pos + len)
			{
				_str[end] = _str[end - len];
				--end;
			}

			strncpy(_str + pos, str, len);
			_size += len;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			
			if (pos + len > _size || len==npos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}

		size_t find(char ch, size_t pos = 0) const
		{
			assert(pos <= _size);

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

			const char* p = strstr(_str + pos, sub);
			if (p == nullptr)
				return npos;
			else
				return p - _str;
		}
		//功能是从某个位置打印len长度的字符串
		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos <= _size);

			string sub;
			//这里怕pos+len会太大
			//打印拿出来
			if (pos > _capacity - len || len==npos)
			{
				for (size_t i = pos; i <_size; i++)
				{
					sub += _str[i];//i=size-1
				}
			}
			//没有超过
			else
			{
				for (size_t i = pos; i < pos + len; i++)
				{
					sub += _str[i];
				}
			}
			return sub;
		}
		//当你将 sub 返回时,std::string 类会自动确保字符串以 null 结尾。不需要手动添加 \0。

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

		void clear()
		{
			_size = 0;
			_str[_size] = '\0';		
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static const size_t npos;
	};
	static const size_t nops = -1;

	void swap(string& x,string &y)//实现两个swap 外面的swap实际还是调用里面的swap
	{
		x.swap(y);
	}

	bool operator==(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret == 0;
	}

	bool operator<(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret < 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);
	}

	//需要实现迭代器
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

	iostream& operator>>(iostream& in,  string& s)
	{
		//先对资源清空
		s.clear();

		char buff[128];
		char ch;
		ch=in.get();
		size_t i = 0;

		while (ch != '\n' && ch != ' ')
		{
			buff[i++] = ch;

			if (i == 127)
			{
				buff[127] = '\0';
				s += buff;		
				i = 0;
			}

			ch = in.get();

		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		//已经手动输入过了
		return in;
	}

	istream& getline(istream in, string& s)
	{
		//先对资源清空
		s.clear();

		char buff[128];
		char ch;
		ch = in.get();
		size_t i = 0;

		while (ch != '\n' && ch != ' ')
		{
			buff[i++] = ch;

			if (i == 127)
			{
				buff[127] = '\0';
				s += buff;
				i = 0;
			}

			ch = in.get();

		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		//已经手动输入过了
		return in;
	}
}


  • 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
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508

以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!
请添加图片描述

注:本文转载自blog.csdn.net的是店小二呀的文章"https://blog.csdn.net/2302_79177254/article/details/141189352"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top