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

news2024/11/15 17:52:24

在这里插入图片描述

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
	{

	};
}

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

二、构造函数

2.1 无参构造函数

string()
    :_str(new char[1])//为'\0'开一块空间
        :_size(0)
            :_capacity(0)
            {
                _str[_size] = '\0';
            }

2.2 有参构造函数

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

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

2.3 全缺省构造函数

【瑕疵版本】

string(const char *str = nullptr)
    :_size(strlen(str))
        ,_capacity(strlen(str))
    {
        _str = new char[_capacity + 1];
        strcpy(_str, str);
    }

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

【完善版本】

string(const char *str="")
    :_size(strlen(str))
    {
        _capacity = _size;
        _str = new char[_capacity + 1];
        strcpy(_str, str);
    }

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

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

三、析构函数

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

清空_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);
}

这里实现了在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;
}

在这里插入图片描述

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

5.2 现代写法

string(const string& str)
{
    string ss(str._str);
    swap(ss);
}

在这里插入图片描述

具体流程:_ 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. 首先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
}

在这里插入图片描述

具体说明

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

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

7.2 现代写法

string& operator=(const string& s)
{
    string ss(s);
    swap(ss);
    return *this;
}

在这里插入图片描述

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

7.3 现代写法优化

string& operator=(string ss)
{
    swap(ss);
    return *this;
}

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

小结:

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

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

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

九、Capacity(获得容量大小)

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

十、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. 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. 使用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;
}

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

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

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

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

【最终方案】:

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

让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;
    }

在这里插入图片描述

具体流程:先提前判断插入字符串长度是否会超过当前容量,这里同上面一致将元素移动备份,将待插入元素进行覆盖操作。需要注意插入在首位置死循环的问题,同样采取上面的解决办法:向后移动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. 实现任意位置插入,直接内部调用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. 实现任意位置插入,直接内部调用insert也是可以的

十五、operator +=

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

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

十六、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. 没有找到就返回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. 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. substr功能是返回从指定位置开始len长度的字符串
  2. 先创建string空对象用于接收截取字符串
  3. len == nposlen >= _size - pos,代表了从pos位置到尾的字符串截取。
  4. 尽量书写len >= _size - pos,而不是len + pos >= _size这种,是为了防止len + pos超过类型最大值范围

十八、clear

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

清空资源,不释放空间

十九、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;
    }
}

在这里插入图片描述

具体说明:如果长度大于从指定位置到结束位置的长度,只需要在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);
	}

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

二十一、实现迭代器

21.1 可读可修改

    typedef char* iterator;

    iterator begin()
    {
        return _str;
    }


    iterator end()
    {
        return _str + _size;
    }

22.2 可读不可修改

    typedef const char* iterator;

    const_iterator begin() const
    {
        return _str;
    }


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

说明:

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

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

23.1 operator<<(流插入)

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

        return out;
    }

说明:

  • 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;
}

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

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

头文件:string.h

#pragma once
#include <assert.h>
#include <iostream>
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<end end>=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;
	}
}



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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「SpEL Validator」使用指南(一套无敌的参数校验组件)

前言 这是一套全新的参数校验组件&#xff0c;并非造轮子。 看完本文你可能会觉得用不上或不屑于使用&#xff0c;但这玩意确实有应用场景&#xff0c;你不妨稍微留意一下&#xff0c;日后你总会发现有用得上的时候。 此乃系列文章&#xff0c;当前为第②篇&#xff0c;其他…

Puppeteer的高级用法:如何在Node.js中实现复杂的Web Scraping

概述 随着互联网的发展&#xff0c;网页数据抓取&#xff08;Web Scraping&#xff09;已成为数据分析和市场调研的重要手段之一。Puppeteer作为一款强大的无头浏览器自动化工具&#xff0c;能够在Node.js环境中模拟用户行为&#xff0c;从而高效地抓取网页数据。然而&#xf…

Java基于微信小程序的实习管理系统

简介 本次开发的实习生管理系统实现了字典管理、公告管理、公司管理、简历管理、老师管理、实习管理、实习日志管理、通知管理、学生管理、职位招聘管理、职位收藏管理、职位留言管理、简历投递管理、管理员管理等功能。系统用到了关系型数据库中王者MySql作为系统的数据库&am…

点云配准之ICP和NDT算法的高斯牛顿法求解

ICP算法 NDT算法 代码&#xff1a;https://github.com/taifyang/pointcloud-registration 参考&#xff1a;高翔《自动驾驶与机器人中的SLAM技术》

打造灵动空间,流动会场的声学优势—轻空间

在现代社会中&#xff0c;各类会议、展览、演出、培训等活动越来越多&#xff0c;对场地的需求也越来越多样化。传统的固定场地往往难以满足不同活动的需求&#xff0c;而“流动会场”凭借其灵活多变的特点&#xff0c;迅速成为各类活动的新宠。特别是其独特的声学优势&#xf…

【数据结构】二叉树的链式结构,二叉树的遍历,求节点个数以及高度

目录 1. 二叉树链式结构的概念 2. 二叉树的遍历 2.1 前序遍历 2.2 中序遍历 2.3 后序遍历 2.4 层序遍历 3. 二叉树的节点个数以及高度 3.1 二叉树节点个数 3.2 二叉树叶子节点个数 3.3 二叉树的高度 3.4 二叉树第k层节点个数 3.5 二叉树查找值为x的节点 4. 二叉树…

数造科技荣登“科创杯”领奖台,开启数据驱动新篇章!

8月27日&#xff0c;第十三届中国创新创业大赛(海南赛区)暨海南省第十届“科创杯”创新创业大赛决赛在海口圆满落幕。数造科技凭其在大数据管理领域的专业技术实力&#xff0c;荣获成长企业组三等奖。 突出重围&#xff0c;崭露头角 海南省“科创杯”创新创业大赛是在中国科技…

安科瑞ADL系列导轨式多功能电能表 带外置互感器 CE认证

产品概述&#xff1a; ‌安科瑞ADL系列导轨式多功能电能表‌是安科瑞企业微电网能效管理事业部推出的一款智能仪表&#xff0c;主要针对光伏并网系统、微逆系统、储能系统、交流耦合系统等新能源发电系统设计。这款电能表具有高精度、体积小、响应速度达100ms&#xff0c;以及…

噪音消除模块调研

一.原理 1.1降噪 noisereduce 库的 reduce_noise 函数使用的是一种基于频谱减法的噪声消除算法。它通过分析音频的频谱&#xff0c;识别出噪声成分&#xff0c;并尝试将这些噪声成分从音频信号中去除&#xff0c;从而提升信号的清晰度。 1.2 动态范围压缩&#xff08;预加重&am…

Ollama:本地大语言模型解决方案

在人工智能领域&#xff0c;大语言模型&#xff08;LLM&#xff09;因其在自然语言处理上的强大能力而备受瞩目。然而&#xff0c;这些模型往往需要大量的计算资源和网络连接&#xff0c;限制了它们在本地环境的应用。Ollama 的推出&#xff0c;为这一问题提供了解决方案。作为…

基于C语言实现文件压缩与解压缩算法

引言 随着互联网的发展&#xff0c;数据传输和存储的需求日益增长&#xff0c;文件压缩技术成为提高数据处理效率的关键技术之一。压缩技术不仅可以减少存储空间的需求&#xff0c;还能加快数据在网络中的传输速度。霍夫曼编码作为一种有效的无损数据压缩算法&#xff0c;广泛…

如何为你的 LLM 应用选择最合适的 Embedding 模型

如果你正在构建 2024 年的生成式人工智能&#xff08;GenAI&#xff09;应用&#xff0c;你现在可能已经听过几次 "嵌入&#xff08;embedding&#xff09; "这个词了&#xff0c;而且每周都能看到新的嵌入模型上架。 那么&#xff0c;为什么会有这么多人突然关心起嵌…

ElasticSearch 集群索引和分片的CURD

一、ES集群的索引 背景&#xff1a;Elasticsearch会对所有输入的文本进行处理&#xff0c;建立索引放入内存中&#xff0c;从而提高搜索效率。在这一点上ES优于MYSQL的B树的结构&#xff0c;MYSQL需要将索引放入磁盘&#xff0c;每次读取需要先从磁盘读取索引然后寻找对应的数据…

OpenAI Gym custom environment: Discrete observation space with real values

题意&#xff1a;OpenAI Gym 自定义环境&#xff1a;具有实数值的离散观测空间 问题背景&#xff1a; I would like to create custom openai gym environment that has discrete state space, but with float values. To be more precise, it should be a range of values wi…

翻译软件 Fastrans 开发日志 #2

就过了几天&#xff0c;我的 Fastrans 项目&#xff08; https://github.com/YaoqxCN/Fastrans &#xff09;又更新了两个版本&#xff0c;现在是 v1.1.1。&#xff08;求个 star 谢谢&#xff01;&#xff09; 上次我初步实现了 Fastrans 的翻译功能以及 UI&#xff0c;可以看…

【C++ Primer Plus习题】8.1

问题: 解答: #include <iostream> using namespace std;void print(const char* str) {cout << str << endl; }void print(const char* str,int size) {static int count 0;count;for (int i 0; i < count; i){cout << str << endl;} }int…

机器学习数学公式推导之线性回归

文章目录 线性回归一、最小二乘法1.1 范数的概念1.2 最小二乘法的推导1.3 几何意义 二、噪声为高斯分布的 MLE2.1 LSE&#xff08;最小二乘估计&#xff09;2.2 MLE&#xff08;极大似然估计&#xff09;2.3 LSE与MLE的联系与区别 三、权重先验也为高斯分布的 MAP四、正则化4.1…

APO的接口级拓扑 VS Dynatrace ServiceFlow

在可观测性系统中&#xff0c;几乎所有的产品都会提供拓扑功能。大部分用户在初看这个拓扑之时都会觉得非常有用&#xff0c;但是一旦真实落地使用&#xff0c;就感觉这个拓扑比较鸡肋。这篇文章重点探讨APO团队是如何考虑让用户能够更好的使用拓扑&#xff0c;真正发挥出拓扑的…

OpenCV绘图函数(14)图像上绘制文字的函数putText()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在图像上绘制指定的文本字符串。 cv::putText 函数在图像上绘制指定的文本字符串。无法使用指定字体渲染的符号会被问号&#xff08;?&#xff…

从理论层面设计简单的电池管理系统(BMS)

前言 最近阅读了《便携式设备的电池电源管理》和《大规模锂离子电池管理系统》这两本书&#xff0c;都是比较容易入门的BMS书籍&#xff0c;书中作者做了很多深层次的思考&#xff0c;所以我摘抄了一些部分&#xff1b;同时结合我个人的项目经验及一些理解&#xff0c;整理成这…