网站建设非功能需求wordpress3d标签

当前位置: 首页 > news >正文

网站建设非功能需求,wordpress3d标签,济南网站建设公司哪家专业,辽宁省电力建设网站当你将放弃作为一种习惯#xff0c;你一辈子也不会有出息… 文章目录一、Default member functions1.Constructor2.Copy constructor#xff08;代码重构#xff1a;传统写法和现代写法#xff09;3.operator#xff08;代码重构#xff1a;传统写法和现代写法#xff…当你将放弃作为一种习惯你一辈子也不会有出息… 文章目录一、Default member functions1.Constructor2.Copy constructor代码重构传统写法和现代写法3.operator代码重构传统写法和现代写法4.destructor二、Other member functions1.Iterators1.1 begin() end()2.Capacity2.1 size() capacity()2.2 resize()2.3 reserve()2.4 clear()3.Element access3.1 operator[]4.Modifiers画重点4.1 push_back()4.2 append()4.3 operator()4.4 insert()头插时的bug集中区4.5 erase()4.6 swap()5.String operations5.1 find()5.2 c_str()三、Non-member function overloads1.operator2.operator3.getline()4.swap()一、Default member functions 1.Constructor

  1. 库里面的构造函数实现了多个版本我们这里就实现最常用的参数为const char *的版本为了同时支持无参的默认构造这里就不在多写一个无参的默认构造而是用全缺省的const char *参数来替代无参和const char *参数的两个构造函数版本。
  2. _size代表数组中有效字符的个数在vs下_capacity代表数组中有效字符所占空间的大小在g下包含了标识字符\0的空间大小我们这里就实现和vs编译器一样的_capacity然后在底层实际开空间的时候多开一个空间存放字符串的\0就可以。
  3. 代码中利用了strlen和strcpy来进行字符串有效字符的计算和字符串的拷贝值得注意的是strcpy在拷贝时会自动将字符串末尾的\0也拷贝过去。 // \0 – 字符0ascll码值为0 // \0 – 字符串有两个\0因为默认有一个\0 // – 有一个\0字符串默认以\0结尾 //将无参构造函数和有参构造函数搞一块儿整个全缺省参数的构造函数就可以直接替代前面两个构造函数了。 string(const char* str )//默认的缺省值是\0字符串如果是\0字符那就相当于ascll码值0那其实就是nullptrstrlen在nullptr中找\0时就会报错崩溃//:_str(str)//权限会放大不能这样初始化。 {//std::string标准中未规定需要\0作为字符串结尾。编译器在实现时既可以在结尾加\0也可以不加。因编译器不同_size strlen(str);_capacity _size;_pstr new char[_capacity 1];//capacity存的是有效字符只是在实际开空间的时候多开一个位置给\0strcpy(_pstr, str); } /string() {_pstr new char[1];_size _capacity 0;_pstr[0] \0; }/2.Copy constructor代码重构传统写法和现代写法
  4. 传统写法就是我们自己手动给被拷贝对象开辟一块与拷贝对象相同大小的空间然后手动将s的数据拷贝到新空间最后再手动将不涉及资源申请的成员变量进行赋值。
  5. 现代写法就是我们自己不去手动开空间手动进行成员变量的赋值而是将这些工作交给其他的接口去做就是去找一个打工人让打工人去替我们做这份工作在下面代码中构造函数就是这个打工人。 所以构造出来的tmp和s就拥有一样大小的空间和数据然后我们再调用string类的swap成员函数进行被拷贝对象this和tmp对象的交换这样只需两行代码就能解决拷贝构造的实现但真的解决了吗 补充如果要进行两个对象的交换不要调用std里的swap因为会进行三次深拷贝效率非常低所以我们利用某一个对象的swap类成员函数来进行两个对象的交换。
  6. 实际上还需要一个初始化列表因为s2的内容不初始化则s2的_pstr就是野指针随机指向一块不属于他的空间这块空间应该属于操作系统那么在交换完毕之后tmp的_pstr就变为了空指针在出函数作用域之后tmp对象会被销毁自动调用析构函数则释放野指针所指向的空间就会发生越界访问程序就会崩溃所以最好的解决办法就是利用初始化列表先将this的成员变量初始化一下对于有资源的_pstr我们利用nullptr来进行初始化避免出现野指针。
  7. 可能会有人有疑问释放nullptr指向的空间时程序不会崩吗实际上无论是delete、delete[]还是free他们在内部实现的时候如果遇到空指针则什么都不做也就是没有任何事情发生因为这也没有做的理由空指针指向的空间没有任何数据我为什么要处理它呢只有说一个空间中有数据需要清理的时候也就是这个指针不为空的时候free和delete、delete[]才有处理它的理由。 //string s2(s1) /string(const string s)//传统写法 {_pstr new char[s._capacity 1];_capacity s._capacity;_size s._size;//类里面不受访问限定符private/public/protected的限制strcpy(_pstr, s._pstr); }/ string(const string s)//现代写法:_pstr(nullptr),_size(0), _capacity(0) {//重构代码:不改变代码外在行为的前提下对代码做出修改以改进程序的内部结构。string tmp(s._pstr);//调用构造函数tmp和s有一样大的空间和一样的值//下面不可以用赋值要不然tmp析构的时候this的_pstr又变成野指针了this被析构时又会发生问题了。//库 里面的swap代价太大需要不断进行实例化swap模板//swap(_pstr, tmp._pstr);//swap(_capacity, tmp._capacity);//swap(_size, tmp._size);//交换之后tmp的_pstr就变成野指针了出了函数栈帧tmp销毁时就会野指针访问用初始化列表可以解决。//this-swap(tmp);swap(tmp);//可以不用this指针调用因为在类里面swap默认的左边第一个参数就是this直接调用就可以。//如果要交换两个对象时不要用swap(s1,s2)这个代价大应该用s1.swap(s2)如果用库的swap则会发生三次深拷贝。//总结:内置类型调算法库里面的swap自定义类型调成员函数swap }3.operator代码重构传统写法和现代写法
  8. 赋值重载的传统写法和拷贝构造非常的相似都是我们自己手动开空间手动进行无资源申请的成员变量的赋值手动进行数据的拷贝。但需要额外关注的一点是一个对象可能被多次赋值那我们就需要对原来可能存在的资源进行释放所以需要手动delete[]或者调用clear()函数来进行原来可能存在的资源的释放。
  9. 现代写法就是我们可以找打工人让自己少手动进行操作这时候的打工人有两种一种是构造函数一种是拷贝构造函数但在这个地方不需要担心临时对象销毁时析构可能造成的野指针访问问题因为赋值重载针对的是两个已经被构造出来的对象就算你什么参数都不传也有缺省值\0给你顶着所以就不会出现野指针而拷贝构造出现野指针的原因是被拷贝的对象还没有被构造出来类成员变量都是随机值所以就有野指针为了避免这样的问题我们才在拷贝构造中加入了初始化列表以防止野指针的出现。 实际上只要一个对象被构造出来哪怕这个对象是调用无参构造函数构造出来的那么这个对象也没有野指针因为即使是空对象在底层中他的数组中都会有一个\0标识字符标识这个对象对应的串是一个空串只要这个数据有效那么指向数据空间的指针也就有效就不会有析构野指针的问题出现。
  10. 我们继续把话题捞回来所以只要让打工人拷贝构造构造出来tmp然后我们再利用类成员函数swap将tmp和this对象进行交换则赋值工作就完成了本质和拷贝构造是一样的都是先让一个打工人帮我们搞好一个和拷贝对象一样的对象然后再用自己的对象和打工人搞好的这个对象进行交换等离开函数时打工人搞的对象就被销毁this对象成功就完成了赋值工作。
  11. 其实还有一个最为简洁的办法就是用传值传递这样的话函数参数天然的就是我们的打工人拷贝构造函数搞出来的对象那我们实际上什么都不用做直接调用swap函数进行this和参数对象的交换即可以后我们写赋值重载就用这个最简洁的方法。 //s2s1 //string operator(const string s)//传统写法 //{ // if (this ! s)//不要自己给自己赋值 // { // delete[] _pstr;//清空掉s2原有的数据然后进行赋值 // _pstr new char[s._capacity 1]; // strcpy(_pstr, s._pstr);// _size s._size; // _capacity s._capacity;// } // return *this; //} //string operator(const string s)//现代写法 //{ // //代码重构 // if (this ! s) // { // //string tmp(s._pstr); // string tmp(s);//调用拷贝构造或者构造函数 // swap(tmp); // } // return *this; //} string operator(string s)//现代写法的另一种更为常用的写法s是现成的打工人身份地位和tmp一样 //传值传参不存在权限的放大和缩小指针和引用才有权限的放大和缩小传值只是权限的平移无论是const还是非const直接拷贝就行 { swap(s);return *this; }4.destructor
  12. 析构函数的实现就比较简单了只要将指针指向的空间进行释放然后将其置为空指针防止野指针的误操作然后再将剩余两个成员变量赋值为0即可完成工作。 ~string() {//delete和free内部实现中都有一个特征他们内部会检查指针如果指针为空则什么都不做。所以delete[]或free空指针不会报错delete[] _pstr;_pstr nullptr;_size _capacity 0; }二、Other member functions 1.Iterators 1.1 begin() end()
  13. 现阶段我们无法完全透彻的理解迭代器但是目前我们确实可以将其理解为指针所以在模拟实现这里我们用typedef来将iterator定义为char型的指针类型。而对于begin和end来说较为简单只要返回首元素和末尾的\0元素对应的地址就可以而_size对应的下标正好就是\0所以直接返回就好。 typedef char* iterator;iterator begin()const {return _pstr; } iterator end()const {return _pstr _size; }2. 实际上C11的新特性基于范围的for循环他的本质实现就是迭代器所以只要有begin()和end()这两个返回迭代器的函数我们就可以使用范围for范围for代码的执行实际上可以理解为宏的替换就是在执行for时编译器会在这个地方作处理等到实际执行时执行的就是迭代器并且范围for只能调用begin和end这是写死的如果这两个函数的名字变一下那范围for就用不了了因为局部的返回迭代器的函数名有问题。 void test_string1() {string s1(hello world);cout s1.c_str() endl;for (size_t i 0; i s1.size(); i){s1[i];}cout s1.c_str() endl;string::iterator it1 s1.begin();while (it1 ! s1.end()){(*it1)–;it1;}cout s1.c_str() endl;for (auto ch : s1){cout ch ;}cout endl;//范围for就是用迭代器实现的在编译范围for的代码之前先将代码替换为迭代器的实现有点类似于宏.//所以在实际编译的时候编译的是替换之后的迭代器的代码替换的迭代器必须是begin和end如果我们将自己的begin改成Begin//则iterator的调用还可以进行但范围for就无法通过因为范围for只能调用begin()和end()这是写死的。//范围for调用我们自己写的迭代器的原因是因为它会先去局部找然后再去全局找局部有我们自己实现的begin和end则范围for就会//自动调用。//只要一个容器有迭代器那么这个容器就可以支持范围for迭代器必须是原模原样的begin和end }2.Capacity 2.1 size() capacity() size_t size()const//利用public的函数让类外能够访问到类中的private成员 {return _size; } size_t capacity()const {return _capacity; }2.2 resize()
  14. 对于resize来说根据所传空间大小的值来看可以分为插入数据和删除数据两种情况对于插入数据直接调用reserve提前预留好空间然后搞一个for循环将字符ch尾插到数组里面去最后再在数组末尾插入一个\0标识字符此刻就体现出来为什么我们在reserve开空间的时候要多开一个空间了因为这个空间就是给\0留的。
  15. 对于删除数据就比较简单了直接在n位置插入\0即可依旧采用惰性删除的方式然后重置一下_size的大小为n即可。 void resize(size_t n, char ch \0)//resize需要开空间和初始化我们将初始化的ch默认缺省为\0 {//分三种情况删除数据不扩容增加数据扩容增加数据后两种情况可以合起来因为是插入数据if (n _size)//插入数据{reserve(n);//插入数据调用扩容接口for (size_t i _size; i n; i){_pstr[i] ch;}_size n;_pstr[_size] \0;}else//删除数据{_pstr[n] \0;_size n;} }2.3 reserve()
  16. reserve的参数代表你要将数组的现有的有效字符所占空间大小调整为的大小注意是有效字符这是不包含标识字符的而在具体实现的时候我们在底层多开一个空间给\0在C中所有的扩容都是异地扩容而不是原地扩容所以每一次扩容都需要进行原数据拷贝到新空间代价确实很大。 void reserve(size_t n) {//reserve尽量不要缩容最好是扩容。//下面代码只有扩容如果是缩容则什么都不做。if (n _capacity){char* tmp new char[n 1];//开空间的时候多开一个给\0n代表的是有效字符的大小strcpy(tmp, _pstr);delete[] _pstr;_pstr tmp;_capacity n;} }2.4 clear()
  17. 这里的clear实现的很巧我们只要将_size搞成0然后将第一个元素赋值为\0就完成资源的清理了之后如果进行元素的修改操作那么直接进行覆盖即可这实际上是一种惰性删除的方式。 void clear() {_size 0;_pstr[0] \0; }3.Element access 3.1 operator[]
  18. 对于operator[]来说调用它时既有可能进行写操作又有可能进行读操作所以为了适应const和非const对象operator[]应该实现两个版本的函数并且这个函数对待越界访问的态度就是assert直接断言而at对于越界访问的态度是抛异常。 //普通对象:可读可写 char operator//operator[]是可能读和可能写的所以需要实现两个版本 {assert(pos _size);return _pstr[pos]; } //const对象:只读 char operatorconst//只读的版本 {assert(pos _size);return _pstr[pos]; }4.Modifiers画重点 4.1 push_back()
  19. push_back有一个需要注意的地方就是在扩容的地方如果是一个空对象进行push_back的话我们采取的二倍扩容就有问题因为0*2还是0所以对于空对象的情况我们应该给他一个初始的capacity值这里我们就给成4其他情况的话只要空间满了我们就二倍扩容采用和g一样的策略。
  20. 很容易忘记的就是在尾插字符之后忘记补\0了千万不要忘记这里否则在打印的时候就会有麻烦了。 我个人认为忘记尾插\0的原因还是对字符串不够敏感对于它的组成还是迷迷糊糊的你只要记住一个字符串就是由有效字符和结尾的标识字符组成的这样无论你在进行什么样的字符串操作之后你肯定就会想一下哎呀操作完之后这个字符串好像没有标识字符啊这会出问题的呀那我应该补上啊。 在理解字符串之后相信你肯定不会忘记补\0了就。 void push_back(char ch) {if (_size _capacity){int new_capacity _capacity 0 ? 4 : _capacity * 2;reserve(new_capacity);}_pstr[_size] ch;_size;_pstr[_size] \0; }4.2 append()
  21. 对于append的实现我们其实可以直接调用strcpy接口来进行字符串的尾插并且我们知道strcpy是会将\0也拷贝过去的这样的话我们就不需要在末尾手动补充\0了。
  22. 值得注意的是string系列的字符串函数是不会进行自动扩容的所以我们需要判断一下是否需要进行扩容在空间预留好的情况下进行字符串的尾插调整strcpy的插入位置为_pstr_size即可实现字符串尾插的工作。 void append(const char* str) {//string系列的库函数是不会自动扩容的都需要在有足够空间的情况下进行操作strcat是行不通的在这里因为空间不一定够size_t len strlen(str);if (_size len _capacity){reserve(_size len);//这里开空间不需要加1只需要传有效字符的个数就可以了底层实际多开一个\0空间的工作交给reserve}strcpy(_pstr _size, str);//strcpy会把\0也拷贝过去_size len; }4.3 operator()
  23. string类的修改模块儿中的yyds函数我们也只实现两个最常用的版本参数分别为字符和字符串的版本。 在已实现push_back和append的情况下我们直接进行函数复用即可。 string operator(char ch) {push_back(ch);return this; } string operator(const char str) {append(str);return *this;//返回对象的引用 }4.4 insert()头插时的bug集中区
  24. insert的逻辑还是很简单的总体来说就是先判段是否需要进行扩容然后就是向后挪动数据最后将目标数据插入到对应的位置即可。但是实现起来坑还是非常多的出现坑的情况实际就是因为头插在这种情况下insert如果不控制好处理的逻辑很容易就会出现bug。
  25. 如果end被定义为字符的下标那么就应该将end位置元素挪到end1位置上去如果进行头插的话end0会继续进入循环循环结束之后end会变为-1回到while的判断位置此刻就出现问题了如果我们的end是size_t定义的那么-1会被认为是无符号整数进行隐式类型转换由于-1的补码是全1被当作无符号整数的话它的原码就被看作是全1了那就是四十二亿九千万大小程序会陷入死循环。所以我们可以不用size_t来定义end防止发生隐式类型转换。 改用int定义end的话稍不注意又会出现问题因为-1在和size_t定义的pos进行比较时又会发生隐式类型转换因为比较运算符也是运算只要是运算就有可能出现隐式类型转换那么-1就又会被转为无符号整型程序就又会陷入死循环所以如果采取int方式定义的end那么在比较时就需要将size_t的pos强转为int类型来和int类型的end进行比较。ps:强转这种方法很好用十分推荐。
  26. 如果你不想将end定义为int类型想继续使用size_t类型那就需要将end定义为字符将要被挪动到的位置的下标所以我们就将end-1位置的元素挪到end位置上去在while循环条件的判断位置我们用end来和pos位置进行比较end应该大于pos的位置一旦endpos我们就跳出循环这样就不会出现bug了。 string insert(size_t pos, char ch)//pos位置不可能是一个负数所以我们用size_t来进行定义 {assert(pos _size);//_size就相当于在\0位置插入数据也就是尾插if (_size _capacity){int new_capacity _capacity 0 ? 4 : _capacity * 2;reserve(new_capacity);}//挪动数据/int end _size;while (end (int)pos)//可以用int类型的end然后再将pos强转为int类型即可。{_pstr[end 1] _pstr[end];–end;}/size_t end _size 1;//end表示需要被挪动的数据应该被挪动到的位置。//但如果将类型改为int也会报错因为会发生隐式类型转换需要将while的判断条件调整一下while (end pos)//如果进行头插这里就会出现问题end会减到-1然后去endpos位置进行判断但是end是size_t类型:死循环//如果改为int这里会用-1和size_t的pos进行比较int会隐式类型转换为size_t程序会死循环{_pstr[end] _pstr[end - 1];–end;}_pstr[pos] ch;_size;return *this; }4. 对于字符串的插入逻辑也是相同的我们需要提前预留好存放字符串的有效字符的空间大小然后进行挪动字符串最后将字符串的所有有效字符插入到对应的位置上去即可。
  27. 插入字符串的情况种类和上面插入字符一样我推荐使用字符的位置来作为end的定义将end下标的元素挪到endlen之后的位置上去因为我们只插入有效字符所以strlen的结果刚好满足我们的要求同样在while判断条件进行比较的时候还是要讲pos强转为int类型来和end进行比较这样的逻辑非常的清晰明了。
  28. 在使用size_t作为end类型的情况下我们需要用字符将要被挪动到的位置来作为end的定义然后将end-len位置的元素赋值到end位置上去我们可以将判断条件控制为endposlen-1因为poslen位置是pos位置元素需要被挪动到的位置-1之后就是需要存放的字符串的最后一个有效字符的位置所以我们应该将条件控制为endposlen-1或者是endposlen这两种条件都成立。
  29. 与插入字符稍有不同的是我们插入的字符串是有标识字符作为结尾的所以在进行字符串拷贝到数组里面时我们需要控制不要将\0拷贝进去因为原来数组的末尾就有\0这个时候就不适合用strcpy函数来进行拷贝可以使用strncpy然后传有效字符大小作为拷贝字符串的字符个数这样就可以解决不拷贝\0的问题。 string insert(size_t pos, const char* str) {//1.判断空间是否足够不够就扩容size_t len strlen(str);if (_size len _capacity){reserve(_size len);//这里开空间不需要加1只需要传有效字符的个数就可以了底层实际躲开一个\0空间的工作交给reserve}//2.挪动数据然后插入字符串。// (1)还是用下面强转的方式简单int end _size;//用int就是为了防止end为-1时循环还能继续进行从而发生死循环while (end (int)pos)//这样的代码是可以支持头插的。因为end是intpos也被强转为int了。{_pstr[end len] _pstr[end];end–;}//(2)这个思考起来太麻烦了//size_t end _size len;//end表示数据将要被向后挪动到的位置。//while (end pos len - 1)//end位置和pos原有数据应该被挪动到的位置进行比较//while (end pos len)//也可以因为这里的等于时end位置不会为0进入循环结束之后就会跳出循环而不是变为-1继续判断//{// _pstr[end] _pstr[end - len];// end–;//}// 不要用strcpy因为strcpy会把\0也拷贝进去。可以使用strncpy或者memcpymemmove比memcpy可以多处理内存重叠的情况。//但如果是两个字符串的拷贝就不存在内存重叠的情况直接用memcpy就够了不用memmovestrncpy(_pstr pos, str, len);_size len;//不要忘了将_size如果不那么扩容就无法正常进行return *this; }4.5 erase()
  30. erase的参数分别为删除的起始位置和需要删除的长度库中实现时如果你不传则默认使用缺省值npos转换过来的意思就是如果你不传删除长度那就默认从删除的起始位置开始将后面的所有字符都进行删除。
  31. 如果lenpos之后的下标大于或者等于_size的话那处理结果和没传删除长度参数一样都是将pos位置之后的元素全部删除我们依旧采用惰性删除的方式来进行删除直接将pos位置下标对应的元素赋值为\0即可。
  32. 对于仅删除字符串的部分字符情况的话我们可以利用strcpy来进行将poslen之后的字符串直接覆盖到pos位置这样实际上就完成了删除的工作。 string erase(size_t pos, size_t len npos) {assert(pos _size);if (len npos || len pos _size )//pos代表pos位置之前还有几个有效数据{_pstr[pos] \0;_size pos;}else{strcpy(_pstr pos, _pstr pos len);_size - len;}return this;//如果不搞引用返回的话则会发生浅拷贝因为我们没写拷贝构造临时对象离开函数会被销毁则对象s1指向空间为OS越界访问。 }4. 对于静态成员变量我们知道必须在类外定义类内只是声明定义时不加static关键字。但如果静态成员变量有了const修饰之后情况就不一样了它可以在类内直接进行定义值得注意的是这样的特性只针对于整型如果你换成浮点型就不适用了。我们的npos就是const static修饰的成员变量可以直接在类内进行定义。 class string { public:private://类模板不支持分离编译因为用的地方进行了实例化但用的地方只有声明没有定义而有定义的地方却没有实例化所以发生链接错误//1.如果在定义的地方进行了实例化则通过.h文件找到方法之后方法已经发生实例化了那么就不会发生链接错误。//2.或者直接将声明和定义放到.hpp文件中只要用的地方包含了.hpp文件则类定义的地方就会进行实例化。char _pstr;size_t _size;//理论上不可能为负数所以我们用size_t类型进行定义size_t _capacity;//如果在调用构造函数的时候没有显示传参初始化成员变量则成员变量会利用C11的缺省值在构造函数的初始化列表进行初始化const static size_t npos -1;//静态成员变量在类中声明定义必须在类外面因为它属于整个类。但const修饰的静态成员变量可以直接在类中进行定义算特例。//但const修饰静态成员变量在类中可以进行定义的特性只针对于整型类型换个类型就不支持了。给整型开绿灯//const static double X ;};4.6 swap() 类成员函数swap的实现非常简单只需要调用std里面的swap将对象的内置类型的每个成员变量进行交换即完成对象的交换工作。 void swap(string str) {std::swap(_pstr, str._pstr);std::swap(_capacity, str._capacity);std::swap(_size, str._size); }5.String operations 5.1 find()
  33. 对于字符的查找遍历一遍即可如果找不到我们就返回npos找到就返回下标
  34. 对于字串的查找我们调用strstr来进行解决如果找到就利用指针-指针来返回字串的首元素下标找不到就返回npos。 size_t find(const char ch, size_t pos 0)const {assert(pos _size);while (pos _size)//一般来说不会查找空字符所以这里就不加{if (_pstr[pos] ch){return pos;}pos;}return npos;//找不到返回npos } size_t find(const char* str, size_t pos 0)const {assert(pos _size);const char* p strstr(_pstr pos, str);if (p nullptr)return npos;return p - _pstr; }5.2 c_str() c_str是C为了兼容C语言增加的一个接口其作用就是返回string类对象的成员变量也就是char 的指针。 const char c_str() {return _pstr; }三、Non-member function overloads 1.operator
  35. 类外获得类内私有成员变量一般有两种方法一种是通过友元函数来进行解决另一种是调用公有成员函数来访问私有成员变量。 这里的流插入重载还是非常简单的我们利用范围for就可以输出字符串的每个字符最后返回ostream类对象的引用即可以此来符合连续流插入的情景。 ostream operator(ostream out, const string s) {//返回值是ostream这样可以连续使用cout//流插入和流提取必须重载成全局的而且得是友元。(这句话是错误的必须是全局但不一定得是友元因为可以用public函数获取private)for (auto ch : s)//会被替换为迭代器*迭代器拿到ch的值{out ch;ch;}/for (size_t i 0; i s.size(); i){out s[i];}/return out; }2.operator
  36. 对于流提取的重载和getline的实现就不一样了流提取是以空格和\n作为间隔标志的 所以在实现时如果遇到这两个字符我们就跳出while循环。
  37. cin和scanf是读取不了空格的所以一旦我们流提取一个含有空格的字符串到某个对象时就会发生问题缓冲区里的空格和换行符是直接过滤的所以等到in读取完缓冲区字符之后in就一直让我输入程序会陷入死循环永远跳不出while循环。最本质的原因就是scanf和cin在读取的时候会自动过滤掉空格和换行符所以in从缓冲区里拿到的字符永远都不可能是空格和换行符那么while循环就永远都跳不出来程序必然会崩溃。 3. 所以为了避免这种问题的出现我们不采用in读取缓冲区字符的方法而是使用in对象的类成员还是get()来进行字符的读取get()是可以拿到所有的字符的等到拿出来空格或换行符的时候我们就跳出循环结束get继续对缓冲区字符的读取。
  38. 但我们知道字符的本质就是push_back如果我们输入的字符串非常非常的长的话就会导致频繁的调用而频繁的调用push_back势必会进行多次的扩容频繁扩容那就会降低效率所以这样低效率的代码是写库的人不可接受的。 所以实际在库中实现流提取时用了buff数组作为一个缓冲来解决频繁扩容带来的效率降低的问题。
  39. 利用reserve来提前预留空间降低频繁扩容的做法是不可取的因为我们不知道用户在输入的时候会输入多少字符的字符串所以如果空间开大了就会浪费开小了又不够。 那什么时候用reserve呢需要提前知道预留空间的大小才适合用reserve来提前预留空间。
  40. 我们用buff来存储get从缓冲区中提出来的字符等到buff数组满了之后我们再将数组中的内容统一到对象是里面去这样就可以减少输入字符串过长带来的效率降低如果输入的字符过短没有达到buff数组的大小的时候我们就将buff里面存在的字符到对象s即可。
  41. 在具体实现的时候其实这里还存在bug如果你用if和else的语句来进行判断的话会出问题当buff满了的时候get读取出来的字符肯定是不了的因为需要进入另一个分支语句进行buff数组内容到对象里面那此刻get读取出来的字符就无法尾插到对象里面buff数组过后get继续进行读取原来的字符就被过滤掉了还没来得及尾插到对象里面就又读取新的字符了。 所以在这样的逻辑情况下实际存储到对象里面的字符是要比我们输入的字符个数少的因为每一次进行buff内容的append时当前读取的字符会被过滤掉然后又重新读取新的字符。
  42. 所以我们应该换一种逻辑就是当append尾插buff内容的时候原有字符依旧不会被过滤掉而是重新再赋值到buff里面这样的逻辑也好说只要用if语句控制即可不要用if else这样的分支语句只用if则可以保证每一次get读取出来的字符都能被插入到buff里面然后等buff满了的时候再统一append到对象s里面。 如果buff没有满不要忘了将buff里面仅存的字符到对象s里面去 istream operator(istream in, string s)//这里不能用const了因为要将控制台输入后的内容拷贝到对象s里面 {s.clear();//上来就清空一下这样就可以支持已初始化对象的流提取了/char ch;in ch;///流提取就是从语言级缓冲区中拿数据但是他拿不到空格和换行符因为istream类的流提取重载就是这么规定的//所以要解决的话我们就不用流提取重载我们改用istream类的get()函数来一个一个获取缓冲区里面的每个字符。char ch in.get();while (ch ! ch ! \n){s ch;//如果输入到缓冲区里的字符串非常非常的长那么就需要频繁的扩容则效率就会降低。// //in ch;ch in.get();//C的get()和C语言的getchar()的功能是一样的都是获取缓冲区的字符}//方法1.reserve解决方案//reserve大了空间浪费如果小了一旦字符串又过大则还会需要频繁的扩容reserve可以但是不是特别好的方法。//方法2.开辟buff数组/如果你输入的字符个数过于少有效字符的个数不到127的话跳出while循环之后我们还需要另外判断再将buff中还没有满的数据 到对象s里面去。如果输入的字符个数过于多无需担心我们以127个有效字符为一组每组满了就将这一组的数据 到对象s里面去库里面大概就是这么实现的。/char buff[128] { \0 };size_t i 0;char ch in.get();while (ch ! ch ! \n){//if (i 127)//这里的大小必须是127最后得留一个位置给\0要不然没有标识字符字符串的结尾具体在哪里找不到打印出错//{// buff[i] ch;//}//else//{// s buff;// i 0;//}//ch in.get();//上面这种逻辑输入的有效字符个数超过127或者更大的时候实际存到s里面的字符个数会变少下面的逻辑是正确的。if(i 127){s buff;//的字符串buff是以\0结尾的i 0;}buff[i] ch;ch in.get();}if (i 0)//i代表已经插入的有效字符的个数个数对应的下标位置正好是最后一个有效元素的下一个位置。{buff[i] \0;s buff;//将上面插入的\0之前的字符串到对象s里。}return in; }3.getline()
  43. 这里实现getline的时候有一点小问题对于istream类的对象在传参时不能使用传值拷贝编译器会自动删除掉istream类的拷贝构造防止出现浅拷贝等不确定的问题如果想要进行解决则需要用引用或者自己实现深拷贝的拷贝构造函数。
  44. getline和cin不同的地方在于cin是以空格和\n作为分隔符而getline是以\n作为分隔符的所以在模拟实现的时候不能使用流提取来进行字符的读取应该用istream类中的读取字符的成员函数get()来进行缓冲区的字符读取。
  45. 在实现内部我们利用来进行string类对象的字符的尾插。 istream getline(istream in, string s) //vs编译器会将istream类的默认构造自动删除防止出现浅拷贝等不确定问题所以需要用引用或者自己定义深拷贝的拷贝构造函数。 {char ch in.get();while (ch ! \n){s ch;ch in.get();//get()一点一点从缓冲区里面拿字符直到遇到\n这才是getline遇到空格和\n的应该是}return in; }4.swap()
  46. std中的swap实际上是支持内置类型和自定义类型的函数模板并且对于内置类型的定义也支持了像自定义类型一样的拷贝构造、赋值重载等用法但在平常写代码中对于内置类型我们还是用原来的写法下面的模板写法只是为了方便兼容内置和自定义类型。 template class T void swap ( T a, T b ) {T c(a); ab; bc; } void test_string9() {//下面这样的写法是为了支持函数模板有时候模板参数可能是自定义类型或内置类型所以为了兼容内置类型就搞了这样的写法。int i(10);//等价于int i 10;int j int();//匿名对象的赋值重载 }