string类——常用函数模拟(C++)

        本篇中,将会详细的介绍 Cpp 中 string 的使用,以及 string 类常用函数的模拟实现。对于 string 的内置函数来说,存在很多很冗余的用法,很多函数都有很多种用法,本篇将会讲解常用内置函数的常用用法,模拟函数的时候,也只是模拟常用的用法代码。本篇实现的 string ,仅仅只是通过一种很简单的方式实现的,对于 vs 、g++ 下的 string 相比都是小巫见大巫。

        本篇的函数讲解顺序按照 cplusplus 官网的顺序,网站:string - C++ Reference (cplusplus.com)

        目录如下:

目录

1. iterator —— 迭代器

2. capacity —— 容量

3. Element access —— 访问成员

4. modify —— 修改串内容

5. string operations —— string 的操作

 6. 默认成员函数

7. 流提取和其他运算符重载

8. 浅谈本篇中的 string

9. All Code

1. iterator —— 迭代器

        首先我们第一个要讲解的就是我们的迭代器,在string中存在许多的迭代器,但是我们常用的就是:

        因此,我们主要围绕这四个迭代器来实现,先讲解其功能,对于 begin 和 end  来说:

        其主要的作用为:begin:返回 string 串中的第一个元素的位置,end:返回 string 串中的最后一个元素的位置。对于 rbegin 和 rend 来说则是相反的,rbegin 返回逆向串的第一个字符,也就返回正向串的最后一个字符,rend 返回逆向串的最后一个字符,也就是返回正向串的第一个元素,所以从功能上来说,begin == rend,rbegin == end,其使用如下:

        如图所示,一个为正向打印,一个为逆向打印,根据以上实现的功能,模拟出的代码为:

namespace MyString {class string {public:typedef char* iterator;typedef const char* const_iterator;typedef char* reverse_iterator;typedef const char* const_reverse_iterator;iterator begin() {return _str;}const_iterator begin() const {return _str;}iterator end() {return _str + _size;}const_iterator end() const {return _str + _size;}reverse_iterator rbegin() {return _str + _size;}const_reverse_iterator rbegin() const {return _str + _size;}reverse_iterator rend() {return _str;}const_reverse_iterator rend() const {return _str;}private:char* _str;size_t _size;size_t _capacity;};
}

        对于如上的代码,我们同样的函数还需要写出两个版本,一个普通版本,一个由 const 修饰的版本,因为对于由 const 修饰的 string 并不能调用普通版本的函数,所以我们需要在写一个由 const 修饰的版本。另外,实现一个属于自己的类,需要使用命名空间封装,否则会与库中自带的 string 相矛盾。

        当然,对于以上的实现,我们更多是基于功能实现的,对于不同的编译器的实现方式存在不同,而且对于不同的类的迭代器实现方式也不同,我们实现出的函数是以简单的方式实现的。

2. capacity —— 容量

        接下来我们将实现与容量相关的类函数,仍然存在很多函数,但是我们只需要掌握使用以下由红框框住的代码即可:

        首先讲解的是最为简单的两个类函数,分别是 size capacity,只需要返回串的尺寸和容量(当前可以容纳多少字符的量),因为 _size 和 _capacity 被 private 封装,不能直接访问,所以我们需要使用函数来获取,在函数中,我们只需要返回 _size 和 _capacity 的值即可。其实 length 和 size 的功能是相同的,返回的都是 _size ,但是最常用的还是 size 函数

        接下来需要掌握的为 clear empty 函数,clear 的功能为:将串的内容清楚,使串变成空串,所以具体实现,我们只需要将串中第 0 个位置的字符置为 '\0' 即可;对于 empty 函数,功能为,判断当前串是否为空串,实现起来仍然轻松,只需要返回 _size 是否 等于 0 即可。

        接下的重点就为 resizereserve 函数了,对于这两个函数的解释如下:

        首先是 reserve 函数,该函数的作用为:将字符串的容量改为 n ,若当前容量已经大于 n 则不需要修改,因为不会将其缩容,只有当 n 大于当前容量时,才会扩容。

        对于 resize 函数,功能为:将字符串的长度修改为长度 n ,但是像比于 reserve 函数,resize 函数可以将其长度缩小,当 n 小于 _size 的时候,就减小长度,大于 _size 的时候就增长长度。并且由图中可以看到,该函数存在两个重载函数,其中一个为,n 大于 _size 之后,在后面追加字符 c。我们在实现的时候,将其改为一个函数,具体的实现如下:

namespace MyString {class string {public:const size_t size() const {return _size;}const size_t capacity() const {return _capacity;}bool empty() const {return _size == 0;}void clear() {_str[0] = '\0';_size = 0;}// 对数据进行扩容void reserve(size_t n) {// 仅仅 n 大于 capacity 的时候,我们才进行扩容if (n > _capacity) {// 每一次分配看见,需要多分配一个,给 '\0'腾一个空间char* tmp = new char[n + 1];// 将原字符串拷贝后删除strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}// 将resize函数改为半缺省函数,不提供字符c的时候,自动在后面补'\0'void resize(size_t n, char c = '\0') {if (n <= _size) {_str[n] = '\0';_size = n;}else {reserve(n);for (int i = _size; i < n; i++)_str[i] = c;_size = n;_str[_size] = '\0';}}private:char* _str;size_t _size;size_t _capacity;};
}

3. Element access —— 访问成员

        接下来我们将完成的函数为可以访问到串中元素的函数,如下:

        上图中有四个函数,但是我们我们经常使用用的函数为第一个运算符重载函数。使用该函数可以向访问数组内容一样访问串中的元素,并且还可以对串中的元素进行修改,对于其实现如下:

		// []运算符重载const char& operator[](size_t pos) const {// 判断获取的位置是否合法assert(pos < _size);return _str[pos];}char& operator[](size_t pos) {assert(pos < _size);return _str[pos];}

        对于以上函数,我们仍然重载了两个,一个普通版,一个被 const 修饰的版本。

4. modify —— 修改串内容

        接下来的内容轮到了 string 的重头戏,也就是对 string 中的内容进行修改,具体将要掌握的函数如下:

        在讲解这些函数之前,我们先介绍一个常数 npos = -1,对于该值,若我们将其赋值给一个无符号整数,结果类型转化,该无符号整数将会变为最大的整型数。

        我们先介绍 push_back 函数,其功能为:在串的后面加上压入一个字符元素,实现起来也相对简单,唯一需要注意的是,我们需要判断是否需要扩容,实现如下:

		void push_back(char c) {// 需要先判断if (_size == _capacity)reserve(_capacity == 0 ? 4 : 2 * _capacity);_str[_size++] = c;_str[_size] = '\0';}

        接下来介绍 append 函数,append 函数有很多重载函数,其中我们最常用为在后面追加字符串,其中需要注意仍为:判断函数是否需要扩容。具体实现如下:

		// 追加字符串void append(const char* str) {// 判断是否需要扩容int len = strlen(str);if (len > _capacity - _size)reserve(len + _size + 1);// 使用strcpy 库函数进行拷贝strcpy(_str + _size, str);_size += len;}

        接着是我们 swap 函数,对于 swap 函数而言,我们不需要太多的拷贝,我们只需要将指针切换可以了,将 _str 的指针与目标串的指针交换,然后交换 _size 和 _capacity 的值,代码如下:

		void swap(string& str) {// 使用 std 中的 swap,若不指定,就会选择就近的 swapstd::swap(str._str, _str);std::swap(str._size, _size);std::swap(str._capacity, _capacity);}

        接着是我们运算符重载函数 operator+= ,对于这个函数而言,也是一个常用的函数,常用的重载有在后面 += 一个字符或者一个串,我们只需要在函数中套用 push_back 与 append 函数即可,如下:

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

        然后介绍一个相对较难实现的函数:insert 函数,该函数的功能为在指定位置插入一个串或者一个一个字符,通常的使用中,我们并不建议使用该函数,因为需要移动串中的元素,效率较为低下,实现的代码如下:

		string& insert(size_t pos, char c) {assert(pos <= _size);// 开始循环后移,判断是否需要扩容if (_size == _capacity)reserve(2 * _capacity);int end = _size;// 需要强制类型转换,防止出现类型转化,进入无限循环while (end >= (int)pos) {_str[end + 1] = _str[end];end--;}_str[pos] = c;_size++;return *this;}string& insert(size_t pos, const char* str) {assert(pos <= _size);size_t len = strlen(str);if (len > _capacity - _size)reserve(_size + len + 1);// 进行循环拷贝int end = len + _size;while (end >= (int)pos + len) {_str[end] = _str[end - len];end--;}strncpy(_str + pos, str, len);//for (int i = 0; i < len; i++)//	_str[i + pos] = str[i];return *this;}

        最后就是我们 erase 函数,该函数的功能为删除从 pos 位置开始的 len 个元素,同时还是一个全缺省函数,若不指定 pos 和 len,那么将会删除所有的元素,我们在删除的时候,也需要判断从 pos 位置开始的 len 个元素是否会大于等于 _size ,若小于,则需要移动覆盖元素,实现如下:

		string& erase(size_t pos = 0, size_t len = npos) {assert(pos < _size);if (pos + len >= _size) {_str[pos] = '\0';return *this;}// 往前覆盖int begin = pos + len;while (begin <= _size) {_str[begin - len] = _str[begin];begin++;}return *this;}

5. string operations —— string 的操作

        接下来将要讲解的是 string 中的一些其他操作,如下:

        其中红框框住的这些是我们需要模拟实现的,至于其他的函数,要求掌握的程度不高,需要使用的时候,查看文档即可。

        首先要介绍的是 c_str 函数,对于这个函数而言,其作用为在 string 中得到一个像在 C语言中的一个字符串,设计这个函数的目的之一就是为了兼容 C 语言。对于该函数的实现还是较为的简单,只需要返回 string 中字符串的首地址即可,对于函数的修饰,直接使用 const 修饰,因为传过去的值不能被修改,如下:

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

        接下来将要实现的函数为 findrfind 函数,这两个函数都作用为在 string 中查找从 pos 位置查找字符或者查找字符串,返回查找到的第一个位置,find 和 rfind 的区别为 find 为从前面开始查找, rfind 为从后面开始查找,这两个函数仅仅为查找,所以不需要修改 string 的值,需要使用 const 修饰。实现原理如下:

		size_t find(const char* str, size_t pos = 0) const {// 普通写法//if (pos >= _size)//	return npos;//size_t len = strlen(str);////for (int i = pos; i < _size; i++) {//	int j = 0;//	for (; j < len; j++) {//		if (_str[i + j] != str[j])//			break;//	}//	if (j == len)//		return i;//}if (pos >= _size)return npos;// 使用 strstr 函数查找子串,返回子串的位置const char* p = strstr(_str + pos, str);if (p)return p - _str;return npos;}size_t find(char c, size_t pos = 0) const {// 若找不到,返回 nposif (pos >= _size)return npos;for (int i = pos; i < _size; i++) {if (_str[i] == c)return i;}return npos;}size_t rfind(const char* str, size_t pos = npos) const {int len = strlen(str);if (pos >= _size) {// 当 pos 大于 _size 直接从 _size - len 位置开始找for (int i = _size - len; i >= 0; i--) {int j = 0;for (; j < len; j++) {if (str[j] != _str[i + j])break;}if (j == len)return i;}}else {for (int i = pos - len; i >= 0; i--) {int j = 0;for (; j < len; j++) {if (str[j] != _str[i + j])break;}if (j == len)return i;}}return npos;}size_t rfind(char c, size_t pos = npos) const {// 若 pos == npos 从最后开始找if (pos >= _size) {for (int i = _size - 1; i >= 0; i--) {if (_str[i] == c)return i;}}else {for (int i = pos; i >= 0; i--) {if (_str[i] == c)return i;}}return npos;}

        最后需要实现的函数为 substr ,拷贝子串,从一个 string 中拷贝这个 string 的一个子串过去,实现如下:

		string substr(size_t pos = 0, size_t len = npos) const {// 只需要函数拷贝过去就可以了,需要判断获取子串的位置是否合法,若等于_size,那么就传递一个空串过去assert(pos <= _size);// 直接传一个串过去,让在返回值是进行构造,if (len > _size - pos) return _str + pos;string tmp;strncpy(tmp._str, _str + pos, len);// 还需要在后面加上 /0tmp._size = len;tmp += '\0';return tmp;}

 6. 默认成员函数

        接下来将会讲解 string 中的默认成员函数,其中包括构造函数,拷贝构造函数,赋值运算符重载,析构函数,如下:

        首先我们先讲解构造函数析构函数,对于构造函数而言,我们只需要将其设置为一个全缺省函数即可,当在初始化不传值的时候,默认初始值为一个空串,然后我们进行开辟空间,赋值即可。析构函数则将空间释放即可,实现如下:

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

        接下来为拷贝构造函数,对于拷贝构造函数而言,就是将 string 进行拷贝一份即可,所以我们只需要对 _str 按照 string.capacity() 进行开辟空间然后将传过来的 string._str 拷贝过去即可,然后给容量和size赋值。

        以上这种写法为常规写法,另一种写法为:在拷贝构造函数中创建一个临时 string 变量,然后使用传过来的 string._str,对其进行初始化,也就是使用构造函数,然后调用 swap 函数交换 tmp 和 *this 的值。(不够这种写法需要将成员进行初始化,将 _str 初始化为 nullptr,因为交换之后,会对 tmp 调用析构函数,释放空间时只需要释放空指针处的空间,这样才不会报错)         

        代码如下:

		//string(const string& s) {//	_str = new char[s.capacity() + 1];//	strcpy(_str, s.c_str());//	_size = s.size();//	_capacity = s.capacity();//}string(const string& s) {// 调用构造函数string tmp(s._str);swap(tmp);// 出了函数之后,会将tmp进行析构,防止释放野生空间 ---> 成员初始化nullptr}

        最后为赋值运算符重载,赋值运算符重载的思路基本和拷贝构造函数一致,创建出空间,然后拷贝,最后将 *this 返回即可。当然这也是普通写法。

        另一种写法如下,在注释中解释:

		//string& operator=(const string& s) {//	char* tmp = new char[s.capacity() + 1];//	strcpy(tmp, s.c_str());//	delete[] _str;//	_str = tmp;//	_capacity = s.capacity();//	_size = s.size();//	//	return *this;//}// 对传过来的串进行调用构造函数,将值构造给s,然后交换s和*this的值// 析构的时候自然会将原来*this的值给析构掉。string& operator=(string s) {swap(s);return *this;}string& operator=(const string& s) {// 调用构造函数string tmp(s._str);// 然后交换tmp和*this的值swap(tmp);return *this;}

7. 流提取和其他运算符重载

        最后需要介绍的知识点便是流提取和其他运算符的重载,流提取是为了方便的使用string 串,因为流提取的特殊性,我们需要将流提取定义在类外,另外在介绍一个 getline 函数,获取一行的信息放入到 string 中,这个函数定义在类中,实现如下:

	ostream& operator<<(ostream& _cout, const MyString::string& s) {//_cout << s.c_str();// 使用范围for进行遍历打印for (auto ch : s)_cout << ch;return _cout;}istream& operator>>(istream& _cin, MyString::string& s) {//_cin >> s._str;// 以下这样写不需要访问private成员,可以不用友元s.clear();char buff[128];char ch = _cin.get();int i = 0;while (ch != ' ' && ch != '\n') {//s += ch;//ch = _cin.get();buff[i++] = ch;if (i == 127) {buff[i] = '\0';s += buff;i = 0;}ch = _cin.get();}if (i > 0) {buff[i] = '\0';s += buff;}return _cin;}istream& getline(istream& _cin, MyString::string& s) {s.clear();char ch = _cin.get();int i = 0;char buff[128];while (ch != '\n') {//s += ch;//ch = _cin.get();buff[i++] = ch;if (i == 127) {buff[i] = '\0';s += buff;i = 0;}ch = _cin.get();}if (i > 0) {buff[i] = '\0';s += buff;}return _cin;}

        如上的流提取我们不需要将其在类中进行友元的声明,因为并没有使用到被 private 修饰的成员变量。对于 >> 流提取而言,我们设置一个 buff 的原因是,将读取进来的字符存入 buff 中,当 buff 满了,获取读取结束了,便将 buff 中的串放入到 string 中去,这样的好处在于,我们不需要频繁的对 string 进行开辟空间,而且每次开辟的空间大小都刚好合适

        加下来便是其他的运算符重载函数,如下:

		bool operator<(const string& s) {if (strlen(s.begin()) == 0)return false;if (strlen(s.begin()) != 0 && _size == 0)return true;int ret = strcmp(_str, s.begin());return ret < 0;}bool operator<=(const string& s) {// 直接判断相等return *this < s || *this == s;}bool operator>(const string& s) {return !(*this <= s);}bool operator>=(const string& s) {return *this > s || *this == s;}bool operator==(const string& s) {if (strlen(s.begin()) != _size)return false;int ret = strcmp(_str, s.begin());return ret == 0;}bool operator!=(const string& s) {return !(*this == s);}

8. 浅谈本篇中的 string

        对于本篇中的 string 而言,其实现的原理是最简单的一种原理,只是用一个字符指针和两个常量来定义串,而在 vs 和 linux 下的实现就更显复杂。如下:

        vs 下的 string 结构:

        string 总共占28个字节,内部结构为一个联合体,联合体用来定义 string 中字符串的存储空间:

        1. 当字符串长度小于16的时候,使用从内部固定的字符数组来存放;

        2. 当字符串长度大于等于16时,从堆上开辟出空间。

union _Bxty
{ // storage for small buffer or pointer to larger onevalue_type _Buf[_BUF_SIZE];pointer _Ptr;char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx

        28字节的来历:

        大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。16

        其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量。  4 + 4
        最后:还有一个指针做一些其他事情。 ---> 16 + 4 + 4 + 4

        g++ 下的 string 结构

        G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

        1. 总空间大小

        2. 字符串有效长度

        3. 引用计数

struct _Rep_base
{size_type _M_length;size_type _M_capacity;_Atomic_word _M_refcount;
};

9. All Code

namespace MyString {class string {public:static const int npos = -1;typedef char* iterator;typedef const char* const_iterator;const size_t size() const {return _size;}const size_t capacity() const {return _capacity;}// 构造函数string(const char* str = ""):_size(strlen(str)){_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);}	// 拷贝构造函数 的两种写法//string(const string& s) {//	_str = new char[s.capacity() + 1];//	strcpy(_str, s.c_str());//	_size = s.size();//	_capacity = s.capacity();//}string(const string& s) {// 调用构造函数string tmp(s._str);swap(tmp);// 出了函数之后,会将tmp进行析构,防止释放野生空间 ---> 初始化nullptr}// 赋值运算符重载 --> 需要修改//string& operator=(const string& s) {//	char* tmp = new char[s.capacity() + 1];//	strcpy(tmp, s.c_str());//	delete[] _str;//	_str = tmp;//	_capacity = s.capacity();//	_size = s.size();//	//	return *this;//}// 对传过来的串进行调用构造函数,将值构造给s,然后交换s和*this的值// 析构的时候自然会将原来*this的值给析构掉。string& operator=(string s) {swap(s);return *this;}string& operator=(const string& s) {// 调用构造函数string tmp(s._str);// 然后交换tmp和*this的值swap(tmp);return *this;}// 析构函数~string() {delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}// c_str const char* c_str() const {return _str;}// 迭代器iterator begin() {return _str;}const_iterator begin() const {return _str;}iterator end() {return _str + _size;}const_iterator end() const {return _str + _size;}void resize(size_t n, char c = '\0') {if (n <= _size) {_str[n] = '\0';_size = n;}else {reserve(n);for (int i = _size; i < n; i++)_str[i] = c;_size = n;_str[_size] = '\0';}}// 获取第 pos 个位置的元素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) {// 仅仅 n 大于 capacity 的时候,我们才进行扩容if (n > _capacity) {char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}// 追加字符串void append(const char* str) {// 判断是否需要扩容int len = strlen(str);if (len > _capacity - _size) reserve(len + _size + 1);strcpy(_str + _size, str);_size += len;}string& operator+=(const char* str) {append(str);return *this;}string& operator+=(char c) {push_back(c);return *this;}void push_back(char c) {// 需要先判断if (_size == _capacity)reserve(_capacity == 0 ? 4 : 2 * _capacity);_str[_size++] = c;_str[_size] = '\0';}bool empty() const {return _size == 0;}void clear() {_str[0] = '\0';_size = 0;}//void swap(string& str) {//	//	// 先进行扩容//	if (_capacity > str.capacity())//		str.reserve(_capacity);//	else//		reserve(str.capacity());//	// 然后进行交换,还要交换size//	size_t max = _capacity > str.capacity() ? _capacity : str.capacity();//	char* tmp = new char[max + 1];//	strcpy(tmp, str.begin());//	str.clear();//	for (int i = 0; i <= _size; i++)//		str += _str[i];//	strcpy(_str, tmp);//	_size = str.size();//	// 修改str的size//}void swap(string& str) {// 使用 std 中的 swap,若不指定,就会选择就近的 swapstd::swap(str._str, _str);std::swap(str._size, _size);std::swap(str._capacity, _capacity);}// 查找函数size_t find(char c, size_t pos = 0) const {// 若找不到,返回 nposif (pos >= _size)return npos;for (int i = pos; i < _size; i++) {if (_str[i] == c)return i;}return npos;}size_t find(const char* str, size_t pos = 0) const {// 普通写法//if (pos >= _size)//	return npos;//size_t len = strlen(str);////for (int i = pos; i < _size; i++) {//	int j = 0;//	for (; j < len; j++) {//		if (_str[i + j] != str[j])//			break;//	}//	if (j == len)//		return i;//}if (pos >= _size)return npos;// 使用 strstr 函数查找子串,返回子串的位置const char* p = strstr(_str + pos, str);if (p)return p - _str;return npos;}// 从后面开始找,没有找到仍然返回npossize_t rfind(char c, size_t pos = npos) const {// 若 pos == npos 从最后开始找if (pos >= _size) {for (int i = _size - 1; i >= 0; i--) {if (_str[i] == c)return i;}}else {for (int i = pos; i >= 0; i--) {if (_str[i] == c)return i;}}return npos;}size_t rfind(const char* str, size_t pos = npos) const{int len = strlen(str);if (pos >= _size) {for (int i = _size - len; i >= 0; i--) {int j = 0;for (; j < len; j++) {if (str[j] != _str[i + j])break;}if (j == len)return i;}}else {for (int i = pos - len; i >= 0; i--) {int j = 0;for (; j < len; j++) {if (str[j] != _str[i + j])break;}if (j == len)return i;}}return npos;}string& insert(size_t pos, char c) {assert(pos <= _size);// 开始循环后移,判断是否需要扩容if (_size == _capacity)reserve(2 * _capacity);int end = _size;// 需要强制类型转换while (end >= (int)pos) {_str[end + 1] = _str[end];end--;}_str[pos] = c;_size++;return *this;}string& insert(size_t pos, const char* str) {assert(pos <= _size);size_t len = strlen(str);if (len > _capacity - _size)reserve(_size + len + 1);// 进行循环拷贝int end = len + _size;while (end >= (int)pos + len) {_str[end] = _str[end - len];end--;}strncpy(_str + pos, str, len);//for (int i = 0; i < len; i++)//	_str[i + pos] = str[i];return *this;}string& erase(size_t pos = 0, size_t len = npos) {assert(pos < _size);if (pos + len >= _size) {_str[pos] = '\0';return *this;}// 往前覆盖int begin = pos + len;while (begin <= _size) {_str[begin - len] = _str[begin];begin++;}return *this;}friend ostream& operator<<(ostream& _cout, const MyString::string& s);friend istream& operator>>(istream& _cin, MyString::string& s);// 子串string substr(size_t pos = 0, size_t len = npos) const {string ret;if (len >= _size - pos) {for (int i = pos; i < _size; i++)ret += _str[i];}else {for (int i = pos; i < len + pos; i++)ret += _str[i];}return ret;}//relational operatorsbool operator<(const string& s) {if (strlen(s.begin()) == 0)return false;if (strlen(s.begin()) != 0 && _size == 0)return true;int ret = strcmp(_str, s.begin());return ret < 0;}bool operator<=(const string& s) {// 直接判断相等return *this < s || *this == s;}bool operator>(const string& s) {return !(*this <= s);}bool operator>=(const string& s) {return *this > s || *this == s;}bool operator==(const string& s) {if (strlen(s.begin()) != _size)return false;int ret = strcmp(_str, s.begin());return ret == 0;}bool operator!=(const string& s) {return !(*this == s);}istream& getline(istream& _cin, MyString::string& s) {s.clear();char ch = _cin.get();int i = 0;char buff[128];while (ch != '\n') {//s += ch;//ch = _cin.get();buff[i++] = ch;if (i == 127) {buff[i] = '\0';s += buff;i = 0;}ch = _cin.get();}if (i > 0) {buff[i] = '\0';s += buff;}return _cin;}private:char* _str = nullptr;size_t _capacity = 0;size_t _size = 0;};ostream& operator<<(ostream& _cout, const MyString::string& s) {//_cout << s.c_str();// 使用范围for进行遍历打印for (auto ch : s)_cout << ch;return _cout;}istream& operator>>(istream& _cin, MyString::string& s) {//_cin >> s._str;// 以下这样写不需要访问private成员,可以不用友元s.clear();char buff[128];char ch = _cin.get();int i = 0;while (ch != ' ' && ch != '\n') {//s += ch;//ch = _cin.get();buff[i++] = ch;if (i == 127) {buff[i] = '\0';s += buff;i = 0;}ch = _cin.get();}if (i > 0) {buff[i] = '\0';s += buff;}return _cin;}}

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

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

相关文章

【御控物联】 Java JSON结构转换(2):对象To对象——属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、案例之《JSON对象 To JSON对象》三、代码实现四、在线转换工具五、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0c;生成新的JS…

抽奖系统设计

如何设计一个百万级用户的抽奖系统&#xff1f; - 掘金 如何设计百万人抽奖系统…… 在实现抽奖逻辑时&#xff0c;Redis 提供了多种数据结构&#xff0c;选择哪种数据结构取决于具体的抽奖规则和需求。以下是一些常见场景下推荐使用的Redis数据结构&#xff1a; 无序且唯一奖…

RabbitMQ消息模型之Direct消息模型

Direct消息模型 * 路由模型&#xff1a; * 一个交换机可以绑定多个队列 * 生产者给交换机发送消息时&#xff0c;需要指定消息的路由键 * 消费者绑定队列到交换机时&#xff0c;需要指定所需要消费的信息的路由键 * 交换机会根据消息的路由键将消息转发到对应的队…

LabVIEW和2D激光扫描的受电弓滑板磨耗精确测量

LabVIEW和2D激光扫描的受电弓滑板磨耗精确测量 在电气化铁路运输中&#xff0c;受电弓滑板的健康状况对于保障列车安全行驶至关重要。受电弓滑板作为连接电网与列车的直接介质&#xff0c;其磨损情况直接影响到电能的有效传输及列车的稳定运行。精确、快速测量受电弓滑板磨损情…

数据接口测试工具 Postman 介绍!

此文介绍好用的数据接口测试工具 Postman&#xff0c;能帮助您方便、快速、统一地管理项目中使用以及测试的数据接口。 1. Postman 简介 Postman 一款非常流行的 API 调试工具。其实&#xff0c;开发人员用的更多。因为测试人员做接口测试会有更多选择&#xff0c;例如 Jmeter…

算法:双指针

算法&#xff1a;双指针 双指针快慢指针对撞指针总结 双指针 LeetCode 283.移动零 以上题目要求我们把所有0移动到数组的末尾&#xff0c;也就是说&#xff0c;我们要把数组转化为以下状态&#xff1a; [ 非0区域 ] [ 0区域 ] 像这种把一个数组划分为多个区域的题型&#xff0…

顺序表(C语言版)

前言&#xff1a;本篇文章我们来详细的讲解一下顺序的有关知识&#xff0c;这篇文章中博主会以C语言的方式实现顺序表。以及用顺序表去实现通讯录的代码操作。 目录 一.顺序表的概念 二.顺序表的分类 1.静态顺序表 三.动态顺序表的实现 1.顺序表的初始化 2.顺序表的尾插…

【Entity Framework】聊一聊EF中继承关系

【Entity Framework】聊一聊EF中继承关系 文章目录 【Entity Framework】聊一聊EF中继承关系一、概述二、实体类型层次结构映射三、每个层次结构一张表和鉴别器配置四、共享列五、每个类型一张表配置六、每个具体类型一张表配置七、TPC数据库架构八、总结 一、概述 Entity Fra…

【linux】set ff=unix、linux设置文件格式

文章目录 一、文件格式二、如何查看文件格式三、设置文件格式、set ffunix四、查看unix与dos的区别 一、文件格式 当我们打开sh脚本时发现有时候格式是unix(LF) ,有时候是windows(CR LF) 。如下图&#xff1a; 文件格式影响了文件中的换行符 linux中sh类型的文件一般要设置为…

【网络编程】Linux网络内核结构以及分布剖析

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的网络编程系列之Linux网络内核结构以及分布剖析&#xff0c;在这篇文章中&#xff0c;你将会学习到在Linux内核中如何实现网络数据的输入和输出的&#xff0c;并且我会给出源码进行剖析&#xff0c;以及手绘UML图来帮助…

Office 365卡顿怎么办?SD-WAN可以解决

随着数字化浪潮的推进&#xff0c;Office 365等云办公应用已成为企业日常运营不可或缺的工具。然而&#xff0c;许多企业在使用Office 365时遭遇了网络卡顿的难题&#xff0c;给工作人员带来诸多不便。随着SD-WAN技术的成熟和普及&#xff0c;这一难题得到了有效的解决。 Offic…

机器人方向控制中应用的磁阻角度传感芯片

磁阻传感器提供的输出信号几乎不受磁场变动、磁温度系数、磁传感器距离与位置变动影响&#xff0c;可以达到高准确度与高效能&#xff0c;因此相当适合各种要求严格的车用电子与工业控制的应用。所以它远比采用其它传感方法的器件更具有优势。 机器人的应用日渐广泛&#xff0…

鸿蒙开发学习笔记第一篇--TypeScript基础语法

目录 前言 一、ArkTS 二、基础语法 1.基础类型 1.布尔值 2.数字 3.字符串 4.数组 5.元组 6.枚举 7.unkown 8.void 9.null和undefined 10.联合类型 2.条件语句 1.if语句 1.最简单的if语句 2.if...else语句 3.if...else if....else 语句 2.switch语句 5.函数…

OpenResty,Nginx实现接口验签与黑名单控制

介绍 nginx与openresty是两种优秀知名的7层负载均衡软件&#xff0c;nginx以其出色的性能和稳定性成为首选&#xff0c;而openresty则是在Nginx基础上构建的&#xff0c;支持嵌入Lua语言&#xff0c;大幅提升了开发效率。 安装OpenResty 版本 openresty-1.25.3.1-win64下载地…

机器学习和深度学习--李宏毅(笔记与个人理解)Day11-12

Day11 when gradient is small…… 怎么知道是局部小 还是鞍点&#xff1f; using Math 这里巧妙的说明了hessan矩阵可以决定一个二次函数的凹凸性 也就是 θ \theta θ 是min 还是max&#xff0c;最后那个有些有些 哈 是一个saddle&#xff1b; 然后这里只要看hessan矩阵是不…

STM32学习和实践笔记(5):时钟树

STM32一共有4个时钟源。外部时钟高低速各一个&#xff0c;内部时钟高低速各一个。 外部高速时钟是&#xff1a;4-16MHZ的HSE OSC。HS表示高速high speed. E表示外部的external。开发板该处安装的8M晶振。 外部低速时钟是&#xff1a;32.768KHz的LSI OSC。LS表示高速low speed…

Go——面向对象

一. 匿名字段 go支持只提供类型而不写字段名的方式&#xff0c;也就是匿名字段&#xff0c;也称为嵌入字段。 同名字段的情况 所以自定义类型和内置类型都可以作为匿名字段使用 指针类型匿名字段 二.接口 接口定义了一个对象的行为规范&#xff0c;但是定义规范不实现&#xff…

强大的压缩和解压缩工具 Keka for Mac

Keka for Mac是一款功能强大的压缩和解压缩工具&#xff0c;专为Mac用户设计。它支持多种压缩格式&#xff0c;包括7z、Zip、Tar、Gzip和Bzip2等&#xff0c;无论是发送电子邮件、备份文件还是节省磁盘空间&#xff0c;Keka都能轻松满足用户需求。 这款软件的操作简单直观&…

用c++实现串匹配问题、选择排序

5.2.2 串匹配问题 【问题】 给定两个字符串S和T&#xff0c;在主串S中查找子串T的过程称为串匹配(string matching,也称模式匹配&#xff09;&#xff0c;T称为模式。在文本处理系统、操作系统、编译系统、数据库系统以及 Internet 信息检索系统中&#xff0c;串匹配是使用最频…

分布式系统:缓存与数据库一致性问题

前言 缓存设计是应用系统设计中重要的一环&#xff0c;是通过空间换取时间的一种策略&#xff0c;达到高性能访问数据的目的&#xff1b;但是缓存的数据并不是时刻存在内存中&#xff0c;当数据发生变化时&#xff0c;如何与数据库中的数据保持一致&#xff0c;以满足业务系统…