【C++初阶】学习string类的模拟实现

目录

  • 前言:
  • 一、创建文件和类
  • 二、实现string类
    • 2.1 私有成员和构造函数
    • 2.2 析构函数
    • 2.3 拷贝构造函数
      • 2.3.1 写法1
      • 2.3.2 写法2
    • 2.4 赋值重载函数
      • 2.4.1 写法1
      • 2.4.2 写法2
    • 2.5 迭代器遍历访问
    • 2.6 下标遍历访问
    • 2.7 reserve
    • 2.8 resize
    • 2.9 判空和清理
    • 2.10 尾插
      • 2.10.1 尾插字符
      • 2.10.2 尾插字符串
    • 2.11 +=运算符重载
    • 2.12 插入
      • 2.12.1 插入字符
      • 2.12.2 插入字符串
    • 2.13 删除
    • 2.14 查找
    • 2.15 截取子串
    • 2.16 关系运算符重载
    • 2.17 >>和<<
  • 三、全部代码
    • 3.1 string.h
    • 3.2 test.cpp

前言:

前面已经学习了string类的用法,这篇文章将更深入的学习string类,了解string类的底层是怎么实现的。当然,这里只是模拟一些常用的,不常用的可以看文档学习。

一、创建文件和类

我们一共创建两个文件,一个是test.cpp文件,用于测试;另一个是string.h文件,用于声明和定义要模拟的string类。模拟的string类会与C++标准库里的string类冲突,所以这里可以使用命名空间来解决这个问题。

namespace yss
{class string{public:private:};
}

我们要模拟的string类在yss这个命名空间里,所以等会使用这个string类就去yss里找。

注:声明和定义都在头文件里写

二、实现string类

2.1 私有成员和构造函数

私有成员变量主要有:

_str——字符数组
_size——字符有效个数
_capacity——数组总空间大小(总空间大小体现在数组开辟的空间大小)
npos——静态成员常量,无符号整型的最大值

private:size_t _size;size_t _capacity;char* _str;const static size_t npos = -1;

为什么声明顺序不是先字符串,这个等会再讨论,先来看下构造函数

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

这里我们定义_capacity 与_size 相同,方便后续开辟空间。_size 可以用strlen计算字符串个数,为什么_capacity 不用?因为再使用一个strlen又要遍历一遍字符串,加大了时间复杂度。

注意:strlen计算字符串长度不包括斜杠0

前面因为总空间大小和有效字符个数一样,又因为字符串末尾要有斜杠0,所以实际开辟的字符数组的空间大小比_capacity 多一个位置,用于放斜杠0;_str 字符数组的空间开辟好后,就把参数字符串拷贝过来即可。

注意:strcpy会连同斜杠0一起拷贝

在这里插入图片描述

如果参数为空字符串该咋办?可以给缺省值,但是不能为斜杠0,因为斜杠0是字符类型,而我们这里的形参是字符串类型,虽然可以发生类型转换,但毕竟不好;nullptr指针呢?也不行,因为如果我们没有传参那么它就是空指针,最后要打印对空指针解引用不就报错了吗。所以这里给个空字符串即可。

回到前面的声明私有成员变量的问题,我们知道声明的顺序就是初始化列表的顺序,但是前面的代码没有用到初始化列表。其实前面的代码只是一种写法,构造函数也可以使用初始化列表,或者初始化列表与函数体混着使用,如果用初始化列表就得注意声明顺序了,所以为了防止出错,声明顺序就与我们在构造函数里成员变量定义的顺序相同。

验证一下前,我们还要实现一个返回C格式的函数,顺便把返回字符有效个数、交换和返回容量也写下,方便后面操作:

//C格式返回
const char* c_str() const
{return _str;
}
//返回字符个数
const size_t size() const
{return _size;
}
//返回容量
const size_t capacity() const
{return _capacity;
}
//交换
void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}

运行结果:
在这里插入图片描述

总结:
一:缺省值给空字符串
二:多开一个位置放斜杠0

2.2 析构函数

析构函数与我们以前的destroy()函数差不多,作用是清理空间。要注意下这里使用的是与new[] 匹配的delete[] ,不能把中括号漏了。

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

2.3 拷贝构造函数

2.3.1 写法1

之前的文章提过,我们不写编译器会默认生成它的拷贝构造,但这是浅拷贝,对于非指针变量没有太大关系,但如果是指针成员变量,导致两个指针指向同一块空间,会有一块内存重复释放的风险,所以要用深拷贝,额外开一块空间,自己来实现。

先上代码:

string(const string& s)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}

深拷贝意味着要临时多开一块空间,这里多开的一块空间就是_str的,因为_str是刚被初始化的对象的字符串,所以给它开辟空间,方式与前面的构造函数相同,_size 与_capacity 值拷贝。

str指的是s._str
在这里插入图片描述
运行结果:
在这里插入图片描述

2.3.2 写法2

这个写法更简化:

	string(const string& s){string tmp(s._str);swap(tmp);}

也要额外开辟一块空间,定义一个string类型的变量tmp,构造的内容与要拷贝的内容相同,然后将s2的内容与tmp交换就行了。

图示:
在这里插入图片描述
s2就是用s1拷贝构造出来的对象

因为tmp是局部变量,出函数作用域要被销毁掉,拷贝构造结束调用析构清理tmp。(交换后tmp指向空,释放空指针不进行任何操作)

2.4 赋值重载函数

2.4.1 写法1

赋值重载类似拷贝构造,也要有一个额外空间。

代码:

string& operator=(const string& s)
{if (this != &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&可以减少拷贝,效率高;开始的 if 语句是为了判断this指针与要赋值的对象是否相同(函数参数列表里的取地址符是引用,if 判断里的是取地址),如果相同,就没必要赋值了,因为是自己赋值自己。如果不同,才可以进行赋值。

创建一个临时的空间,把要赋值的内容拷贝到临时空间里,然后把this指针指向的原来的空间销毁掉(this指针不显示写,还有this指针的字符串原来是有自己的一块空间的),然后指向这块临时的空间,其他成员变量值拷贝。最后返回this指针。

图:
在这里插入图片描述
运行结果:
在这里插入图片描述

2.4.2 写法2

赋值重载的另一个写法也很简洁:

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

形参s没有引用,说明它是实参的一份拷贝,独立占一块空间,正因为独立占一块空间,所以它就可以作为this指针的_str交换的临时对象。
在这里插入图片描述

如果传的是引用,形参s与原来的被赋值的对象共用一块空间,交换的话不就把被赋值的对象的内容改变了吗
在这里插入图片描述
好了,既然如此,那么不加引用,可是为什么不加const了呢?注意,不是什么情况都能加const。如果这里加上const,那么s就不可以改变了,交换就是改变s,会报错。
在这里插入图片描述

注意一下:拷贝构造函数的参数列表里也可以不加const,但是某些情况就必须加const,这“某些情况”后面会提到。此时这里加不加const都没关系,但为了保险起见,一般带上const较好

2.5 迭代器遍历访问

有两种写法,先来看第一种,没有const的

typedef char* iterator;
iterator begin()
{return _str;
}
iterator end()
{return _str + _size;
}

iterator是迭代器,我们以重命名的方式让它成为字符指针的别名。begin返回的是指向首元素地址的指针,end返回的是指向斜杠0的指针。
在这里插入图片描述
遍历方式是定义一个指针刚开始指向字符串首元素的位置,然后循环不等于斜杠0就打印,直到遇到斜杠0跳出。

	yss::string s1("hello world");yss::string::iterator it = s1.begin();while (it != s1.end()){cout << *it;++it;}cout << endl;

运行结果:
在这里插入图片描述

有const的写法:

typedef const char* const_iterator;
const_iterator begin() const
{return _str;
}
const_iterator end() const
{return _str + _size;
}

用法是上面的一样,只不过不可以修改值,只能打印。
在这里插入图片描述

2.6 下标遍历访问

也有const和非const的写法,使用方式就是常见的下标遍历

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

使用assert可以预防pos下标越界的问题

在这里插入图片描述

2.7 reserve

如果要插入字符或字符串,当字符数组的空间大小不满足需求时,就要进行扩容。模拟实现的函数是reserve。

reserve:
1.不改变有效字符个数,不影响字符串内容,只影响空间大小
2.当参数大于空间总大小时,空间总大小增加;小于等于时不变
3.不会缩容

代码:

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

扩容的也需要开辟一块临时空间tmp,tmp的实际空间大小为n+1(多一个放斜杠0),将_str的内容拷贝到tmp临时空间里,然后销毁掉原来的空间,指向新的空间,原来的空间大小_capacity扩到n,完成扩容。

在这里插入图片描述

2.8 resize

resize可以改变字符串的有效字符个数,当传入的参数n小于原来有效字符个数时,有效字符个数为n,同时字符串的内容也发生改变;如果大于,在原来字符串的末尾填充字符,填充的个数为参数减去原来的有效字符个数。

void resize(size_t n, char ch)
{if (n <= _size){_str[n] = '\0';}else{if (n > _capacity){reserve(n);}for (size_t i = _size; i < n; i++){_str[i] = ch;}_str[n] = '\0';}_size = n;
}

n小于等于_size,就在下标为n的位置改为斜杠0,(等于_size就是原来斜杠0的位置放斜杠0)。n大于有效字符个数的情况:如果n大于空间大小,就要扩容;只要大于不管有没有扩容都要在末尾逐个插入字符,然后在下标为n处放置斜杠0(这里不用担心越界问题,有或者没有扩容,实际字符数组都有多一个位置来处理这个斜杠0)。最后有效字符个数要改为n。

运行结果:
在这里插入图片描述

2.9 判空和清理

如果有效字符个数为0返回真,否则假(打印结果真为1,假为0)。清理的是有效字符个数,对空间大小没有影响

// 判空
bool empty()
{return _size == 0;
}
// 清理
void clear()
{_size = 0;_str[0] = '\0';
}

在这里插入图片描述

注意:清理函数里如果字符串的首元素不改为斜杠0,清理完后再打印它依然可以打印出这个字符串,因为返回C格式字符串是有带斜杠0返回的,也就是说正常情况下打印这个字符串到它的斜杠0位置结束,所以这里的意思是函数里如果字符串的首元素不改为斜杠0,在实际的空间里这个字符串于原来没被清理的状态一样,只是有效字符个数_size改变了而已。在字符串的首元素改为斜杠0,那么打印时遇到斜杠0停下,即什么都没有打印出来,这个字符串就与被清理的效果一样了。

2.10 尾插

2.10.1 尾插字符

尾插字符要注意空间是否够用,不够要扩容

代码:

void push_back(char ch)
{if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';
}

当_size等于_capacity就要扩容,扩到原来_capacity的两倍大小;然后尾插字符,有效字符个数加1,再最后放斜杠0
在这里插入图片描述
运行结果:
在这里插入图片描述

2.10.2 尾插字符串

先要计算要插入的字符串的字符个数,如果原来字符串的有效个数加上插入的字符串的字符个数大于空间大小,就要扩容。然后进行尾插,有效字符个数为相加的结果。

void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;
}

为什么这里是_size + len > _capacity而不是>=
假设原来的字符串的_size=8,_capacity=9,新插入的字符串就一个字符。此时_capacity还有一个位置刚好可以放这一个字符,所以这种情况就没必要扩容。还有一种情况是要插入的字符串为空串,那么最后得到的还是自己原来的字符串,就算原来的字符串_size == _capacity,也不需要扩容,因为此时又没有要插入的东西。(或者说插入空串相当于啥也没插入)

在这里插入图片描述

2.11 +=运算符重载

+=运算符可以实现两种尾插方式,既可以尾插字符,也可以尾插字符串

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

复用前面的函数,然后返回this指针

在这里插入图片描述

2.12 插入

2.12.1 插入字符

插入操作都要考虑是否要扩容,扩容与前面尾插字符一样。插入的位置可能是头也可能是中间,所以要挪动数据。

void insert(char ch, size_t pos)
{assert(pos < _size);//插入要考虑是否要扩容if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}//挪动数据int end = _size;while (end >= (int)pos)//强转类型{_str[end + 1] = _str[end];--end;}_str[pos] = ch;_size++;
}

在这里插入图片描述
运行结果:
在这里插入图片描述

2.12.2 插入字符串

主要这几点,考虑是否扩容,挪动数据,拷贝字符串

代码:

void insert(const char* str, size_t pos)
{assert(pos < _size);size_t len = strlen(str);//考虑是否扩容if (_size + len > _capacity){reserve(_size + len);}//挪动数据int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}strncpy(_str + pos, str, len);_size++;
}

这里的拷贝字符串就跟前面的不一样了,strncpy会把要插入的字符串拷贝到中间空出来的位置去,并且会控制个数,即在中间某个位置插入一个字符串,这个位置之后原来的字符串没有被覆盖掉,如果是用strcpy,那么插入的位置之后原来的字符串就都被要插入的字符串覆盖。

在这里插入图片描述
运行结果:
在这里插入图片描述

2.13 删除

如果要删除的个数len是默认的缺省值,从pos位置开始后面全部删除;或者pos的值加上len大于等于字符个数,也是从pos位置开始后面全部删除,pos位置就放斜杠0,有效字符个数变为pos的值。从pos开始后面不全删,就把pos位置加len后的字符串覆盖到pos位置之后,_size减去len。
在这里插入图片描述

代码:

void erase(size_t pos, size_t len = npos)
{assert(pos < _size);// 从pos开始后面都删除if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}//从pos开始后面不全删else{strcpy(_str + pos, _str + pos + len);_size -= len;}
}

运行结果:
在这里插入图片描述

2.14 查找

查找字符/字符串
从左往右查找字符,找到了返回该字符的下标,否则返回npos

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

从左往右查找字符串,使用字符串函数strstr,定义一个变量得到字符串函数的返回结果,这个返回结果如果是空,说明没找到,就返回npos,否则返回该指针的下标,即找到字符串的第一个字符下标。

size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);char* ptr = strstr(_str, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}
}

运行结果:
在这里插入图片描述

2.15 截取子串

代码:

string substr(size_t pos = 0, size_t len = npos)
{assert(pos < _size);size_t end = pos + len;if (len == npos || pos + len >= _size){end = _size;}string ss;ss.reserve(end - pos);for (size_t i = pos; i < end; i++){ss += _str[i];}return ss;
}

先定义一个变量end确定截取的字符串在源字符串的最后位置,假设pos+len的值就是end指向的下标处。如果len是缺省值,或者pos加上要len大于_size,end就等于_size;不满足条件,就是假设的值。定义一个对象,使其尾插原字符串从pos位置到end位置的字符,最后返回的就是截取的字符串。

运行一下:
在这里插入图片描述

这里就可以解决前面的一个问题了:拷贝构造函数的第二种写法某些情况必须加const

刚刚写的截取字符串是传值返回,返回的是返回值的临时拷贝,这个临时对象具有常属性,用其他对象接收时要带上const

图示:
在这里插入图片描述
可能有人会想这里为什么要用引用接收,不用引用行不行?答案是绝对不行,因为没有引用,拷贝构造会无限递归,一直循环调用。

2.16 关系运算符重载

代码:

		//大于bool operator>(string& s) const{return strcmp(_str, s._str) > 0;}//等于bool operator==(string& s) const{return strcmp(_str, s._str) == 0;}//大于等于bool operator>=(string& s) const{return (*this == s && *this > s);}//小于bool operator<(string& s) const{return !(*this >= s);}//小于等于bool operator<=(string& s) const{return !(*this > s);}//不等于bool operator!=(string& s) const{return !(*this == s);}

在这里插入图片描述

2.17 >>和<<

之前我们写输出的函数要用友元函数,现在有个方法可以不用友元:

	ostream& operator<<(ostream& out, const string& s){for (auto e : s){out << e;}return out;}

范围for直接打印对象里面的字符。

在这里插入图片描述

输入的函数也不需要友元:

istream& operator>>(istream& in, string& s)
{char ch;in >> ch;while (ch != '\n' && ch != ' '){s += ch;in >> ch;}return in;
}

运行一下:
在这里插入图片描述
结果发现好像停不下来了,因为cin和scanf一样,读取时对空格或者换行会进行忽略,所以一直在循环里

解决方法:使用cin的get函数,可以读取到空格或者换行

	istream& operator>>(istream& in, string& s){s.clear();//清理之前的字符串char ch = in.get();while (ch != '\n' && ch != ' '){           s += ch;ch = in.get();}return in;}

在这里插入图片描述

三、全部代码

3.1 string.h

#include <iostream>
#include <assert.h>
#include<string.h>
using namespace std;namespace yss
{class string{public://迭代器遍历typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//构造string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//析构~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}//C格式返回const char* c_str() const{return _str;}//返回字符个数const size_t size() const{return _size;}//返回容量const size_t capacity() const{return _capacity;}//交换void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷贝构造/*string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}*/string(const string& s){string tmp(s._str);swap(tmp);}//赋值重载/*string& operator=(const string& s){if (this != &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=(string s){swap(s);return *this;}//下标遍历访问char& operator[](size_t pos){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){if (n <= _size){_str[n] = '\0';}else{if (n > _capacity){reserve(n);}for (size_t i = _size; i < n; i++){_str[i] = ch;}_str[n] = '\0';}_size = n;}// 判空bool empty(){return _size == 0;}// 清理void clear(){_size = 0;_str[0] = '\0';}//尾插字符void push_back(char ch){if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//尾插字符串void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}// +=string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}//插入void insert(char ch, size_t pos){assert(pos < _size);//插入要考虑是否要扩容if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}//挪动数据int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}_str[pos] = ch;_size++;}void insert(const char* str, size_t pos){assert(pos < _size);size_t len = strlen(str);//考虑是否扩容if (_size + len > _capacity){reserve(_size + len);}//挪动数据int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}strncpy(_str + pos, str, len);_size++;}//删除void erase(size_t pos, size_t len = npos){assert(pos < _size);// 从pos开始后面都删除if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}//从pos开始后面不全删else{strcpy(_str + pos, _str + pos + len);_size -= len;}}//查找字符size_t find(char ch, size_t  pos = 0){assert(pos < _size);for (size_t i = 0; i < _size; i++){if (ch == _str[i]){return i;}}return npos;}//查找字符串size_t find(const char* str, size_t pos = 0){assert(pos < _size);char* ptr = strstr(_str, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}}//截取子串string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);size_t end = pos + len;if (len == npos || pos + len >= _size){end = _size;}string ss;ss.reserve(end - pos);for (size_t i = pos; i < end; i++){ss += _str[i];}return ss;}//大于bool operator>(string& s) const{return strcmp(_str, s._str) > 0;}//等于bool operator==(string& s) const{return strcmp(_str, s._str) == 0;}//大于等于bool operator>=(string& s) const{return (*this == s && *this > s);}//小于bool operator<(string& s) const{return !(*this >= s);}//小于等于bool operator<=(string& s) const{return !(*this > s);}//不等于bool operator!=(string& s) const{return !(*this == s);}private:size_t _size;size_t _capacity;char* _str;const static size_t npos = -1;};// <<ostream& operator<<(ostream& out, const string& s){for (auto e : s){out << e;}return out;}// >>/*istream& operator>>(istream& in, string& s){char ch;in >> ch;while (ch != '\n' && ch != ' '){s += ch;in >> ch;}return in;}*/istream& operator>>(istream& in, string& s){s.clear();char ch = in.get();while (ch != '\n' && ch != ' '){s += ch;ch = in.get();}return in;}
}

3.2 test.cpp

#include "string.h"int main()
{/*yss::string s1("hello yss");cout << s1.c_str() << endl;*//*yss::string s1("hello yss");yss::string s2(s1);cout << s2.c_str() << endl;*//*yss::string s1("hello yss");yss::string s2 = s1;cout << s2.c_str() << endl;*///yss::string s1("hello world");//yss::string::iterator it = s1.begin();//while (it != s1.end())//{//	cout << *it;//	++it;//}//cout << endl;/*yss::string s1("hello world");yss::string::const_iterator it = s1.begin();while (it != s1.end()){cout << *it;++it;}cout << endl;*//*yss::string s1("hello world");for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << " ";}cout << endl;*//*yss::string s1("hello yss");cout << s1.c_str() << endl;cout << s1.size() << endl;cout << s1.capacity() << endl;s1.resize(12,'q');cout << s1.c_str() << endl;cout << s1.size() << endl;cout << s1.capacity() << endl;*//*yss::string s1("hello yss");cout << s1.empty() << endl;cout << s1.size() << endl;cout << s1.c_str() << endl;cout << s1.capacity() << endl;s1.clear();cout << s1.empty() << endl;cout << s1.size() << endl;cout << s1.c_str() << endl;cout << s1.capacity() << endl;*//*yss::string s1("hello yss");s1.push_back('a');cout << s1.c_str() << endl;*//*yss::string s1("hello yss");s1.append("abcd");cout << s1.c_str() << endl;*//*yss::string s1("hello yss");s1 += 'a';cout << s1.c_str() << endl;yss::string s2("hello yss");s2 += "abcd";cout << s2.c_str() << endl;*//*yss::string s1("hello yss");s1.insert('a', 3);cout << s1.c_str() << endl;*//*yss::string s1("hello yss");s1.insert("abcd", 3);cout << s1.c_str() << endl;*//*yss::string s1("hello yss");s1.erase(3);cout << s1.c_str() << endl;yss::string s2("hello");s2.erase(2, 2);cout << s2.c_str() << endl;*//*yss::string s1("hello yss");size_t p = s1.find('o');cout << p << endl;p = s1.find("ss");cout << p << endl;*//*yss::string s1("hello yss");yss::string s2 = s1.substr(1, 7);cout << s2.c_str() << endl;*//*yss::string s1("hello yss");cout << s1 << endl;*//*yss::string s1;cin >> s1;cout << s1 << endl;*//*yss::string s1("hello");cout << s1 << endl;cin >> s1;cout << s1 << endl;*/yss::string s1("hello");yss::string s2("aabbc");cout << (s1 > s2) << endl;cout << (s1 == s2) << endl;cout << (s1 >= s2) << endl;cout << (s1 < s2) << endl;cout << (s1 <= s2) << endl;cout << (s1 != s2) << endl;return 0;
}

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

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

相关文章

计算机组成原理综合2

21、和外存储器相比&#xff0c;内存储器的特点是________。C A. 容量大、速度快、成本低 B. 容量大、速度慢、成本高 C. 容量小、速度快、成本高 D. 容量小、速度快、成本低 22、某计算机字长16位&#xff0c;存储器容量64KB&#xff0c;若按字编址&#xf…

diffusers-Inpainting

原文链接&#xff1a;添加链接描述 白色mask区域仅使用生成出来的&#xff0c;非白色mask区域使用原始影像&#xff0c;但是图像有点不平滑 import PIL import numpy as np import torchfrom diffusers import AutoPipelineForInpainting from diffusers.utils i…

Ubuntu 常用命令之 gzip 命令用法介绍

gzip 是一个在 Linux 和 Unix 系统中常用的文件压缩工具。它的名字来源于 GNU zip&#xff0c;作为一个自由软件&#xff0c;它是 GNU 项目的一部分。gzip 命令通常用于压缩文件&#xff0c;以节省磁盘空间&#xff0c;或者减小文件的大小&#xff0c;以便于网络传输。 gzip 命…

音视频直播核心技术介绍

直播流程 采集&#xff1a; 是视频直播开始的第一个环节&#xff0c;用户可以通过不同的终端采集视频&#xff0c;比如 iOS、Android、Mac、Windows 等。 前处理&#xff1a;主要就是美颜美型技术&#xff0c;以及还有加水印、模糊、去噪、滤镜等图像处理技术等等。 编码&#…

网络基础介绍

1.网线制作 1.1 网线制作需要的工具 网线 网线钳 水晶头 测试仪 ​编辑 1.2 网线的标准 1.3 网线的做法 2.集线器&交换机&路由器的介绍 3.OSI七层模型 4.路由器的设置 4.1 常见的路由器设置地址 4.2 常见的路由器账号密码 4.3 登录路由器 设置访客网…

管理类联考——数学——真题篇——按知识分类——代数——数列

【等差数列 ⟹ \Longrightarrow ⟹ 通项公式&#xff1a; a n a 1 ( n − 1 ) d a m ( n − m ) d n d a 1 − d A n B a_n a_1(n-1)d a_m(n-m)dnda_1-dAnB an​a1​(n−1)dam​(n−m)dnda1​−dAnB ⟹ \Longrightarrow ⟹ A d &#xff0c; B a 1 − d Ad&#x…

从零开始构建高效的网校平台:在线教育系统源码的开发指南

随着科技的不断发展&#xff0c;在线教育在现代社会中变得愈发重要。本文将为您提供一份详尽的指南&#xff0c;从零开始构建高效的网校平台&#xff0c;覆盖在线教育系统源码的关键开发步骤。 第一步&#xff1a;明确需求和目标 在开始之前&#xff0c;明确您的网校平台的需…

软件测试工程师的职业发展方向

一、软件测试工程师大致有4个发展方向: 1 资深软件测试工程师 一般情况&#xff0c;软件测试工程师可分为测试工程师、高级测试工程师和资深测试工程师三个等级。 达到这个水平比较困难&#xff0c;这需要了解很多知识&#xff0c;例如C语言&#xff0c;JAVA语言&#xff0c…

《opencv实用探索·二十一》人脸识别

Haar级联分类器 在OpenCV中主要使用了两种特征&#xff08;即两种方法&#xff09;进行人脸检测&#xff0c;Haar特征和LBP特征。用的最多的是Haar特征人脸检测。 Haar级联分类器是一种用于目标检测的机器学习方法&#xff0c;它是一种基于机器学习的特征选择方法&#xff0c;…

【JAVA】仓库、货架、货物

当前只有添加、查询&#xff0c;没有删除和修改部分&#xff1a; import java.util.LinkedList;class Goods {String id;String name;int price;public Goods(String id, String name, int price) {this.id id;this.name name;this.price price;}Overridepublic String toS…

【matlab】绘制竖状双组渐变柱状图

【matlab】绘制竖状双组渐变柱状图

QT 前端技术 QSS(1)

伪装态是指当鼠标悬停在按钮上的时候&#xff0c;按钮的字体背景颜色等会发生改变。 这句话意思在this里面找到QTextEdit控件#后面表示指定的控件名称就是从所有QTextEdit中选出叫做lbsize的QTextEdit并对其进行后面的操作。 如果将QTextEdit#lbsize删除的话就是将this中全部的…

ansibe的脚本---playbook剧本

playbook剧本组成部分&#xff1a; 1、task 任务&#xff1a; 主要是包含要在目标主机上的操作&#xff0c;使用模块定义操作。每个任务都是模块的调用。 2、variables变量&#xff1a;存储和传递数据。变量可自定义&#xff0c;可以在playbook中定义为全局变量&#xff0c;可…

亚信安慧AntDB数据库——助力5G计费核心替换,全面自主可控

数字经济时代&#xff0c;5G以更快、更丰富、更智能的连接方式服务于各行各业。AntDB数据库&#xff0c;源于亚信科技&#xff0c;自2008年起成功落地全国24个省份的中国移动、中国电信、中国联通和中国广电等运营商项目&#xff0c;为数字化服务和信息化基础建设提供支持。 在…

Linux Mint 21.3 代号为“Virginia”开启下载

Linux Mint 团队今天放出了 Linux Mint 21.3 Beta ISO 镜像&#xff0c;正式版计划在今年圣诞节发布。 支持 在实验性支持 Wayland 之外&#xff0c;Cinnamon 6.0 版 Linux Mint 21.3 Beta 镜像还带来了其它改进&#xff0c;Nemo 文件夹管理器右键菜单支持下载相关操作。 Cin…

【python】进阶--->并发编程之协程

一、协程 是另外一种实现多任务的方式,只不过比线程占用更小的执行单元(需要的资源).因为它自带cpu上下文,就可以在合适的时机,从一个协程切换到另一个协程. (任务数量多于cpu时)系统在很短的时间内,将cpu轮流分配给不同的任务执行,造成了多任务同时运行的错觉.而每个任务在运行…

1.CentOS7网络配置

CentOS7网络配置 查看网络配置信息 ip addr 或者 ifconfig 修改网卡配置信息 vim /etc/sysconfig/network-scripts/ifcfg-ens192 设备类型&#xff1a;TYPEEthernet地址分配模式&#xff1a;BOOTPROTOstatic网卡名称&#xff1a;NAMEens192是否启动&#xff1a;ONBOOTye…

暴雨AI服务器:推动大模型算力底座发展

语言大模型作为人工智能领域的重要分支&#xff0c;其强大的自然语言处理能力和模仿人类的对话决策能力&#xff0c;正逐渐成为人们的关注焦点。近日&#xff0c;据央视新闻报道&#xff0c;工业和信息化部赛迪研究院数据显示&#xff0c;今年我国语言大模型市场规模实现较快提…

最新鸿蒙HarmonyOS 使用Progress、Toggle开发一个接单界面

Progress 进度条组件&#xff0c;用于显示内容加载或操作处理等进度。 接口 Progress(options: {value: number, total?: number, type?: ProgressType}) Toggle组件提供勾选框样式、状态按钮样式及开关样式。 接口 Toggle(options: { type: ToggleType, isOn?: boolean …

oracle vm virtualBox虚拟机网卡设置

一、桥接模式 1、桥接模式自动分配IP 通过dns自动分配Ip方式、重启服务器可能会出现IP变动的情况。 选中虚拟机--设置--网络&#xff0c;链接方式选择“桥接网卡”&#xff0c;界面名称选择“需要桥接的网卡名称” 不清楚的可以在宿主机网络设置查看&#xff08;需要桥接哪…