【C++】string模拟实现
各位读者老爷好,俺最近在学习string的一些知识。为了更好的了解string的结构,俺模拟实现了一个丐版string,有兴趣的老爷不妨垂阅!!!
目录
1.string类的定义
2.模拟实现成员函数接口
2.1.constructor(构造函数)
2.2.destructor(析构函数)
2.3.c_str
2.4.size
2.5.operator[]
2.6.begin、end
2.7.capacity
2.8.reserve
2.9.push_back
2.10.append
2.11.operator+=
2.12.insert
2.13.erase
2.14.find
2.15.substr
2.16.constructor
2.17.operator=
2.18.clear
2.19.swap
3.模拟实现非成员函数接口
3.1.relational operators
3.2.operator<<
3.3.operator>>
3.4.getline
4.个别接口的不同写法
4.1.constructor的不同写法
4.2.operator=的不同写法
4.3.operator>>的不同写法
5. string模拟实现完整代码
5.1.string.h
5.2.string.cpp
5.3.test.cpp
俺在上篇博客介绍过,string是一个由类模板实例化而来的类,是用来管理字符串的。其底层实现大致就是字符顺序表。
那么俺模拟实现string,是不是要从类模板开始搞起呢?当然不是,俺可没有这个本事。俺直接定义string类就好。并且string的接口众多,俺只模拟实现部分常用接口噢噢!!
1.string类的定义
namespace HD
{class string{char* _str = nullptr;size_t _capacity = 0;size_t _size = 0;public:typedef char* iterator;typedef const char* const_iterator;static const size_t npos;//string()// :_str(new char[1]{'\0'})// ,_capacity(0)// ,_size(0)//{//}//string(const char* s)//{// _size = strlen(s);// _capacity = _size;// _str = new char[_capacity+1];// strcpy(_str, s);//}string(const char* s = "")//以上两构造合二为一{_size = strlen(s);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, s);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str()const{return _str;}size_t size()const{return _size;}char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos <= _size);return _str[pos];}iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}size_t capacity()const{return _capacity;}string(const string& str)//拷贝构造函数{/写法一//*_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;*//写法二/string tmp(str.c_str());swap(tmp);}void reserve(size_t n = 0);void push_back(char c);string& append(const char* s);string& operator+= (const char* s);string& operator+=(char c);string& insert(size_t pos, size_t n, char c);string& insert(size_t pos, const char* s);string& erase(size_t pos = 0, size_t len = npos);size_t find(char c, size_t pos = 0) const;size_t find(const char* s, size_t pos = 0) const;string substr(size_t pos = 0, size_t len = npos) const;string& operator=(const string& str);//string& operator=(const string str);void clear();void swap(string& str);};
}
对于这个string类的定义如上。
1.俺们知道string类底层是字符顺序表,所以俺定义3个private成员变量_str、_capacity、_size来实现,并且给这3个成员变量缺省值,这3个缺省值是很有意义的,意义的体现会在下面的讲解可见。
2.俺将一些成员函数(接口)的声明和定义全部放到string类当中,则默认当成内联函数处理;还有一些成员函数(接口)的声明和定义分离,只将这些成员函数的声明放到string类当中。
3.为了防止与命名空间std的string(也就是STL的string)产生冲突,俺实现的string使用自己命名空间(如HD)封装起来。
PS:以下所说的string若没有特殊声明,都默认指俺模拟实现的string。若指STL的string,俺会用红色字体标注string,如string。
2.模拟实现成员函数接口
2.1.constructor(构造函数)
对于这个接口,string内重载了很多个函数。俺暂时就模拟实现两个,如下:
1.string()
这个函数的模拟实现如下,俺将其声明和定义都放到string类中:
string():_str(new char[1]{'\0'}),_capacity(0),_size(0){}
这里俺使用初始化列表来初始化。易知这是一个构造空的string对象的,那么当然将_capacity和_size初始化成0;但是对于_str,俺将其指向堆区动态开辟的一个字节的空间,这块空间存储字符'\0'。
为什么_str不用nullptr来初始化呢?其实大有意义,让子弹再飞一会,原因俺下面介绍。
3.string (const char* s)
这个函数的模拟实现如下,俺将其声明和定义都放到string类中:
string(const char* s){_size = strlen(s);_capacity = _size;_str = new char[_capacity+1];strcpy(_str, s);}
这里俺在函数体内初始化。俺们知道这个构造函数的功能是用C-string来构造string类对象。那么我们开好空间(记得多开一个空间存放'\0'),将C_string(也就是s)拷贝到空间上,_size和_capacity 初始化为C_string的字节数(长度)就好。
2.2.destructor(析构函数)
这个接口就是析构函数,string类中申请了资源,俺们必须显示写析构函数,不然会造成资源的泄露。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:
~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}
若不显示写析构函数,_str指向的空间就泄露了。
2.3.c_str
这个接口功能就是返回C形式的字符串,也就是返回底层的_str。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:
const char* c_str()const{return _str;}
这是一个对this指针指向对象的成员变量只进行读访问的函数,所以我们将其实现成const成员函数,且这样子普通对象和const对象都可调用这个函数。
俺们来使用以下自己模拟实现的接口。事先要先知道的是本工程包含3个文件:
……
string.h:存放string类的定义和非成员函数的声明;
……
string.cpp:存放string类部分成员函数的定义、静态成员变量的定义和非成员函数的定义;
……
test.cpp:存放测试代码,来测试咱们模拟实现的string的功能;
……
俺们到现在介绍了几个接口的模拟实现了,咱们在test.cpp中试试:
#include"string.h" namespace HD {void test1(){string S1;string S2("hello world");cout << S1.c_str() << endl << S2.c_str() << endl;} } int main() {HD::test1();return 0; }
运行结果:
有几个点需要明确:
……
1.为什么test1函数需要用命名空间HD封装起来?
因为若是不用命名空间HD封装起来,string类实例化对象S1、S2会调用string的构造函数接口,且c_str接口的调用亦是调用string的接口,这样子就违背了我们测试模拟实现的接口的初衷。有关命名空间和编译器访问规则的介绍可以看[C++]C++入门1.0。
……
2.主函数中调用test1函数为什么要指定命名空间HD域?
因为若是不指定命名空间HD域,编译器会访问不到test1函数。编译器的访问规则介绍可以看[C++]C++入门1.0。
……
3.string()函数的定义中,_str不用nullptr来初始化呢?
若是用nullptr来初始化_str,当空string对象调用c_str接口时就返回nullptr,当我们对这个返回值进行操作时就有可能访问到空指针,程序就会崩溃。
2.4.size
这个接口这个接口返回字符串的长度。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:
size_t size()const{return _size;}
这是一个对this指针指向对象的成员变量只进行读访问的函数,所以我们将其实现成const成员函数,且这样子普通对象和const对象都可调用这个函数。
在test.cpp中测试一下:
#include"string.h"
namespace HD
{void test2(){string S1;string S2("hello world");cout << S1.size() << endl << S2.size() << endl;}
}
int main()
{HD::test2();return 0;
}
运行结果:
2.5.operator[]
本接口返回对字符串中位置pos处的字符的引用。且string中本接口重载了2个函数分别供普通对象和const对象使用。俺也模拟实现2个函数,并将其声明和定义都放到string类中:
char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos <= _size);return _str[pos];}
在test.cpp中测试一下:
#include"string.h"
namespace HD
{void test3(){string S1("nbo");for (int i = 0; i < S1.size(); i++){S1[i] -= 1;cout << S1[i];}cout << endl;const string S2("hello world");for (int i = 0; i < S2.size(); i++){cout << S2[i];}}
}
int main()
{HD::test3();return 0;
}
对于char& operator[](size_t pos)这个函数,这个函数是一个对this指针指向对象的成员变量只进行读访问的函数,可是我们不能将其实现成const成员函数,为什么呢?
因为俺们实现了const char& operator[](size_t pos)const这个函数供const对象调用,若是还将char& operator[](size_t pos)实现成const成员函数的话,无法与const char& operator[](size_t pos)构成函数重载!
2.6.begin、end
有一个问题:基于范围的for循环的方式,对所有容器都适用。那么到现在为止俺们模拟实现的string适用于基于范围的for循环吗?
俺们在test.cpp试试:
#include"string.h"
namespace HD
{void test4(){string S1("hello world");for (auto ch : S1){cout << ch;}cout << endl;}
}
int main()
{HD::test4();return 0;
}
发现行不通的,错误列表有一堆报错:
到现在为止模拟实现的string不适用基于范围的for循环的原因也很简单,因为范围for的底层很简单,容器遍历实际就是替换为迭代器。咱们模拟实现的string还没有模拟实现迭代器,也没有模拟实现迭代器相关接口,当然不适用了。
那么如何模拟实现迭代器呢?
迭代器的实现很复杂,其实迭代器本质就是模拟指针的行为啊,不管迭代器是用什么方法实现的,都希望迭代器能像指针般使用达到访问容器的目的。
那么针对string的模拟实现来说,不用搞那么复杂,我们正好可以返璞归真,利用string类的底层是字符顺序表这个特点,俺们就可以用指针模拟实现迭代器,因为本身就可以用指针来访问顺序表啊!
正向迭代器的模拟实现如下,对应的迭代器属于对应容器的类域,所以俺将其声明放在string类中:
typedef char* iterator;typedef const char* const_iterator;
俺就不模拟实现反向迭代器了,反向迭代器就不是用指针可以模拟实现的了,复杂的很!
模拟实现了正向迭代器,俺们就可以模拟实现正向迭代器相关的接口了,以下接口的声明和定义都放到string类中:
iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}
这些接口的模拟实现好像不用解释什么,根据接口的作用很容易就能写出来啊。
模拟实现了正向迭代器和正向迭代器相关的接口,俺们在test.cpp中测试一下:
#include"string.h"
namespace HD
{void test4(){string S1("hello world");for (auto ch : S1){cout << ch;}cout << endl;const string S2("God is a girl");string::const_iterator cit = S2.begin();while (cit != S2.end()){cout << *cit;cit++;}cout << endl;}
}
int main()
{HD::test4();return 0;
}
运行结果没问题的,基于范围的for循环也能正常使用了:
2.7.capacity
这个接口这个接口返回字符串的容量。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:
size_t capacity()const{return _capacity;}
这是一个对this指针指向对象的成员变量只进行读访问的函数,所以我们将其实现成const成员函数,且这样子普通对象和const对象都可调用这个函数(这点以下不再赘述)。
在test.cpp中测试一下:
#include"string.h"
namespace HD
{void test5(){string S1;string S2("hello world");cout << S1.capacity() << endl << S2.capacity() << endl;}
}
int main()
{HD::test5();return 0;
}
运行结果:
2.8.reserve
本接口功能是为string预留空间,不改变有效元素个数。俺模拟实现这个接口不搞那么复杂,当扩容才使其生效。模拟实现本成员函数采用声明和定义分离的写法:
string类(再次说明string的定义放在了string.h中)中声明:
void reserve(size_t n = 0);
string.cpp中定义:
#include"string.h"
namespace HD
{void string::reserve(size_t n){if (n > _capacity){char* p = new char[n + 1];strcpy(p, _str);delete[] _str;_str = p;_capacity = n;}}
}
1.缺省参数不能在函数声明和定义中同时出现,当函数声明和定义分离时在声明给缺省参数 。
2.别忘记使用命名空间HD封装起来。
3.该函数声明和定义分离,所以定义成员函数时,成员函数名前需要加string::。
4.该接口模拟实现的思想:当需要扩容时,动态开辟n+1(多开一个空间存储'\0')个字节新空间,在将原来的数据拷贝到新空间,释放旧空间,再让_str指向新空间,更新_capacity即可。
在test.cpp中试试:
#include"string.h"
namespace HD
{void test6(){string S1;string S2("hello world");cout << S1.capacity() << " " << S2.capacity() << endl;S1.reserve(10); S2.reserve(1);cout << S1.capacity() << " " << S2.capacity() << endl;}
}
int main()
{HD::test6();return 0;
}
运行结果符合预期:
2.9.push_back
本接口需要实现将字符c附加到字符串末尾,使其长度增加1。俺的模拟实现采取本成员函数声明和定义分离的写法:
string类(再次说明string的定义放在了string.h中)中声明:
void push_back(char c);
string.cpp中定义:
#include"string.h"
namespace HD
{void string::push_back(char c){if (_capacity == _size)//扩容{reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = c;_str[_size] = '\0';}
}
1.别忘记使用命名空间HD封装起来(这点以下不再赘述)。
2.该函数声明和定义分离,所以定义成员函数时,成员函数名前需要加string::(这点以下不再赘述)。
3.插入字符之前先检查字符串容量是否充足,若是不充足,就调用reserve接口扩容(当字符串旧容量为0时,给4个字节空间;当字符串旧容量不为0时,2倍扩容)。将字符c插入字符串末尾并使字符串长度加一,再在字符串末尾插入'\0'就好了。
在test.cpp中试试:
#include"string.h"
namespace HD
{void test7(){string S1;string S2("hello world");S1.push_back('!');S2.push_back('!');cout << S1.capacity() << " " << S2.capacity() << endl;cout << S1.c_str()<<" " << S2.c_str() << endl;}
}
int main()
{HD::test7();return 0;
}
运行结果:
2.10.append
这个接口string中重载了很多个函数,俺就模拟实现string& append (const char* s)这个函数。这个函数功能就是在字符串后追加字符串s。
俺的模拟实现采取本成员函数声明和定义分离的写法:
string类中声明:
string& append(const char* s);
string.cpp中定义:
#include"string.h"
namespace HD
{string& string::append(const char* s){if (_capacity < strlen(s) + _size)//扩容{size_t n = strlen(s);reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}/写法一///for (int i = 0; i < strlen(s); ++i)//{// _str[_size++] = s[i];//}//_str[_size] = '\0';/写法二/strcpy(_str + _size, s);_size += strlen(s);return *this;}
}
插入字符串s之前先检查字符串容量是否充足,若是不充足,就使用reverse接口扩容(这里的扩容个数不能使用push_back接口的逻辑了,因为有可能4个字节空间或者字符串旧容量的2倍仍然不够字符串s插入的需要)。再将字符串s插入到字符串末尾并且使得字符串长度加strlen(s)并且返回*this就好了。这里插入字符串s到字符串末尾的写法有很多,俺写了两个,但是不管是那种写法都要保证插入之后字符串末尾有'\0'。
在test.cpp中试试:
#include"string.h"
namespace HD
{void test8(){string S1;string S2("hello");S1.append("young man");S2.append(" Totoro!");cout << S1.capacity() << " " << S2.capacity() << endl;cout << S1.c_str()<<" " << S2.c_str() << endl;}
}
int main()
{HD::test8();return 0;
}
运行结果:
2.11.operator+=
这个接口string中重载了3个函数,功能就是通过在字符串的当前值末尾附加其他字符来扩展字符串。俺就模拟实现其中2个函数:string& operator+= (const char* s)和string& operator+= (char c)。其实模拟实现这2个函数分别直接调用俺模拟实现的append和push_back,然后返回*this就好了。
俺的模拟实现采取本成员函数声明和定义分离的写法:
string类中声明:
string& operator+= (const char* s);string& operator+=(char c);
string.cpp中定义:
#include"string.h"
namespace HD
{string& string::operator+= (const char* s){append(s);return *this;}string& string::operator+=(char c){push_back(c);return *this;}
}
在test.cpp中试试:
#include"string.h"
namespace HD
{void test9(){string S1;string S2("hello");S1 += '!'; S2 += " Totoro";cout << S1.c_str()<<" " << S2.c_str() << endl;}
}
int main()
{HD::test9();return 0;
}
运行结果:
2.12.insert
这个接口string中重载了7个函数,都是在字符串中pos(或p)指示的字符之前插入其他字符。俺就模拟实现其中两个:string& insert(size_t pos, size_t n, char c)和string& insert(size_t pos, const char* s)。
俺的模拟实现采取本成员函数声明和定义分离的写法:
string类中声明:
string& insert(size_t pos, size_t n, char c);string& insert(size_t pos, const char* s);
string.cpp中定义:
#include"string.h"
namespace HD
{string& string::insert(size_t pos, size_t n, char c){assert(pos <= _size);if (_capacity < n + _size)//扩容{reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}size_t end = _size + n;while (end > pos + n - 1){_str[end] = _str[end - n];end--;}for (size_t i = 0; i < n; i++){_str[pos + i] = c;}_size += n;return *this;}string& string::insert(size_t pos, const char* s){assert(pos <= _size);size_t n = strlen(s);if (_capacity < n + _size){reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}size_t end = _size + n;while (end > pos + n - 1){_str[end] = _str[end - n];end--;}strncpy(_str + pos, s, n);_size += n;return *this;}
}
这2个函数的模拟实现逻辑都不难。无非就是处理好字符串容量使之足够插入的需要。然后把从字符位置为pos的字符到字符'\0'在内的所有字符往后挪动指定个数个字节位置(指定个数就是所插入字符的个数或者字符串长度),再插入指定个数的字符或者字符串,最后返回*this就好了。
在test.cpp中试试:
#include"string.h"
namespace HD
{void test10(){string S1("A man");string S2("hello");S1.insert(2, "young");S2.insert(S2.size(), 5, 'A');cout << S1.c_str()<<" " << S2.c_str() << endl;}
}
int main()
{HD::test10();return 0;
}
运行结果:
2.13.erase
string在这个接口重载了3个函数。作用都是擦除字符串的一部分,缩短其长度。这里俺只模拟实现1个函数:string& erase(size_t pos = 0, size_t len = npos)。
俺的模拟实现采取本成员函数声明和定义分离的写法:
string类中声明:
string& erase(size_t pos = 0, size_t len = npos);
string.cpp中定义:
#include"string.h"
namespace HD
{string& string::erase(size_t pos, size_t len){assert(pos < _size);if (_size - pos <= len){_str[pos] = '\0';_size -= (_size - pos);}size_t begin = pos + len;while (begin < _size + 1){_str[begin - len] = _str[begin];begin++;}_size -= len;return *this;}
}
1.这里用到了缺省参数npos,所以我们必须处理npos:
npos在string是string类的公共静态成员变量,那么俺模拟实现也在string类中声明为公共静态成员变量:
static const size_t npos;
根据static成员的知识:静态成员变量必须在类外定义,定义时不添加static关键字。俺在string.cpp中定义:
#include"string.h"
namespace HD
{const size_t string::npos = -1;
}
2.缺省参数不能在函数声明和定义中同时出现,当函数声明和定义分离时在声明给缺省参数(以下不再赘述)。
3.这个接口的模拟实现思路:若是len>=_size-pos,说明要擦除从pos位置开始的全部字符,那么处理好'\0',并使得字符串长度减去擦除字符个数就好了;否则,将需要擦除的字符(串)后面剩余的字符(串)往前挪动len个字节位置,并且字符串长度减去len即可。最后返回*this。
在test.cpp中试试:
#include"string.h"
namespace HD
{void test11(){string S1("A man");string S2("hello fat Totoro");S1.erase();S2.erase(5, 4);cout << S1.c_str() << endl << S2.c_str() << endl;}
}
int main()
{HD::test11();return 0;
}
运行结果:
2.14.find
string中这个接口重载了四个函数。功能就是在字符串中搜索其参数指定的序列的第一个匹配项,找到了返回第一个匹配项的字符位置,找不到返回string::npos。俺就模拟实现其中2个函数:size_t find(char c, size_t pos = 0) const和size_t find(const char* s, size_t pos = 0) const。
俺的模拟实现采取本成员函数声明和定义分离的写法:
string类中声明:
size_t find(char c, size_t pos = 0) const;size_t find(const char* s, size_t pos = 0) const;
string.cpp中定义:
#include"string.h"
namespace HD
{size_t string::find(char c, size_t pos) const{for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}size_t string::find(const char* s, size_t pos) const{if (const char* p = strstr(_str + pos, s)){return p - _str;}return npos;}
}
这个模拟实现逻辑很简单,不解释了。其中size_t find(const char* s, size_t pos = 0) const这个函数俺直接套用了函数strstr,对于这个函数有兴趣可以看【C语言】字符函数和字符串函数的介绍。
在test.cpp中试试:
#include"string.h"
namespace HD
{void test12(){string S1("A man");string S2("hello fat Totoro");cout << S1.find('m') << endl;cout << S2.find("fat") << endl;}
}
int main()
{HD::test12();return 0;
}
运行结果:
2.15.substr
功能:在str中从pos位置开始,截取len个字符,将这些字符构造一个新的字符串对象返回。
俺的模拟实现采取本成员函数声明和定义分离的写法:
string类中声明:
string substr(size_t pos = 0, size_t len = npos) const;
string.cpp中定义:
#include"string.h"
namespace HD
{string string::substr(size_t pos, size_t len) const{assert(pos <= _size);if (len >= _size - pos){len = _size - pos;}string S1;S1.reserve(len);for (int i = 0; i < len; i++){S1 += _str[pos + i];}return S1;}
}
这个模拟实现思路:主要就是构造一个局部空字符串S1,将需要截取的字符一个个摘取到S1当中,返回S1就好了。
俺们在test.cpp中试试:
#include"string.h"
namespace HD
{void test13(){string S1("hello fat Totoro");string S2 = S1.substr(6, 3);cout << S2.c_str() << endl;}
}
int main()
{HD::test13();return 0;
}
运行结果:
其实模拟实现到此为止,这个代码不是在任何环境都能运行成功的!
因为substr接口是传值返回,理论上是将substr的返回值S1拷贝到一个临时对象当中,局部对象S1便销毁了,再用临时对象拷贝构造S2的。C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,但是俺还没有模拟实现拷贝构造呢,若未显式定义,编译器会生成默认的拷贝构造函数,默认的拷贝构造函数完成的是浅拷贝噢!
结论就是若没有显示实现拷贝构造函数,substr的调用理论上是会出问题的,因为浅拷贝导致S2底层_str指向一块已经销毁的空间(substr的局部对象底层的_str)。
那么在俺的环境(VS2022)中为什么能运行成功呢?
因为俺这个VS2022优化了,但是不是任何编译器都会进行优化,所以为了保证substr的正常调用,必须显示实现拷贝构造函数。
2.16.constructor
模拟实现其中的拷贝构造函数。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:
string(const string& str)//拷贝构造函数{_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;}
这个模拟实现思路很简单,不解释了。一定记住,类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,会出问题。
2.17.operator=
string中重载了3个函数。俺就模拟实现了1个函数:string& operator=(const string& str)。
注意模拟实现的时候需要完成深拷贝就好,思路跟拷贝构造函数大同小异了。若是实现浅拷贝,问题就很大,俺画图方便理解:
俺的模拟实现采取本成员函数声明和定义分离的写法:
string类中声明:
string& operator=(const string& str);
string.cpp中定义:
#include"string.h"
namespace HD
{string& string::operator=(const string& str){if (this==&str){return *this;}delete[]_str;_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str.size();_capacity = str.capacity();return *this;}
}
这种写法要注意的是,防止自己给自己赋值噢,若是自己给自己赋值没有检查的话,一上来就把_str给delete掉了,那就没得玩了!
2.18.clear
这个接口擦除字符串的内容,该字符串将变为空字符串(长度(size接口的返回值)为0个字符),一般不会清理字符串容量。
俺的模拟实现采取本成员函数声明和定义分离的写法:
string类中声明:
void clear();
string.cpp中定义:
#include"string.h"
namespace HD
{void string::clear(){_str[0] = '\0';_size = 0;}
}
这个接口的模拟实现在test.cpp中试试:
#include"string.h"
namespace HD
{void test14(){string S1("hello fat Totoro");cout << S1.capacity() << endl;S1.clear();cout << S1.c_str() << " " << S1.capacity() << endl;}
}
int main()
{HD::test14();return 0;
}
运行结果:
2.19.swap
这个接口的功能就是与str交换内容,就是两个字符串底层的内容。
俺的模拟实现采取本成员函数声明和定义分离的写法:
string类中声明:
void swap(string& str);
string.cpp中定义:
#include"string.h"
namespace HD
{void string::swap(string& str){std::swap(str._str, _str);std::swap(str._size, _size);std::swap(str._capacity, _capacity);}
}
对于这个模拟实现,俺直接调用C++算法库的swap ,调用3次swap分别交换_str、_size、_capacity。
俺们在test.cpp中试试:
#include"string.h"
namespace HD
{void test15(){string S1("hello world");string S2("I do love you,yes I do love you");cout << "交换前S1:" << S1 << " " << S1.size() << " " << S1.capacity() << endl;cout << "交换前S2:" << S2 << " " << S2.size() << " " << S2.capacity() << endl;cout << endl;S1.swap(S2);cout << "交换后S1:" << S1 << " " << S1.size() << " " << S1.capacity() << endl;cout << "交换后S2:" << S2 << " " << S2.size() << " " << S2.capacity() << endl;}
}
int main()
{HD::test15();return 0;
}
运行结果:
C++算法库的swap是一个函数模板,任何两个同类型的变量都可以直接调用以达到交换的目的,俺们可以看看截图:
但是这个函数模板对于交换两个申请了资源的自定义类型变量就不是很友好,虽然可以完成交换,但是消耗比较大,为什么消耗大?
当2个申请了资源的自定义类型类型变量要交换而调用这个函数模板时,这个模板就生成对应自定义类型的函数,我们看这个生成的函数体内:
……
T c(a):用第1个形参a来拷贝构造1个同类型临时对象c,由于本自定义类型申请了资源,那么调用拷贝构造时就完成1次深拷贝!
……
a=b:将第2个形参b赋值给a,调用operator=,涉及资源申请的自定义类型来说,operator=需要实现深拷贝,完成第2次深拷贝!
……
b=c:将临时对象c赋值给b,调用operator=,完成第3次深拷贝!
交换两个申请了资源的自定义类型变量需要完成3次深拷贝,消耗当然大了!
这就是为什么string中为什么成员函数接口实现了1个swap了,非成员函数接口还要实现1个swap的意义:
俺们看string中的成员函数接口swap的底层实现跟俺模拟实现的成员函数接口swap是大同小异的,都是把自定义类型拆成若干个内置类型分别交换的,这样子交换就不会涉及深拷贝,1次深拷贝也没有。
……
string中非成员函数接口swap的行为跟成员函数接口swap是一模一样的,也是把自定义类型拆成若干个内置类型分别交换,也不涉及深拷贝。
……
若是string中没有实现非成员函数接口swap,那么当2个string对象想要交换且没有调用成员函数接口swap时,可以调用swap完成交换,会走函数模板,就需要3次深拷贝。
……
若是string中实现了非成员函数接口swap,那么那么当2个string对象想要交换且没有调用成员函数接口swap时,当去调用swap完成交换时,就会走非成员函数接口swap,而不是走函数模板,因为对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例,这样子不是避免深拷贝了吗。。。
……
所以,string中的非成员函数接口swap和成员函数接口swap都是避免两个string类对象交换时陷入深拷贝的。
3.模拟实现非成员函数接口
3.1.relational operators
string类重载了一堆运算符重载用于比较两个C++字符串的关系或者比较一个C++字符串和一个C字符串的关系。 俺在这里只是模拟实现比较2个C++字符串的关系的函数如下:
俺的模拟实现采取函数声明和定义分离的写法:
在string.h中声明:
namespace HD
{bool operator>(const string& lhs, const string& rhs);bool operator==(const string& lhs, const string& rhs);bool operator<(const string& lhs, const string& rhs);bool operator>=(const string& lhs, const string& rhs);bool operator<=(const string& lhs, const string& rhs);bool operator!=(const string& lhs, const string& rhs);
}
别忘了用命名空间HD封装起来以区分命名空间std内实现的非成员函数(这点以下不再赘述)。
在string.cpp中定义:
#include"string.h"
namespace HD
{bool operator>(const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) > 0;}bool operator==(const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) == 0;}bool operator<(const string& lhs, const string& rhs){return !(lhs >= rhs);}bool operator>=(const string& lhs, const string& rhs){return lhs > rhs && lhs == rhs;}bool operator<=(const string& lhs, const string& rhs){return !(lhs > rhs);}bool operator!=(const string& lhs, const string& rhs){return !(lhs == rhs);}
}
模拟实现直接调用strcmp比较2个C++字符串底层的_str就好了。实现了operator>和operator==,其他的函数实现可以直接复用这2个函数的逻辑。
在test.cpp中试试:
#include"string.h"
namespace HD
{void test16(){string S1("hello world");string S2("I do love you,yes I do love you");cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}
}
int main()
{HD::test16();return 0;
}
运行结果:
俺虽然只是模拟实现了比较2个C++字符串的关系的函数,但是却也可以支持1个C++字符串与1个C字符串的比较。举例如下
#include"string.h"
namespace HD
{void test17(){const char* S1 = "hello world";string S2("I do love you,yes I do love you");cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}
}
int main()
{HD::test17();return 0;
}
运行结果:
为什么可以这样子呢?
因为构造函数不仅可以构造与初始化对象,对于单个参数构造函数,还具有类型转换的作用。具体介绍可以去看【C++】类和对象3.0 。
俺模拟实现了比较2个C++字符串的关系的函数,却不支持2个C字符串的比较噢,因为俺在【C++】类和对象2.0中介绍过重载操作符必须有一个类类型参数,不能通过运算符重载改变内置类型对象的含义。
例如:
#include"string.h"
namespace HD
{void test18(){const char* S1 = "hello world";const char* S2 = "I do love you,yes I do love you";cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}
}
int main()
{HD::test18();return 0;
}
要搞清楚,若是代码写成这样子,不会调用模拟实现的非成员函数接口relational operators的函数 ,也不是2个C字符串的比较,两个C字符串的比较要用函数strcmp。这里本质是比较2个地址呢。
运行结果:
3.2.operator<<
流插入运算符重载模拟实现还是很简单的,将底层_str的字符一个个输出就好了。
俺的模拟实现采取函数声明和定义分离的写法:
在string.h中声明:
namespace HD
{ostream& operator<<(ostream& os, const string& str);
}
在string.cpp中定义:
#include"string.h"
namespace HD
{ostream& operator<<(ostream& os, const string& str){for (size_t i = 0; i < str.size(); i++){os << str.c_str()[i];}return os;}
}
在test.cpp中试试:
#include"string.h"
namespace HD
{void test19(){string S1;string S2("hello world");const string S3("I love Totoro");cout << S1 << endl << S2 << endl << S3 << endl;}
}
int main()
{HD::test19();return 0;
}
运行结果:
3.3.operator>>
流提取运算符重载的实现好像也不难,俺们模拟实现采用声明和定义分离的写法:
在string.h中声明:
namespace HD
{istream& operator>>(istream& is, string& str);
}
在strng.cpp中定义:
#include"string.h"
namespace HD
{istream& operator>>(istream& is, string& str){str.clear();char ch;is >> ch;while (ch == ' ' || ch == '\n')//确保第一次拿到的ch不是空格或者换行{is >> ch;}while (ch != ' ' && ch != '\n'){str += ch;is >> ch;}return is;}
}
这样子写其实是有问题的,俺们可以先在test.cpp中试试:
#include"string.h"
namespace HD
{void test20(){string S1;cin >> S1;cout << S1;}
}
int main()
{HD::test20();return 0;
}
运行结果如下,会陷入死循环:
俺们看模拟实现函数体内的第2个while循环,导致运行结果陷入死循环的就是第2个while循环。原因就是上一篇博客介绍过的:使用C++的标准输入对象(键盘)cin和C语言的函数scanf获取字符串的时候获取不到空格(' ')或换行('\n'),它们均把空格(' ')和换行('\n')作为默认分隔符号,当它们在缓冲区提取到这两个默认分隔符号之一时,就停止在缓冲区拿数据了。也就是说第2个while循环的条件语句永远是真的,那不就陷入死循环了吗。
解决的办法很多,其中一种办法就是调用istream类的成员函数接口get来获取字符:
get这个接口就是单单取一个字符,没有声明分隔符的概念。
在string.cpp种定义:
#include"string.h"
namespace HD
{istream& operator>>(istream& is, string& str){str.clear();char ch;is.get(ch);while (ch == ' ' || ch == '\n')//确保第一次拿到的ch不是空格或者换行{is.get(ch);}while (ch != ' ' && ch != '\n'){str += ch;is.get(ch);}return is;}
}
在test.cpp中试试:
#include"string.h"
namespace HD
{void test20(){string S1;cin >> S1;cout << S1;}
}
int main()
{HD::test20();return 0;
}
运行结果如下,没问题:
3.4.getline
这个接口string中重载了2个函数。俺也模拟实现2个函数,实现方法跟operator>>大同小异,如下:
俺们模拟实现采用声明和定义分离的写法:
在string.h中声明:
namespace HD
{istream& getline(istream& is, string& str);istream& getline(istream& is, string& str, char delim);
}
在string.cpp中定义:
#include"string.h"
namespace HD
{istream& getline(istream& is, string& str){str.clear();char ch;is.get(ch);while (ch != '\n'){str += ch;is.get(ch);}return is;}istream& getline(istream& is, string& str, char delim){str.clear();char ch;is.get(ch);while (ch != delim){str += ch;is.get(ch);}return is;}
}
在test.cpp中试试:
#include"string.h"
namespace HD
{void test21(){string S1;getline(cin, S1);cout << S1 << endl<<endl;string S2("hello world");getline(cin, S2, '@');cout << S2 << endl;}
}
int main()
{HD::test21();return 0;
}
运行结果:
4.个别接口的不同写法
4.1.constructor的不同写法
俺一共模拟实现了3个构造函数,分别是string()、string(const char* s)、string(const string& str)。
对于string()和string(const char* s)俺们可以用一个带缺省参数的构造函数代替:
string(const char* s = "")//以上两构造合二为一{_size = strlen(s);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, s);}
1.string的构造函数接口内并没有重载带缺省参数的构造函数,俺实现这个函数可以用来替代以上两个函数罢了
2.当调用这个函数时,若我们给了s,那么就相当于调用string(const char* s);若我们没有给s,那么就相当于调用string()。
3.这里缺省值给空字符串(""),字符串默认结尾有'\0'的。若是缺省值给了"\0",其实这个字符串包含2个'\0',概念要分清捏。
对于string(const string& str)俺们还可以这样写
string(const string& str)//拷贝构造函数{string tmp(str.c_str());swap(tmp);}
俺们希望用C++字符串str来构造一个新对象。俺们可以调用构造函数,用C字符串str.c_tr()来构造一个新对象tmp,这个新对象tmp不就是我们想要的吗,调用swap将tmp和*this的内容交换一下不就好了。
俺们知道string类底层是字符顺序表,所以俺定义3个private成员变量_str、_capacity、_size来实现,并且给这3个成员变量缺省值,这3个缺省值的意义就可以在这里体现:
当调用swap将tmp和*this的内容交换后,tmp得到的就是*this的旧内容且出了函数作用域就会销毁tmp。若是没有给缺省值,当调用构造函数走初始化列表的时,_str就得到一个随机值,也就是指向某块随机空间,当调用swap后并销毁tmp时,这块随机空间不就随着tmp的销毁而被delete掉了,这是不允许的;若是给缺省值,_str就得到nullptr,当tmp被销毁时是没问题的。
4.2.operator=的不同写法
operator=还可以这样子写,跟构造函数的不同写法的思想是一样的:
string& string::operator=(const string& str){string tmp(str);swap(tmp);return *this;}
operator=也可以如以下代码,只不过string中并没有重载以下operator=,但是下面这个operator=也能实现2个string类对象赋值的:
俺的模拟实现采取本成员函数声明和定义分离的写法:
string中声明:
string& operator=(const string str);
在string.cpp中定义:
#include"string.h"
namespace HD
{string& string::operator=(string str){swap(str);return *this;}
}
思路:调用swap时要先传参,由于C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,那么理论上,operator=的形参str拷贝构造一个临时对象,这个临时对象再拷贝构造swap的参数str,哪怕编译器优化掉了临时对象,swap的形参str也是通过ooperator=形参str拷贝构造的一个新对象,将这个新对象str与*this的内容交换一下不就搞定了。
这个operator=的模拟实现为什么参数部分设计成传值而不是传引用呢?
若是传引用,那么调用swap时,就不存在拷贝行为,就不会调用拷贝构造函数,就没有新对象的产生。在将operator=的形参str与*this交换内容,不就出大问题了吗。
以上2种写法若是自己给自己赋值也不会出现问题,仔细想想就能明白!
4.3.operator>>的不同写法
istream& operator>>(istream& is, string& str){str.clear();char ch;const size_t n = 256;char p[n];is.get(ch);size_t i = 0;while (ch == ' ' || ch == '\n'){is.get(ch);}while (ch != ' ' && ch != '\n'){p[i++] = ch;is.get(ch);if (i == n-1){p[i] = '\0';str += p;i = 0;}}if (i > 0){p[i] = '\0';str += p;}return is;}
这种写法与原本那种写法最大的不同就是,原本的写法是每取一个字符就插入str,容易造成频繁扩容;这种写法是每得到一串字符串再插入str,扩容次数就少了,一种优化吧!
5. string模拟实现完整代码
关于这个string模拟实现完整代码如下,看完整代码也方便理解,有兴趣可以瞅瞅。本工程包括3个文件,分别是:string.h、string.cpp、test.cpp。
5.1.string.h
存放string类的定义和非成员函数的声明。
#pragma once
#include<assert.h>
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
namespace HD
{class string{char* _str = nullptr;size_t _capacity = 0;size_t _size = 0;public:typedef char* iterator;typedef const char* const_iterator;static const size_t npos;//string()// :_str(new char[1]{'\0'})// ,_capacity(0)// ,_size(0)//{//}//string(const char* s)//{// _size = strlen(s);// _capacity = _size;// _str = new char[_capacity+1];// strcpy(_str, s);//}string(const char* s = "")//以上两构造合二为一{_size = strlen(s);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, s);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str()const{return _str;}size_t size()const{return _size;}char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos <= _size);return _str[pos];}iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}size_t capacity()const{return _capacity;}string(const string& str)//拷贝构造函数{/写法一//*_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;*//写法二/string tmp(str.c_str());swap(tmp);}void reserve(size_t n = 0);void push_back(char c);string& append(const char* s);string& operator+= (const char* s);string& operator+=(char c);string& insert(size_t pos, size_t n, char c);string& insert(size_t pos, const char* s);string& erase(size_t pos = 0, size_t len = npos);size_t find(char c, size_t pos = 0) const;size_t find(const char* s, size_t pos = 0) const;string substr(size_t pos = 0, size_t len = npos) const;string& operator=(const string& str);//string& operator=(const string str);void clear();void swap(string& str);};bool operator>(const string& lhs, const string& rhs);bool operator==(const string& lhs, const string& rhs);bool operator<(const string& lhs, const string& rhs);bool operator>=(const string& lhs, const string& rhs);bool operator<=(const string& lhs, const string& rhs);bool operator!=(const string& lhs, const string& rhs);ostream& operator<<(ostream& os, const string& str);istream& operator>>(istream& is, string& str);istream& getline(istream& is, string& str);istream& getline(istream& is, string& str, char delim);
}
5.2.string.cpp
存放string类部分成员函数的定义、静态成员变量的定义和非成员函数的定义。
#include"string.h"
namespace HD
{const size_t string::npos = -1;void string::reserve(size_t n){if (n > _capacity){char* p = new char[n + 1];strcpy(p, _str);delete[] _str;_str = p;_capacity = n;}}void string:: push_back(char c){if (_capacity == _size)//扩容{reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = c;_str[_size] = '\0';}string& string::append(const char* s){if (_capacity < strlen(s) + _size)//扩容{size_t n = strlen(s);reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}/写法一///for (int i = 0; i < strlen(s); ++i)//{// _str[_size++] = s[i];//}//_str[_size] = '\0';/写法二/strcpy(_str + _size, s);_size += strlen(s);return *this;}string& string::operator+= (const char* s){append(s);return *this;}string& string::operator+=(char c){push_back(c);return *this;}string& string::insert(size_t pos, size_t n, char c){assert(pos <= _size);if (_capacity < n + _size)//扩容{reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}size_t end = _size + n;while (end > pos+n-1){_str[end] = _str[end - n];end--;}for (size_t i = 0; i < n; i++){_str[pos + i] = c;}_size+=n;return *this;}string& string::insert(size_t pos, const char* s){assert(pos <= _size);size_t n = strlen(s);if (_capacity < n + _size){reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}size_t end = _size + n;while (end > pos + n - 1){_str[end] = _str[end - n];end--;}strncpy(_str + pos, s, n);_size += n;return *this;}string& string::erase(size_t pos, size_t len){assert(pos < _size);if (_size - pos <= len){_str[pos] = '\0';_size -= (_size - pos);}size_t begin = pos + len;while (begin < _size + 1){_str[begin - len] = _str[begin];begin++;}_size -= len;return *this;}size_t string::find(char c, size_t pos ) const{for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}size_t string::find(const char* s, size_t pos) const{if (const char* p = strstr(_str + pos, s)){return p - _str;}return npos;}string string::substr(size_t pos, size_t len) const{assert(pos <= _size);if (len >= _size - pos){len = _size - pos;}string S1;S1.reserve(len);for (int i = 0; i < len; i++){S1 += _str[pos + i];}return S1;}string& string::operator=(const string& str){/写法一//*if (this==&str)//防止自己给自己赋值{return *this;}delete[]_str;_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str.size();_capacity = str.capacity();return *this;*//写法二/string tmp(str);swap(tmp);return *this;}/*string& string::operator=(string str){swap(str);return *this;}*/void string::clear(){_str[0] = '\0';_size = 0;}void string::swap(string& str){std::swap(str._str, _str);std::swap(str._size, _size);std::swap(str._capacity, _capacity);}bool operator>(const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) > 0;}bool operator==(const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) == 0;}bool operator<(const string& lhs, const string& rhs){return !(lhs >= rhs);}bool operator>=(const string& lhs, const string& rhs){return lhs > rhs && lhs == rhs;}bool operator<=(const string& lhs, const string& rhs){return !(lhs > rhs);}bool operator!=(const string& lhs, const string& rhs){return !(lhs == rhs);}ostream& operator<<(ostream& os, const string& str){for (size_t i = 0; i < str.size(); i++){os << str.c_str()[i];}return os;}istream& operator>>(istream& is, string& str){/写法一//*str.clear();char ch;is.get(ch);while (ch == ' ' || ch == '\n'){is.get(ch);}while (ch != ' ' && ch != '\n'){str += ch;is.get(ch);}return is;*//写法二/str.clear();char ch;const size_t n = 256;char p[n];is.get(ch);size_t i = 0;while (ch == ' ' || ch == '\n'){is.get(ch);}while (ch != ' ' && ch != '\n'){p[i++] = ch;is.get(ch);if (i == n-1){p[i] = '\0';str += p;i = 0;}}if (i > 0){p[i] = '\0';str += p;}return is;}istream& getline(istream& is, string& str){str.clear();char ch;is.get(ch);while (ch != '\n'){str += ch;is.get(ch);}return is;}istream& getline(istream& is, string& str, char delim){str.clear();char ch;is.get(ch);while (ch != delim){str += ch;is.get(ch);}return is;}
}
5.3.test.cpp
存放测试代码,来测试咱们模拟实现的string的功能。
#include"string.h"
namespace HD
{void test1(){string S1;string S2("hello world");cout << S1.c_str() << endl << S2.c_str() << endl;}void test2(){string S1;string S2("hello world");cout << S1.size() << endl << S2.size() << endl;}void test3(){string S1("nbo");for (int i = 0; i < S1.size(); i++){S1[i] -= 1;cout << S1[i];}cout << endl;const string S2("hello world");for (int i = 0; i < S2.size(); i++){cout << S2[i];}}void test4(){string S1("hello world");for (auto ch : S1){cout << ch;}cout << endl;const string S2("God is a girl");string::const_iterator cit = S2.begin();while (cit != S2.end()){cout << *cit;cit++;}cout << endl;}void test5(){string S1;string S2("hello world");cout << S1.capacity() << endl << S2.capacity() << endl;}void test6(){string S1;string S2("hello world");cout << S1.capacity() << " " << S2.capacity() << endl;S1.reserve(10); S2.reserve(1);cout << S1.capacity() << " " << S2.capacity() << endl;}void test7(){string S1;string S2("hello world");S1.push_back('!');S2.push_back('!');cout << S1.capacity() << " " << S2.capacity() << endl;cout << S1.c_str() << " " << S2.c_str() << endl;}void test8(){string S1;string S2("hello");S1.append("young man");S2.append(" Totoro!");cout << S1.capacity() << " " << S2.capacity() << endl;cout << S1.c_str() << " " << S2.c_str() << endl;}void test9(){string S1;string S2("hello");S1 += '!'; S2 += " Totoro";cout << S1.c_str() << " " << S2.c_str() << endl;}void test10(){string S1("A man");string S2("hello");S1.insert(2, "young");S2.insert(S2.size(), 5, 'A');cout << S1.c_str() << " " << S2.c_str() << endl;}void test11(){string S1("A man");string S2("hello fat Totoro");S1.erase();S2.erase(5, 4);cout << S1.c_str() << endl << S2.c_str() << endl;}void test12(){string S1("A man");string S2("hello fat Totoro");cout << S1.find('m') << endl;cout << S2.find("fat") << endl;}void test13(){string S1("hello fat Totoro");string S2 = S1.substr(6, 3);cout << S2.c_str() << endl;}void test14(){string S1("hello fat Totoro");cout << S1.capacity() << endl;S1.clear();cout << S1.c_str() << " " << S1.capacity() << endl;}void test15(){string S1("hello world");string S2("I do love you,yes I do love you");cout << "交换前S1:" << S1 << " " << S1.size() << " " << S1.capacity() << endl;cout << "交换前S2:" << S2 << " " << S2.size() << " " << S2.capacity() << endl;cout << endl;S1.swap(S2);cout << "交换后S1:" << S1 << " " << S1.size() << " " << S1.capacity() << endl;cout << "交换后S2:" << S2 << " " << S2.size() << " " << S2.capacity() << endl;}void test16(){string S1("hello world");string S2("I do love you,yes I do love you");cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}void test17(){const char* S1 = "hello world";string S2("I do love you,yes I do love you");cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}void test18(){const char* S1 = "hello world";const char* S2 = "I do love you,yes I do love you";cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}void test19(){string S1;string S2("hello world");const string S3("I love Totoro");cout << S1 << endl << S2 << endl << S3 << endl;}void test20(){string S1;cin >> S1;cout << S1;}void test21(){string S1;getline(cin, S1);cout << S1 << endl << endl;string S2("hello world");getline(cin, S2, '@');cout << S2 << endl;}
}
int main()
{HD::test21();return 0;
}
感谢阅读!
相关文章:

【C++】string模拟实现
各位读者老爷好,俺最近在学习string的一些知识。为了更好的了解string的结构,俺模拟实现了一个丐版string,有兴趣的老爷不妨垂阅!!! 目录 1.string类的定义 2.模拟实现成员函数接口 2.1.constructor&am…...

Springboot 使用EasyExcel导出含图片并设置样式的Excel文件
Springboot 使用EasyExcel导出含图片并设置样式的Excel文件 Excel导出系列目录:★★★★尤其注意:引入依赖创建导出模板类逻辑处理controllerservice 导出效果总结 Excel导出系列目录: 【Springboot 使用EasyExcel导出Excel文件】 【Springb…...

技术分享:《越南语翻译通》App高效学习越南语的智能助手,是怎么实现高精度语音识别翻译功能的呢?
在数字化时代,语言学习和跨文化交流变得日益重要。对于那些计划前往越南工作、旅游或学习的人来说,掌握越南语无疑是一个巨大的优势。然而,对于非越南语母语者来说,语言障碍可能会成为一大难题。幸运的是,《越南语翻译…...

工业互联网实验实训解决方案核心优势
工业互联网实验实训解决方案旨在通过模拟真实的工业环境,提供给学生或从业人员一个实践学习的平台,它结合了理论教学与实际操作,旨在培养具备工业互联网相关技能的专业人才。 工业互联网实验室必备的软件工具包括: 仿…...
Ceph client 写入osd 数据的两种方式librbd 和kernel rbd
在Ceph存储系统中,客户端(Ceph client)写入OSD(Object Storage Daemon)数据确实可以通过两种主要方式:librbd和kernel rbd。这两种方式各有特点和适用场景,下面将分别进行详细介绍。 librbd方式…...
相机光学(四十二)——sony的HDR技术
1.概述 索尼的HDR技术包括以下几种,这些技术共同构成了索尼在HDR领域的技术矩阵,旨在提供更宽广的动态范围、更丰富的色彩表现以及更真实的光影效果: Multi-frame HDR(多帧异曝光HDR):这是一种通过不同曝光时间图像的多帧合成来实…...

文件上传漏洞--理论
什么是文件上传漏洞? Web应用允许用户上传文件,但是没有对上传的文件进行严格的过滤和检测,导致网站执行了文件中包含的恶意代码。 漏洞的基本利用方式是: 成功上传包含恶意代码的文件,并在服务端成功执行该文件。 …...

快速入门Selenium自动化测试
一、背景与意义 Selenium是常用的Web自动化测试工具,前端开发工程师可以在完成每项开发任务之后,使用Selenuim做一下回归测试,以避免被提BUG太多导致后面做项目总结时太难看。测试工程师学习Selenium时需要掌握很多API接口,例如页…...
C++指针使用指南
指针 适合指针的情况 动态内存管理 C 语言中,malloc、calloc 和 free 等函数用于动态分配和释放内存,这些函数返回的都是指针类型。通过指针,可以动态创建和管理内存区域,尤其在需要动态数组或复杂数据结构(如链表、…...

一文学会,利用LLaMA 3.2打造能“识图断字”的个人AI助理
人工智能最直接也是最普及的应用之一肯定是聊天机器人,或者叫个人 AI 助理。尽管聊天机器人以各种形式存在了30年,但在过去两年中,这些个人AI助理才真正成为热门应用。它们已经从前沿技术范畴落地到生活的各个方面、社交场合和商业场景中。 虽…...
idea的mapper.xml文件里写sql语句出现Tag name expected错误提示
原因如下: xml文件的某些特殊字符是自动转义的, xml解析器会忽视CDATA中的内容 解决方法: 使用下面的表达式表示对应符号。 小于(<):<大于(>):>和号(&):&单引号():'双引…...
EasyExcel 使用多线程按顺序导出数据
通过多线程读取数据,使用EasyExcel按顺序导出数据 导出时如果要保证顺序需要使用单线程,但是查询时可以用多线程,因为多线程查询后返回数据不是按照顺序排列的,所以我的思路是再循环时给每个线程打标识,通过标识来排序…...
数据驱动的投资分析:民锋科技的量化模型探索
在全球金融市场中,数据驱动的投资分析正在变革传统投资方式。民锋科技通过精密的量化模型和智能算法,为投资者提供更加科学的市场预测和投资分析工具,以帮助他们更好地理解市场波动、优化投资组合,实现风险管理。 #### 一、数据驱…...

cesium 设置相机视角 flyTo 参数destination,orientation
效果 // 监听相机的改变 用来设置相机飞行到哪里window.viewer.camera.changed.addEventListener(function () {// 当相机改变时,获取相机的参数let camera window.viewer.camera;// 获取当前摄像机的位置(经纬度)let cameraPosition wind…...

vue+Leaflet.PM插件实现创建和编辑几何图形(点、线、面、圆等)
场景 VueLeaflet实现加载OSM显示地图:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/122317394在上面加载显示OSM的基础上,使用Leaflet.pm插件实现在页面上绘制、编辑、剪切、移动几何元素。Leaflet.pm插件 用于创建和编辑几何图层的插件可…...

Rust语言在系统编程中的应用
💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 Rust语言在系统编程中的应用 Rust语言在系统编程中的应用 Rust语言在系统编程中的应用 引言 Rust 概述 定义与原理 发展历程 Ru…...

test 是 JavaScript 中正则表达式对象 (RegExp) 的一种方法,用于测试一个字符串是否匹配某个正则表达式
在你的代码中,test 方法用于验证扫描结果是否符合特定的格式要求。具体来说,/^[A-Za-z\d]{16}$/.test(res.result) 这一行代码用于检查扫描结果 res.result 是否是一个由16个字母或数字组成的字符串。 test 方法的作用 正则表达式匹配: ^ 表…...

大厂社招3年-力扣热点高频刷题记录(已更新100+道热点题)
前言: 最近从大厂出来看机会,大厂面试基本都考察算法,于是维护此文档,一是查缺补漏,确保整体热点算法题目的应知应会,与思路的灵活理解;二是分享出来给其他同学朋友做一个参考借鉴,共…...
6.2 对角化矩阵(2)
五、不能对角化的矩阵 假设 λ \lambda λ 是 A A A 的一个特征值,我们从两个方面发现这个事实: 特征向量(几何的): A x λ x A\boldsymbol x\lambda\boldsymbol x Axλx 有非零解。特征值(代数的&…...

ubuntu24.04播放语音视频
直接打开ubuntu自带的video播放.mp4文件,弹窗报错如下: 播放此影片需要插件 MPEG-4 AAC 编码器安装方式: sudo apt install gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly sudo apt install ffmpeg验证AA…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...

Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...