【STL学习】(2)string的模拟实现

前言

本文将模拟实现string的一些常见功能,目的在于加深理解string与回顾类与对象的相关知识。

一、前置知识

  1. string是表示可变长的字符序列的类
  2. string的底层是使用动态顺序表存储的
  3. string对象不以’\0’字符为终止算长度,而是以size有效字符的个数算长度
  4. 为了兼容C,所以string对象在最后追加的一个’\0’字符,但是这个’\0’字符不属于string对象的有效字符
  5. 建议在模拟实现之前熟悉string的常用接口,并且查看文档。

二、string常用接口的模拟实现

1、string的成员变量

//我们模拟实现的string,将其封装在wjs的命名空间中,与库中的string区别开
namespace wjs
{class string{//成员变量private:size_t _size;//有效字符个数size_t _capacity;//存储有效字符的空间容量,注不包含'\0'char* _str;//指向堆申请的连续空间//静态成员变量public:const static size_t npos;//了解:const整数类型的静态成员可以在类内部初始化//static const size_t npos = -1;};//静态成员变量在类外部定义const size_t string::npos = -1;
}

tip:

  1. 命名空间
    • 作用:使用命名空间的目的就是对标识符的名称进行本地化,以避免命名冲突或命名污染
    • 定义:namespace后面跟命名空间名字,然后接一对{}即可,{}中即为命名空间的成员
  2. 静态成员变量:
    • 静态成员属于类为所有类对象共享,不属于某个具体的对象,存放在静态区
    • 静态成员也是类的成员,受访问限定符的限制
    • 一般静态成员变量不能在声明时给缺省值,因为缺省值是给初始化列表使用的,初始化列表是初始化对象的成员变量,而静态成员变量不属于类的任何一个对象
    • 静态成员函数没有隐藏的this指针,不能访问任何非静态成员。常与静态成员变量配套使用
    • 在类外部定义静态成员
    • 静态成员只要能突破类域和访问限定符就可以访问
  3. 了解:const整数类型的静态成员变量可以在类内部初始化,但是不建议。
  4. string的npos:
    • size_t npos = -1,表示该类型的最大值
    • 当string成员函数的参数的缺省值为npos时,表示“直到字符串结束”
    • 当npos作为返回值时,一般表示没有匹配(例如:find)

2、构造函数

string类常用的构造函数有:

  1. string():默认构造函数,构造一个空的string对象,即空字符串。
  2. string(const char* str):用C格式字符串来构造一个string对象。
//默认构造函数——即可以传参构造,也可以使用缺省值构造
string(const char* str = "")//注意:初始化列表按照类中声明次序初始化,建议不要修改顺序,易错点!:_size(strlen(str)),_capacity(_size),_str(new char[_capacity + 1])//C字符串后默认以'\0'结束,为了兼容C所以要多开一个空间,保存'\0'
{//上面的new只是开了空间,所以需要把str拷贝到_str中//注意:strcpy拷贝到'\0'才结束strcpy(_str, str);
}

tip:

  1. 默认构造函数:
    • 三种默认构造函数:无参构造函数、全缺省构造函数、编译器默认生成的构造函数
    • 注意:默认构造函数只能存在一个——虽然语法上可以同时存在,但是无参调用时存在歧义,所以默认构造函数只能有一个
    • 推荐使用全缺省默认构造函数——优点:即可以不传参使用缺省值初始化对象,也可以传参自己初始化对象
    • 不传参就可以调用的就是默认构造函数
  2. 给成员变量赋初值的方式有:
    • 使用初始化列表
    • 使用构造函数的函数体
    • 建议给成员变量赋初值都使用初始化列表,因为初始化列表是成员变量定义的地方
    • 构造函数体与初始化列表结合使用,因为总有一些事情是初始化列表不能完成的。
  3. 初始化列表:
    • 初始化列表是成员变量定义的地方,所以每一个成员变量在初始化列表中最多只能出现一次
    • 引用成员变量、const成员变量、没有默认构造函数的自定义成员,必须在初始化列表初始化(因为这三种成员变量有一个共同特征,在定义时就必须初始化)
    • 注意: 成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
  4. string的底层存储是使用动态顺序表实现
    • 在C++中使用new和delete操作符进行动态内存管理
    • 申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:配合使用
  5. C字符串后默认以’\0’结束,为了兼容C所以string要多开一个空间,保存’\0’

3、析构函数

//析构函数——有动态申请资源,需要显示实现析构释放资源
~string()
{delete[] _str;_str = nullptr;//delete与free一样释放完了,不会改变_str,所以将其置为空_size = _capacity = 0;
}

tip:

  1. 一般当对象涉及动态申请资源,就需要显示实现析构函数
  2. delete释放完空间之后与free一样,不会改变指向动态空间的指针变量,有危险,建议释放完之后将其置为空

4、拷贝构造函数

  • 拷贝构造的浅拷贝 按字节序完成拷贝。

在这里插入图片描述
浅拷贝按字节序拷贝,s1和s2的内容是一样的。当类的成员变量涉及动态资源申请时,会造成两个问题:①同一块空间,析构两次,程序崩溃;②修改一个对象会影响另一个对象。

所以当类的成员变量涉及动态资源申请时,需要有自己的空间,即深拷贝。

  • 拷贝构造深拷贝 有自己的空间。

在这里插入图片描述
深拷贝是把值拷贝到自己的空间。

深浅拷贝就像我们平时考试时抄别人的作业,浅拷贝——名字、学号都抄别人的,深拷贝——知道修改名字、学号。

编译器默认生成的拷贝构造只能完成浅拷贝,所以一旦涉及动态资源申请,即深拷贝,就需要我们显式实现拷贝构造。

拷贝构造的深拷贝的两种实现:

  1. 传统写法:自己开空间,自己拷贝
  2. 现代写法:先叫别人帮我们开好空间,拷贝好了,再把数据给我们。

传统写法:

//传统写法——自己开空间,自己拷贝
string(const string& s)//参数只有一个且必须是类类型对象的引用
{//自己开空间_str = new char[s._capacity + 1];//自己拷贝_size = s._size;_capacity = s._capacity;//不能使用strcmp拷贝,因为strcmp到'\0'结束,但是string不是以'\0'结束的//例如:hello\0xxxmemcpy(_str, s._str, s._size + 1);
}

现代写法:

//方式2:现代写法——先叫别人帮我们开好空间,拷贝好了,再把数据给我们。
//拷贝构造的现代写法对于hello\0xxxx的string有bug,所以我们使用传统写法
string(const string& s)//参数只有一个且必须是类类型对象的引用//注意:C++并没有规定对内置类型进行初始化,所以我们需要将其初始化:_size(0),_capacity(0),_str(nullptr)
{string tmp(s._str);//缺点:string不以'\0'字符结束swap(tmp);
}

tip:

  1. string不以’\0’字符为终止,所以拷贝构造的现代写法有bug
  2. 所以string的拷贝构造,我们不使用现代写法,使用传统写法

5、operator=赋值重载

  • 赋值重载的浅拷贝 按字节序完成拷贝。

在这里插入图片描述
浅拷贝按字节序拷贝,s1和s2的内容是一样的。当类的成员变量涉及动态资源申请时,会造成三个问题:①同一块空间,析构两次,程序崩溃;②修改一个对象会影响另一个对象;③旧空间没释放,造成内存泄漏。

所以当类的成员变量涉及动态资源申请时,需要有自己的空间,即深拷贝。

  • 赋值重载的深拷贝 有自己的空间且要释放旧空间。

在这里插入图片描述
深拷贝是把值拷贝到自己的空间,并且将旧空间释放。

编译器默认生成的赋值重载只能完成浅拷贝,所以一旦涉及动态资源申请,即深拷贝,就需要我们显式实现拷贝构造。

赋值重载的深拷贝的两种实现:

  1. 传统写法:自己开空间,自己拷贝,自己释放旧空间。
  2. 现代写法:先叫别人帮我们开好空间,拷贝好了,再把数据给我们,最后的旧空间也由别人帮我们释放。

传统写法:

//传统写法:自己开空间,自己拷贝,自己释放旧空间。
string& operator=(const string& s)
{//避免自己给自己赋值if (this != &s){//自己开空间char* tmp = new char[s._capacity + 1];memcpy(tmp, s._str, s._size + 1);//自己释放旧空间delete[] _str;//自己拷贝_str = tmp;_capacity = s._capacity;_size = s._size;}return *this;
}

现代写法:

//现代写法:全部叫别人做,然后交给自己
//string& operator=(const string& s)
//{
//	if (this != &s)
//	{
//		string tmp(s);
//		swap(tmp);
//		//std::swap(tmp, *this);//swap的内部实现调用了operator=,所以会造成无限递归
//	}
//	return *this;
//}
//现代写法:进一步叫别人做,直接从参数开始
string& operator=(string tmp)
{swap(tmp);return *this;
}void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}

tip:

  1. 现代写法:就是别人做好了,我们交换拿结果就可以了。

6、c_str成员函数获取C格式字符串

//获取C格式字符串
const char* c_str()const//内部不改变成员变量,建议加上const
{return _str;
}

tip:

  1. const成员
    • 将const修饰的成员函数称为const成员函数
    • 建议只要成员函数内部不修改成员变量,都应该加const,这样普通对象和const对象都可以调用
  2. C格式字符串与string:
    • C格式字符串不是一种类型,string是表示字符序列的一个类
    • C字符串以’\0’字符为终止算长度,string不以’\0’字符为终止,以size为终止算长度(说明:C++为了兼容C,所以string对象的最后都追加了一个’\0’字符,但是这个’\0’不属于string对象的元素)

7、size成员函数获取string的长度

//获取string的长度
size_t size()const//内部不改变成员变量,建议加上const
{return _size;
}

tip:

  1. string的长度,即有效字符个数。
  2. 注意:string不以’\0’字符为终止算长度,是以有效字符的个数算长度。

8、capacity成员函数获取string当前空间容量

//获取string的当前空间容量
size_t capacity()const//内部不改变成员变量,建议加上const
{return _capacity;
}

tip:

  1. 说明:capacity不一定等于string的长度。他可以大于或等于。

9、reserve成员函数申请n个字符的空间容量

//申请n个字符的空间容量——只会改变容量,不会改变长度
void reserve(size_t n = 0)
{//如果n大于当前string容量,则按需申请n个字符的空间容量if (n > _capacity){//避免申请失败,先使用一个临时变量保存char* tmp = new char[n + 1];//不能使用strcmp拷贝,因为strcmp到'\0'结束,但是string不是以'\0'结束的//例如:hello\0xxxmemcpy(tmp, _str, _size + 1);//成功之后,将申请的新空间给string对象,旧空间释放delete[] _str;_str = tmp;tmp = nullptr;//注:reserve只改变容量,不改变长度_capacity = n;}
}

tip:

  1. reserve申请n个字符的空间容量:
    • 如果n大于当前string容量,则申请扩容到n或比n大。
    • 如果n小于当前string容量,则申请缩容,但是该申请是不具约束力的。
  2. 我们模拟实现的reserve,只有n大于当前string容量时,按需申请n个字符的空间容量,n小于当前string容量时不做处理。
  3. 注意:reserve只是单纯的开空间,所以reserve只会改变容量,不改变长度

10、resize成员函数调整string的长度

//调整string的长度——不仅会改变长度,还会改变容量
void resize(size_t n, char ch = '\0')//ch用于填值赋值,不传参使用缺省值
{//1、如果n小于string长度,删除n之后的字符,但不会缩容if (n < _size){//即只改变长度_size = n;//为了兼容C,string对象的最后都追加了一个'\0'_str[_size] = '\0';}else//如果n大于string长度,则扩容+填值赋值{//1、扩容——改变容量reserve(n);//2、填值赋值——改变长度for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;//为了兼容C,string对象的最后都追加了一个'\0'_str[_size] = '\0';}
}

tip:

  1. resize是将string的长度调整为n个字符的长度:
    • 如果n大于string长度,则扩容+填值赋值(默认填’\0’)
    • 如果n小于string长度,删除n之后的字符,但不会缩容
  2. 注意:当n大于当前string的长度时,resize既影响size也影响capacity。

11、clear成员函数清理string的有效字符

//清空string的有效字符——注:不会影响容量
void clear()
{_size = 0;_str[_size] = '\0';
}

tip:

  1. clear:清空string的有效字符,使之成为空字符串。
  2. 注意:clear不会影响容量。

12、empty成员函数判断字符串是否为空

//判断string是否为空
bool empty()const//内部不改变成员变量,建议加上const
{return _size == 0;
}

tip:

  1. 有效字符个数为空,string即为空。

13、operator[]成员函数返回pos位置字符的引用

//operator[]
//版本1:能读能写
char& operator[](size_t pos)
{//注意:operator[]越界断言assert(pos < _size);return _str[pos];
}
//版本2:只能读不能写
const char& operator[](size_t pos)const
{//注意:operator[]越界断言assert(pos < _size);return _str[pos];
}

tip:

  1. operator[]:
    • operator[]越界是断言处理
    • operator[]必须是成员函数
    • operator[]通常定义两个版本:一个返回普通引用能读能写,一个返回常量引用只能读不能写
  2. 重载函数调用时,会走最匹配的,普通对象调用普通的,const对象调用const的

14、迭代器

//迭代器
// 在string中迭代器就是字符指针
//版本1:能读能写
typedef char* iterator;
iterator begin()
{//返回指向string第一个字符的指针return _str;
}
iterator end()
{//返回指向string尾字符的下一个位置的指针return _str + _size;
}
//版本2:只能读不能写
typedef const char* const_iterator;
const_iterator begin()const
{//返回指向string第一个字符的指针return _str;
}
const_iterator end()const
{//返回指向string尾字符的下一个位置的指针return _str + _size;
}

tip:

  1. 迭代器类似于指针类型,提供了对对象的间接访问,可以读写对象。在string中迭代器就是字符指针
  2. begin成员返回指向第一个字符的迭代器
  3. end成员返回指向尾字符的下一个位置的迭代器
  4. begin和end也有普通版本和const版本
  5. 有了迭代器,就可以使用范围for,因为范围for底层就是end和begin实现的(C++11)

15、insert成员函数在stringpos位置字符之前插入字符或字符串

//insert
//1、在string中pos指向的字符前插入n个字符ch
string& insert(size_t pos, size_t n, char ch)
{assert(pos <= _size);//1、插入之前判断是否扩容if (_size + n > _capacity){//至少扩容到_size + nreserve(_size + n);}//2、pos指向的字符前插入n个字符ch//①往后挪动size_t end = _size;//从'\0'开始while (end >= pos && end != npos){//向后挪动n个字符_str[end + n] = _str[end];//迭代end--;//特殊:当pos为0时,end=npos时,结束循环}//②插入for (size_t i = 0; i < n; i++){_str[i + pos] = ch;}_size += n;return *this;
}
//2、在string中pos指向的字符前插入C字符串
string& insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);//1、插入之前判断是否扩容if (_size + len > _capacity){//至少扩容到_size + lenreserve(_size + len);}//2、pos指向的字符前插入C字符串//①往后挪动size_t end = _size;while (end >= pos && end != npos){//向后挪动C字符串的长度_str[end + len] = _str[end];//迭代end--;//特殊:当pos为0时,end=npos时,结束循环}//②插入for (size_t i = 0; i < len; i++){_str[i + pos] = str[i];}_size += len;return *this;
}

tip:

  1. 首先断言pos位置是否合理
  2. 判断是否需要扩容
  3. 把[pos,size]区间的字符都往后挪动len
    • len:len是插入字符的有效个数
    • size:size位置是’\0’,‘\0’也挪动保证插入之后的string对象最后也有’\0’
  4. 插入:注意是从pos位置开始插入len个字符在这里插入图片描述
  5. 注意:当循环变量的类型是size_t时,一定要注意边界0
  6. 当insert不是尾插时,需要挪动数据,效率低,所以一般很少使用

16、push_back成员函数在string后尾插一个字符

//push_back
//在string对象后尾插一个字符
void push_back(char ch)
{方式1:自己实现1、插入之前判断是否扩容//if (_size == _capacity)//{//	//按2倍扩容//	reserve(_capacity == 0 ? 4 : 2 * _capacity);//注意为空串的情况//}2、尾插ch//_str[_size] = ch;//_size++;//_str[_size] = '\0';// 方式2:复用insertinsert(_size, 1, ch);
}

tip:

  1. 方式1:自己实现
    • 插入之前判断是否需要扩容
    • 插入:直接尾插,插入之后需要注意:①有效字符个数+1;②string对象为了兼容C最后要加’\0’
  2. 方式2:复用insert
  3. string对象的尾插时间复杂度为O(1),效率高

17、append成员函数在string后追加C字符串或string对象

//append
//1、在string对象后追加C字符串
void append(const char* str)
{//方式1:自己实现//size_t len = strlen(str);1、插入之前判断是否扩容//if (_size + len > _capacity)//{//	//至少扩容到_size + len//	reserve(_size + len);//}2、尾插str//memcpy(_str + _size, str, len + 1);//_size += len;//方式2:复用insertinsert(_size, str);
}
//2、在string对象后追加string对象
void append(const string& str)
{size_t len = str._size;//1、插入之前判断是否扩容if (_size + len > _capacity){//至少扩容到_size + lenreserve(_size + len);}//2、尾插strmemcpy(_str + _size, str._str, len + 1);_size += len;
}

tip:

  1. 插入之间判断是否需要扩容
  2. 插入:使用memcpy拷贝即可,memcpy由我们自己控制拷贝多少字节,不是拷贝到’\0’就结束
  3. 插入之后记得更新string的有效字符个数

18、operator+=成员函数追加字符或字符串

//operator+=
string& operator+=(const string& str)
{append(str);return *this;
}
string& operator+=(const char* str)
{append(str);return *this;
}
string& operator+=(char ch)
{push_back(ch);return *this;
}
  1. string的operator+=的实现就是复用append和push_back
  2. 所以operator+=不仅可以追加单个字符,还可以追加字符串
  3. operator+=的使用相比push_buck和append更加人性化,所以一般我们更加喜欢使用operator+=
  4. operator+=
    • 一般将复合赋值运算符重载定义为类的成员函数
    • 为与内置类型的复合赋值一致,类的复合赋值运算符也要返回其左侧运算对象的引用
    • 复合运算符都会影响左侧操作数,因为他们都会返回左侧操作数
  5. 自定义类型做函数返回值时,为了提高程序效率,能引用返回尽量引用返回
  6. 引用做返回值:
    • 优点:①减少拷贝提高效率;②可以读写返回值
    • 注意:当返回值出了函数体,不存在了,就不能用引用返回

19、erase成员函数从pos位置删除len个字符

//erase
//从pos位置开始删除len个字符
string& erase(size_t pos = 0, size_t len = npos)
{assert(pos < _size);//如果len=npos或pos+len>=size时,则从pos删除到string末尾if (len == npos || pos + len >= _size){_size = pos;_str[_size] = '\0';}else{//向前挪动数据size_t begin = pos + len;while (begin <= _size)//=size是为了把'\0'也挪动了{_str[pos++] = _str[begin++];}_size -= len;}return *this;
}

tip:

  1. 断言pos是否合理
  2. 如果当len为缺省值npos或len太大时,则把pos后的所有字符删掉
  3. 反之len+pos<size时,删除len个字符,即从pos+len向前挪动数据
  4. 删除之后记得更新有效字符个数在这里插入图片描述

20、find成员函数从stringpos位置开始查找字符或字符串

//find
//1、从pos位置开始往后找字符ch
size_t find(char ch, size_t pos = 0)const//内部不改变成员变量,建议加上const
{assert(pos < _size);for (size_t i = pos; i < _size; i++){//找到返回该字符的位置if (_str[i] == ch){return i;}}//找不到,返回nposreturn npos;
}
//2、从pos位置开始往后找字符串str
size_t find(const char* str, size_t pos = 0)const//内部不改变成员变量,建议加上const
{assert(pos < _size);//strstr找到返回子串位置,找不到返回nullconst char* ret = strstr(_str, str);if (ret){return ret - _str;//后一个元素的下标等于前面的元素个数}else{return npos;}
}

tip:

  1. find:从string对象pos位置开始往后找字符或字符串,找到则返回该字符或字符串在string中第一次出现的位置,找不到返回npos
  2. 注意断言pos位置是否合理
  3. 指针-指针:
    • 前提:两个指针要指向同一块空间
    • 作用:得到两个指针之间的元素个数

21、substr成员函数获取string的子串

//substr
//获取string对象的子串,子串从pos开始,截取len个字符
string substr(size_t pos = 0, size_t len = npos)const//内部不改变成员变量,建议加上const
{assert(pos < _size);//避免多次扩容,算出子串大小,一次reservesize_t n = len;if (len == npos || len >= _size){n = _size - len;}string tmp;tmp.reserve(n);//拷贝子串for (size_t i = pos; i < pos + n; i++)//注意:结束条件为pos+n{tmp += _str[i];}return tmp;
}

tip:

  1. 断言pos位置是否合理
  2. 避免多次扩容,算出子串的大小,一次reserve
  3. 从pos位置拷贝子串

22、operator<<非成员函数输出string对象

//operator<<
//1、ostream必须引用
//2、必须在类外定义
ostream& operator<<(ostream& out, const string& s)
{for (auto ch : s){out << ch;}return out;
}
  1. operator<<必须是非成员函数:
    • 因为成员函数的左操作数是隐含的this,所以operator<<必须是非成员函数
  2. ostream不允许拷贝构造,所以ostream对象必须引用
  3. 范围for的底层是迭代器,所以只要实现了迭代器就可以直接使用
  4. <<运算符从左向右结合,可以连续打印,所以要返回ostream

23、operator>>非成员函数输入string对象

//operator>>
istream& operator>>(istream& in, string& s)
{//每次输入前清空字符串,避免追加s.clear();//多个数值用换行或空格分割,所以cin不会读取换行和空格//所以istream提供了一个成员函数get,读取每一个字符char ch = in.get();//处理前缓冲区前面的空格或者换行while (ch == ' ' || ch == '\n'){ch = in.get();}//读取数据char buff[128];//避免多次扩容,先把数据读到buff数组,数组满了和读取结束了,将数组中的数据给string对象size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;//数组满了if (i == 127){buff[i] = '\0';s += buff;//追加字符串i = 0;}ch = in.get();}//读取结束了if (i != 0){buff[i] = '\0';s += buff;}return in;
}
  1. operator>>必须是非成员函数:
    • 因为成员函数的左操作数是隐含的this,所以operator>>必须是非成员函数
  2. istream不允许拷贝构造,所以istream对象必须引用
  3. string的operator<<实现:
    • 读取之前,需要清空string对象
    • 处理前缓存区的空格和换行
    • 避免多次扩容,创建一个局部数组,先保存读取的数据,再将数组的数据追加到string对象
    • <<运算符也是可以连续读取的,所以需要返回istream

24、关系运算重载非成员函数比较string对象

//关系比较
//①先实现operator==和operator<
//②其余利用他们之间的互斥关系复用
//operator<的方式1:自己实现
//bool operator<(const string& s1, const string& s2)
//{
//	//对应位置的字符比较
//	size_t i = 0;
//	size_t j = 0;
//	size_t len1 = s1.size();
//	size_t len2 = s2.size();
//	while (i < len1 && j < len2)
//	{
//		if (s1[i] > s2[j])
//		{
//			return false;
//		}
//		else if (s1[i] < s2[j])
//		{
//			return true;
//		}
//		else
//		{
//			//迭代
//			i++;
//			j++;
//		}
//	}
//	// "hello" "hello"   false
//	// "helloxx" "hello" false
//	// "hello" "helloxx" true
//	//即只有s1的长度小于s2时,才为真
//	return len1 < len2;
//}
//operator<的方式2:复用memcmp
bool operator<(const string& s1, const string& s2)
{//对应位置的字符比较size_t len1 = s1.size();size_t len2 = s2.size();int ret = memcmp(s1.c_str(), s2.c_str(), len1 < len2 ? len1 : len2);//ret<0为真// "hello" "hello"   false// "helloxx" "hello" false// "hello" "helloxx" true//当ret = 0时,s1的长度小于s2时也为真return ret == 0 ? len1 < len2 : ret < 0;
}bool operator==(const string& s1, const string& s2)
{size_t len1 = s1.size();size_t len2 = s2.size();return len1 == len2&& memcmp(s1.c_str(), s2.c_str(), len1) == 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. string对象的关系运算:
    • 比较对应字符的ASCII码值,如果相等,则继续比较,直到出现不同的字符
    • 特殊:当其中一个string对象比较完之后都相等,这个时候比较两个string对象的长度
  2. 关系运算重载的实现:
    • 先实现<和==( 或>和==)
    • 其余利用他们之间的互斥直接复用

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

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

相关文章

华为openGauss数据库全面解读:数据类型一览与实战指南

引言&#xff1a; 华为openGauss数据库作为一款优秀的国产企业级数据库产品&#xff0c;其丰富多样的数据类型设计为多样化的业务场景提供了强大的数据存储与处理能力。本文将深入浅出地介绍openGauss支持的主要数据类型&#xff0c;并辅以实际应用案例&#xff0c;帮助读者更…

分布式系统面试全集通第一篇(dubbo+redis+zookeeper----分布式+CAP+BASE+分布式事务+分布式锁)

目录 分布式系统面试全集通第一篇什么是分布式?和微服务的区别什么是分布式分布式与微服务的区别 什么是CAP?为什么不能三者同时拥有分区容错性一致性可用性 Base理论了解吗基本可用软状态最终一致性 什么是分布式事务分布式事务有哪些常见的实现方案?2PC&#xff08;Two Ph…

2024-03-26 Android8.1 px30 WI-FI 模块rtl8821cu调试记录

一、kernel 驱动&#xff0c;我这里使用v5.8.1.2_35530.20191025_COEX20191014-4141这个版本&#xff0c;下载这个版本的驱动可以参考下面的文章。 2021-04-12 RK3288 Android7.1 USB wifi bluetooth 模块RTL8821CU 调试记录_rk平台rtl8821cu蓝牙调试-CSDN博客 二、Makefile文…

vue 复制到剪切板 公共方法组件 clipboard 第一次没反应第二次成功

背景&#xff1a;使用clipboard时候想提出组件&#xff0c;后续发现第一次没反应第二次成功 原理&#xff1a;clipboard是创建一个dom监听&#xff0c;然后点击dom时候复制并触发回调函数。所以第一次点击时候是创建监听&#xff0c;第二次点击才能被监听到 解决方案&#xff1…

KY228 找位置(用Java实现)

描述 对给定的一个字符串&#xff0c;找出有重复的字符&#xff0c;并给出其位置&#xff0c;如&#xff1a;abcaaAB12ab12 输出&#xff1a;a&#xff0c;1&#xff1b;a&#xff0c;4&#xff1b;a&#xff0c;5&#xff1b;a&#xff0c;10&#xff0c;b&#xff0c;2&…

2960. 统计已测试设备

文章目录 题意思路代码 题意 题目链接 思路 设备数1&#xff0c;time1 代码 class Solution { public:int countTestedDevices(vector<int>& batteryPercentages) {int time 0;int ans 0;for (auto &i:batteryPercentages){if (i - time > 0){time;ans…

Postwoman 安装

Postwoman作为Postman的女朋友&#xff0c;具有免费开源、轻量级、快速且美观等特性&#xff0c;是一款非常好用的API调试工具。能帮助程序员节省时间&#xff0c;提升工作效率。 Github地址&#xff1a;GitHub - hoppscotch/hoppscotch: &#x1f47d; Open source API devel…

深入了解Hadoop:特性与伪分布式运行进程

引言 Hadoop是一个强大的分布式计算框架&#xff0c;它能够对大规模数据进行可靠、高效和可伸缩的处理。随着数据量的不断增长&#xff0c;企业对于处理大规模数据的需求也越来越高&#xff0c;Hadoop因此成为了大数据处理领域的首选技术。本文将深入探讨Hadoop的特性以及伪分…

如何打包springboot项目并部署服务器

创建一个springboot项目&#xff0c;先写一个接口&#xff0c;我这里是dabaimao/jiekou,启动访问 在pom中加上maven插件 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin<…

2010年之前电脑ubuntu安装nvidia驱动黑屏处理

装好驱动 仿真fps直接到60Hz 陈旧设备 都是非常老旧的电脑&#xff0c;没钱换新电脑&#xff0c;就这么穷…… 电脑详细配置&#xff1a; 冲动 想装显卡驱动提升一下性能&#xff0c;结果……黑了 黑习惯了也无所谓&#xff0c;几分钟就能解决&#xff0c;关键还是太穷&…

ES6 字符串/数组/对象/函数扩展

文章目录 1. 模板字符串1.1 ${} 使用1.2 字符串扩展(1) ! includes() / startsWith() / endsWith()(2) repeat() 2. 数值扩展2.1 二进制 八进制写法2.2 ! Number.isFinite() / Number.isNaN()2.3 inInteger()2.4 ! 极小常量值Number.EPSILON2.5 Math.trunc()2.6 Math.sign() 3.…

【Go】八、Gin 入门使用简介

GIN GIN 是一个高性能&#xff0c;简单易用的轻量级 WEB 框架 快速尝试 package mainimport ("github.com/gin-gonic/gin""net/http" )func pong(c *gin.Context) {// 这里的 gin.H 是 map[string]interface{} 的缩写c.JSON(http.StatusOK, gin.H{"…

Spring Cloud Gateway 3.x 获取body中的数据鉴权

前言 SpringCloud Gateway建立在Spring Framework5、Project Reactor和Spring Boot2.0之上&#xff0c;使用WebFlux非阻塞API 什么是WebFlux? 官网&#xff1a;https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html 传统的Web框架&…

YOLOv9改进策略:注意力机制 | 动态稀疏注意力的双层路由方法BiLevelRoutingAttention | CVPR2023

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a; CVPR2023 动态稀疏注意力的双层路由方法BiLevelRoutingAttention&#xff0c;强烈推荐&#xff0c;涨点很不错&#xff0c;同时被各个领域的魔改次数甚多&#xff0c;侧面验证了性能。 &#x1f4a1;&#x1…

我们该如何优化迭代自己?

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 一款软件如果想变得完美&#xff0c;那么肯定需要不断的试运行和更新迭代。 我们和软件一样&#xff0c;生活中难免会有错误的决策&#xff0c;失误的事件&#xff0c;为了能够解决我们自身存在的BUG&#xff0c;我们该…

设计用于驱动12 V汽车接地负载,VN5E160ASTR、VND5E160MJTR、VND5E025AKTR、VND5E050ACKTR 单/双通道高侧驱动器

摘要 意法半导体VIPower系列高侧开关符合汽车应用要求&#xff0c;内嵌先进的控制功能&#xff0c;其新型保护机制适用于各种负载类型及额定功率。 此类开关是汽车系统的理想选择&#xff0c;如&#xff1a;接线盒、内部/外部照明、直流电机驱动等&#xff0c;并适用于任何需…

[JAVA]12.ArrayList

一、ArrayList 1.1ArrayList类概述 - 什么是集合 ​ 提供一种存储空间可变的存储模型&#xff0c;存储的数据容量可以发生改变 - ArrayList集合的特点 ​ 底层是数组实现的&#xff0c;长度可以变化 - 泛型的使用 ​ 用于约束集合中存储元素的数据类型 1.2ArrayList类常…

基于nodejs+vue基于协同过滤算法的私人诊python-flask-django-php

实现后的私人诊所管理系统基于用户需求分析搭建的&#xff0c;并且会有个人中心&#xff0c;患者管理&#xff0c;医生管理&#xff0c;科室管理&#xff0c;出诊医生管理&#xff0c;预约挂号管理&#xff0c;预约取消管理&#xff0c;病历信息管理&#xff0c;药品信息管理&a…

qt事件机制学习笔记

实现闹钟功能 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), speecher(new QTextToSpeech(this)) //给语音播报者实例化空间 {ui->setupUi(this); }Widget::~Widget() {delete …

【GameFramework框架内置模块】18、界面(UI)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群&#xff1a;398291828 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录&#xff1a;…