第一部分:构造,析构,拷贝构造,赋值重载,打印函数这几个大头写出来先
namespace xxx
{
class string
{
public:
//
//
private:
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos = -1;//c++允许const static整数可以定义在类里-但只有几个特定的整数可以这样
};
}
//构造函数
string(const char* str = "")//给缺省值
// :_size(strlen(str))
// ,_capacity(strlen(str))
//这里不用初始化列表,这里要一个个strlen赋值比较麻烦
{
_size = strlen(str);//_size为有效字符的长度
_capacity = _size;//capacity初始化都为有效字符的长度
_str = new char[_capacity + 1];//留一个字符给'\0'
strcpy(_str, str);//拷贝过去
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//拷贝构造-深拷贝噢
string(const string& s)//传引用
{
_str = new char[s._capacity + 1];//留一个字符给斜杠0
strcpy(_str, s._str);//_str拷贝
_size = s._capacity;
_capacity = s._capacity;
}
//赋值重载
string& operator=(const string& s)//传引用
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];//开新空间
strcpy(tmp, s._str);//拷贝
delete[]_str;//删除_str的就空间
_str = tmp;//指向新空间
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
对于【为什么要开一块新空间然后还要把之前的旧空间释放掉】有疑惑的朋友可以来看这篇
https://blog.csdn.net/m0_71841506/article/details/127291467/
//返回一个指向正规C字符串的指针常量, 内容与本string串相同
const char* c_str()const
{
return _str;
}
size_t size()const
{
return _size;
}
// capacity
size_t capacity()const
{
return _capacity;
}
基本上写到这就能输出hello world拉!
第二部分:各类功能和升级
typedef char* iterator;
iterator begin()
{
return _str;//返回第一个指针位置
}
iterator end()
{
return _str + _size;//返回最后一个有效字符的下一个位置
}
实现iterator之后我们可以这样玩
还能用范围for
但是我们把iterator实现的部分注释掉的话,范围for就用不了了;又或者说把iterator begin()改为iterator Begin()我们会发现iterator打印还能用,但范围for也用不了了;这说明范围for底层与iterator实现有关!至于为啥嘛。。。知道的可以在评论区留言~
char &operator[](int pos)const//方括号重载
{
return _str[pos];
}
重载之后我们就能用数组【】访问
void push_back(char ch)//尾插字符
{
if (_size == _capacity)//扩容
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size] = ch;//在末尾追加字符ch
_size++;
_str[_size] = '\0';//加上\0
}
string& operator+=(char ch)//追加字符重载
{
push_back(ch);
return *this;
}
//后面追加追加字符串
void append(const char* str)
{
size_t len = strlen(str);//记录str有效字符串长度
if (_size + len > _capacity)//直接扩容
{
reserve(_size + len);
}
strcpy(_str + _size, str);//strcpy把str连同\0一起拷贝过来了,所以不需要考虑\0
_size += len;
}
string& operator+=(const char* str)//追加字符串重载
{
append(str);
return *this;
}
void clear()//清除所有有效字符
{
_str[_size] = '\0';
_size = 0;
}
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.clear();//如果流提取之前s里面有字符,则要清除
char buff[128] = { '\0' };//开一块空间做中转站
size_t i = 0;
char ch= in.get();//用ch一个个接收s的字符
while (ch != ' ' && ch != '\n')//当ch接收到换行或者空格则停止
{
if (i == 127)
{
//满了-buff尾接给s
s += buff;
i = 0;
}
//没满ch继续接收
buff[i++] = ch;
ch = in.get();
}
if (i>0)//如果没满则buff没有尾插到s后面;又或者buff满过的部分尾接给了s,但剩下的部分没有尾接给s
{
buff[i] = '\0';//要携带\0 !
s += buff;
}
return in;
}
那么我们就能用流插入打印,流提取我们输入的内容拉
size_t和int类型同时在操作符两边,会造成整形提升-小的往大的提升(int->size_t);那么int也转变为无符号数,则没有负数,比较大小会出错,会进入死循环;
在pos位置插入字符ch
string& insert(size_t pos, char ch)//插入字符-
{
assert(pos <=_size);
if (_size == _capacity)//扩容
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
//挪动数据
//size_t end = _size+1;//(size_t)无符号版
//while (end>pos)
//{
// _str[end] = _str[end-1];
// end--;
//}
//(int)有符号版
int end = _size-1;
while (end >=(int) pos)
{
_str[end + 1] = _str[end];
end--;
}
//插入字符
_str[pos] = ch;
_size++;
_str[_size] = '\0';
return *this;
}
在pos位置插入len长的字符串
string& insert(size_t pos, const char* str)//插入字符串
{
//扩容
size_t len = strlen(str);//记录要插入字符串的长度
if (_size + len > _capacity)
{
reserve(_size + len);
}
//挪动数据
//(size_t)无符号版
//size_t end = _size+1;//+1把\0也挪到_str[_szie+len-1]的位置上
//while (end > pos)
//{
// _str[end + len-1] = _str[end-1];
// end--;
//}
//有(int)符号版
int end = _size;//把\0也挪到_str[_size+len]位置上
while (end >= (int)pos)
{
_str[end + len] = _str[end];
--end;
}
//插入字符串
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
从pos位置往后删除len个字符,不给len则按缺省值npos删除(全删完)
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || len >= _size - pos)
//如果len长度大于pos之后的有效字符串长度,那么也在pos位置\0
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
size_t find(char ch, size_t pos=0)//寻找字符-给缺省值从0开始找
{
assert(pos < _size);
while (pos<_size)
{
if (_str[pos] == ch)
{
return pos;//,返回下标
}
++pos;
}
return npos;//没找到返回npos
}
size_t find(const char* s, size_t pos = 0)//寻找字符串-给缺省值从0开始找
{
assert(pos < _size);
const char* ptr = strstr(_str, s);//strstr在字符串里面寻找字串-找到返回指针-没找到返回空
if (ptr == nullptr)
{
return npos;//没找到
}
else
{
return ptr - _str;//指针相减得指针之间字符串数量-即为pos的位置
}
}
我们要用s1来拷贝构造s2,那么我们可以先用s1来构造tmp;然后在让指向tmp的指针和指向s2的指针交换指向;那么s2也完成了对s1内容的深拷贝;(这里要注意:要给原本是野指针的s2一个初始化空指针,交换后保证后续tmp析构时不越界析构其他空间【析构函数遇到空指针不析构】
//拷贝构造-现代写法
//s2(s1)
string(const string& s)
:_str(nullptr)//要初始化给个空指针,不然是野指针,tmp析构的时候会越界空间析构报错
,_size(0)
,_capacity(0)
{
string tmp(s._str);//用s构造tmp
swap(_str,tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity);
}
但是我们盯着上面的现代写法,一共是交换了_str, _szie, _capacity一共三次深拷贝。那么能不能再简化呢?
我们通过查表格知道std库里的swap是先把对象实例化再交换;那么在这里是要进行三次深拷贝,深拷贝要开空间然后赋值。。。可见这样做代价非常大!
而string库里的swap是直接把两个string的指针交换这样代价相对小拉!
所以我们可以写一个swap函数
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
那么简化后的版本是这样的:
//拷贝构造-现代写法-简化后
//s2(s1)
string(const string& s)
:_str(nullptr)//要初始化给个空指针,不然是野指针,tmp析构的时候会越界空间析构报错
,_size(0)
,_capacity(0)
{
string tmp(s._str);//用s构造tmp
//this->swap(tmp);this可以不写
swap(tmp);
}
//赋值重载-现代写法
string& operator=( const string& s)//传引用
{
if (this != &s)
{
string tmp(s);//s构造tmp
swap(tmp);//交换this和tmp指针
}
return *this;
}
简化后版本
string& operator=(string& s)//传引用
{
if (this != &s)
{
swap(s);//不需要tmp做中间人直接交换
}
return *this;
}
关于string类的模拟实现的总结就到这里拉,看到这里的观众老爷们不妨点赞收藏起来看吧~~~