模拟实现string【C++】

liuyunluoxiao 2024-06-30 10:05:02 阅读 100

文章目录

全部的实现代码放在了文章末尾准备工作包含头文件定义命名空间和类类的成员变量

构造函数默认构造拷贝构造

重载赋值拷贝函数析构函数迭代器和获取迭代器迭代器获取迭代器

resize【调整size】图解

reserve【调整capacity】empty【判断串是否为空】operator[]appendpush_backoperator+=insert【在pos之前插入字符串】erase【删除从pos开始的len个字符】swapfindsubstroperator+compare比较运算符重载operator>operator==operator<operator>operator<=operator!=

c_stroperator<<【输出运算符重载】operator>>【输入运算符重载】全部代码mystring.hmystring.cpp

全部的实现代码放在了文章末尾

准备工作

创建三个文件,一个头文件mystring.h,两个源文件mystring.cpp tesr.cpp

因为是简单实现,所以只实现了string里放char这一种实现,就没有用模板了

mystring.h:存放包含的头文件,命名空间的定义mystring.cpp:存放成员函数和命名空间中的函数的定义test.cpp:存放main函数,以及测试代码


包含头文件

iostream:用于输入输出string.h:C语言的头文件,用于使用其中的操作字符数组的函数assert.h:用于使用报错函数


定义命名空间和类

在文件mystring.hmystring.cpp中都定义上一个命名空间mystring

mystring.h中类的声明放进命名空间,把mystring中的函数实现也放进命名空间

注意:

不同源文件的同名的命名空间经过编译链接之后可以合成在一起

类的成员变量

在这里插入图片描述


构造函数

默认构造

在这里插入图片描述

给默认构造的str加上缺省值(“”)就可以实现不传参数时就是空串了


拷贝构造

因为成员str申请了堆区空间,所以要手写拷贝构造,并且要实现深拷贝

在这里插入图片描述


重载赋值拷贝函数

因为成员str申请了堆区空间,所以要手写赋值拷贝,并且要实现深拷贝

在这里插入图片描述

为什么要防止自己给自己赋值?

【this指针指向接收赋值的对象,所以只要this传入的参数的地址相等就是自己赋值给自己】

因为要深拷贝,所以要把一个对象中的所有成员都拷贝一遍,时间复杂度是

O(N),所以有必要防止自己给自己赋值


析构函数

因为成员str申请了堆区空间,所以要手写析构,不能用编译器给的默认析构

在这里插入图片描述


迭代器和获取迭代器

迭代器

因为存放字符的空间的地址是连续的,且只是简单模拟

所以我用了char*作为普通迭代器,const char*作为const迭代器

直接把char*重命名为iterator,把const char*重命名为const_iterator就完成了迭代器的实现


获取迭代器

在这里插入图片描述

因为使用了char*作为迭代器

所以str就是第一个有效元素,str+size就是最后一个有效字符的下一个位置(\0)

又因为const修饰的对象只能调用const修饰的成员函数

所以如果是const修饰的对象调用begin()和end()的时候,就会自动调用到const修饰的begin和end.

在这里插入图片描述


resize【调整size】

void string::resize(size_t n,char c='\0')

{

if (n > _capacity) 当要调整的容量n大于最大容量时,就要扩容

{

char* tmp = new char[n +1];申请size+1个空间,那多出来的1是给'\0'的

strcpy(tmp, _str);拷贝字符串(把右参数拷贝给左参数)

delete[] _str;释放扩容前str指向的空间

_str = tmp;完成扩容

for (; _size <n; _size++)把多出来的有效字符用c补上

{

_str[_size] = c;

}

_str[_size] = '\0'; 补完之后加上字符串结束标志'\0'

_capacity = n;更新最大容量

}

else

{

if (n > _size)如果调整之后的size 大于 原来的size

就要把少(n-size)的有效字符用c补上

{

for (; _size < n; _size++)把多出来的有效字符用c补上

{

_str[_size] = c;

}

_str[_size] = '\0';补完之后加上字符串结束标志'\0'

}

else 如果调整之后的size 小于 原来的size

就要把多的字符删除

{

_size = n;调整有效字符大小

_str[_size] = '\0';直接把n位置改成'\0'即可删除多余字符

}

}

}

图解

在这里插入图片描述

在这里插入图片描述


reserve【调整capacity】

在这里插入图片描述


empty【判断串是否为空】

在这里插入图片描述


operator[]

返回值要是char&这样才能像字符数组一样访问和修改串中的字符

在这里插入图片描述


append

在这里插入图片描述

在这里插入图片描述


push_back

可以直接服用append

在这里插入图片描述


operator+=

也可以直接服用append

在这里插入图片描述


insert【在pos之前插入字符串】

在这里插入图片描述


erase【删除从pos开始的len个字符】

在这里插入图片描述


swap

因为存放字符串的空间是在堆区开辟的,用成员str去指向的

所以直接交换两个对象的str中存放的地址就可以完成存储的字符串的交换了

不需要拷贝

在这里插入图片描述

在这里插入图片描述


find

在这里插入图片描述


substr

在这里插入图片描述


operator+

直接复用operator+=

因为+不改变字符串本身,所以要用一个临时对象存储+=之后的字符,再用传值返回即可

在这里插入图片描述


compare

在这里插入图片描述


比较运算符重载

operator>

复用compare

在这里插入图片描述


operator==

复用compare

在这里插入图片描述


operator<

复用operator>operator==

在这里插入图片描述


operator>

复用operator>operator==

在这里插入图片描述


operator<=

复用operator>

在这里插入图片描述


operator!=

复用operator==

在这里插入图片描述


c_str

作用是获取string对象中的str成员。

一般是要在C++中使用C语言函数时使用,因为C语言不支持string,所以只能用字符指针代替一下

在这里插入图片描述

operator<<【输出运算符重载】

重载了之后就可以

直接用cout输出string实例化的对象了

string a(“aaaaa”);

cout<<a<<endl;

在这里插入图片描述


operator>>【输入运算符重载】

重载了之后就可以

直接用cin把字符串输入到string实例化的对象里面了

istream是输入流对象,比如我们常用的 cin 就是 istream实例化的对象

istream& operator>>(istream& is, string& obj)

{

char str[100]; 存储 输入的 每一行的字符

int sum = 0; 使用sum记录输入的 每一行 的字符个数

char c = 0;

\n是换行符(回车),所以 没遇到 \n就 没换行

while ((c = is.get()) != '\n') 一次读取一个字符

{

if (sum == 99) 当sum等于99时说明 str数组存不下了

{

str[sum] = '\0'; 末尾加上了\0才是字符串

obj += str; 使用+=把str数组中的字符先加上去

sum = 0; sum置成0,继续记录

}

else 否则

{

把读取到的字符存进str数组里

str[sum++] = c;

}

}

sum!=0说明最后输入的最后一行字符个数 小于99个,没有+=上

if (sum != 0)

{

str[sum] = '\0'; 末尾加上了\0才是字符串

obj += str; 使用+=把str数组中的字符先加上去

}

return is; 为支持链式编程,返回 输入流对象 的引用

}


全部代码

mystring.h

#include<iostream>

#include<string.h>

#include<assert.h>

using namespace std;

namespace mystring

{

class string

{

public:

typedef char* iterator;

typedef const char* const_iterator;

string(const string& obj);

string(const char* str = "");

string& operator=(const string&obj);

~string();

iterator begin();

const_iterator begin() const;

iterator end();

const_iterator end() const;

size_t size() const;

size_t capacity() const;

void resize(size_t n, char c='\0');

void reserve(size_t n = 0);

void clear();

bool empty() const;

char& operator[](size_t pos);

const char& operator[](size_t pos)const;

string& append(const string& obj);

string& append(char ch);

void push_back(char c);

string& operator+=(const string& obj);

string& operator+=(char c);

string& assign(const string& str);

string& insert(size_t pos, const string& str);

string& insert(size_t pos,char c);

string& erase(size_t pos = 0, size_t len = npos);

void swap(string& str);

const char* c_str() const;

size_t find(const string& str, size_t pos = 0) const;

size_t find(char c, size_t pos = 0) const;

string substr(size_t pos = 0, size_t len = npos) const;

int compare(const string& str) const;

string operator+(const string& obj)const;

string operator+(char c)const;

bool operator>(const string&obj)const;

bool operator<(const string& obj)const;

bool operator>=(const string& obj)const;

bool operator<=(const string& obj)const;

bool operator==(const string& obj)const;

bool operator!=(const string& obj)const;

private:

char* _str;//指向存放字符串的堆区空间的指针

size_t _size;//字符串的有效字符个数

size_t _capacity;//字符串的最大容量

static const size_t npos;//理论上可以存储的最大字符个数

};

ostream& operator<< (ostream& os, const string& obj);

istream& operator>>(istream& is,string& obj);

}


mystring.cpp

#include"string.h"

namespace mystring

{

const size_t string::npos = -1;

string::string(const string& obj)//因为成员在堆区申请了空间所以要写深拷贝的拷贝构造

{

_str = new char[obj._size + 1];// 申请size + 1个空间,那多出来的1是给'\0'的

strcpy(_str, obj._str);//使用该函数把字符串把obj中的字符串拷贝过来

_capacity = obj._capacity;

_size = obj._size;

}

string::string(const char* str) //让str的缺省值为""(空字符串)

:_size(strlen(str))//构造函数会先走成员初始化列表,借此先计算出size

{

assert(str != nullptr);//防止传入的字符指针是空的

_str = new char[_size + 1];//申请size+1个空间,那多出来的1是给'\0'的

strcpy(_str, str);//使用该函数把字符串str中的字符拷贝过来

_capacity = _size;//设置初始最大容量和size一样大

}

string& string::operator=(const string&obj)

{

if (this != &obj)//防止自己赋值给自己

{

delete[] _str;//先释放接收赋值之前str指向的堆区空间

//因为接收赋值之后,str中存放的地址就被覆盖了

_str = new char[obj._size + 1];//申请size+1个空间,那多出来的1是给'\0'的

strcpy(_str, obj._str);//拷贝字符串(把右参数拷贝给左参数)

_size = obj._size;

_capacity = obj._capacity;

}

return *this;

}

string::~string()

{

delete[] _str;//释放str指向的堆区空间

_str = nullptr;

_size = 0;

_capacity = 0;

}

string::iterator string::begin()//普通起始迭代器

{

return _str;

}

string::const_iterator string::begin() const//const起始迭代器

{

return _str;

}

string::iterator string::end()//普通结束迭代器

{

return _str + _size;

}

string::const_iterator string::end() const//const结束迭代器

{

return _str + _size;

}

size_t string::size() const

{

return _size;

}

void string::resize(size_t n,char c)

{

if (n > _capacity)//当要调整的size,n大于最大容量时,就要扩容

{

char* tmp = new char[n +1];//申请size+1个空间,那多出来的1是给'\0'的

strcpy(tmp, _str);//拷贝字符串(把右参数拷贝给左参数)

delete[] _str;//释放扩容前str指向的空间

_str = tmp;//完成扩容

for (; _size <n; _size++)//把多出来的有效字符用c补上

{

_str[_size] = c;

}

_str[_size] = '\0'; //补完之后加上字符串结束标志'\0'

_capacity = n;//更新最大容量

}

else

{

if (n > _size)//如果调整之后的size 大于 原来的size

//就要把少(n-size)的有效字符用c补上

{

for (; _size < n; _size++)//把多出来的有效字符用c补上

{

_str[_size] = c;

}

_str[_size] = '\0';//补完之后加上字符串结束标志'\0'

}

else//如果调整之后的size 小于 原来的size

//就要把多的字符删除

{

_size = n;//调整有效字符大小

_str[_size] = '\0';//直接把n位置改成'\0'即可删除多余字符

}

}

}

size_t string::capacity() const

{

return _capacity;

}

void string::reserve(size_t n)//注意:n是指元素个数,不是字节数

{

if (n > _capacity)//当要调整的容量n大于capacity时,才扩容

{

char* tmp = new char[n + 1];//申请size+1个空间,那多出来的1是给'\0'的

strcpy(tmp, _str);//拷贝字符串(把右参数拷贝给左参数)

delete[] _str;//拷贝之后再释放旧空间

_str = tmp;//指向新开辟的空间

_capacity = n;//更改最大容量

}

}

void string::clear()

{

_size = 0;

_str[0] = '\0';

}

bool string::empty() const

{

return _size == 0;//如果size==0就为真,就会return true

//如果size!=0就为假,就会return false

}

char& string::operator[](size_t pos)

{

assert(pos<_size);//防止越界访问

return _str[pos];//因为存放字符的空间是连续的

//所以直接像数组一样,使用pos位置的字符就可以

}

//const修饰的对象会自动调用下面这个

const char& string::operator[](size_t pos)const

{

assert(pos < _size);//防止越界访问

return _str[pos];

}

//在串尾加上一个string对象或者 字符串【可以隐式类型转换为string对象】

string& string::append(const string& obj)

{

if (_capacity < obj._size + _size)//如果容量不够了

{

reserve(obj._size + _size);//就扩容

}

//从指定地址开始 拷贝字符串(把右参数拷贝给左参数)

strcpy(_str + _size, obj._str);

_size += obj._size;//改变有效字符个数

return *this;

}

//在串尾加上一个字符

string& string::append(char ch)

{

if (_capacity <_size+1)//如果容量不够了

{

reserve(_size + 1);//就扩容

}

_str[_size++] = ch;

_str[_size] = '\0';//\0被ch覆盖了,再加回来

return *this;

}

void string::push_back(char c)

{

append(c);//复用append,尾插一个字符c

}

string& string::operator+=(const string& obj)

{

append(obj);//复用append,尾插一个string对象/字符串

return *this;

}

string& string::operator+=(char c)

{

append(c);//复用append,尾插一个字符c

return *this;

}

string& string::assign(const string& obj)

{

*this = obj;

return *this;

}

string& string::insert(size_t pos, const string& obj)

{

if (_capacity < obj._size + _size)//如果容量不够了

{

reserve(obj._size + _size);//就扩容

}

//从要插入的pos位置开始,把pos和其之后的字符都

//向后移动 传入的对象的size个位置

for (int i = _size; i >=(int) pos; i--)

{

_str[i + obj._size] = _str[i];

}

//把字符串插入进去

for (int i = pos,j=0; i<obj._size+pos; i++,j++)

{

_str[i] = obj._str[j];

}

_size += obj._size;

_str[_size] = '\0';//再补上\0

return *this;

}

string& string::insert(size_t pos, char c)

{

if (_capacity < _size + 1)

{

reserve(_size + 1);

}

for (int i = _size; i >=(int) pos; i--)

{

_str[i + 1] = _str[i];

}

_str[pos] = c;

return *this;

}

string& string:: erase(size_t pos , size_t len)

{

if (len > _size - pos)//如果len大于pos及其之后的字符的长度 或者 len==npos

{

_str[pos] = '\0';//就直接把pos及其之后的字符 全部删除

_size = pos;//把size更改成新的有效长度

}

else//否则

{

//把从pos开始的字符都 用它+len之后的字符进行覆盖,即可完成删除

for (int i = pos; i <=_size-len; i++)

{

_str[i] = _str[i + len];

}

_size -= len;//把size更改成新的有效长度

}

return *this;

}

void string::swap(string& obj)

{

//使用库里面的swap把 两个对象的成员变量交换即可

std::swap(_str, obj._str);

std::swap(_size, obj._size);

std::swap(_capacity, obj._capacity);

}

//获取string对象中的 str成员

const char* string::c_str() const

{

return _str;

}

//从pos位置开始查找字符串

size_t string::find(const string&obj, size_t pos) const

{

const char* p = NULL; //p为找到的字符串的首地址

//使用string.h里面的strstr查找子串,如果 找不到 就返回NULL

if ((p=strstr(_str + pos, obj._str)) == NULL)

{

return npos;//找不到 就返回 npos

}

else

{

return p - _str;//返回下标,p为找到的字符串的 首地址

//p-字符数组的首地址str 等于str到p之前的元素个数-1,即下标

}

}

size_t string::find(char c, size_t pos ) const

{

for (int i = pos; i < _size; i++)

{

if (_str[i] == c)

return i;

}

return npos;

}

//把从pos开始的长度为len的子串 作为一个新的字符串返回

string string::substr(size_t pos, size_t len) const

{

if (len > _size - pos)//如果len大于pos及其之后的所有 字符的长度或者len==npos

{

string tmp(_str + pos);//直接用默认构造把pos及其之后的字符全部 做新串返回

return tmp;

}

else//否则

{

//申请长度为len+1的空间,多出的1是给\0的

char* p = new char[len + 1];

strncpy(p, _str+pos, len);//把从pos开始的长度为len的子串拷贝给p

p[len] = '\0';

string tmp(p);//用默认构造创建出新字符串

return tmp;

}

}

//比较两个字符串的大小

//返回值大于0就是 左>右

// 返回值等于0就是 左=右

// 返回值小于0就是 左<右

int string::compare(const string& obj) const

{

//使用string.h里面的 strcmp即可完成判断

return strcmp(_str, obj._str);

}

string string::operator+(const string& obj)const

{

string tmp(*this);//拷贝构造出一个临时对象

tmp += obj;//让临时对象去+=,就不会改变字符串自己了

return tmp;//传值返回

}

string string::operator+(char c)const

{

string tmp(*this);//拷贝构造出一个临时对象

tmp += c;

return tmp;

}

bool string::operator>(const string& obj)const

{

if (compare(obj) > 0)//返回值大于0就是 左>右

//即调用函数的对象>传入的对象

return true;

else

return false;

}

bool string::operator<(const string& obj)const

{

if (!(*this >= obj))// < 就是>=取反

{

return true;

}

else

return false;

}

bool string::operator>=(const string& obj)const

{

//大于等于 是 大于或者等于

if (*this > obj || *this == obj)

return true;

else

return false;

}

bool string::operator<=(const string& obj)const

{

//小于等于就是 不大于,即大于取反

if (!(*this > obj))

return true;

else

return false;

}

bool string::operator==(const string& obj)const

{

if (compare(obj) == 0)//返回值大于0就是 左=右

//即调用函数的对象=传入的对象

return true;

else

return false;

}

bool string::operator!=(const string& obj)const

{

//不等于就是 等于取反

if (!(*this==obj))

return true;

else

return false;

}

//ostream是输出流对象,比如我们常用的cout就是 ostream实例化的对象

ostream& operator<< (ostream& os, const string& obj)

{

const char* p = obj.c_str();//获取string对象中的 str成员

os << p;//可以用字符指针直接输出 它指向 的字符串

return os;//为支持链式编程,返回输出流对象的引用

}

//istream是输入流对象,比如我们常用的 cin 就是 istream实例化的对象

istream& operator>>(istream& is, string& obj)

{

char str[100];//存储 输入的 每一行的字符

int sum = 0;//使用sum记录输入的 每一行 的字符个数

char c = 0;

//\n是换行符(回车),所以 没遇到 \n就 没换行

while ((c = is.get()) != '\n')//一次读取一个字符

{

if (sum == 99)//当sum等于99时说明 str数组存不下了

{

str[sum] = '\0';//末尾加上了\0才是字符串

obj += str;//使用+=把str数组中的字符先加上去

sum = 0;//把sum置成0,继续记录

}

else//否则

{

//把读取到的字符存进str数组里

str[sum++] = c;

}

}

//sum!=0说明最后输入的最后一行字符个数 小于99个,没有+=上

if (sum != 0)

{

str[sum] = '\0';//末尾加上了\0才是字符串

obj += str;//使用+=把str数组中的字符先加上去

}

return is;//为支持链式编程,返回 输入流对象 的引用

}

}




声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。