南昌网站建设排行,php电商网站开发贴吧,网站建设哪里招标,网页搜索一个网站全包前言#xff1a;上篇文章我们对string类及其常用的接口方法的使用进行了分享#xff0c;这篇文章将着重进行对这些常用的接口方法的内部细节进行分享和模拟实现。 目录
一.基础框架
二.遍历字符串
1.[]运算符重载
2.迭代器
3.范围for
三.常用方法
1.增加
2.删除
3.调…前言上篇文章我们对string类及其常用的接口方法的使用进行了分享这篇文章将着重进行对这些常用的接口方法的内部细节进行分享和模拟实现。 目录
一.基础框架
二.遍历字符串
1.[]运算符重载
2.迭代器
3.范围for
三.常用方法
1.增加
2.删除
3.调整
4.交换
5.查找
6.截取
7.比较
四.流操作
总结 一.基础框架
首先我们要清楚string类定义的是字符串对象所以就类似于线性表有长度容量等成员变量 class string{public://构造函数string(const char* str ):_size(strlen(str)){_capacity _size;_str new char[_capacity 1];strcpy(_str, str);}//析构函数~string(){delete[] _str;_str nullptr;_size 0;_capacity 0;}//转换C语言格式const char* c_str() const{return _str;}//清除void clear(){_size 0;_str[_size] \0;}//深拷贝s2(s1)string(const string s){_str new char[_capacity 1];strcpy(_str, s._str);_size s._size;_capacity s._capacity;}//s1 s2string operator(const string s){char* tmp new char[s._capacity 1];strcpy(tmp, s._str);delete[] _str;_str tmp;_size s._size;_capacity s._capacity;}private:char* _str;size_t _size;size_t _capacity;};
其中不能缺少的就是构造函数、析构函数和拷贝构造函数这里我们直接用缺省函数将无参构造和带参构造结合为一体。
但是由于如果我们不自己写一个深拷贝函数就会默认执行浅拷贝成员函数这样会导致两个字符串同源所以需要给出深拷贝函数。
值得注意的是真正的strlen不会统计字符串中的‘\0’所以我们给_str开空间时应1。 二.遍历字符串
1.[]运算符重载
上篇文章中我们知道遍历字符串有三种方式[]运算符重载迭代器以及范围for。下面我们就来一一实现。
首先我们需要将字符串的长度方法size和容量方法capacity定义出来 //长度size_t size() const{return _size;}//容量size_t capacity() const{return _capacity;}
一般情况下当方法里调用的成员无需发生改变时都会将这些方法用const修饰。
而[]运算符重载自然是通过运算符重载函数来实现 //遍历char operator[](size_t pos){assert(pos _size);return _str[pos];}
这里我们添加assert函数来断言防止越界访问。
返回值使用引用格式能够实现可读可写 但此时会产生一个问题如果我想让一个const修饰的对象来调用该方法就会导致权限放大而出错。
如果给这个方法加上const那我们就无法修改其内容了。
所以我们使用函数重载为其单独创造一个只读的const修饰的函数方法 const char operator[](size_t pos) const{assert(pos _size);return _str[pos];} 2.迭代器
我们已经了解迭代器的本质和指针类似我们这里我们就先用指针的实现迭代器的功能 //迭代器typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}
用typedef将char*指针重命名为iterator再定义出begin和end两个方法便能实现迭代器功能 值得注意的是const修饰的对象想要调用迭代器也必须调用对应const修饰的迭代器所以迭代器我们也需要进行重载 typedef const char* const_iterator;const_iterator begin() const{return _str;}const_iterator end() const{return _str _size;}
迭代器这样的模拟实现方法也能帮助我们更深入的了解其内部构造。 3.范围for
范围for实际上并没有那么复杂其本质也是迭代器。
所以只要有迭代器的存在保证iterator、begin。end这些单词不变不管是我们自己实现的还是C库内的都可以使用范围for 但是如果iterator、begin。end这些单词发生变化就无法在使用范围for。 三.常用方法
1.增加
增加无非有四种方式尾插单个字符push_back、尾插字符串append和运算符重载以及任意位置的插入insert增加字符就意味着要考虑扩容问题这就要实现reserve方法来配合使用。
尾插单个字符可以通过每次扩容两倍容量但是如果尾插一个长度为len的字符串每次扩容两倍或是更多倍都并不一定就能满足容量 所以这里直接扩容sizelen个空间 //扩容void reserve(size_t len){if (len _capacity){char* tmp new char[len 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity len;}}//尾插单字符void push_back(char ch){if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] ch;_size;_str[_size] \0;}//尾插字符串void append(const char* str){size_t len strlen(str);if (_size len _capacity){reserve(_size len);}strcpy(_str _size, str);_size len;}
因为字符串的末尾都要有\0的存在所以扩容时要始终保持实际容量比字符串容量多1。
当尾插单个字符时因为该字符是直接覆盖了\0所以尾插之后要再字符串末尾再补上\0。
而尾插字符串时因为strcpy在进行拷贝时也会直接将\0拷贝所以无需再补。
而运算符的重载就是以上述两个方法为基层的扩展 //运算符重载string operator(char ch){push_back(ch);return *this;}string operator(const char* str){append(str);return *this;}
测试如下 任意位置的插入 则需要进行字符串的挪动 //任意位置插入//单字符void insert(char ch, size_t pos){assert(pos _size);if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}size_t end _size 1;while (end pos){_str[end] _str[end - 1];end--;}_str[pos] ch;_size;}//字符串void insert(const char* str, size_t pos){assert(pos _size);size_t len strlen(str);if (_size len _capacity){reserve(_size len);}size_t end _size len;while (end pos len){_str[end] _str[end - len];end--;}for (size_t i 0; i len; i){_str[pos] str[i];}_size len;}
在进行字符串插入时要注意的是我们要从pos位置将后边的字符向后空出len个位置。
同时我们不能使用strcpy进行插入因为它会将要插入的字符串的\0一并插入导致一个位置的字符被覆盖所以这里我们采用循环插入。 测试如下 2.删除
string类中只有一个常用的删除方法erase它的功能是在指定位置后删除若干个字符如果没有指定要删除的字符数量则默认将后边的字符全删除 //删除void erase(size_t pos, size_t len npos){assert(pos _size);if (len npos || len _size - pos){_str[pos] \0;_size pos;}strcpy(_str pos, _str pos len);_size - len;}
采用缺省函数的方式如果我们没有传入len他就会等于nposnpos在C中表示一个常数表示不存在的位置。
如果len无传入或是len的长度不小于要删除的字符串长度这都可以认为是要将pos位置后的字符串全部删除此时便可直接在pos位置用\0。
使用npos需要在类中定义public成员变量 public: static const int npos; 以及在类外赋值 const int string::npos -1; 测试如下 3.调整
在string中有一个方法可以兼备增加和删除两种简单功能名为resize它的作用是调整字符串
传入一个参数n和一个字符ch如果当前字符串长度小于n则扩容字符串长度至n个并将多出的位置用字符ch填充如果不传字符ch则默认填充\0。
如果当前字符串长度大于n则将字符串长度缩减到n //调整void resize(size_t n, char ch \0){if (n _size){_str[n] \0;_size n;}else{reserve(n);for (size_t i _size; i n; i){_str[i] ch;}_str[n] \0;_size n;}}
测试如下 4.交换
自己实现string类中的swap交换函数有一个很好用的方法那就是借用std库中的swap函数 //交换void swap(string s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);} 因为std库中的swap函数是模版函数可以进行任意类型的交换所以我们直接投机取巧将两者的成员变量依次进行交换测试如下 但是这样的写法并不是我们所熟悉的swap(s1,s2)所以我们可以通过函数重载扩展一下 void swap(string x, string y){x.swap(y);}
值得注意的是这个函数要写在string类的外边按照就近原则去调用它否则会默认先调用库里的模版swap函数。
测试如下 5.查找
string类中的查找也分为查找单个字符、查找字符串以及在指定的pos位置向后去查找找到返回下标找不到返回npos所以依然要使用缺省函数 //查找//单字符size_t find(char ch, size_t pos 0) const{assert(pos _size);for (size_t i pos; i _size; i){if (_str[i] ch)return i;}return npos;}//字符串size_t find(const char* str, size_t pos 0) const{assert(pos _size);const char* p strstr(_str pos, str);if (p)return p - _str;elsereturn npos;}
在查找字符串时我们使用到了strstr函数其返回值为所找到的字符串的首字符指针。
测试如下 6.截取
截取字符串方法substr其作用是从字符串的pos位置开始向后截取len长度的字符当然无论是pos位置还是长度len都可以没有依然是缺省函数 //截取string substr(size_t pos 0, size_t len npos){string sub;if (len _size - pos){for (size_t i pos; i _size; i){sub _str[i];}}else{for (size_t i pos; i pos len; i){sub _str[i];}}return sub;}
测试如下 7.比较
字符串直接的比较需要我们实现运算符重载 bool operator(const string s1, const string s2){int ret strcmp(s1.c_str(), s2.c_str());return ret 0;}bool operator(const string s1, const string s2){int ret strcmp(s1.c_str(), s2.c_str());return ret 0;}bool operator(const string s1, const string s2){return s1 s2 || s1 s2;}bool operator(const string s1, const string s2){return !(s1 s2);}bool operator(const string s1, const string s2){return !(s1 s2);}bool operator!(const string s1, const string s2){return !(s1 s2);}
这一块的方法我们建议实现在类外定义两个参数这样能够允许一个字符串和一个string对象进行比较。
因为在类内定义因为默认类内的成员函数的第一个参数都是隐藏的非静态string对象所以静态的普通字符串传入就会使权限放大而出错。
测试如下 四.流操作
直接上代码 //流输出ostream operator(ostream out, const string s){for (auto ch : s){out ch;}return out;}//流提取istream operator(istream in, string s){s.clear();char ch;ch in.get();while (ch ! ch ! \n){s ch;ch in.get();}return in;}
输出较为简单直接使用范围for循环输出。
而对于提取到的字符会直接覆盖s中原有的字符串所以要先进行清除此外因为in默认会跳过空格和回车而不提取它们这会导致死循环所以我们使用in.get()函数来提取。
测试如下 总结
关于string类及其内部常用方法的模拟实现就分享到这里啦。
最后希望能得到您的一键三连支持我们下期再见