欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > string类的模拟实现

string类的模拟实现

2025/5/12 13:27:44 来源:https://blog.csdn.net/shylyly_/article/details/143327823  浏览:    关键词:string类的模拟实现

前提:

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;}
}

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词