网站设计管理方向,wordpress 站点群,申请备案网站空间,网站宣传工作手搓string类 文章目录手搓string类string的成员一.构造#xff0c;拷贝构造#xff0c;赋值重载和析构1.构造函数2.拷贝构造3.swap问题4.赋值重载5.析构函数二.简单接口1.c_str2.size(有效字符长度)3.capacity#xff08;有效字符容量#xff09;4.operator[]5.迭代器和范…手搓string类 文章目录手搓string类string的成员一.构造拷贝构造赋值重载和析构1.构造函数2.拷贝构造3.swap问题4.赋值重载5.析构函数二.简单接口1.c_str2.size(有效字符长度)3.capacity有效字符容量4.operator[]5.迭代器和范围for三.容量1.reverse2.resize3.clear四.插入1.push_back2.append3.operator4.insert5.erase五.查找find六.流插入和流提取的重载1.流插入重载2.流提取重载七.整体实现代码string的成员
class string()
{private:char *str;size_t _size;size_t _capacity;const static size_t npos-1;
};在类和对象中提到过静态成员变量不能给缺省值必须要在类外定义。 但是其实有一个特例那就是针对整形开了一个后门静态的整形成员变量可以直接在类中定义。 一.构造拷贝构造赋值重载和析构
1.构造函数
在类和对象时提到过如果要显示定义构造函数最好是给全缺省
string(const char *str)//注意:\0和空指针一样,\0是字符常量char类型“”和“\0一样
{_size strlen(str);//这是成员函数有this指针_capacity _size;_str new char [_capacity 1];//多开一个字节给\0strcpy(_str, str);//可能会传参过来构造
}这里的_capacity是给有效字符预留的空间为了给’\0’留位置在开空间的时候要多开一个。
2.拷贝构造
首先是老实人写法构造一个新的空间将s._str的值拷贝到新的空间再将其他的值拷贝
//拷贝构造
string(const string s)
{//先构造一个新空间给_str_str new char[s._capacity 1];//然后将s的其他值赋值给this_size s._size;_capacity s._capacity;strcpy(_str, s._str);
}在下面的写法中会经常复用C语言中的字符串函数。不为别的就是好用
不是谁都想当老实人所以又有了一种现代写法并不是为了提高效率而是为了让代码更简洁。
首先构造一个tmp然后将tmp和s交换
void swap(strings)
{std::swap(_str, s._str);//库中和string分别提供了一个swap函数,用库中的swap交换内置类型std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}string(const string s):_str(nullptr) //这里必须要给_str初始化否则拿到一个非法的地址析构会报错,_size(0),_capacity(0)
{string tmp(s._str);//这里复用构造函数swap(tmp);//有隐藏的this指针一个参数就够了
}如果不给_str初始化成空指针话交换以后临时变量tmp就指向了 _str的空间这可能是一个野指针临时变量tmp在出这个函数就要被销毁调用析构的时候delete一个野指针就会产生错误。 可以delete一个空指针无论是free还是delete当接收的参数是一个空指针时就不做任何处理。 3.swap问题 标准库中的swap函数是一个模板在交换自定义类型时有一次构造和拷贝构造代价比较大所以我们提供一个成员函数会比较好。在成员函数中交换内置类型时就可以使用标准库中的swap函数**要指定域因为编译器默认是现在局部找局部找不到再去全局找再找不到就报错。**如果去局部找的话找到的swap函数参数不匹配。
4.赋值重载
复制重载也分为传统写法
string operator(const string s)
{if (this ! s)//要注意不要自己给自己赋值{//深拷贝char* tmp new char[s._capacity 1];strcpy(tmp, s._str);//为新空间赋值_str tmp;_capacity s._capacity;_size s._size;return *this;}}现代写法使用传值传参然后直接使用临时变量交换这个写法是我比较推荐的太简洁了
void swap(strings)
{std::swap(_str, s._str);//库中和string分别提供了一个swap函数,用库中的swap交换内置类型std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string operator(string s)
{swap(s);//传值传参s是一个拷贝直接使用这个临时变量反正临时变量出了这个函数就被销毁了return *this;
}s是一个拷贝的临时变量在销毁的时候会自动调用析构函数清理不用我们做额外处理而且直接使用临时变量调用swap还省去了我们创建临时变量。十分简洁但是可读性不算太好 5.析构函数
直接使用delete销毁空间再将_size和 _capacity置0就行
//析构函数
~string()
{//释放空间delete[] _str;_str nullptr;_size _capacity 0;
}二.简单接口
1.c_str
这个主要是返回一个C类型的数组指针
const char* c_str()const
{return _str;
}对于只读的函数接口建议加上const这样不但普通对象可以使用const类型的对象也可以使用 2.size(有效字符长度)
size_t size()const
{return _size;
}3.capacity有效字符容量
size_t capacity()const
{return _capacity;
}4.operator[]
char operator[](size_t pos)
{//虽然string是自定义类型但_str是内置类型assert(pos _size);return _str[pos];
}const char operator[](size_t pos)const//const对象只读
{assert(pos _size);return _str[pos];
}因为const对象只能读不可写所以这里要重载两个重载了[]string就可以像访问数组那样访问了
5.迭代器和范围for
强调一下迭代器虽然行为像指针但不一定是指针
typedef char* iterator;//这里为了简单就实现成指针iterator begin()
{return _str;
}iterator end()
{return _str _size;
}有了迭代器就可以使用范围for了但是范围for只认识beging和end所以如果要使用范围for在手搓迭代器的时候就不要乱取名哦。 三.容量
1.reverse
这个是string类中用于扩容的成员函数
void reverse(size_t n)
{//只扩容所以先检查情况if (n _capacity){//_str中有数据不能直接改_str的空间要先建立临时空间char*tmp new char[n 1];//将_str中的数据拷贝给tmp再将_str所指的空间释放strcpy(tmp, _str);delete[]_str;_str tmp;_capacity n;//更新容量}
}reverse是控制容量的成员函数但是缩容的代价太大了。所以只考虑缩容其实string类中给的reverse是会缩容的。 2.resize
这个是改变有效字符长度的成员函数
void resize(size_t n,char ch\0)//改变size可能会改变capacity默认插入补空间的字符是\0
{if (n _capacity)//这里也可以写n_size{reverse(n);//扩大空间以后要用字符初始化后续空间for (size_t i _size; i n; i){_str[i] ch;}//这里还要改变size_size n;//可能使用者会传其他字符来初始化前面的循环没有在size位置补\0_str[_size] \0;}else {//如果是缩小的话,就直接在n位置补\0_str[n] \0;_size n;}}有看到n_size就扩容的但在我看来只有大于容量的时候才有必要改变有效容量。 在缩小的时候同样没必要更改容量直接在n位置插入一个’\0’就无法访问到n后面的元素了间接改变了__size 3.clear
这个成员函数是将string变成一个空串在重载流提取的时候会用到这个函数
void clear()
{_size 0;_str[0] \0;
}四.插入
1.push_back
尾插单个字符插入就要考虑扩容
void push_back(char c)
{if (_size _capacity)//容量不够要扩容{//扩容要调用reverse这里还要检查一下_capacity第一次可能是0int newCapacity _capacity 0 ? 4 : 2 * _capacity;reverse(newCapacity);//这里面更新了_capacity,外面不用再更新}//扩容完毕以后就可以开始插入了_str[_size] c;_size;_str[_size] \0;
}这样尾插只能插入单个字符所以string还提供了一个append追加字符串 2.append
这是一个在末尾追加字符串的成员函数
string append(const char* s)
{//这里可以使用C标准库中的strcpy,不过还是要考虑扩容的问题,要检查剩下的空间是否足够插入int len strlen(s);if (_size len _capacity){//容量不够插入就要扩容reverse(_capacity len);}//复用C标准库函数strcpy(_str _size, s);_size len;return *this;
}追加一个字符串开原本空间的两倍可能还不够用最正确的写法是计算一下字符串的长度用于增加空间 3.operator
重载是string类一个非常正确的选择在做oj的时候你将发现比尾插和追加好用太多了。
string operator(char c)
{//插入一个字符直接_size位置插入,复用push_backpush_back(c);return *this;
}//还要重载一个字符串类型,可以复用append
string operator(const char* str)
{append(str);return *this;
} 先介绍push_back和append不是没有道理的复用可以减少代码冗余还省事还不快复用起来 4.insert
在pos位置插入一个字符或者字符串需要挪动数据。
插入单个字符
string insert(size_t pos, char c)
{assert(pos _size);//string没有单独的扩容函数在每个插入数据的地方都要检查容量if (_size _capacity){int newCapacity _capacity 0 ? 4 : 2 * _capacity;reverse(newCapacity);}//插入字符要挪动字符这里要小心,在pos位置插入size_t end _size 1;//指向\0的下一个位置while (end pos){_str[end] _str[end - 1];--end;}_str[pos] c;_size 1;return *this;
} 这里有一个问题要知道end和pos都是size_t类型的数据如果在写判断条件的时候写成endpos可能会陷入死循环。 如果pos是0end永远无法小于零这就死循环咯。 解决办法有两种其一是把end写成有符号的int并且在判断的时候强转pos也就是这样 int end _size;while (end (int) pos)
{_str[end] _str[end - 1];--end;
}第二种就是我写的这样把end放在_size1的位置这样在判断的时候可以不用取等号也就完美避免了所有问题。我比较推荐这种写法因为end作为下标本身取值范围就应该要大于零。 插入字符串
string insert(size_t pos, const char* str)
{assert(pos _size);size_t len strlen(str);if (_size len _capacity){int newCapacity _capacity 0 ? 4 : 2 * _capacity;reverse(newCapacity);}//扩容完毕开始挪动数据这次要挪动len个位置要考虑一下长度是否越界size_t end _size len;while (end pos len-1){_str[end] _str[end - len];end--;}//位置挪出来以后要插入字符串/*for (size_t i pos; i pos len; i){_str[i] *str;3str;}*///此外还可以复用C库函数strncpystrncpy(_str pos, str, len);_size len;return *this;
}这里要用strncpy而不能使用strcpy因为strcpy会将’\0’也拷贝过来而字符串的结束标志就是以’\0’为准的。 5.erase
从pos位置开始删除len个长度的字符
//实现一下erase
string erase(size_t pos 0, size_t len npos)
{assert(pos _size);//删除要判断是否会越界if (len npos || pos len _size){//说明要删除的长度超过了pos后面有的字符串,只要直接在pos位置插入\0就行_str[_size] \0;_size pos;}else{//如果不越界就要覆盖删除可以复用strcpystrcpy(_str pos, _str pos len);_size - len;}return *this;
}说到erase就不得不说一下nposnpos是一个无符号整形默认是-1也就是四十二亿九千万如果长度大于npos就是越界了。 五.查找
find
从pos位置开始查找一个字符或者一段字符串
size_t find(char c, size_t pos)
{assert(pos _size);//断言检查for (int i pos; i _size; i){if (_str[i] c){return i;}}return npos;
}size_t find(const char* s, size_t pos)
{//这里可以考虑复用C的库函数strstrassert(pos _size);char *ret strstr(_str pos, s);//这个C语言库函数返回的是一个指针//检查合法性if (ret NULL){return npos;}else{return ret - _str;}
}其实在一般的情况下使用strstr查找子串已经够了kmp算法其实是一个外强中干的家伙。 六.流插入和流提取的重载
这个我们在日期类中就已经接触过了不能写在类中否则会被this指针抢第一个参数位置还是使用友元然后定义在类外。
1.流插入重载
ostream operator(ostream out, const string s)
{for (size_t i 0; i s.size(); i){out s[i];}return out;
}2.流提取重载
istream operator(istream in, string s)
{s.clear();char ch in.get();while (ch ! ch ! \n){s ch;//我就说好用吧ch in.get();}return in;
}但这个代码有一个不好的地方在于插入长字符串时可能会频繁的扩容。
为了减少扩容次数我可以建立一个数组这个数组满了就往s中插入数组满一次才扩容一次有效减少扩容次数
stream operator(istream in, string s)//要对s插入数据s不能为const类型
{s.clear();//清空s//定义一个数组往这个数组中放数据当这个数组满了以后给s这样可以避免频繁的扩容char buff[128] { \0 };char ch in.get();//从缓冲区拿字符size_t i 0;while (ch ! ch ! \n){if (i 127){s buff;i 0;//满了以后geis再将i重置为0开始下一轮}buff[i] ch;ch in.get();}//如果输入的数据不能让数组满呢if (i 0){//只要有数据就应该导进来,字符数组是以\0作为结束标志的对于只插入几个字符的情况要手动补\0buff[i] \0;//后置是有效字符的下一个位置s buff;}return in;
}getline就是在判断的时候把空格去掉就行只以换行作为结束标志。但是coutcin以及scanf和printf都是以空格和换行为结束标志的。 七.整体实现代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#includeiostream
#includeassert.h
#includestring.h
using namespace std;
namespace wbm
{class string{public://重载流插入和流提取,不能写在类中使用友元friend istream operator(istream in, string s);friend ostream operator(ostream out, const string s);//首先写构造函数,给一个全缺省的构造string(const char *str)//注意:\0和空指针一样,\0是字符常量char类型“”和“\0一样{_size strlen(str);//这是成员函数有this指针_capacity _size;_str new char [_capacity 1];//多开一个字节给\0strcpy(_str, str);//可能会传参过来构造}//析构函数~string(){//释放空间delete[] _str;_str nullptr;_size _capacity 0;}拷贝构造//string(const string s)//{// //先构造一个新空间给_str// _str new char[s._capacity 1];// //然后将s的其他值赋值给this// // _size s._size;// _capacity s._capacity;// strcpy(_str, s._str);//}//不当老实人使用现代写法主要是调用swap函数void swap(strings){std::swap(_str, s._str);//库中和string分别提供了一个swap函数,用库中的swap交换内置类型std::swap(_size, s._size);std::swap(_capacity, s._capacity);}string(const string s):_str(nullptr) //这里必须要给_str初始化否则拿到一个非法的地址析构会报错,_size(0),_capacity(0){string tmp(s._str);//这里复用构造函数swap(tmp);//有隐藏的this指针一个参数就够了}//重载//string operator(const string s)//{// if (this ! s)// {// //深拷贝// char* tmp new char[s._capacity 1];// strcpy(tmp, s._str);//为新空间赋值// _str tmp;// _capacity s._capacity;// _size s._size;// return *this;// }//}//不当老实人现代写法主要是为了简洁并不能提高效率string operator(string s){swap(s);//传值传参s是一个拷贝直接使用这个临时变量反正临时变量除了这个函数就被销毁了return *this;}//重载两种形式一种加字符还有加字符串//string operator(char ch)//{// //在字符串size位置直接插入然后补一个\0// //但是这里要考虑一个问题就是扩容所以不如直接写尾插// _str[_size] ch;// _size;// _str[_size] \0;//}void reverse(size_t n){//只扩容所以先检查情况if (n _capacity){//_str中有数据不能直接改_str的空间要先建立临时空间char*tmp new char[n 1];//将_str中的数据拷贝给tmp再将_str所指的空间释放strcpy(tmp, _str);delete[]_str;_str tmp;_capacity n;}}//reverse都有了这不来个resizevoid resize(size_t n,char ch\0)//改变size可能会改变capacity默认插入补空间的字符是\0{if (n _capacity){reverse(n);//扩大空间以后要用字符初始化后续空间for (size_t i _size; i n; i){_str[i] ch;}//这里还要改变size_size n;//可能使用者会传其他字符来初始化前面的循环没有在size位置补\0_str[_size] \0;}else {//如果是缩小的话,就直接在n位置补\0_str[n] \0;_size n;}}void push_back(char c){if (_size _capacity)//容量不够要扩容{//扩容要调用reverse这里还要检查一下_capacity第一次可能是0int newCapacity _capacity 0 ? 4 : 2 * _capacity;reverse(newCapacity);//这里面更新了_capacity,外面不用再更新}//扩容完毕以后就可以开始插入了_str[_size] c;_size;_str[_size] \0;}string operator(char c){//插入一个字符直接_size位置插入,复用push_backpush_back(c);return *this;}//还要重载一个字符串类型,可以复用appendstring operator(const char* str){append(str);return *this;}string append(const char* s){//这里可以使用C标准库中的strcpy,不过还是要考虑扩容的问题,要检查剩下的空间是否足够插入int len strlen(s);if (_size len _capacity){//容量不够插入就要扩容reverse(_capacity len);}//复用C标准库函数strcpy(_str _size, s);_size len;return *this;}//要重载一下[]方便读写char operator[](size_t pos){//虽然string是自定义类型但_str是内置类型assert(pos _size);return _str[pos];}const char operator[](size_t pos)const//const对象只读{assert(pos _size);return _str[pos];}//[]都有了这不来个迭代器(行为像指针但不一定是指针这里写成指针typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}//写一个find函数,搞两个一个是找字符一个是找字符串size_t find(char c, size_t pos){assert(pos _size);//断言检查for (int i pos; i _size; i){if (_str[i] c){return i;}}return npos;}size_t find(const char* s, size_t pos){//这里可以考虑复用C的库函数strstrassert(pos _size);char *ret strstr(_str pos, s);//这个C库函数返回的是一个指针//检查合法性if (ret NULL){return npos;}else{return ret - _str;}}//还要insert和erase//实现insert分为插入字符和插入字符串string insert(size_t pos, char c){assert(pos _size);//string没有单独的扩容函数在每个插入数据的地方都要检查容量if (_size _capacity){int newCapacity _capacity 0 ? 4 : 2 * _capacity;reverse(newCapacity);}//插入字符要挪动字符这里要小心,在pos位置插入size_t end _size 1;//指向\0的下一个位置while (end (int) pos){_str[end] _str[end - 1];--end;}_str[pos] c;_size 1;return *this;}string insert(size_t pos, const char* str){assert(pos _size);size_t len strlen(str);if (_size len _capacity){int newCapacity _capacity 0 ? 4 : 2 * _capacity;reverse(newCapacity);}//扩容完毕开始挪动数据这次要挪动len个位置要考虑一下长度是否越界size_t end _size len;while (end pos len-1){_str[end] _str[end - len];end--;}//位置挪出来以后要插入字符串/*for (size_t i pos; i pos len; i){_str[i] *str;str;}*///此外还可以复用C库函数strncpystrncpy(_str pos, str, len);_size len;return *this;}//实现一下erasestring erase(size_t pos 0, size_t len npos){assert(pos _size);//删除要判断是否会越界if (len npos || pos len _size){//说明要删除的长度超过了pos后面有的字符串,只要直接在pos位置插入\0就行_str[_size] \0;_size pos;}else{//如果不越界就要覆盖删除可以复用strcpystrcpy(_str pos, _str pos len);_size - len;}return *this;}//下面写几个简单函数比如返回_size,_capacity,返回一个C类的指针const char* c_str()const{return _str;}size_t size()const{return _size;}size_t capacity()const{return _capacity;}void clear(){_size 0;_str[0] \0;}private:char* _str;size_t _size;size_t _capacity;const static size_t npos-1; //静态成员变量只有整形可以给缺省值};ostream operator(ostream out, const string s){for (size_t i 0; i s.size(); i){out s[i];}return out;}istream operator(istream in, string s)//要对s插入数据s不能为const类型{s.clear();//定义一个数组往这个数组中放数据当这个数组满了以后给s这样可以避免频繁的扩容char buff[128] { \0 };char ch in.get();//从缓冲区拿字符size_t i 0;while (ch ! ch ! \n){if (i 127){s buff;i 0;//满了以后geis再将i重置为0开始下一轮}buff[i] ch;ch in.get();}//如果输入的数据不能让数组满呢if (i 0){//只要有数据就应该导进来,字符数组是以\0作为结束标志的对于只插入几个字符的情况要手动补\0buff[i] \0;//后置是有效字符的下一个位置s buff;}return in;}
}urn _str; } size_t size()const{return _size;}size_t capacity()const{return _capacity;}void clear(){_size 0;_str[0] \0;}
private:char* _str;size_t _size;size_t _capacity;const static size_t npos-1; //静态成员变量只有整形可以给缺省值
};
ostream operator(ostream out, const string s)
{for (size_t i 0; i s.size(); i){out s[i];}return out;
}istream operator(istream in, string s)//要对s插入数据s不能为const类型
{s.clear();//定义一个数组往这个数组中放数据当这个数组满了以后给s这样可以避免频繁的扩容char buff[128] { \0 };char ch in.get();//从缓冲区拿字符size_t i 0;while (ch ! ch ! \n){if (i 127){s buff;i 0;//满了以后geis再将i重置为0开始下一轮}buff[i] ch;ch in.get();}//如果输入的数据不能让数组满呢if (i 0){//只要有数据就应该导进来,字符数组是以\0作为结束标志的对于只插入几个字符的情况要手动补\0buff[i] \0;//后置是有效字符的下一个位置s buff;}return in;
}} ---文章到此就结束啦希望对各位有所帮助。