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

怎么做网站的后台管理系统济南网站建设新风向

怎么做网站的后台管理系统,济南网站建设新风向,结构设计师之家官网,wordpress欢迎邮件代码1. 前言 了解 string 的各个常用的接口之后#xff0c;可以尝试模拟实现 string 类。模拟实现string 类并不是为了实现一个与库中一样的 string #xff0c;而是为了让我们更好的了解 string 类的底层#xff0c;在之后使用 string 类出现错误时#xff0c;能快速的找到问…1. 前言 了解 string 的各个常用的接口之后可以尝试模拟实现 string 类。模拟实现string 类并不是为了实现一个与库中一样的 string 而是为了让我们更好的了解 string 类的底层在之后使用 string 类出现错误时能快速的找到问题并解决问题同时能够加深对 C 知识的掌握。这里的模拟实现函数的声明与定义分离。在模拟实现的过程中为了避免命名冲突的问题三个文件中都可以使用关键字 namespace 。 2. string 类的模拟实现 2.1 无参带参构造函数以及 c_str 函数 在实现构造函数之前先实现 string 的底层结构在介绍 string 的时候提到string的底层其实就是一个存储字符的顺序表顺序表在数据结构中已经接触到了它的底层结构是指向数组的指针有效数据个数当前数组的容量大小。如下代码所示 namespace AY {class string{public:private:char* m_str;size_t m_size;size_t m_capacity;}; } 底层的结构实现完毕后接下来先实现无参的构造函数 string() 。首先面对的问题是对成员变量初始化一开始我们可能会想这里是否与数据结构中的顺序表的初始化函数中的思路一致呢先这样尝试将指针初始化为空指针其余两个变量初始化为0 namespace AY {// 指明类域string::string(): m_str(nullptr), m_size(0), m_capacity(0){} } 无参的构造函数实现完毕后接下来实现 c_str 函数。该函数的作用是返回 string 的底层指针即 m_str。代码如下所示 // c_str 函数 const char* string::c_str() const {return m_str; } c_str 函数实现完毕后下面来检验之前实现的无参的构造函数是否存在问题 #include string.husing namespace std;namespace AY {void func1(){string str;cout str.c_str() endl;} }int main() {AY::func1();return 0; } 当运行上述程序时会发现程序崩溃接下来使用库中 string 来运行同一段程序观察结果如何 当使用库中 string 时程序却正常运行这是怎么一回事呢c_str 函数是没有问题的问题出现在无参的构造函数的初始化。m_str 被初始化为 nullptr 那么 c_str 函数返回的就是空指针相当于返回了 const char* 类型的空指针。需要注意的是 const char* 类型的指针与其他类型的指针有些不同其它类型的指针都会按照十六进制去打印地址而 const char* 类型的指针不会那样去打印地址。为什么呢因为 const char* 的第一原则并不是去打印地址而是先去访问该指针指向的数组的内容。访问指针指向的数组的内容需要解引用这里的 m_str 是空指针所以程序会崩溃是因为对空指针进行了解引用操作。优化后的代码如下所示 string::string(): m_str(new char[1]{}), m_size(0), m_capacity(0) {} 先开辟一个char类型大小的空间为了能够表示数组中没有数据用空字符串来填充这个空间。当然也可以这样 m_str(new char[1]{\0}) 。无参的构造函数实现完毕后接下来开始实现带参的构造函数即 string(const char* s) 。 首先面临的问题仍然是对成员变量初始化这里不能直接将形参 s 赋值给成员变量 m_str 因为 m_str 指向的字符串是可以改变的而这里的形参 s 是常量串这里应该开辟一块空间将字符串 s 中的数据拷贝到 m_str 中开辟的空间大小为字符串 s 的大小并且要加1即开辟 strlen(str) 1 大小的空间strlen 求得的字符串长度并不包含’\0’这里的加1是为了存储 ’\0’ 其余的两个成员变量初始化为 strlen(str) 。如下代码所示 // 带参的构造函数 string::string(const char* s): m_str(new char[strlen(s) 1]), m_size(strlen(s)), m_capacity(strlen(s)) {// 记得引用头文件 string.hmemcpy(m_str, s, strlen(s) 1); } 其实这样写还是存在一些不足之处 —— strlen 函数的多次使用。众所周知strlen 和 sizeof 都可以求取字符串的长度但是它俩有些不同sizeof 求得的结果包含 \0 而 strlen 不包含除此以外sizeof 求取字符串的长度是在编译时计算的而strlen 是在运行时计算的。这里使用了三次 strlen 函数会降低程序的运行效率所以这样初始化有些不足。那么怎么优化呢既然多次使用 strlen 函数不好那么少使用 strlen 函数或者只使用一次 strlen 函数就可以了呀让 m_size 用 strlen(s) 来初始化其余的两个变量的初始化都复用m_size的结果代码如下所示 // 带参的构造函数 string::string(const char* s): m_size(strlen(s)), m_str(new char[m_size 1]), m_capacity(m_size) {// 记得引用头文件 string.hmemcpy(m_str, s, strlen(s) 1); } 讲解了 strlen 的不足之处后很容易想到这样的方法但是这种方法是正确的吗并不正确需要注意初始化列表的初始化顺序是按照成员变量的声明顺序来初始化的按照现在所写的成员变量的声明m_str 是第一个初始化的这时 m_size 还未初始化只是一个随机值或者是 0 最终会产生意料之外的结果。既然是成员变量的声明顺序导致的这个结果那么调整成员变量的声明顺序不就可以了调整结果如下所示 size_t m_size; char* m_str; size_t m_capacity; 这样写也并不正确这样会增加代码之间的耦合。也就是说只要改了这里的成员变量的声明的顺序那么就会导致程序的崩溃。代码之间的耦合太高并不好修改这串代码影响那串代码修改那串代码影响这串代码。所以这种思路也存在不足将成员变量的声明顺序改为原来的顺序开始思考更好的解决办法。 既然初始化列表这里有很多坑那么不使用初始化列表来初始化成员变量或者部分成员变量使用初始化列表来初始化问题不就解决了吗虽然所有的成员变量都会通过初始化列表来初始化但并不意味着这些成员变量只能通过初始化列表来初始化还可以在函数体内初始化。所以可以让 m_size 通过初始化列表来初始化其余的两个成员变量在构造函数的函数体内初始化。代码如下所示 // 带参的构造函数 string::string(const char* s): m_size(strlen(s)) {m_str new char[m_size 1];m_capacity m_size;// 记得引用头文件 string.hmemcpy(m_str, s, strlen(s) 1); } 2.2 全缺省的构造函数和析构函数 2.2.1 全缺省的构造函数 无参和带参的构造函数实现完毕后将而二者合一实现一个全缺省的构造函数全缺省的构造函数的函数体与带参的构造函数的函数体中的代码差不多只是在函数参数上有些不同代码如下所示 // 函数的声明 string(const char* s )// 函数的定义 // 全缺省的构造函数 string::string(const char* s): m_size(strlen(s)) {m_str new char[m_size 1];m_capacity m_size;// 记得引用头文件 string.hmemcpy(m_str, s, strlen(s) 1); } 将全缺省的构造函数实现完毕后将可以将无参和带参的构造函数注释掉了。 2.2.2 析构函数 既然有资源的申请那么就有资源的释放接下来实现析构函数即 ~string() 该函数实现起来并不复杂与数据结构中顺序表的 Destory 函数差不多只是这里释放资源是使用 delete 。代码如下所示 // 析构函数 string::~string() {delete[] m_str;m_str nullptr;m_size m_capacity 0; } 2.3 size 函数和 operator[ ] 函数 2.3.1 size 函数 size 函数的作用的是返回字符串的有效字符长度不包含\0在 string 的底层中 m_size 的大小就表示字符串的有效字符长度所以 size 函数的实现直接返回 m_size 即可代码如下所示 // size 函数 size_t string::size() const {return m_size; } 2.3.2 operator[ ] 函数 operator[ ] 函数有两个版本一个是普通版本一个是 const 版本。但是它俩的函数体的代码都是一样的返回 pos 位置的数据。在返回 pos 位置的数据之前需要检验 pos 的合法性即是否大于当前字符串的有效字符串长度 —— m_size)代码如下所示 // operator[] 函数 —— 普通版本 char string::operator[] (size_t pos) {// 记得引用头文件assert.hassert(pos m_size);return m_str[pos]; }// operator[] 函数 —— const 版本 const char string::operator[] (size_t pos) const {// 记得引用头文件assert.hassert(pos m_size);return m_str[pos]; } 2.4 迭代器和范围 for 之前提到过范围 for 的底层就是迭代器。这里要实现的迭代器有两个版本 —— 普通正向迭代器和 const 正向迭代器。 先来实现普通迭代器在此之前先实现 begin 和 end 函数的普通版本。begin 和 end 函数的返回值类型为 iterator 但是内置类型中并没有 iterator 类型呀观察 string 的源码可以发现其实 iterator 是某个类型 typedef 得到的所以我们也可以这么干。而迭代器又是像指针一样的东西所以不妨将 char* typedef 为 iterator 。 解决了函数的返回值类型的问题之后接下来分析 begin 和 end 函数怎么实现我们可以知道 begin 和 end 函数的作用分别是返回指向字符串的第一个字符迭代器 和 返回指向容器末尾最后一个元素之后即‘\0的迭代器。m_str 指向的就是字符串的首字符的地址所以 begin 函数直接返回 m_str 即可怎么得到指向容器末尾的迭代器呢 m_str 加上 m_size 指向的就是字符串最后一个字符的地址这样一来就得到了指向容器末尾的迭代器。 之所以可以这样实现是因为将 char* 重命名为 iterator 。分析完毕具体代码如下所示 // 重命名 —— 在.h文件中 typedef char* iterator;// 函数的实现 // begin 函数 string::iterator string::begin() {return m_str; }// end 函数 string::iterator string::end() {return m_str m_size; } 普通版本的 begin 和 end 函数实现完毕后接下来使用这两个函数来实现迭代器和范围 for 。运行结果如下图所示 普通迭代器实现完毕后接下来再实现 const 迭代器在此之前先实现 const 版本的 begin 和 end 函数。与普通迭代器一致将 const char* typedef 为 const_iterator 。函数体的实现与普通迭代器的一模一样这里就不过多赘述。分析完毕具体代码如下所示 // 重命名 —— 在.h文件中 typedef const char* const_iterator;// 函数的实现 // const 版本的 begin 函数 string::const_iterator string::begin() const {return m_str; }// const 版本的 end 函数 string::const_iterator string::end() const {return m_str m_size; } const 迭代器是不可以修改字符串的字符的大小的只能遍历字符串的数据不能修改字符串的数据。我们来尝试修改字符串中的数据观察编译器是否会报错若报错则说明我们实现的函数没有问题反之则有问题。若想要范围 for 使用的 const 迭代器可以在类型的前面加上 const 。测试结果如下图所示 2.5 reserve 函数push_back 函数和 append 函数 2.5.1 reserve 函数 reserve 函数的作用是扩容为字符串预留空间将字符串的 capacity 的大小改变到 n 。需要注意的是 reserve 函数的扩容是异地扩容既然是异地扩容就免不了开辟新空间拷贝旧数据释放旧空间更新变量的数据这几个步骤。这里的扩容逻辑与数据结构顺序表的逻辑并无二异。只是这里的扩容需要手动扩new 一块空间。具体代码如下所示 // reserve 函数 —— 扩容异地扩容 void string::reserve(size_t n) {if(n m_capacity){// 开辟新空间 —— 为\0预留一块空间char* str new char[n 1];// 拷贝旧数据memcpy(str, m_str, m_size 1);// 释放旧空间delete[] m_str;// 更新变量的数据m_str str;m_capacity n;} } 2.5.2 push_back 函数 push_back 函数的作用是尾插字符。既然要插入数据那么就得考虑是否需要扩容即需要调用 reserve 函数。什么情况下需要扩容呢当有效数据个数大于或等于当前容器的空间大小时就需要扩容。执行完扩容操作后接下来插入数据往字符串的末尾插入数据即 m_str[m_size] 赋值为字符 c然后有效数据个数再加1之后记得手动加上 \0 因为m_size 处的数据由 \0 变成 字符 c 了以表示是字符串。分析完毕代码如下所示 // push_back 函数 void string::push_back(char c) {// 判断是否需要扩容if (m_size m_capacity){size_t newcapacity m_capacity 0 ? 4 : 2 * m_capacity;reserve(newcapacity);}// 插入数据m_str[m_size] c;// 有效数据个数 1m_size;// 手动加上 \0m_str[m_size] \0; } 2.5.3 append 函数  这里我们只实现追加字符串函数原型为string append(const char* s) 。插入字符串也意味着需要扩容那么怎么扩容呢扩多大呢不能再像之前那样无脑2倍扩容了这里在扩容之前先计算要追加的字符串的长度 len 之后再判断 size  len 与 2 * capacity 之间的关系若是小于则就2倍扩容若是大于则扩的空间为 size len 大小。当然也可以简单一点需要多少开辟多少直接开 size len 大小的空间。分析完毕具体代码如下所示 // append 函数 string string::append(const char* s) {//先求出待插入的字符串的长度size_t len strlen(s);// 判断是否需要扩容if (m_size len m_capacity){size_t newcapacity m_size len 2 * m_capacity? m_size len : 2 * m_capacity;reserve(newcapacity);}// 插入数据 —— 1 是算上了\0memcpy(m_str m_size, s, len 1);// 有效数据个数 lenm_size len;return *this; } append 函数实现完毕检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 2.6 operator 函数和流插入运算符函数 2.6.1 operator 函数 operator 函数的作用与 push_back 和 append 函数的实现并无二异都是在字符串的尾部操作这里只实现 字符和 字符串这两个函数的函数原型分别为string operator (char c) 和 string operator (const char* s) 。 字符可以直接复用 push_back 函数 字符串可以直接复用 append 函数。具体代码如下所示 // operator 函数 —— 字符 string string::operator (char c) {push_back(c);return *this; }// operator 函数 —— 字符串 string string::operator (const char* s) {append(s);return *this; } 2.6.2 流插入运算符函数 流插入运算符函数的原型为ostream operator (ostream os, const string str) 。该函数并不是 string 类的成员函数所以在声明的时候将其声明至 string 类的外面。那么怎么实现流插入运算符函数呢在之前没实现流插入运算符时打印字符串都是使用 c_str 函数那么这里流插入函数的实现是否可以复用 c_str 函数呢我们先试试看代码如下所示 // 流插入运算符 函数 ostream operator (ostream os, const string str) {os str.c_str() endl;return os; } 我们来使用自己实现的流插入运算符函数来打印字符串运行结果如下所示 为什么没有将 * 打印出来换成库里面的流插入运算符函数试试看看它的结果是怎样的运行结果如下图所示 咦为什么库里面的流插入运算符却可以将 * 打印出来这个不同的结果说明了我们实现的流插入运算符函数存在问题流插入运算符函数的实现是基于 c_str 函数 c_str 函数的返回值是 m_str 。打印数据的时候遇到 ’\0’ 就会终止打印这才出现了上述的问题。所以这里的流插入函数不能基于 c_str 函数来实现而是将字符串 str 中的数据依次输出。代码如下所示 // 流插入运算符 函数 ostream operator (ostream os, const string str) {// 依次输出for (size_t i 0; i str.size(); i){os str[i];}return os; } 将之前的程序再运行一遍观察其结果是否和库中的流插入运算符函数的结果一致运行结果如下所示 2.7 insert 函数和 erase 函数 2.7.1 insert 函数 insert 函数的作用是在指定位置 pos 处插入数据。这里我们只实现插入字符和插入字符串这两个函数的原型为string insert(size_t pos, char c) 和 string insert(size_t pos, const char* s) 。 先实现插入字符函数既然要在 pos 位置处插入数据就需要考虑是否需要扩容以及 pos 位置是否合法这里的扩容逻辑与 push_bcak 函数的逻辑一致另外还要考虑挪动数据在数据结构的顺序表中演示过了这里就不过多的赘述。具体代码如下所示 // insert 函数 —— 插入字符 string string::insert(size_t pos, char c) {// 判断 pos 位置是否合法assert(pos m_size);// 判断是否需要扩容if (m_size m_capacity){size_t newcapacity m_capacity 0 ? 4 : 2 * m_capacity;reserve(newcapacity);}// 挪动数据size_t end m_size;while (end pos){m_str[end 1] m_str[end];end--;}// 插入数据m_str[pos] c;m_size;return *this; } 接下来调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 往中间插入数据没有问题那么往头部插入数据呢结果如下图所示 非正常状态退出说明我们实现的插入字符函数有问题问题出现在哪呢我们来调试观察 从上图可以发现当数据挪完之后 end-- 并没有变成 -1 而是变成了一个很大的数据导致循环永远不会终止为什么 end 为 0 的时候 -1没有变成 -1 呢这是因为 end 是 size_t 类型的数据size_t 类型的数据没有负数。那么怎么解决这个问题呢这时有人可能会想既然因为 end 为 size_t 类型才出现的错误那将 end 的数据类型改为 int 类型不就好了吗其实这样改变也不好因为这里就涉及整型提升了int 类型向 size_t 类型提升先提升再比较大小。当然对于这种问题也有解决办法再将 end 的数据类型改为 int 类型的基础上将 pos 强制类型转换成 int 类型就可以解决了但是我个人并不推荐这种方法。 接下来介绍较为好的方法之所以会出现上述的问题是因为 while 循环中的终止条件中的 号的等于存在问题要想将源字符串中 pos 位置的数据向后挪动就需要遍历至 pos 位置处就避免不了 pos但是将 end的初始值改变一下就可以使用 pos 的条件了将 end 的 的初始值改为 m_size 1这里之所以 1 是因为只插入一个字符若插入的是字符串加的则是字符串的长度大小。如此一来数据的挪动也需要改动改为 m_str[end] m_str[end - 1] 。下面通过画图来理解 分析完毕具体代码如下所示 // insert 函数 —— 插入字符 string string::insert(size_t pos, char c) {// 判断 pos 位置是否合法assert(pos m_size);// 判断是否需要扩容if (m_size m_capacity){size_t newcapacity m_capacity 0 ? 4 : 2 * m_capacity;reserve(newcapacity);}// 挪动数据size_t end m_size 1;while (end pos){m_str[end] m_str[end - 1];end--;}// 插入数据m_str[pos] c;m_size;return *this; } 调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 无论是中间插入还是头部插入都没有问题说明我们实现的 insert 插入字符函数没有问题。 insert 插入字符函数实现完毕接下来实现 insert 插入字符串函数。这里插入字符串的扩容逻辑与append 函数的扩容逻辑一致。挪动数据所用到的思想与插入字符的思想一致在扩容步骤中求得待插入字符串的长度 len 所以 end 的初始值为 m_size len那么循环的条件为 end pos len - 1 数据的挪动为 m_str[end] m_str[end - len] 。下面通过画图来理解 挪动数据的思路分析清楚之后接下来分析插入数据先找到开始插入的位置即 pos 位置再寻找结束插入的位置即 pos len 位置起始位置和终止位置确定之后用 for 循环插入字符。所有的步骤都分析完毕接下来编写代码 // insert 函数 —— 插入字符串 string string::insert(size_t pos, const char* s) {// 判断 pos 位置是否合法assert(pos m_size);//先求出待插入的字符串的长度size_t len strlen(s);// 判断是否需要扩容if (m_size len m_capacity){size_t newcapacity m_size len 2 * m_capacity? m_size len : 2 * m_capacity;reserve(newcapacity);}// 挪动数据size_t end m_size len;while (end pos len - 1){m_str[end] m_str[end - len];end--;}// 插入数据for (size_t i 0; i len; i){m_str[pos i] s[i];}// m_size 的大小增加 lenm_size len;return *this; } 调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 2.7.2 erase 函数 erase 函数的作用是删除字符串中从字符位置pos开始len个字符的部分若 len 的大小大于从 pos 位置开始到 \0 结束之后的字符串的长度那么有多少删除多少。该函数的原型为string erase(size_t pos 0, size_t len npos) 。下面来分析 erase 函数怎么实现首先检验 pos 位置的合法性接着由 erase 函数的作用可以看出存在两种情况的结果一是当 len 的长度大小或者等于大于从 pos 位置开始到 \0 结束之后的字符串的长度则将 pos 位置后的字符全部删除二是 len 的长度小于的情况这种情况就需要挪动数据了。 第一种情况先将 m_size 的大小变为 pos 再将 m_size 位置的数据置为 \0 这样就将 pos 位置后的字符全部“删除”了第二种情况数据的挪动逻辑与 insert 插入字符串的逻辑一致先确定插入的起始位置即 pos再确定插入的结束位置即 pos len 中间的数据的插入使用 for 循环接下来再改变 m_size 的大小记得将 m_size 处的数据赋值为 \0 。分析完毕具体代码如下所示 // 函数声明 string erase(size_t pos 0, size_t len npos);// 需要添加 npos 成员函数 // 声明 —— 在.h 文件中 static const size_t npos; // 定义 —— 在.cpp 文件中 const size_t string::npos -1;// 函数的实现 string string::erase(size_t pos, size_t len) {// 判断 pos 位置是否合法assert(pos m_size);if (len m_size - pos || pos npos){m_size pos;m_str[m_size] \0;}else{// 挪动数据for (size_t i pos; i pos len; i){m_str[i] m_str[i len];}// m_size 的大小 - lenm_size - len;m_str[m_size] \0;}return *this; } 调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 2.8 pop_back 函数和 find 函数 2.8.1 pop_back 函数 先实现 pop_back 函数pop_back 函数的作用是尾删字符。需要实现的函数原型为 void pop_back() 。既然是删除字符那么有效数据的个数不能为0否则就没有字符要删除了。具体代码如下所示 // pop_back 函数 void string::pop_back() {assert(m_size ! 0);m_size--;m_str[m_size] \0; } 2.8.2 find 函数 find 函数是用来查找字符串中的数据这里实现的函数是查找字符和查找字符串函数原型为size_t find(char c, size_t pos 0) const 和 size_t find(const char* s, size_t pos 0) const 。找到了则返回对应的下标找不到则返回 npos 。 先实现查找字符实现起来很简单从pos 位置开始遍历字符串看是否有与字符 c 相等的字符。分析完毕具体代码如下所示 size_t string::find(char c, size_t pos) const {for (size_t i pos; i m_size; i){if (m_str[i] c){return i;}}return npos; } 调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 find 查找字符函数实现完毕接下来实现 find 查找字符串函数。可以使用 strstr 函数函数原型为 char * strstr ( char * str1, const char * str2 ) 作用为如果在str1中找到了str2子字符串则返回指向str2在str1中首次出现位置的指针如果未找到str2子字符串则返回 NULL。 解决完怎么查找字符串之后开始考虑返回值的问题找不到返回 npos 找到了返回对应的下标下标是整型这里全是指针怎么表示呢用指针- 指针的方法在strstr 函数找到子串时会返回一个指针让该指针减去指向字符串首字符的指针即 m_str 就是子串第一次出现的位置的下标。分析完毕代码如下所示 // find 函数 —— 查找字符串 size_t string::find(const char* s, size_t pos) const {char* ret strstr(m_str pos, s);if (ret ! nullptr){return ret - m_str;}else{return npos;} } 调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 2.9 substr 函数 substr 函数的函数原型为string substr (size_t pos 0, size_t len npos) const 作用为在字符串中从pos位置开始截取len个字符然后将其返回若 len 的大小大于从pos位置开始直到 \0 结束的字符串的长度那么将从 pos 位置后的所有字符都返回。 涉及 pos 可以先检查 pos 位置的合法性也可以不检查。由该函数的作用可知这里的 len 的大小需要根据情况的不同进行调整若 len 的大小大于从 pos 位置开始直到\0结束的字符串的长度那么要返回的字符串只有从 pos 位置开始到之后的字符串了返回的长度并不是 len 而是 m_size - pos左闭右开相减就是该区间字符串的长度。 该函数的返回值是 string 类型可以先创建一个 string 类对象作为返回值将要返回的字符串拷贝到该对象中。分析完毕代码如下所示 // substr 函数 string string::substr(size_t pos, size_t len) const {// 判断 pos 的位置是否合法assert(pos m_size);// 调整 len 的大小if (len m_size - pos || len npos){len m_size - pos;}// 先创建一个 string 类对象用来返回string tmp;// 提前预留好空间tmp.reserve(len);for (size_t i 0; i len; i){tmp m_str[pos i];}return tmp; } 调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 也许有人会有疑问这里的不是传值返回吗传值返回不是会产生临时对象吗那么不就需要调用拷贝构造函数吗这里没有实现拷贝构造函数使用的是编译器自己生成的拷贝构造函数而编译器自己生成的拷贝构造函数是浅拷贝/值拷贝这里需要的是深拷贝需要自己实现拷贝构造函数但是并没有实现编译器却不会报错这是为什么呢因为编译器针对这些情况进行了优化即便自己没有实现拷贝构造函数也没有问题并且这里的返回 tmp 时也不会调用析构函数这也是因为编译器自己优化了。 2.10 六种比较函数 先实现 与 这两个比较函数其余四个函数的实现直接复用这两个函数。 先来实现 比较函数比较两个字符串的大小比较的是对应位置的 ASCII 码值所以先定义两个遍历变量 i1 和 i2 一个遍历左对象一个遍历右对象循环遍历遍历条件为小于对象的 m_size 。当左对象对应的字符大于或等于右对象对应的字符时返回 false 反之返回 true。 当跳出循环时说明左右两个对象中有其中一个或者两个对象遍历完毕了此时应当返回什么在小于比较中无非就以下三种情况 观察上述三种情况的结果返回 true 时右对象还未遍历完毕即 i2 小于右对象的 m_size 其余的情况都是返回 false 。所以跳出循环后返回的表达式为 i2 右对象的 m_size。分析完毕代码如下所示 bool string::operator (const string str) {size_t i1 0;size_t i2 0;// 遍历两个字符串while (i1 m_size i2 str.m_size){if (m_str[i1] str[i2]){return true;}else if (m_str[i1] str[i2]){return false;}else{i1;i2;}}return i2 str.m_size; } 调用该函数观察程序的运行结果运行结果如下图所示 小于比较函数实现完毕后接下来实现等于比较函数。与小于比较函数的逻辑一致只是循环体内部的代码需要改动只要左右相对应的字符不相等则直接返回 false 。跳出循环后说明左右两个对象中有其中一个或者两个对象遍历完毕了此时应当返回什么既然是等于比较函数只有跳出循环后左右对象都遍历完毕了才返回 true 其余的情况都返回 false 。分析完毕具体代码如下所示 bool string::operator (const string str) {size_t i1 0;size_t i2 0;// 遍历两个字符串while (i1 m_size i2 str.m_size){if (m_str[i1] ! str[i2]){return false;}i1;i2;}return i1 m_size i2 str.m_size; } 调用该函数观察程序的运行结果运行结果如下图所示 小于比较函数和等于比较函数实现完毕后其余四个比较函数实现的代码如下所示 bool string::operator (const string str) {return (*this str) || (*this str); }bool string::operator (const string str) {return !(*this str || *this str); }bool string::operator (const string str) {return !(*this str); }bool string::operator! (const string str) {return !(*this str); } 2.11 clear 函数流提取运算符函数和 getline 函数 2.11.1 clear 函数 clear 函数的作用为 清除当前字符串中的所有字符即将 size 变为 0 但是不改变 capacity 的大小。该函数的原型为void clear() 。与 erase 函数删除字符的逻辑类似直接将有效数据个数 m_size 的大小赋值为 0 然后在 m_size 位置处赋值为 \0 。分析完毕代码如下所示 // clear 函数 void string::clear() {m_size 0;m_str[m_size] \0; } 2.11.2 流提取运算符函数 流提取运算符函数的原型为istream operator (istream is, string str) 。流提取运算符函数是非 string 成员函数所以该函数的声明写在 string 类的外部。可以不断的向对象一个一个的输入字符当输入的字符等于空格字符或者换行字符时就终止输入并且跳出循环。分析完毕代码如下所示 // 流提取运算符 函数 istream operator (istream is, string str) {char c;// 依次输入字符while (is c){if (c || c \n){break;}str c;}return is; } 当我们实际使用该函数时发现即便是输入空格字符或者换行字符循环也不会停止这是因为因为 is c 默认会跳过空格和换行符。那该怎么实现流提取运算符函数呢在 istream 中提供了一个 get 函数该函数的作用是从流中提取单个字符该函数的函数原型有多个这里只介绍我们需要使用的istream get(char c) 。接下来使用 get 函数来实现流提取运算符函数代码如下所示 // 流提取运算符 函数 istream operator (istream is, string str) {char c;// 依次输入字符while (is.get(c)){if (c || c \n){break;}str c;}return is; } 调用该函数观察程序的运行结果运行结果如下图所示 观察上图程序的运行结果可以发现为什么 str2 字符串中的原数据也打印出来了呢std 库中也是这样的吗如下图所示 使用 std 库中的 string 运行该代码时并没有将字符串中的原数据打印出来说明我们实现的流提取运算符存在问题。之所以会将 str2 中原数据打印出来是因为没有将这些原数据进行清除清除字符串中的数据可以使用 clear 函数。代码如下所示 // 流提取运算符 函数 istream operator (istream is, string str) {// 清除 str 中的数据str.clear();char c;// 依次输入字符while (is.get(c)){if (c || c \n){break;}str c;}return is; } 调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 程序的运行结果与库中一致说明现在实现的函数没有问题。但是当输入的字符串非常长时会不断的扩容会浪费空间降低效率。那么有没有什么优化方案呢如下代码所示 // 流提取运算符 函数 istream operator (istream is, string str) {// 清除 str 中的数据str.clear();char c;size_t i 0;char buff[128];// 依次输入字符while (is.get(c)){if (c || c \n){break;}if (i 127){buff[i] \0;str buff;i 0;}buff[i] c;}if (i 0){buff[i] \0;str buff;}return is; } 既然不足出现在字符串较长时会多次扩容那么我提前开辟好一块较大的空间即buff将输入的字符都存储到 buff 中等到空间满了之后再将 buff 中的字符存储到 string 对象 str 中然后再将 buff 中的数据清空清空数据之前将 buff 的末尾字符赋值为 \0 以表示是字符串。以此往复直到 c 为终止字符。跳出循环对 buff 中剩余未满的字符进行处理执行 while 循环中同样的操作。这里 buff 的初始空间大小可以根据自己需求来规定。 2.11.3 getline 函数 getline 函数的原型为istream getline(istream is, string str, char delim \n) 。 getline 函数是非 string 成员函数所以函数的声明需要写在 string 类的外部该函数与流提取运算符函数差不多只是终止字符串可以自己定义未定义时默认为 \n 所以给 delim 缺省值\n。分析完毕代码如下所示 // getline 函数 istream getline(istream is, string str, char delim) {// 清除 str 中的数据str.clear();char c;size_t i 0;char buff[128] ;// 依次输入字符while (is.get(c)){if (c delim){break;}if (i 127){buff[i] \0;str buff;i 0;}buff[i] c;}if (i 0){buff[i] \0;str buff;}return is; } 调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 2.12 swap 函数拷贝构造函数和赋值重载函数 2.12.1 swap 函数 swap 函数的函数原型为void swap(string x, string y) 作用为交换字符串对象 x 和 y 的值。需要注意的是 std 中也有 swap 函数这里 swap 函数的实现就是借助库中的 swap 。swap 是将两个对象的值交换也就是将 m_strm_sizem_capacity 都交换。具体代码如下所示 // swap 函数 void string::swap(string str) {// 调用 std 库中的 swap 函数std::swap(m_str, str.m_str);std::swap(m_size, str.m_size);std::swap(m_capacity, str.m_capacity); } 调用该函数检验实现的函数是否能达到该函数本来的效果检验结果如下图所示 2.12.2 拷贝构造函数 这里介绍拷贝构造函数的两种实现方法。拷贝构造函数的原型为string(const string str) 1 第一种实现方法 拷贝构造函数的作用是将 str 对象中的数据拷贝到 *this 对象中。既然是拷贝那么步骤为开辟新空间拷贝旧数据这里不需要释放旧空间这里的旧空间指的就是str 的空间拷贝构造函数并不会影响被拷贝对象随即在更新 *this 对象的成员变量的数据。分析完毕第一种实现方法的代码如下所示 // 拷贝构造函数 —— 第一种实现方法 string::string(const string str) {// 开辟新空间m_str new char[str.m_capacity 1];// 拷贝旧数据memcpy(m_str, str.m_str, str.m_size 1);// 更新变量的数据m_size str.m_size;m_capacity str.m_capacity; } 2 第二种实现方法 第一种实现方法是自己开辟空间自己拷贝数据自己更新数据。其实不需要这么麻烦可以借助其它的函数来实现这些操作第二种实现方法的代码如下所示 // 拷贝构造函数 —— 第二种实现方法 string::string(const string str) {string tmp(str.m_str);// 这里的 swap 函数是 this 在调用// 实际上是这样的 this-swap(tmp)swap(tmp); } 定义一个 tmp 对象让它调用构造函数这里实际上是让 tmp 去开辟新空间和拷贝数据然后*this 对象再调用 swap 函数将 *this 与 tmp 对象中的数据交换实现了更新成员变量数据的效果当拷贝构造调用结束后tmp 就被析构了。 对于这两种实现方法推荐使用第二种。这两种方法在效率上并没有区别只是写起来代码更简洁该执行的操作一个都没有少只是这些操作是由其它函数来实现的。 2.12.3 赋值重载函数 这里介绍赋值重载函数的两种实现方法。赋值重载函数的原型是string operator (const string str) 1 第一种实现方法 赋值重载函数的作用是将 str 对象的数据赋值给 *this 对象改变 *this 对象原有的数据。这里的步骤与拷贝构造函数的差不多只是多了一步释放旧空间将 *this 对象中的 m_str 的空间给释放掉避免造成内存的泄漏。分析完毕第一种实现方法的代码如下所示 // 赋值运算符重载 string string::operator (const string str) { // 避免自己给自己赋值if (*this ! str){// 开辟新空间char* tmp new char[str.m_capacity 1];// 拷贝旧数据memcpy(tmp, str.m_str, str.m_size 1);// 释放旧空间delete[] m_str;// 更新变量的数据m_str tmp;m_size str.m_size;m_capacity str.m_capacity;return *this;} } 2 第二种实现方法 与拷贝构造函数的第二种实现方法一致借助其它的函数来实现开辟空间拷贝数据释放空间的操作第二种实现方法的代码如下所示 // 赋值重载函数 string string::operator (const string str) {if (*this ! str){string tmp(str);swap(tmp);}return *this; } 定义一个 tmp 对象让它调用拷贝构造函数开辟新空间拷贝数据都是拷贝构造函数完成的然后 *this 对象再调用 swap 函数将 *this 与 tmp 对象中的数据交换实现了更新成员变量数据的效果而旧空间的释放就交给 tmp 对象来处理反正当赋值构造函数调用结束时会析构 tmp 。 对于这两种实现方法推荐使用第二种。这两种方法在效率上并没有区别只是写起来代码更简洁该执行的操作一个都没有少只是这些操作是由其它函数来实现的。 3. 结言 在博客的末尾需要提到一个问题在拷贝数据的时候为什么不能使用 strcpy 函数呢其实有些函数的拷贝数据使用 strcpy 函数没有问题但是有些函数使用 strcpy 函数来拷贝数据会有大问题而所有的函数使用 memcpy 函数来拷贝数据没有问题为了保持代码的一致性所有的函数在拷贝数据时都使用 memcpy 函数。为什么有些函数使用 strcpy 函数会出现问题呢strcpy函数遇到’\0’就会停止拷贝而 memcpy 函数直接从源头拷贝到结尾才会停止不会因为其它原因而停止拷贝。
http://www.laogonggong.com/news/103125.html

相关文章:

  • 房地产型网站建设报价网站建设的个人总结
  • 佛山网站设计是绍兴做企业网站的公司
  • 做网站会提供源代码吗怎样做网站代理
  • 珠海专业网站建设费用静态网站模板源码下载
  • 深圳做网站的给说最近国语视频在线观看
  • 北京ui设计seo网络优化师就业前景
  • 百度站长工具对seo的帮助网站建设创新点
  • 烟台市住房和城乡建设局网站网站建设客户需求调查表
  • 网站备案连接seo和sem是什么意思啊
  • 网站域名在哪里查询做淘宝客网站是如何盈利的
  • 电动车行业网站建设如何做微信公众号
  • 小语种服务网站wordpress点击量最多的文章
  • 网站维护管理网络运维是干嘛的
  • 怎么棋牌网站建设网站登记备案
  • 企业网站管理系统视频教程天天传媒有限公司网站
  • 如何做商业推广网站表格里怎么做链接网站
  • html php网站开发请别人做网站会不会被盗
  • 网络营销教学网站如何用百度云文件做网站
  • 深圳 网站设计公司价格php学院网站源码
  • 如何用wordpress站群黑龙江省建设集团网站
  • 微网站开发素材wordpress插件文件
  • 石家庄建站外贸网站云梦县城乡建设局网站
  • 深圳学校网站建设报价郑州seo优化顾问
  • 网站设计制作收费明细wordpress微信博客模板下载
  • 山东建设厅网站网址网站多少钱一年
  • app设计网站有哪些功能dede医院网站模板
  • wordpress多站点更改网站模板
  • 网站建设哪个公司怎么优化网站
  • 做一个自己的免费网站昆山商城网站建设
  • 做品牌推广网站需要多少钱宿迁公司做网站