建设阿里巴巴网站首页,网站域名有效期,whois查询 站长工具,wordpress轮播图替换前面已经对 string 类进行了简单的介绍和应用#xff0c;大家只要能够正常使用即可。 在面试中#xff0c;面试官总喜欢让学生自己 来模拟实现string类#xff0c; 最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。 为了更深入学习STL#xff0c;下面我… 前面已经对 string 类进行了简单的介绍和应用大家只要能够正常使用即可。 在面试中面试官总喜欢让学生自己 来模拟实现string类 最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。 为了更深入学习STL下面我们就自己来模拟实现一下string的常用接口函数 目录 1. string默认成员函数 1.1 构造和析构 1.2 深浅拷贝介绍 1.3 拷贝构造的实现 1.4 赋值的实现 1.5 写时拷贝了解 2. string 的部分函数实现 2.1 完整默认成员函数代码 2.2 c_str() 的实现 2.3 全缺省构造函数的实现 2.4 size() 和 operator[] 的实现 3. string的迭代器 3.1 string迭代器的实现 3.2 迭代器和范围for再思考 4. string的增删查改函数实现 4.1 reserve() 的实现 4.2 push_back() 的实现 4.3 append() 的实现 4.4 operator 的实现 4.5 insert() 的实现 4.6 resize() 的实现 4.7 find() 的实现 4.8 erase() 的实现 5. 传统写法和现代写法 5.1 拷贝构造的现代写法 5.2 赋值重载的现代写法 6. operator 运算符重载 6.1 六个比较运算符重载 6.2 流插入和流提取重载 7. 完整代码 string.h: Test.c: 本章完。 1. string默认成员函数
1.1 构造和析构
我们先试着来实现 string 的构造和析构
整体框架
string.h
#pragma once#includeiostream
#includestring
#includeassert.h
using namespace std;namespace rtx
{class string{public:string(const char* s){}~string() {}private:char* _str;};void test_string1(){string s1(hello world);}
}
Test.c
#include string.hint main()
{try{rtx::test_string1();}catch (const exception e){cout e.what() endl;}return 0;
}
这里为了和原有的 string 进行区分我们搞一个命名空间给它们括起来。
我们的测试就放在简单提到的try catch上然后该序号就能测试了。 构造函数是这样写吗这样写的话拷贝构造能直接用默认生成的吗 string(const char* s): _str(new char[strlen(s) 1])// 开strlen大小的空间(多开一个放\0){strcpy(_str, str);}
然后我们先实现析构用 new[] 对应的 delete[] 来析构 ~string() {delete[] _str;_str nullptr;}
放到上面的框架编译通过 此时我们改一下测试用例 test_string1如果我们要用 s1 拷贝构造一下 s2 详细解析 说明上述string类没有显式定义其拷贝构造函数与赋值运算符重载此时编译器会合成默认的当用 s1 构 造 s2 时编译器会调用默认的拷贝构造。最终导致的问题是 s1 、 s2 共用同一块内存空间在释放时同一块 空间被释放多次而引起程序崩溃 这种拷贝方式称为浅拷贝 1.2 深浅拷贝介绍
如何解决这样的问题呢
我们 s2 拷贝构造你 s1本意并不是想跟你指向一块空间
我们的本意是想让 s2 有一块自己的空间并且能使其内容是 s1 里的 hello world
这就是深拷贝。 所以这里就涉及到了深浅拷贝的问题我们下面就来探讨一下深浅拷贝的问题。 浅拷贝直接把内存无脑指过去 也称位拷贝编译器只是将对象中的值拷贝过来。如果对象中管理资源最后就会导致多个对象共享同一份资源当一个对象销毁时就会将该资源释放掉而此时另一些对象不知道该资源已经被释放以为还有效所以当继续对资源进项操作时就会发生发生了访问违规。 深拷贝开一块一样大的空间再把数据拷贝下来指向我自己开的空间 如果一个类中涉及到资源的管理其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。 1.3 拷贝构造的实现 我们之前实现日期类的时候用自动生成的拷贝构造浅拷贝是可以的
所以当时我们不用自己实现拷贝构造让它默认生成就足够了。
但是像 string 这样的类它的拷贝构造我们不得不亲自写 string(const string s):_str(new char[s._capacity 1]){strcpy(_str, s._str);} 这就实现了深拷贝。 1.4 赋值的实现 现在有一个 s3如果我们想把 s3 赋值给 s1 void test_string1(){string s1(hello world);string s2(s1);string s3(!!!);s1 s3;}
如果你不自己实现赋值就和之前一样会是浅拷贝也会造成崩溃。
所以我们仍然需要自己实现一个 operator 首先思路如下 string operator(const string s){if (this ! s){delete[] _str;// 释放原有空间_str new char[s._capacity 1];// 开辟新的空间strcpy(_str, s._str);// 赋值_size s._size;_capacity s._capacity;}return *this;}
根据我们的实现思路首先释放原有空间然后开辟新的空间
最后把 s3 的值赋值给 s1。为了防止自己给自己赋值我们可以判断一下。
这时我们还要考虑一个难以发现的问题如果 new 失败了怎么办
抛异常失败了没问题也不会走到 strcpy但问题是我们已经把原有的空间释放掉了
走到析构那里二次释放可能会崩所以我们得解决这个问题。
可以试着把释放原有空间的步骤放到后面 string operator(const string s){if (this ! s){char* tmp new char[s._capacity 1];// 开辟新的空间strcpy(tmp, s._str);// 赋值到tmpdelete[] _str;// 释放原有空间_str tmp;// tmp赋值到想要的地方出去tmp就销毁了_size s._size;_capacity s._capacity;}return *this;}
这样一来就算是动态内存开辟失败了我们也不用担心出问题了。
这是更标准的实现方式我们先去开辟空间放到临时变量 tmp 中tmp 翻车就不会执行下面的代码tmp 没有翻车再去释放原有的空间最后再把 tmp 的值交付给 s1
这是非常保险的有效避免了空间没开成还把 s1 空间释放掉的 偷鸡不成蚀把米 的事发生。 1.5 写时拷贝了解 写时拷贝就是一种拖延症是在浅拷贝的基础之上增加了引用计数的方式来实现的。 引用计数用来记录资源使用者的个数。在构造时将资源的计数给成 1 每增加一个对象使用该资源就给计数增加1 当某个对象被销毁时先给该计数减 1 然后再检查是否需要释放资源如果计数为 1 说明该对象时资源的最后一个使用者将该资源释放否则就不能释放因为还有其它对象在使用该资源。 写时拷贝技术实际上是运用了一个 “引用计数” 的概念来实现的。在开辟的空间中多维护四个字节来存储引用计数。 有两种方法 ①多开辟四个字节(pCount)的空间用来记录有多少个指针指向这片空间。 ②在开辟空间的头部预留四个字节的空间来记录有多少个指针指向这片空间。 当我们多开辟一份空间时让引用计数1如果有释放空间那就让计数-1但是此时不是真正的释放是假释放等到引用计数变为 0 时才会真正的释放空间。如果有修改或写的操作那么也让原空间的引用计数-1并且真正开辟新的空间。
写时拷贝涉及多线程等不好的问题所以了解一ha就行 2. string 的部分函数实现
刚才我们为了方便讲解深浅拷贝的问题有些地方所以没有写全。
我们知道string有这几个接口函数 我们实现只是实现常用的且length和size是一样的我们现在增加一些成员
private:char* _str;size_t _size;size_t _capacity; // 有效字符的空间数不算\0 2.1 完整默认成员函数代码
#pragma once#includeiostream
#includestring
#includeassert.h
using namespace std;namespace rtx
{class string{public:string(const char* s){_size strlen(s);// 因为要算多次strlen 效率低 且放在初始化列表关联到声明顺序 所以不用初始化列表_capacity _size;_str new char[_size 1];// 开_size1大小的空间(多开一个放\0)strcpy(_str, s);}string(const string s):_str(new char[s._capacity 1]), _size(s._size), _capacity(s._capacity){strcpy(_str, s._str);}string operator(const string s){if (this ! s){char* tmp new char[s._capacity 1];// 开辟新的空间strcpy(tmp, s._str);// 赋值到tmpdelete[] _str;// 释放原有空间_str tmp;// tmp赋值到想要的地方出去tmp就销毁了_size s.size();_capacity s._capacity;}return *this;}~string() {delete[] _str;_str nullptr;}private:char* _str;size_t _size;size_t _capacity;};void test_string1(){string s1(hello world);string s2(s1);string s3(!!!);s1 s3;}
} 2.2 c_str() 的实现 c_str() 返回的是C语言字符串的指针常量是可读不写的 const char* c_str() const {return _str;}
返回const char*因为是可读不可写的所以我们需要用 const 修饰。
c_str 返回的是当前字符串的首字符地址这里我们直接 return _str 即可实现。
测试一下 void test_string1(){string s1(hello world);string s2(s1);string s3(!!!);s1 s3;cout s1.c_str() endl;cout s2.c_str() endl;cout s3.c_str() endl;} 2.3 全缺省构造函数的实现
还要考虑不带参的情况比如下面的 s4 void test_string1(){string s1(hello world);string s2(s1);string s3(!!!);s1 s3;cout s1.c_str() endl;cout s2.c_str() endl;cout s3.c_str() endl;string s4;}
无参构造函数 string(): _str(new char[1]), _size(0), _capacity(0) {_str[0] \0;}
一般的类都是提供全缺省的值得注意的是这里缺省值给的是
有人看到指针 char* 可能给缺省值一个空指针 nullptr
string(const char* str nullptr)
也就相当于直接对这个字符串进行解引用了这里的字符串又是空所以会引发空指针问题。
所以我们这里给的是一个空的字符串 常量字符串默认就带有 \0这样就不会出问题 string(const char* s ){_size strlen(s);// 因为要算多次strlen 效率低 且放在初始化列表关联到声明顺序 所以不用初始化列表_capacity _size;_str new char[_size 1];// 开_size1大小的空间(多开一个放\0)strcpy(_str, s);}
这样达到的效果和无参构造函数是一样的且无参编译器不知道调用哪个
所以我们就需把无参构造函数删了。 2.4 size() 和 operator[] 的实现 size()的实现 size_t size() const{return _size;}
size() 只需要返回成员 _size 即可考虑到不需要修改我们加上 const。 operator[] 的实现 char operator[](size_t pos){assert(pos _size);return _str[pos];}
直接返回字符串对应下标位置的元素
因为返回的是一个字符所以我们这里引用返回 char。
我们来测试一下遍历整个字符串这样既可以测试到 size() 也可以测试到 operator[] void test_string2() {string s1(hello world);string s2;for (size_t i 0; i s1.size(); i) {cout s1[i] ;}cout endl;s1[0] x;for (size_t i 0; i s1.size(); i){cout s1[i] ;}cout endl;} 普通对象可以调用但是 const 对象呢所以我们还要考虑一下 const 对象。
我们写一个 const 对象的重载版本 const char operator[](size_t pos) const{assert(pos _size);return _str[pos];}
因为返回的是 pos 位置字符的 const 引用所以可读但不可写。 3. string的迭代器
在上上篇中我们首次讲解迭代器为了方便理解我们当时解释其为像指针一样的类型。
实际上有没有一种可能它就是一种指针呢
遗憾的是迭代器并非指针而是类模板。 只是它表现地像指针模拟了指针的部分功能。
string迭代器的实现非常简单它就是一个 char* 的指针罢了。
后面我们讲解 list 的时候它的迭代器又不是指针了又是自定义类型了。
所以迭代器是一个像指针的东西有可能是指针有可能不是指针。 3.1 string迭代器的实现
实现迭代器的 begin() 和 end() typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;} const 迭代器就是可以读但是不可以写的迭代器实现一下 const 迭代器 typedef const char* const_iterator;const_iterator begin() const {return _str;}const_iterator end() const{return _str _size;} 3.2 迭代器和范围for再思考
迭代器的底层是连续的物理空间给原生指针解引用能正好贴合迭代器的行为能做到遍历。
但是对于链表和树型结构来说迭代器的实现就没有这么简单了。
但是强大的迭代器通过统一的封装无论是树、链表还是数组……
它都能用统一的方式遍历这就是迭代器的优势也是它的强大之处。 我们上一章提到过范围for遍历string我们能不能直接用在我们写的迭代器上 void test_string3(){string s1(hello world);string::iterator it s1.begin();while (it ! s1.end()){cout *it ;// 读it;}cout endl;it s1.begin();while (it ! s1.end()){(*it);// 写的优先级比*高就低可以 *it 1;cout *it ;it;}cout endl;for (auto e : s1){cout e ;}cout endl;} 我们也妹写范围 for 啊怎么就能直接用了
所以范围 for 根本就不需要自己实现你只要把迭代器实现好范围 for 直接就可以用。
范围 for 的本质是由迭代器支持的编译时范围 for 会被替换成迭代器。
这么一看又是自动加加又是自动判断结束的范围 for好像也没那么回事儿。 4. string的增删查改函数实现
4.1 reserve() 的实现
我们先实现一下 reserve 增容
这里可以检查一下是否真的需要增容万一接收的 new_capacity 比 _capacity 小就不动。 void reserve(size_t new_capacity){if (new_capacity _capacity){char* tmp new char[new_capacity 1];// 开新空间strcpy(tmp, _str);// 搬运delete[] _str; // 释放原空间_str tmp;// 没问题递交给_str_capacity new_capacity;// 更新容量}}
这里我们之前讲数据结构用的是 realloc现在我们熟悉熟悉用 new 还是用申请新空间、原空间数据拷贝到新空间再释放空间地方式去扩容。
我们的 _capacity 存储的是有效字符没算 \0所以这里还要 1 为 \0 开一个空间。 4.2 push_back() 的实现 void push_back(const char ch){if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] ch;// 在_size位置放字符后_str[_size] \0;// 易漏}
首先检查是否需要增容如果需要就调用我们上面实现的 reserve 函数
参数传递可以用三目操作符防止容量是0的情况0乘任何数都是0从而引发问题的情况。
测试一下效果如何 4.3 append() 的实现
append 是追加字符串的首先我们把要追加的字符串长度计算出来
然后看容量够不够不够我们就交给 reserve 去扩容扩 _size len够用就行。 void append(const char* str){int len strlen(str);if (_size len _capacity){reserve(_size len);}strcpy(_str _size, str);// 首字符_size大小就是\0位置_size len;}
这里我们甚至都不需要用 strcat因为它的位置我们很清楚不就在 _str _size 后面插入吗。
用 strcat 还需要遍历找到原来位置的 \0麻烦且效率低strcat 函数我们以后尽量都不用。 4.4 operator 的实现
这就是我们一章说的 用起来爽到飞起 的 因为字符和字符串都可以用 去操作。
所以我们需要两个重载版本一个是字符的一个是字符串的。
我们不需要自己实现了直接复用 push_back 和 append 就好了 string operator(const char ch){push_back(ch);return *this;}string operator(const char* str){append(str);return *this;}
测试 void test_string4() {string s1(hello world);cout s1.c_str() endl;s1.push_back(!);cout s1.c_str() endl;s1.push_back(R);cout s1.c_str() endl;s1.append(abcd);cout s1.c_str() endl;s1 e;s1 fgh;cout s1.c_str() endl;} 4.5 insert() 的实现
insert字符 string insert(size_t pos, const char ch){assert(pos _size);if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}for (size_t i _size 1;i pos; --i)// 挪动数据,1是挪动\0{_str[i] _str[i - 1];}_str[pos] ch;_size;return *this;}
insert字符串 string insert(size_t pos, const char* str){assert(pos _size);int len strlen(str);if (_size len _capacity){reserve(_size len);}for (size_t i _size len ;i pos len - 1; --i)// 挪动数据画图注意边界参考上面inser字符的len 1{_str[i] _str[i - len];// 首先看\0 _sizelen-len就是\0的位置}strncpy(_str pos, str, len);_size len;return *this;} 测试 void test_string4() {string s1(hello world);cout s1.c_str() endl;s1.push_back(!);cout s1.c_str() endl;s1.push_back(R);cout s1.c_str() endl;s1.append(abcd);cout s1.c_str() endl;s1 e;s1 fgh;cout s1.c_str() endl;s1.insert(0, x);s1.insert(6, T);cout s1.c_str() endl;s1.insert(6, PPPPPPPPPP);cout s1.c_str() endl;s1.insert(0, PPPPPPPPPP);cout s1.c_str() endl;} 测试后push_back 和 append 直接复用还能测试一波
又是复用push_back 和 append 的直接套娃开始 void reserve(size_t new_capacity){if (new_capacity _capacity){char* tmp new char[new_capacity 1];// 开新空间strcpy(tmp, _str);// 搬运delete[] _str; // 释放原空间_str tmp;// 没问题递交给_str_capacity new_capacity;// 更新容量}}void push_back(const char ch){if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] ch;// 在_size位置放字符后_str[_size] \0;// 易漏//insert(_size, ch);} 4.6 resize() 的实现
我们为了扩容先实现了 reverse现在我们再顺便实现一下 resize。
这里再提一下 reverse 和 resize 的区别
resize 分给初始值和不给初始值的情况所以有两种 库里面也是这么实现的。
但是我们上面讲构造函数的时候说过我们可以使用全缺省的方式这样就可以二合一了。
resize 实现的难点是要考虑种种情况我们来举个例子分析一下 如果欲增容量比 _size 小的情况
因为标准库是没有缩容的所以我们实现的时候也不考虑去缩容。我们可以加一个 \0 去截断。
如果预增容量比 _size 大的情况
resize 是开空间 初始化开空间的工作我们就可以交给已经实现好的 reserve
然后再写 resize 的初始化的功能我们这里可以使用 memset 函数。 void resize(size_t new_capacity, const char ch \0){if (new_capacity _size)// 插入数据{reserve(new_capacity);//for (size_t i _size; i new_capacity; i)//{// _str[i] ch;//}memset(_str _size, ch, new_capacity - _size);// 上面的for循环即memset的功能_str[new_capacity] \0;_size new_capacity;}else// 删除数据{_str[new_capacity] \0;_size new_capacity;}} 4.7 find() 的实现 find查找字符
如果遍历完整个字符串都没找到就返回 npos找到库的来。
这个 npos 我们可以在成员变量中定义 size_t find(const char ch) const{for (size_t i 0; i _size; i) {if (ch _str[i])// 找到了{return i; // 返回下标}}return npos;// 找不到}private:char* _str;size_t _size;size_t _capacity;public:const static size_t npos -1;// const static 语法特殊处理直接可以当成定义初始化 find查找字符串
这里我们可以用 strstr 去找子串如果找到了返回的是子串首次出现的地址。
如果没找到返回的是空。所以我们这里可以做判断如果是 nullptr 就返回 npos。
如果找到了就返回对应下标子串地址 - 开头就是下标了。 size_t find(const char* str, size_t pos 0) const{const char* ptr strstr(_str pos, str);if (ptr nullptr) {return npos;}else {return ptr - _str; // 减开头}} 4.8 erase() 的实现 string erase(size_t pos, size_t len npos) {assert(pos _size);if (len npos || pos len _size)// 如果pos后面的都删完了注意len npos 不能忽略因为npos len 有可能重回到 1{_str[pos] \0;_size pos;}else{strcpy(_str pos, _str pos len);_size - len;}return *this;}
测试find 和 erase 5. 传统写法和现代写法
对于拷贝构造的深拷贝传统写法就是本本分分分地去完成深拷贝 string(const string s)/传统写法:_str(new char[s._capacity 1]), _size(s._size), _capacity(s._capacity){strcpy(_str, s._str);}
5.1 拷贝构造的现代写法
现在我们来介绍一种现代写法它和传统写法本质工作是一样的即完成深拷贝。
现代写法的方式不是本本分分地去按着 Step 一步步干活而是 投机取巧 地去完成深拷贝。 void swap(string s)// s和*this换{::swap(s._str, _str);//注意这里要加域作用符默认是全局的不然就是自己调自己了::swap(s._size, _size);::swap(s._capacity, _capacity);}string(const string s)/现代写法:_str(nullptr), _size(0), _capacity(0){string tmp(s._str);swap(tmp);// tmp和*this换}
现代写法的本质就是复用了构造函数。
我想拷贝但我又不想自己干我把活交给工具人 tmp 来帮我干。 值得注意的是如果不给原_str赋空指针那么它的默认指向会是个随机值。和tmp交换后tmp 是一个局部对象我们把 s2 原来的指针和 tmp 交换了那么 tmp 就成了个随机值了。tmp 出了作用域要调用析构函数对随机值指向的空间进行释放怎么释放都不是你自己的 new / malloc 出来的你还硬要对它释放就可能会引发崩溃。但是 delete / free 一个空是不会报错的因为会进行一个检查。所以是可以 delete 一个空的我们这里初始化列表中把 nullptr 给 _str是为了交换完之后 nullptr 能交到 tmp 手中这样 tmp 出了作用域调用析构函数就不会翻车了。 5.2 赋值重载的现代写法
赋值重载的传统写法 string operator(const string s)/传统写法{if (this ! s){char* tmp new char[s._capacity 1];// 开辟新的空间strcpy(tmp, s._str);// 赋值到tmpdelete[] _str;// 释放原有空间_str tmp;// tmp赋值到想要的地方出去tmp就销毁了_size s._size;_capacity s._capacity;}return *this;}
赋值重载的现代写法 string operator(const string s)/现代写法{if (this ! s){string tmp(s);swap(tmp);// tmp和*this换}return *this;}
比上面的拷贝构造的现代写法还要压榨tmp交换完之后正好让 tmp 出作用域调用析构函数属实是一石二鸟的美事。把 tmp 压榨的干干净净还让 tmp 帮忙把屁股擦干净释放空间。
用上面的测试简单测一下 总结
现代写法在 string 中体现的优势还不够大因为好像和传统写法差不多。 但是到后面实现 vector、list 的时候会发现现代写法的优势真的是太大了。
现代写法写起来会更简单些比如如果是个链表传统写法就不是 strcpy 这么简单的了
你还要一个一个结点拷贝过去但是现代写法只需要调用 swap 交换一下就可以了。
现代写法更加简洁只是在 string 这里优势体现的不明显罢了我们后面可以慢慢体会。 6. operator 运算符重载
6.1 六个比较运算符重载
学日期类的时候就说过只需实现 和 剩下的都可以复用解决
而且 和 可以直接用strcmp 剩下的复用你不想用strcmp时剩下的也不用改了 bool operator(const string s) const{return strcmp(_str, s._str) 0;}bool operator(const string s) const{return strcmp(_str, s._str) 0;} bool operator(const string s) const// 养成this指针写在前面的习惯{return *this s || *this s;}bool operator(const string s) const{return !(*this s);} bool operator(const string s) const{return !(*this s);} bool operator!(const string s) const{return !(*this s);} 6.2 流插入和流提取重载 我们当时实现日期类的流插入和流提取时也详细讲过这些当时讲解了友元。 在友元那一章我们说过 占参问题 这里就不再多做解释了。
如果我们重载成成员函数第一个位置就会被隐含的 this 指针占据。
这样实现出来的流插入必然会不符合我们的使用习惯所以我们选择在全局实现。
在全局里不存在隐含的 this 指针了。
而且我们已经有operator [ ] 可以访问私有成员了所以不需要设置成友元函数
流插入很简单 ostream operator(ostream out, string s){for (size_t i 0;i s.size();i){out s[i];}return out;}
但是流提取是这么简单吗下面的代码有什么问题 istream operator(istream in, string s){char ch;in ch;while (ch ! ch ! \n){s ch;in ch;}return in;}
我们发现这样输入空格和换行也终止不了程序因为cin会自动忽略空格和换行。
有什么办法cin有一个get的成员函数可以获取每一个字符现在我们查下文档会用就行
后面我们还会详细的讲解IO流流提取普通实现 istream operator(istream in, string s){char ch;ch in.get();while (ch ! ch ! \n){s ch;ch in.get();}return in;}
这样实现的流提取有一个缺陷频繁的 效率低能想到什么办法优化
以下是类似库里面的实现思路类似缓冲区 istream operator(istream in, string s)// 流插入优化类似库里面的{char ch;ch in.get();const size_t N 32;char buff[N];// C11支持的变长数组size_t i 0;while (ch ! ch ! \n){buff[i] ch;if (i N - 1)// 如果buff的容量满了{buff[i] \0;// 在后面放\0s buff;// 到 s 上i 0;// 把 i 重新变成0 用来再次使用buff数组}ch in.get();}buff[i] \0;// 处理一下buff剩余的s buff;return in;}
简单测试下 void test_string6(){string s1;string s2;cin s1 s2;cout s1 endl s2 endl;cout (s1 s2) endl;cout (s1 s2) endl;cout (s1 s2) endl;cout (s1 s2) endl;cout (s1 s2) endl;cout (s1 ! s2) endl;} 7. 完整代码
string.h:
#pragma once#includeiostream
#includestring
#includeassert.h
using namespace std;namespace rtx
{class string{public:string(const char* s ){_size strlen(s);// 因为要算多次strlen 效率低 且放在初始化列表关联到声明顺序 所以不用初始化列表_capacity _size;_str new char[_size 1];// 开_size1大小的空间(多开一个放\0)strcpy(_str, s);}//string(const string s)/传统写法// :_str(new char[s._capacity 1])// , _size(s._size)// , _capacity(s._capacity)//{// strcpy(_str, s._str);//}void swap(string s)// s和*this换{::swap(s._str, _str);//注意这里要加域作用符默认是全局的不然就是自己调自己了::swap(s._size, _size);::swap(s._capacity, _capacity);}string(const string s)/现代写法:_str(nullptr), _size(0), _capacity(0){string tmp(s._str);swap(tmp);// tmp和*this换}//string operator(const string s)/传统写法//{// if (this ! s)// {// char* tmp new char[s._capacity 1];// 开辟新的空间// strcpy(tmp, s._str);// 赋值到tmp// delete[] _str;// 释放原有空间// _str tmp;// tmp赋值到想要的地方出去tmp就销毁了// _size s._size;// _capacity s._capacity;// }// return *this;//} string operator(const string s)/现代写法{if (this ! s){string tmp(s);swap(tmp);// tmp和*this换}return *this;}~string() {delete[] _str;_str nullptr;}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];}typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}typedef const char* const_iterator;const_iterator begin() const {return _str;}const_iterator end() const{return _str _size;}void reserve(size_t new_capacity){if (new_capacity _capacity){char* tmp new char[new_capacity 1];// 开新空间strcpy(tmp, _str);// 搬运delete[] _str; // 释放原空间_str tmp;// 没问题递交给_str_capacity new_capacity;// 更新容量}}void push_back(const char ch){if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] ch;// 在_size位置放字符后_str[_size] \0;// 易漏//insert(_size, ch);}void append(const char* str){int len strlen(str);if (_size len _capacity){reserve(_size len);}strcpy(_str _size, str);// 首字符_size大小就是\0位置_size len;//insert(_size, str);}string operator(const char ch){push_back(ch);return *this;}string operator(const char* str){append(str);return *this;}string insert(size_t pos, const char ch){assert(pos _size);if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}for (size_t i _size 1;i pos; --i)// 挪动数据,1是挪动\0{_str[i] _str[i - 1];}_str[pos] ch;_size;return *this;}string insert(size_t pos, const char* str){assert(pos _size);int len strlen(str);if (_size len _capacity){reserve(_size len);}for (size_t i _size len ;i pos len - 1; --i)// 挪动数据画图注意边界参考上面inser字符的len 1{_str[i] _str[i - len];// 首先看\0 _sizelen-len就是\0的位置}strncpy(_str pos, str, len);_size len;return *this;}void resize(size_t new_capacity, const char ch \0){if (new_capacity _size)// 插入数据{reserve(new_capacity);//for (size_t i _size; i new_capacity; i)//{// _str[i] ch;//}memset(_str _size, ch, new_capacity - _size);// 上面的for循环即memset的功能_str[new_capacity] \0;_size new_capacity;}else// 删除数据{_str[new_capacity] \0;_size new_capacity;}}size_t find(char ch) const{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) const{const char* ptr strstr(_str pos, str);if (ptr nullptr) {return npos;}else {return ptr - _str; // 减开头}}string erase(size_t pos, size_t len npos) {assert(pos _size);if (len npos || pos len _size)// 如果pos后面的都删完了注意len npos 不能忽略因为npos len 有可能重回到 1{_str[pos] \0;_size pos;}else{strcpy(_str pos, _str pos len);_size - len;}return *this;}bool operator(const string s) const{return strcmp(_str, s._str) 0;}bool operator(const string s) const{return strcmp(_str, s._str) 0;} bool operator(const string s) const// 养成this指针写在前面的习惯{return *this s || *this s;}bool operator(const string s) const{return !(*this s);} bool operator(const string s) const{return !(*this s);} bool operator!(const string s) const{return !(*this s);}private:char* _str;size_t _size;size_t _capacity;public:const static size_t npos -1;// const static 语法特殊处理直接可以当成定义初始化};ostream operator(ostream out, string s){for (size_t i 0;i s.size();i){out s[i];}return out;}//istream operator(istream in, string s)// 流插入普通实现//{// char ch;// ch in.get();// while (ch ! ch ! \n)// {// s ch;// ch in.get();// }// return in;//}istream operator(istream in, string s)// 流插入优化类似库里面的{char ch;ch in.get();const size_t N 32;char buff[N];// C11支持的变长数组size_t i 0;while (ch ! ch ! \n){buff[i] ch;if (i N - 1)// 如果buff的容量满了{buff[i] \0;// 在后面放\0s buff;// 到 s 上i 0;// 把 i 重新变成0 用来再次使用buff数组}ch in.get();}buff[i] \0;// 处理一下buff剩余的s buff;return in;}void test_string1(){string s1(hello world);string s2(s1);string s3(!!!);s1 s3;cout s1.c_str() endl;cout s2.c_str() endl;cout s3.c_str() endl;string s4;cout s4.c_str() endl;}void test_string2() {string s1(hello world);string s2;for (size_t i 0; i s1.size(); i) {cout s1[i] ;}cout endl;s1[0] x;for (size_t i 0; i s1.size(); i){cout s1[i] ;}cout endl;}void test_string3(){string s1(hello world);string::iterator it s1.begin();while (it ! s1.end()){cout *it ;// 读it;}cout endl;it s1.begin();while (it ! s1.end()){(*it);// 写的优先级比*高就低可以 *it 1;cout *it ;it;}cout endl;for (auto e : s1){cout e ;}cout endl;}void test_string4() {string s1(hello world);cout s1.c_str() endl;s1.push_back(!);cout s1.c_str() endl;s1.push_back(R);cout s1.c_str() endl;s1.append(abcd);cout s1.c_str() endl;s1 e;s1 fgh;cout s1.c_str() endl;s1.insert(0, x);s1.insert(6, T);cout s1.c_str() endl;s1.insert(6, PPPPPPPPPP);cout s1.c_str() endl;s1.insert(0, PPPPPPPPPP);cout s1.c_str() endl;s1.resize(100,x);cout s1.c_str() endl;}void test_string5(){string s1(hello world);string s2(s1);string s3 s1;cout s2.c_str() endl s3.c_str() endl;cout s1.find(d) endl;// 打印d的下标:6cout s1.find(world) endl;// 打印了w的下标:10cout s1.find(wold) endl;// 打印了npos:4294967295cout s1.find(world, 9) endl;// 打印了npos:4294967295s1.erase(9, 2);// 从下标9开始删除2个字符cout s1.c_str() endl;s1.erase(s1.find(o), 2);// 找到o其下标为4从下标4开始删除2个字符cout s1.c_str() endl;s1.erase(5);// 从下标5开始删完cout s1.c_str() endl;}void test_string6(){string s1;string s2;cin s1 s2;cout s1 endl s2 endl;cout (s1 s2) endl;cout (s1 s2) endl;cout (s1 s2) endl;cout (s1 s2) endl;cout (s1 s2) endl;cout (s1 ! s2) endl;}
}
Test.c:
#define _CRT_SECURE_NO_WARNINGS 1#include string.hint main()
{try{rtx::test_string6();}catch (const exception e){cout e.what() endl;}return 0;
}
本章完。
这篇博客两万多个字了......刚开始接触确实有点累der
不过STL的实现都是类似的以后的学习就轻松多了
下一步了解vector的接口函数写写vector的OJ题模拟实现vector。
想笑~ 来伪装掉下的眼泪~ 23年5月27早起把博客发出去我又行了。