文章目录
前言
本章为了深入了解string类的底层原理,用数据结构的思想来管理资源模拟实现出string类的一些接口。具体来说,1.能像int类型那样定义变量,并且支持赋值,赋值。2.能用做函数的参数类型以及返回类型。3.能用做标准库容器的元素类型,即vector/list/deque的value_type。(摘自coolshell)
一、String类的模拟实现
上章主要对string类以及接口简单的介绍,在很多面经中,面试官让学生来模拟实现string类,主要实现string类的构造、拷贝构造、运算符重载以及析构函数,本章将一一介绍思想以及实现方法。
首先要明确,string类是由一个字符串进行封装构造的,要在这个类中实现增删查改,就需要和顺序表一样的size和capacity,分别表示有效字符串的个数,和这块容量的大小。
class string { pubilc: //实现一些方法 private: //成员变量 char * _str; size_t _size; //存多少个有效字符 size_t _capacity; //这段空间的大小 }
1.浅拷贝
也称为位拷贝,编译器只是将对象中的值拷贝过来,如果对象中管理资源,最后就会导致多个对象共享一份资源,当一个对象销毁时,就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为仍有效,所以继续对这项资源进行操作时就会发生访问违规。
2.深拷贝
如果一个类中涉及到资源管理,其拷贝构造函数、赋值运算符重载以及析构必须要显示给出。可以采用深拷贝解决浅拷贝的问题,即:每个对象都有一份独立的资源,不和其他对象共享。
1.构造
1)无参构造
目的:构造空的string类对象,即空字符串
string s1; //无参初始化
实现思想:使用无参构造,就是传入一个空字符串,在构造列表中,如果给str传入一个nullptr,当我们使用c_str()//返回str字符串的时候,就会访问到空指针,cout无法打印。
string() :_str(new char[1]) // 这里 new char 也是一个 加[]是和析构delete[]匹配 ,_size(0) ,_capacity(0) { _str[0] = '\0'; //字符串里只有一个\0 }
2)有参构造
string s2("hello world");
string(const char * str) //如果这里直接使用初始化列表new出来,就需要不停的扩容 //:_str(str) //如果这里直接用const str给了str 后面要对str进行修改 则不能修改 //, _size(strlen(str)) //初始化顺序得按声明顺序来,为了避免这种,只初始化一个 //,_capacity(strlen(str)+1) { _capacity = _size; _str = new char[_capacity+1]; //new出char类型的capacity+1个大小的字节 +1是给\0 strcpy(str,str); //按字节拷贝过来 } private: char * str; size_t _size; size_t _capacity; void test() { string s1("hello world"); }
2.拷贝构造
拷贝构造,即新对象要和旧对象的size,capacity都相同,可以在构造列表中直接赋值,使用strcpy拷贝过来字符串
string(const string&s)
:_size(s._size)
,_capacity(s._capacity)
{
_str = new char[s._capacity+1]; //_str 是一个字符数组 new capacity个空间
strcpy(_str,s._str);
}
3.赋值运算符重载
这里单独写出来是和拷贝构造进行对比
参数类型使用引用,返回值也使用引用,目的是支持连续赋值。同时要检查是否自己给自己赋值,返回*this。这里存在一个问题:被赋值的对象和赋值的对象容量的大小可能不相同,可能大于,小于或者等于,这里存在要再开空间的情况。编译器的处理方法是:先开辟一个临时空间和拷贝对象相同,然后拷贝进来字符串,再销毁掉被赋值对象原来的空间,将临时对象拷贝的str赋值给被赋值对象。这里开临时空间的原因是:需要创建一段内存,拷贝传入对象的内容,为了防止再析构的时候,对同一段内存重复释放,导致程序崩溃
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;
}
3.析构
析构中主要完成资源的清理,注意这里_str要指向一个Nullptr
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
4.运算符重载
1)operator[ ]
函数功能:返回pos位置的字符
const size_t & operator[](size_t pos) const //传入的this不能修改
{
return _str[pos];
}
char & operator[](size_t pos)
{
assert(pos<_size);
return _str[pos];
}
2)operator>
函数功能:比较两个字符串的大小,这里比的是ascii码,使用strcmp函数
bool operator>(string&s)const
{
return strcmp(_str,s._str) >0 ;
}
3)operator==
bool operator==(string& s) const
{
return strcmp(str,s._str) == 0;
}
3)operator>=
bool operator>=(string& s) const
{
return *this>s || *this ==s;
}
4)operator<
bool operator<(const string* s) const
{
return !(this>=s);
}
5)operator<=
bool operator<=(const string&s) const
{
return *this < s || *this == s;
}
6)operator!=
bool operator!=(const string &s) const
{
return !(*this ==s);
}
5.一些接口实现
1)resize
函数功能:1.将str中的有效字符改成n个,多出的空间用字符c填充(开空间+初始化)
void resize(size_t n,char ch= '\0')
{
if(n<=_size)
{
_str[n] = '\0';
_size = n;
}
else
{
if(n> _capacity)
{
reserve(n);
}
size_t i = _size;
while(i<n)
{
_str[i] = ch;
++i;
}
_size = n;
_str[_size] = '\0';
}
}
2)reverse
函数功能:为字符串预留空间,新增的空间用\0占位。先new出一段临时空间,将原来空间的字符串拷贝进去,再释放掉原来的空间,新增的空间赋值给_str。如果比原来空间小,也不缩容。
void reserve(size_t n,char ch='\0')
{
char * tmp = new char[n+1];
strcpy(tmp,_str);
delete[] str;
_srt = tmp;
_capacity = n;
}
3)begin+end
函数功能:使用迭代器,begin获取第一个字符,end获取最后一个字符的下一个位置
char* begin()
{
return _str;
}
char* end()
{
return _str+_size;
}
4)pushback
函数功能:在字符串后面增加一个字符。增加一个字符就要判断容量是否够,如果不够,扩容2倍,再把字符放在_size的位置
void pushback(char ch)
{
if(_size+1 >_capacity)
{
reserve(2*_capacity);
}
_str[size] = ch;
++size;
_str[_size] = '\0'; //注意这里有\0
}
5)append
函数功能:在字符串后面增加一个字符串
void append(const char * str)
{
size len = strlen(str);
if(_size+len>_capacity)
{
reverse(2*capacity);
}
strcpy(_str+_size,str);
_size+= len;
}
6)operator+=
1.在原来字符串后面增加一个字符
2.在原来字符串后面增加一个字符串
string& operator+=(char ch)
{
push_back(ch);
retrun *this;
}
string& operator+=(const char * str)
{
append(str);
return *this;
}
7)insert
1.函数功能:在字符串的任意位置增加一个字符
2.函数功能:在字符串的任意位置增加一个字符串
string& insert(size_t pos,const char ch)
{
assert(pos<_size);
//插入要检查容量
int len = strlen(str);
if(1+_size >_capacity)
reverse(2*_capacity);
//开始插入 先要挪动数据 插入一个字符 就挪动一个位置
//现在要插入len个字符,就要挪动len个位置
size_t _end = _size;
while(pos < end)
{
_str[end+1] = _str[end]
--end;
}
//放上数据
_str[pos] = ch;
++size;
return *this;
}
string& insert(size_t pos, const char * str)
{
assert(pos<_size);
int len = strlen(str);
if(_size+len >_capacity)
{
reverse(2*_capacity);
}
size_t end = _size;
//挪动数据
while(end>pos)
{
_str[end+len] = _str[end];
--end;
}
strnpy(_str+pos,_str,len);
return * this;
}
8)swap
函数功能:交换两个string类中的字符串,并且size和capacity也要参与交换
void swap(string &s)
{
std::swap(_str,s._str);
std::swap(_capacity,s._capacity);
std::swap(_size,s._size);
}
9)c_str
返回string类对象中的字符串,直接返回即可
char& c_str(string& s)
{
return s._str;
}
10)find
1.函数功能:从npos位置开始查找字符c,返回该字符在字符串中的位置
2.函数功能:从npos位置开始查找字符串,返回该字符在字符串中的位置
size_t find(char ch,size_t pos = 0)
{
assert(pos<_size);
for(int i =0; i<_size;++i)
{
if(_str[i] == ch)
return i;
}
return npos;
}
size_t find(char * str,pos = 0)
{
assert(pos<_size);
//strstr(const char* str1, const char*str2) 在str1中寻找是否存在str2,如果存在返回str2的地址,不存在返回null
char * p = strstr(_str+pos,str);
if( p == nullptr)
return npos;
else
return p- _ str;
}
11)clear
函数功能:将_str中的值都置空,有效字符就是0,容量不变
void clear()
{
_str[0] = '\0';
_size = 0;
}
12)erase
函数功能:删除pos位置的len个字符,如果len>strlen(_str),从pos位置有多少删多少
//比如现在有一段字符串 1234567890 10个字符 删除pos = 5位置的两个字符
或者删除pos位置的10个字符 或者删除npos个字符(全删除)
// pos = 5 ;len = 2 1234890 890三个数字挪动到pos = 5的位置
// pos = 5 ; len = 10 pos = 5的位置开始删除 _str[pos] = '\0';
//这两种都只改变_size,不用改变_capacity
string& eraser(size_t pos,size_t len= npos)
{
assert(pos<_size);
//注意这里 npos = -1 pos+len 就溢出了 所以需要单独判断
if(len == npos || pos+len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str+pos,_str+pos+len);
_size -=len;
}
return *this;
}
6.流插入和流提取
1)operator<<
cout<<string<<endl;
ostream& operator<<(ostream& out, string& s) { //遍历这个字符串打印 for(auto ch:s) { out<<ch; } return out; }
2)operator>>
cout>>string
void clear() { _str[_size] = '\0'; _size = 0; } instream& operator>>(istream& in , string& s) { //每次清空 s.clear(); //从缓冲区拿到数据 char ch = in.get(); size_t i= 0; while(ch != ' ' && ch == '\n') { s+= ch; buff[i++] = ch; //拿到127个清空数组,然后再加后面的字符串 if(i == 127) { buff[127] = '\0'; s+= buff; i= 0; } ch = in.get(); } return in; }
总结
本文主要模仿stl库中的一些源码,技术有限,如有错误请指正。