前提:
1:须知知识点
strcpy的模拟实现-CSDN博客
strstr的模拟实现-CSDN博客
两个函数都会有使用,不会点开链接
2:实现的文件结构:
①:全在一个.h文件里面进行实现
②:将①分离成一个.h 和 一个.cpp文件
3:实现函数一览
本文只模拟实现string类的常用重点的函数
A:默认成员函数
①:构造函数(无参 和 字符串作为参数)
②:拷贝函数(传统写法和现代写法)
③:赋值运算符重载(传统写法和现代写法)
④:析构函数
B:功能性函数
①:swap()函数
②:reserve()函数
③:erase()函数
④:clear()函数
⑤:find()函数
⑥:c_str()函数
C:重要函数
①:push_back()函数
②:append()函数
③:+=运算符重载
④:[ ]运算符重载
⑤:insert()函数
⑥:substr()函数
⑦:<< 和 >> 运算符重载
一:默认成员函数的实现
初始状态:
namespace key
{class string {public:private:char* _str = nullptr;size_t size = 0;size_t capacity = 0;static const size_t npos;};const size_t string::npos = -1;}
解释:
1:在库中的string类中的npos是一个静态成员变量,按照规则,声明初始化规则如图所示
2:为了避免和库中的string冲突 选择用一个key的域保护起来
①:构造函数
//构造函数string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//缺省函数 //既能string s1;//也能string s1("hello");
解释:
1:缺省函数,既能string s1,也能string s1("hello")
2:string库中的_capacity 和 _size都是不计入'\0'的,模拟实现就要跟库统一,所以开空间的时候多开了一个放置'\0'
3:strlen的复杂度不低,建议只使用一次strlen,用得到的值再去进行成员变量的初始化
②:拷贝构造函数
a 传统写法:
//拷贝函数(传统写法)
//string s2(s1) 或 string s2 = s1string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}
解释:
1:传统写法就是老老实实地去完成深拷贝。
b 现代写法:
//拷贝函数(现代写法)
//string s2(s1)或 string s2 = s1;string(const string& s){//用s(s2的引用)的_str去构造一个tmp对象string tmp(s._str);//然后用swap函数把tmp对象 和 *this 进行交换swap(tmp);}
解释:
1:不再自己开辟空间,而是通过 string tmp(s._str); 这一步,复用构造函数,得到一个tmp对象,然后再自己和tmp对象交换
2:而且tmp对象的所有成员变量及_str指向的空间,出了函数都会被释放
3:总的来说,就是一种复用构造函数和swap函数去简化拷贝函数的方法
4:我们在成员变量初始化的时候,给了缺省值(_str = nullptr),在这里至关重要,有些编译器不会自动置空,此时交换后,tmp内的_str指向的是随机空间,tmp 出了作用域要调用析构函数,对随机值指向的空间进行释放,此时再去delete[ ] _str,就可能会引发崩溃。但是 delete / free 一个空,是不会报错的,所以成员变量_str在声明的时候给的nullptr至关重要
③:赋值运算符重载
a 传统写法:
//赋值重载(传统写法)
//s1 = s3
string& operator=(const string& s) {if (this != &s) { // 防止自己给自己赋值 char* tmp = new char[s._capacity + 1]; // Step1:先在tmp上开辟新的空间strcpy(tmp, s._str); // Step2:把s3的值赋给tmpdelete[] _str; // Step3:释放原有的空间_str = tmp; // Step4:把tmp的值赋给s1// 把容量和大小赋过去_size = s._size; _capacity = s._capacity;}return *this; // 结果返回*this
}
解释:传统写法,就是自己开空间自己拷贝数据
b 现代写法:
//赋值函数(现代写法)//s1 = s3;string& operator=(string s){//该函数参数s去接受s3的时候,s3拷贝生成了s//直接swap 交换s 和 *this(s1)swap(s);return *this;}
解释:
1:参数从 string& s 变成了 string s ,这意味着 s3 到 s 这一步,是拷贝构造,生成s这个对象,而我们赋值的意义就在于要一个和s3一样的东西,但是空间的地址又不一样,此时s3拷贝构造生成的s这个对象是经过我们自己写好的深拷贝生成的,所以符合要求
2:所以直接swap交换了 s1 和 s 即可
3:返回值是string& 的意义在于,可以连续的使用赋值操作符,这也是和库一致的
④:析构函数
//析构函数~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}
解释:过于简单 不作赘述
二:功能性函数的实现
①:swap()函数
//swap()函数void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}
解释:交换两个对象的一切东西,不生产中间拷贝
②:reserve()函数
//扩容函数//s1.reverse(10)void reserve(size_t n){//vs的string类的reserve函数不支持缩容//所以我们为了保持一致,只对n>capacity进行扩容处理if (n > _capacity){//开辟新的空间(n+1是因为多开一个给'/0')char* tmp = new char[n + 1];//把旧空间的_str strcpy 到 tmp空间中strcpy(tmp, _str);//释放旧空间delete[] _str;//_str指向tmp_str = tmp;//容量重置成n_capacity = n;}}
解释:
1:vs的string类的reserve函数不支持缩容,所以我们为了保持一致,只对n>capacity进行扩容处理
2:因为n是和_capcacity比较,_capcacity是不计入'\0'的,所以我们开空间的时候自然是要多开一个的
3:只是重置_capacity为n即可,因为_capacity就是代表容量,而_size则不用管,其实代表存储的有效字符的大小,扩容并没有影响存储的有效字符
③:erase()函数
//erase()函数//s1.erase(2,5);从下标为2开始,清理5个字符void erase(size_t pos, size_t len = npos){//断言assert(pos < _size);//不传len值,代表从pos位置开始全部清理//len + pos >= _size 也是从pos位置开始全部清理if (len == npos || len + pos >= _size){_str[pos] = '\0';_size = pos;}else//代表从下标pos位置开始清理一部分{//将_str + pos + len往后的内容 strcpy到 _str + pos往后strcpy(_str + pos, _str + pos + len);//置_size-=len_size -= len;}}
解释:
1:断言是必要的,因为下标是_size代表,是'\0'的位置,从这里及以后的位置开始清理,无任何的意义
2:参数len给了缺省值,这是与库一致,不给len即代表len等于npos,npos在前文已经说过,一个size_t的类型,值为-1,即整形的最大值 ,所以len不给,代表从下标为pos位置开始全部清理
3:经过第2点,可以len == npos 代表一种从下标为pos位置开始全清理,初次之外,当len + pos >= _size 的时候,也是从下标为pos位置开始全清理
4: 2和3的判断条件必须 len == npos在前,len + pos >= _size在后,因为如果len等于npos,你是先判断len + pos >= _size时,会整形溢出,会出各种各样的bug,所以len == npos必须在前
5:也不能仅仅要len + pos >= _size这个条件,因为理由还是可能会整形溢出
④:clear()函数
//clear()函数//s1.clear();void clear(){//_size置0 且 在第一个位置放入'\0'_size = 0;_str[0] = '\0';}
解释:
1:此函数一般不直接使用,但在operator>> 的实现中有着重要作用
2:不仅要_size = 0 还要 _str[0] = '\0',因为有一些遍历不看size的大小,而是看什么时候遇到'\0',它才会停止
⑤:find()函数
find()找字符
//find()函数 找字符//s1.('h',2)从下标为2开始查找 'h'字符size_t find(char c, size_t pos = 0)const{//assert(pos < _size);//遍历查找for (size_t i = pos; i < _size; i++){if (_str[i] == c)return i;}//来到这里 代表找不到 返回npos(与库一致)return npos;}
解释:
1:find()函数找字符串,即遍历查找即可,找到返回下标,找不到返回npos(与库一致)
2: pos不给,则为0(与库的参数用形式一致)
3:const修饰的是*this,因为find函数不会对对象的内容改变,所以写成const,这样正常对象和const对象都能够使用
find()找字符串
//find()函数 找字符串//s1.("hello",2)从下标为2开始查找 "hello"字符串size_t find(const char* str, size_t pos = 0){//assert(pos < _size);//用strstr函数来从_str+pos位置开始查找字符串const char* ptr = strstr(_str + pos, str);//ptr为空 代表没找到 返回npos(于库一致)if (ptr == nullptr){return npos;}else{//指针相减得到下标return ptr - _str;}}
解释:
1:pos不给,则为0(与库的参数用形式一致)
2:strstr函数来进行查找字符串的功能,其次需要注意的是,在被查找的字符串中_str + pos指定查找的起始位置,pos为0则从头查找
3:strstr函数返回的如果是空,则代表没找到,此时返回npos即可(与库一致)
4:找到了返回的是一个被查找的字符串首次出现位置(指针),那我们要得到的是下标,所以ptr - _str 即可,指针相减得到的是下标
Q:为什么find函数都没有对pos进行断言
A:因为pos不管为什么值,都有对应的返回值,所以不需要断言
⑥:c_str()函数
//c_str()函数const char* c_str() const//后面加const 对于是非const的对象都能调用{return _str;}
解释:
1:c_str() 返回的是当前字符串的首字符地址,是可读不写的,所以返回值类型是const char *
2:直接 return _str 即可实现。
⑦:迭代器
typedef char* iterator;//只可读不可写的迭代器typedef const char* const_iterator;//iterator用的begin()iterator begin(){return _str;}//iterator用的end()iterator end(){return _str + _size;}//const_iterator用的begin()const_iterator begin()const{return _str;}//const_iterator用的end()const_iterator end()const{return _str + _size;}
解释:
1:与库一致,针对const对象和非const对象都有对应的迭代器
2:
string的底层是连续地物理空间,给原生指针++解引用能正好贴合迭代器的行为,就能做到遍历。
但是对于链表和树型结构来说,迭代器的实现就没有这么简单了。
但是,强大的迭代器通过统一的封装,无论是树、链表还是数组……
它都能用统一的方式遍历,这就是迭代器的优势,也是它的强大之处
三:string类重要的函数的实现
①:push_back()函数
//尾插void push_back(char c){//if判满if (_size == _capacity){//两种满的给不同容量newcapacitysize_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;//根据newcapacity扩容reserve(newcapacity);}//原先的_str[_size]是'\0',现在成为新尾插的字符_str[_size] = c;//_size++_size++;//'\0'往后移动一个_str[_size] = '\0';}
解释:
1:push_back意为尾插,既然是插入吗,肯定要先判断容量,在针对不同的容量为满的类型进行扩容
2:_str[_size] = c; 原本的'\0'的位置,插入了尾插的字符c,然后_size++,再补'\0'
②:append()函数
//append//s1.append("hello")void append(const char* str){//计算出要追加的字符串的长度size_t len = strlen(str);//已有的字符串长度(_size)+len 超过容量即扩容if (_size + len > _capacity){reserve(_size + len);}//把追加的str strcpy 到 _str + _size 往后strcpy(_str + _size, str);//置_size+=len_size += len;//reserve里面已经置capacity}
解释:
1:照样是判断容量,和push_back不同的是,是 _size + len 去判断,满了也是扩_size + len这么大容量的容
2:尾插字符串,则用strcpy函数即可,起始位置为_str + _size,即原本的'\0'的位置开始,然后更新_size即可,_capacity在扩容函数中就已重置了,所以不用再置
总结:push_back 和 append 都不常用,因为有更好的 +=运算符重载,其内部复用了push_back 和 append
③:+=运算符重载
//+=运算符重载函数 //s1+='w' 给s1+=一个字符string& operator+=(char c){//复用push_back()push_back(c);return *this;}
//+=运算符重载函数//s1+="hello" 给s1+=一个字符串string& operator+=(const char* str){//复用append()append(str);return *this;}
解释:
1:返回值是string& 是因为可以减少拷贝 ,二者分别复用了push_back 和 append
④:[ ]运算符重载
//[]运算符重载(只可读)const char& operator[](size_t pos)const{//'\0'也能读assert(pos <= _size);return _str[pos];}
//[]运算符重载(可读可写)char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}
解释:
1:和库一致,[ ]针对 const对象和正常对象都写了一个
2:返回值是char& 引用的意义是可以对其直接++
⑤:insert()函数
insert插入字符
//insert()函数 在下标为pos处插入字符//s1.insert(2,'w')在下标为2的位置,插入一个字符void insert(size_t pos, char c){//断言 最多能在'\0'的位置插入数据assert(pos <= _size);//判满 与push_back类似if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}//把下标从pos开始的所有字符向后移动一个size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}//腾出来的下标为pos的位置 给上要插入的字符_str[pos] = c;//_size++_size++;}
解释:
1:断言 最多能在'\0'的位置插入数据
2:判满,与push_back一致
3:把下标从pos开始的所有字符向后移动一个,统一往后移动一个,至于为什么end 初始化为_szie+1,后面会讲
4:腾出来的下标为pos的位置 给上要插入的字符
insert插入字符串
//insert()函数 在下标为pos处插入字符串//s1.insert(2,"xxxx") 在下标为2处插入字符串"xxxx"void insert(size_t pos, const char* str){//断言 最多能在'\0'的位置插入数据assert(pos <= _size);//判满 与append类似size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//把下标从pos开始的所有字符向后移动len个size_t end = _size + len;while (end > pos){_str[end] = _str[end - len];end--;}//把str的全部字符 复制到 s1的_str+pos处及往后的位置//strncpy 不计入'\0'strncpy(_str + pos, str, len);//置_size+=len_size += len;}
解释:
1:大部分都类似,移动的时候,变成了移动len个
2:用了strncpy函数,因为strcpy会默认把被复制的字符串的'\0'也复制过去,因为这里是局部插入,所以用strcpy的话,则会有两个'\0',必然是错的
3: strncpy函数 可以对被复制的字符串进行指定字节个数的复制,所以可以避免复制到'\0',_str + pos即起始位置,len即我们算出来的str的大小,strlen函数本身就是不计算'\0'的,所以这里刚好可以
4:重置_size
⑥:substr()函数
//substr()函数 截取_str的从下标为pos位置开始的len个字符去创建一个新的对象//s1.substr(2,5)string substr(size_t pos = 0, size_t len = npos){//断言 不能从'\0'及以后的位置进行截取assert(pos < _size);//截取字符串末尾下标为 endsize_t end = len + pos;//不传len值,代表从pos位置开始全部清理//len + pos >= _size 也是从pos位置开始全部清理if (len == npos || len + pos >= _size){//end就变成了最后的位置('\0'的位置)end = _size;}//end-pos 代表 从pos位置开始到最后一个字符的长度(不是'\0')//reserve 会为'\0'多开一个空间//建立一个新的对象string s2;//新对象的空间为end-pos 大小不包含'\0'但是reserve里面会多开辟一个给'\0's2.reserve(end - pos);//把截取的字符串 让新对象str不断地+=字符for (size_t i = pos; i < end; i++){s2 += _str[i];}return s2;}
解释:
1:断言,'\0'位置及以后进行截取无意义
2:pos不给为0,代表从头开始截取,len不给,代表从下标为pos的位置开始全部截取
3:对len和len+pos的判断与erase一致,在判断之前就size_t end = len + pos;这样判断不通过也会维持原样,判断通过了才会重置
4:得到正确的end后,end-pos得到新对象的_capacity的大小,然后再对该部分的字符串进行循环
得放入到新对象的_str(遍历从下标pos开始,到end-1)
5:+=的时候,就会自动置_size,所以不要再置
⑦:<< 和 >> 运算符重载
// cout << s1 → operator<<(cout, s1)
ostream& operator<<(ostream& out, const string& s) {//for (auto ch : s) {// out << ch;//}for (size_t i = 0; i < s.size(); i++) {out << s[i];}return out;
}
// cin >>
istream& operator<<(istream& in, string& s) {char ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;
}
解释:都很简单,需要注意的是istream类的对象对我们输入的字符串进行读的时候,遇到空格和换行不会进行读取,而是默认跳过,所以我们需要istream的成员函数get()才能读取到空格和换行
四:代码的测试
void test(){string s1;string s2("world");cout << "构造出的空s1:" << s1;cout << "构造出的s2:" << s2;cout << "使用+=,连续给s1+=字符" << endl;s1 += 'h';cout << s1;s1 += 'e';cout << s1;cout << "使用+=,给s1+=一个字符串llo " << endl;s1 += "llo";cout << "最终的的s1:" << s1;cout << endl;cout << endl;string s3 = s1;cout << "由s1拷贝得到的s3:" << s3;string s4 = s2;cout << "由s2拷贝得到的s4:" << s4;cout << endl;string s5;s5 = s1;string s6;s6 = s2;cout << "由s1赋值得到的s5:" << s5;cout << "由s2赋值得到的s6:" << s6;cout << endl;string s7("hello");cout << "打印正常非const对象s7:" << s7;const string s8("world");cout << "打印const对象s8:" << s8;cout << endl;//迭代器验证cout << "正常对象s7使用迭代器进行遍历打印:";string::iterator it = s7.begin();while (it != s7.end()){cout << *it;++it;}cout << endl;cout << "正常对象s7使用迭代器进行更改再打印:";string::iterator it2 = s7.begin();while (it2 != s7.end()){*it2 = 'x';cout << *it2;++it2;}cout << endl;cout << endl;cout << "const对象s8使用const迭代器进行遍历打印:";string::const_iterator it3 = s8.begin();while (it3 != s8.end()){cout << *it3;++it3;}cout << endl;//const对象s8无法进行写 /*cout << "const对象s8使用const迭代器进行遍历打印:";string::const_iterator it4 = s8.begin();while (it4 != s8.end()){*it4 = 'x';cout << *it;++it;}*/s1.swap(s2);cout << "用swap()函数交换s1和s2" << endl;cout << "交换后的s1:" << s1;cout << "交换后的s2:" << s2;cout << endl;cout << "在s1中找字符串rld" << endl;int pos = s1.find("rld");//不给pos 即从头开始找cout << "将下标给substr得到一个新的对象s9" << endl;string s9 = s1.substr(pos);//不传len 即从pos开始全部截取cout << "s9:" << s9;cout << "inset()函数在s9头插字符串wo:" << endl;s9.insert(0, "wo");cout << "s9:" << s9;}
测试结果如下:
解释:对多个函数进行了测试,准确无误
五:一些细节
①:构造/拷贝/赋值函数
解释:
1:string类的成员变量都可以直接在构造函数的体内进行初始化,那就不用也不要去初始化列表进行初始化了,因为和成员变量声明的顺序不一致可以会导致出错
2:string类的成员变量不是全部都是内置类型,其中一个变量指向了一块空间,所以构造函数 拷贝函数 赋值函数我们都是要进行深拷贝的,不管是传统写法还是现代写法,本质都是进行深拷贝
②:insert()函数
1:为什么end 初始化为_szie+1?因为这样当pos为0的时候么也就是头插的时候,end最小也只会小到0,不会为-1,为-1会怎么样?size_t为-1代表整形的最大值,所以根本无法跳出循环,如下图所示:
//把下标从pos开始的所有字符向后移动一个size_t end = _size;while (end >= pos){_str[end+1] = _str[end ];end--;}
解释:当我们end等于_size时,循环条件必须是end>=pos,才能把该向后移动的字符全部进行移动,此时就会出现死循环的情况
2:除开 end 初始化为_szie+1 然后循环条件end>pos,还有强转的方法:
int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}
解释:end和pos都是int了,就不会出错,切记不要仅仅把end写作int,因为这样循环判断里面的end>=pos,pos是size_t,二者比较会发生隐式类型转换(int向size_t转换),此时连监视窗口看不出来错误(end依旧是-1,pos依旧是0),这是万万不可的,如图:
//错误!!!
int end = _size;while (end >= pos){_str[end + 1] = _str[end];--end;}
六:源码
①:全写在一个.h里面的源码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;namespace key
{class string{public://构造函数string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//缺省函数 //既能string s1;//也能string s1("hello");//swap()函数void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷贝函数(现代写法)//string s1(s2);string(const string& s){//用s(s2的引用)的_str去构造一个tmp对象string tmp(s._str);//然后用swap函数把tmp对象 和 *this 进行交换swap(tmp);}拷贝函数(传统写法)//string(const string& s)//{// _str = new char[s._capacity + 1];// strcpy(_str, s._str);// _size = s._size;// _capacity = s._capacity;//}//赋值函数(现代写法)//s1 = s2;string& operator=(string s){//该函数参数s去接受s2的时候,s2拷贝生成了s//直接swap 交换s 和 *this(s1)swap(s);return *this;}赋值函数(传统写法)//string& operator=(const string& s)//{// if (this != &s)// {// char* tmp = new char[s._capacity + 1];// strcpy(tmp, s._str);// delete[] _str;// _str = tmp;// _size = s._size;// _capacity = s._capacity;// }// return *this;//}//析构函数~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}//以上是默认成员函数//---------------------------------------------------------//迭代器//可读可写的迭代器typedef char* iterator;//只可读不可写的迭代器typedef const char* const_iterator;//iterator用的begin()iterator begin(){return _str;}//iterator用的end()iterator end(){return _str + _size;}//const_iterator用的begin()const_iterator begin()const{return _str;}//const_iterator用的end()const_iterator end()const{return _str + _size;}//扩容函数//s1.reverse(10)void reserve(size_t n){//vs的string类的reserve函数不支持缩容//所以我们为了保持一致,只对n>capacity进行扩容处理if (n > _capacity){//开辟新的空间(n+1是因为多开一个给'/0')char* tmp = new char[n + 1];//把旧空间的_str strcpy 到 tmp空间中strcpy(tmp, _str);//释放旧空间delete[] _str;//_str指向tmp_str = tmp;//容量重置成n_capacity = n;}}//c_str()函数const char* c_str() const//后面加const 对于是非const的对象都能调用{return _str;}//size函数()size_t size() const{//返回成员变量_size的值return _size;}//[]运算符重载(只可读)const char& operator[](size_t pos)const{//'\0'也能读assert(pos <= _size);return _str[pos];}//对const类型的对象 返回值也必须是const 这叫权限一致//[]运算符重载(可读可写)char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}//find()函数 找字符//s1.('h',2)从下标为2开始查找 'h'字符size_t find(char c, size_t pos = 0)const{//assert(pos < _size);//遍历查找for (size_t i = pos; i < _size; i++){if (_str[i] == c)return i;}//来到这里 代表找不到 返回npos(与库一致)return npos;}//find()函数 找字符串//s1.("hello",2)从下标为2开始查找 "hello"字符串size_t find(const char* str, size_t pos = 0){//assert(pos < _size);//用strstr函数来从_str+pos位置开始查找字符串const char* ptr = strstr(_str + pos, str);//ptr为空 代表没找到 返回npos(于库一致)if (ptr == nullptr){return npos;}else{//指针相减得到下标return ptr - _str;}}//erase()函数//s1.erase(2,5);从下标为2开始,清理5个字符void erase(size_t pos, size_t len = npos){//断言assert(pos < _size);//不传len值,代表从pos位置开始全部清理//len + pos >= _size 也是从pos位置开始全部清理if (len == npos || len + pos >= _size){_str[pos] = '\0';_size = pos;}else//代表从下标pos位置开始清理一部分{//将_str + pos + len往后的内容 strcpy到 _str + pos往后strcpy(_str + pos, _str + pos + len);//置_size-=len_size -= len;}}//clear()函数//s1.clear();void clear(){//_size置0 且 在第一个位置放入'\0'_size = 0;_str[0] = '\0';}//以上是一些工具函数//-----------------------------------------------------------------//尾插void push_back(char c){//if判满if (_size == _capacity){//两种满的给不同容量newcapacitysize_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;//根据newcapacity扩容reserve(newcapacity);}//原先的_str[_size]是'\0',现在成为新尾插的字符_str[_size] = c;//_size++_size++;//'\0'往后移动一个_str[_size] = '\0';}//append//s1.append("hello")void append(const char* str){//计算出要追加的字符串的长度size_t len = strlen(str);//已有的字符串长度(_size)+len 超过容量即扩容if (_size + len > _capacity){reserve(_size + len);}//把追加的str strcpy 到 _str + _size 往后strcpy(_str + _size, str);//置_size+=len_size += len;//reserve里面已经置capacity}//+=运算符重载函数 //s1+='w' 给s1+=一个字符string& operator+=(char c){//复用push_back()push_back(c);return *this;}//+=运算符重载函数//s1+="hello" 给s1+=一个字符串string& operator+=(const char* str){//复用append()append(str);return *this;}//insert()函数 在下标为pos处插入字符//s1.insert(2,'w')在下标为2的位置,插入一个字符void insert(size_t pos, char c){//断言 最多能在'\0'的位置插入数据assert(pos <= _size);//判满 与push_back类似if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}//把下标从pos开始的所有字符向后移动一个size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}//腾出来的下标为pos的位置 给上要插入的字符_str[pos] = c;//_size++_size++;}//insert()函数 在下标为pos处插入字符串//s1.insert(2,"xxxx") 在下标为2处插入字符串"xxxx"void insert(size_t pos, const char* str){//断言 最多能在'\0'的位置插入数据assert(pos <= _size);//判满 与append类似size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//把下标从pos开始的所有字符向后移动len个size_t end = _size + len;while (end > pos){_str[end] = _str[end - len];end--;}//把str的全部字符 复制到 s1的_str+pos处及往后的位置//strncpy 不计入'\0'strncpy(_str + pos, str, len);//置_size+=len_size += len;}//substr()函数 截取_str的从下标为pos位置开始的len个字符作为一个新的对象//s1.substr(2,5)string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);size_t end = len + pos;if (len == npos || len + pos >= _size){end = _size;}string s2;s2.reserve(end - pos);for (size_t i = pos; i < end; i++){s2 += _str[i];}return s2;}//以上是string的重要函数//-------------------------------------- private:size_t _size = 0;size_t _capacity = 0;char* _str = nullptr;static const size_t npos;};const size_t string::npos = -1;//<<运算符重载ostream& operator<<(ostream& out, const string& s){for (size_t i = 0; i < s.size(); i++){out << s[i];}cout << '\n';return out;}//>>运算符重载istream& operator>>(istream& in, string& s){s.clear();char buff[128];char ch = in.get();int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();if (i > 0){buff[i] = '\0';s += buff;}}return in;}//流插入 流提取的运算符重载//-----------------------------------------------void test(){string s1;string s2("world");cout << "构造出的空s1:" << s1;cout << "构造出的s2:" << s2;cout << "使用+=,连续给s1+=字符" << endl;s1 += 'h';cout << s1;s1 += 'e';cout << s1;cout << "使用+=,给s1+=一个字符串llo " << endl;s1 += "llo";cout << "最终的的s1:" << s1;cout << endl;cout << endl;string s3 = s1;cout << "由s1拷贝得到的s3:" << s3;string s4 = s2;cout << "由s2拷贝得到的s4:" << s4;cout << endl;string s5;s5 = s1;string s6;s6 = s2;cout << "由s1赋值得到的s5:" << s5;cout << "由s2赋值得到的s6:" << s6;cout << endl;string s7("hello");cout << "打印正常非const对象s7:" << s7;const string s8("world");cout << "打印const对象s8:" << s8;cout << endl;//迭代器验证cout << "正常对象s7使用迭代器进行遍历打印:";string::iterator it = s7.begin();while (it != s7.end()){cout << *it;++it;}cout << endl;cout << "正常对象s7使用迭代器进行更改再打印:";string::iterator it2 = s7.begin();while (it2 != s7.end()){*it2 = 'x';cout << *it2;++it2;}cout << endl;cout << endl;cout << "const对象s8使用const迭代器进行遍历打印:";string::const_iterator it3 = s8.begin();while (it3 != s8.end()){cout << *it3;++it3;}cout << endl;//const对象s8无法进行写 /*cout << "const对象s8使用const迭代器进行遍历打印:";string::const_iterator it4 = s8.begin();while (it4 != s8.end()){*it4 = 'x';cout << *it;++it;}*/s1.swap(s2);cout << "用swap()函数交换s1和s2" << endl;cout << "交换后的s1:" << s1;cout << "交换后的s2:" << s2;cout << endl;cout << "在s1中找字符串rld" << endl;int pos = s1.find("rld");//不给pos 即从头开始找cout << "将下标给substr得到一个新的对象s9" << endl;string s9 = s1.substr(pos);//不传len 即从pos开始全部截取cout << "s9:" << s9;cout << "inset()函数在s9头插字符串wo:" << endl;s9.insert(0, "wo");cout << "s9:" << s9;}
}
②:分离开的.h和.cpp
.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<assert.h>
#include<iostream>using namespace std;namespace bit
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}const char* c_str() const{return _str;}size_t size() const{return _size;}string(const char* str = "");// ִдstring(const string& s);string& operator=(string s);~string();const char& operator[](size_t pos) const;char& operator[](size_t pos);void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void erase(size_t pos, size_t len = npos);void swap(string& s);size_t find(char ch, size_t pos = 0);//21:10size_t find(const char* str, size_t pos = 0);string substr(size_t pos = 0, size_t len = npos);void clear();private:size_t _capacity = 0;size_t _size = 0;char* _str = nullptr;const static size_t npos = -1;};istream& operator>>(istream& in, string& s);ostream& operator<<(ostream& out, const string& s);
}
.cpp
#include"string.h"namespace bit
{string::string(const char* str){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}// ִдstring::string(const string& s){string tmp(s._str);swap(tmp);}string& string::operator=(string s){swap(s);return *this;}string::~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}const char& string::operator[](size_t pos) const{assert(pos <= _size);return _str[pos];}char& string::operator[](size_t pos){assert(pos <= _size);return _str[pos];}void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void string::push_back(char ch){if (_size == _capacity){size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}_str[_size] = ch;_size++;_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}/*int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;}void string::insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}strncpy(_str + pos, str, len);_size += len;}void string::erase(size_t pos, size_t len){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}size_t string::find(char ch, size_t pos){for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t string::find(const char* str, size_t pos){const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}}string string::substr(size_t pos, size_t len){assert(pos < _size);size_t end = pos + len;if (len == npos || pos + len >= _size){end = _size;}string str;str.reserve(end - pos);for (size_t i = pos; i < end; i++){str += _str[i];}return str;}void string::clear(){_size = 0;_str[0] = '\0';}ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear();char buff[128];char ch = in.get();int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}
}