文章目录
- 全部的实现代码放在了文章末尾
- 准备工作
-
- 包含头文件
- 定义命名空间和类
-
- 类的成员变量
- 构造函数
-
- 默认构造
- 拷贝构造
- 重载赋值拷贝函数
- 析构函数
- 迭代器和获取迭代器
-
- 迭代器
- 获取迭代器
- resize【调整size】
-
- 图解
- reserve【调整capacity】
- empty【判断串是否为空】
- operator[]
- append
- push_back
- operator+=
- insert【在pos之前插入字符串】
- erase【删除从pos开始的len个字符】
- swap
- find
- substr
- operator+
- compare
- 比较运算符重载
-
- operator>
- operator==
- operator<
- operator>
- operator<=
- operator!=
- c_str
- operator<<【输出运算符重载】
- operator>>【输入运算符重载】
- 全部代码
-
- mystring.h
- mystring.cpp
全部的实现代码放在了文章末尾
准备工作
创建三个文件,一个头文件mystring.h
,两个源文件mystring.cpp
tesr.cpp
【因为是简单实现,所以只实现了string里放char这一种实现,就没有用模板了
】
- mystring.h:存放包含的头文件,命名空间的定义
- mystring.cpp:存放成员函数和命名空间中的函数的定义
- test.cpp:存放main函数,以及测试代码
包含头文件
- iostream:用于输入输出
- string.h:C语言的头文件,用于使用其中的操作字符数组的函数
- assert.h:用于使用报错函数
定义命名空间和类
在文件mystring.h
和mystring.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】
cpp
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实例化的对象了
例
cpp
string a("aaaaa");
cout<<a<<endl;
operator>>【输入运算符重载】
重载了之后就可以
直接用cin把字符串输入到string实例化的对象里面了
cpp
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
cpp
#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
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;//为支持链式编程,返回 输入流对象 的引用
}
}