✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿
🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟
🌟🌟 追风赶月莫停留 🌟🌟
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
🌟🌟 平芜尽处是春山🌟🌟
🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟
🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅
🍋string模拟实现
- 🍑构造函数
- 🍑析构函数
- 🍑重载
- 🍍方括号引用
- 🍍+=操作
- 🍍=操作
- 🍑迭代器
- 🍑增删查改
- 🍍增
- 🍍找
- 🍍流提取和流插入
🍑构造函数
//有参构造
string(char* str)
{
size_t _size = strlen(str);
_capacity = _size;
str = new char[_capacity+1];
strcpy(_str, str);
}
//无参构造
string()
{
_str = new char[1];
_size = 0;
_capacity = 0;
_str[0] = '\0';
}
至于无参构造中,为了防止解引用发生错误,所以特有开了一个空间,并赋值为斜杠0。
上面代码就是普通的构造函数的写法,无参和有参构造,这里我们并不采用初始化列表的方式进行初始化。
但实际我们并不会这么写,会直接写一个全缺省:
string (const char* str = "\0")
{
size_t _size = strlen(str);
_capacity = _size;
_str = new char[_capacity+1];
strcpy(_str, str);
}
上面中初始化时并不能直接赋值给’\0’,str是字符串类型而’\0’是字符,两者类型不一样,所以应赋值"\0"。当然也可以不用赋值,直接空字符串也是,因为常量字符串中自带斜杠0。
🍑析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
析构函数就没有什么特别需要注意的了,和常规写法差不多
🍑重载
🍍方括号引用
char& operator[](size_t pos)
{
assert(pos <= _size);
return _str[pos];
}
引用在这里的作用是减少拷贝和修改返回值,如果单纯返回该里面的元素,就不会用引用了。
🍍+=操作
string& operator+=(char str)
{
push_back(str);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
operator+=()函数操作就会简单就能实现,直接分别调用push_back()和append()函数即可,当然也可以自己在里面实现,不过实现的过程都差不多。
还有一个点需要大家注意,就是运算符重载需要有返回值。
🍍=操作
string& operator=(cconst string& s)
{
if(_str != &s)
{
char* tem = new char[s._capacity+1];
strcpy(tem, s._str);
delete[] _str;
_str = tem;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
该函数就是重载等号,如:
string s1("abcdef");
string s2 = s1;
该函数的作用就是string类同等类型相互赋值时,
🍑迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string类中迭代器的实现就很简单了。
不同容器的迭代器方式不一样,大家以后可以在学习其它容器的迭代器实现的时候可以进行比较着学习
🍑增删查改
void reserve(size_t n)
{
if (n > _capacity)
{
char* cur = new char[n+1];
strcpy(cur, _str);
delete[] _str;
_str = cur;
_capacity = n;
}
}
这是一个扩容函数,在push_back或insert时,空间都有可能满,所以会先进行扩容。
🍍增
void push_back(char ch)
{
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
push_back()函数是在原本string中最后面的位置添加一个字符。
void append(const char* str)
{
size_t len = strlen(str);
if (_size+len > _capacity)
{
reserve(_size+len);
}
strcpy(_str+_size, str);
_size += len;
}
append()函数和push_back()函数还是有点大径相同,只不过append()是增加一个字符串在原string类后面,不过有一些细节还是需要注意。
void insert(size_t pos, char str)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
size_t end = _size ;
while (end >= pos)
{
_str[end + 1] = str[end];
end--;
}
_str[pos] = str;
_size++;
}
insert()函数是一个可以在容器内插入字符的函数,在进行插入的时候都要对容器内的空间进行检查,防止空间满。在这里还要注意一点,我们是利用了end来当做循环条件,而end的类型是size_t ,而size_t是无符号整型。
而当pos为0时,这个时候循环结束条件就是end=-1,但end是size_t 类型,所以当end=-1时,实际上并不会得到你所期望的负数结果。由于size_t是一个无符号整形,所以它会将-1解释为一个非常大的正数。
所以这种情况下,程序就会陷入死循环。
void insert(size_t pos, char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size;
while (end >= pos)
{
_str[end + len] = _str[end];
end--;
}
strncpy(_str+pos, str, len);
_size += len;
}
该insert()函数和上面的insert()函数功能差不多,但是这里insert()是插入一段字符,而上面的insert()函数是插入一个字符。
这里最后不能用strcpy(),因为strcpy()会把斜杠0一起复制过去,这样就会覆盖原有的一个值。
🍍找
size_t Find(char ch, size_t pos = 0)
{
for(size_t i = pos; i < _str.size(); i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
Find()函数是查找某个元素,并返回下标值,npos是全局静态变量,它值为-1, 在这里我们给了pos默认值,默认是从0开始找,也可以指定从那个数开始找。
size_t Find(const char* str, size_t pos = 0)
{
const char* ptr = strstr(_str+pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
该函数是查找一段字符串的函数,并返回该字符串的起始位置,npos是全局静态变量,值为-1;
string substr(size_t pos, size_t len = npos)
{
assert(pos < _size);
size_t end = pos + len;
if (len == npos || pos + len > _size)
{
end = _size;
}
string str;
for (size_t i = pos; i < end; i++)
{
str += _str[i];
}
return str;
}
substr()函数从主容器中取出指定范围的数据,其中npos是在上面已经提到过了,而在这len也是size_t类型,上面说过size_t是无符号整型,一旦len为负数,系统就会理解len是一个很大的值,也就相当于要取出的数大于pos后面的数据长度了,所以只是把后面的数全部取出来,和pos+len的情况是一样,所以在这里就吧它俩当成一种情况
使用substr()函数时要注意有没有深拷贝构造函数,程序会报错,需要提前写构造函数:
string(const string& s)
{
_str = new char[s._capacity+1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
🍍流提取和流插入
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
不仅仅可以使用范围for,也可以使用迭代器或者原生指针输出。
该函数就是模拟流插入。string类的流提取函数写法还是很简单,另外这里也不需要写成友元形式,上面我们模拟实现了很多函数,都可以帮助我们完成,不过也有一些类的流提取需要加友元形式,才能正常运行。
void clear()
{
_size = 0;
_str[0] = '\0';
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while(ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in
}
上述是流提取函数,get()是C++中读取空格的操作符,相当于C语言中getchar()是一个作用。
关于clear()函数,因为我们在这里是 s += ch,是+=操作,所以数据并不会覆盖,所以我们需要先清理掉原有数据,再进行流插入。
这里关于流提取还有一个小问题,当我们输入的字符过多的时候,如果按照原来的扩容方法就会造成空间浪费,有一个改进的写法:
void clear()
{
_size = 0;
_str[0] = '\0';
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char open[128] = {0};
char ch = in.get();
while(ch != ' ' && ch != '\n')
{
open[i++] = ch;
if (i == 127)
{
open[i] = '\0';
s += open;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
open[i] = '\0';
s += ch;
}
return in;
}
改进过的流提取就节省了很多空间,这里是利用了数组open,来临时存储数据,数组满了,再直接利用操作符"+="操作即可。
关于本章知识点如果有不足或者遗漏,欢迎大家指正,谢谢!!!