作者前言
🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言、C++和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂
string的模拟实现
成员
前面我们学习了string的大致知道了这个类里面有很多的类的成员函数
如果我们要模拟这个自定义类型,我们需要开辟一个命名空间
namespace bit
{
class string
{
private:
char* _str;
size_t _size;//字符串的大小
size_t _capacity;//初始化的时候永远比真正的字符串大小小于1
const static size_t npos;//静态成员,不属于某个对象,属于全部对象共用
}
}
成员函数
构造函数
在string中有很多种构造函数,我模拟其中的两种
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity+1];
strcpy(_str, str);
}
string(const string& str)
{
_size=str._size;
_str = new char[str._capacity + 1];
strcpy(_str, str._str);
_capacity = str._capacity;
}
记住,我们一定要进行深拷贝否则的话,我们在赋值其他string的对象的时候,如果是浅拷贝,就会只拷贝值过去,就会共用一块地址,也有可以用到的地址是一个临时地址,
析构函数
这里我简单的模拟一下
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
其他函数
c_str
const char* c_str() const
{
return _str;
}
这个函数是把自定义类型的字符串转变成内置类型的字符串
字符串长度
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
尾插
void push_back(char c)
{
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
_capacity = newcapacity;
char* tmp = new char[newcapacity];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
}
_str[_size++] = c;
_str[_size] = '\0';
}
reserve
更改容量
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n+1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
append
增加字符串
void append(const char* str)
{
size_t size = strlen(str);
if (_size + size > _capacity)
{
reserve(_size + size);
}
strcpy(_str + _size, str);
_size += size;
}
insert
指定位置插入字符串
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t size = strlen(str);
if (size + _size > _capacity)
{
reserve(_size + size);
}
int idx = _size-1;
_str[_capacity] = '\0';
while (idx>=(int)pos)
{
_str[idx + size] = _str[idx];
idx--;
}
strncpy(_str + pos, str, size);
_size += size;
return *this;
}
erase
删除字符串
void erase(size_t pos = 0, size_t len = npos)
{
if (pos + len >= _size - 1)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
find
寻找字符串
size_t find(size_t pos, const char* str)
{
char* ptr = strstr(_str + pos, str);
if (ptr != nullptr)
return ptr - _str;
return -1;
}
substr
string substr(size_t pos = 0, size_t len = npos)//这个函数,如果没有写深拷贝或者赋值的深拷贝,就会程序崩溃,
{
assert(pos < _size);
int end = pos + npos;
if (len == npos || pos + len >= _size)
{
end = _size;
}
string str;
str.reserve(end - pos);
int i = 0;
for (i = pos; i < end; i++)
{
push_back(_str[i]);
}
return str;
}
这里主要注意的是拷贝构造的时候,一定要深拷贝,或者的话返回的string的对象只是一个临时对象具有常性,需要用const进行接收,不能发生权限放大,销毁的只是这个对象的地址,而_str指向的那块地址有没有销毁,要看析构函数(不是临时对象销毁,而是在substr中定义的对象),
迭代器
前面我们知道string的迭代器,大概是一个指针
所以我们可以把char* 类型重定义为 iteraotr
typedef char* iterator;
iterator begin()
{
return _str;
}
const iterator begin() const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator end() const
{
return _str + _size;
}
操作符重载
[]
这两个一个是给const类型的,一个是给没有const类型的
//可读
const char& operator[](size_t i) const
{
assert(i < _size);
return _str[i];
}
//可读可写
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
+和+=
string& operator=(const string& str)
{
_size = str._size;
_str = new char[str._capacity + 1];
strcpy(_str, str._str);
_capacity = str._capacity;
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator+=(char str)
{
push_back(str);
return *this;
}
<< 重载
前面的一篇文章,使用了友元函数,主要就是友元函数可以访问 类的私有成员,
这里我们不使用友元,直接在类外面进行重载
ostream& operator<<(ostream& on, string& str)
{
for (auto& e : str)
{
cout << e;
}
return on;
}
>> 重载
istream& operator>>(istream& in, string& str)
{
str.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = in.get();
}
return in;
}
这里不能使用cin,因为cin不能读取空格和\n,只是使用cin.get(),
需要注意的是istream和ostream类型是在std中的
string的一些成员函数的现代写法
#include<iostream>
#include<string>
using namespace std;
namespace bit
{
class String
{
public:
String(const char* str)
{
_size = strlen(str);
_capacity = _size;
_str = new char[_size];
strcpy(_str, str);
}
//现代写法拷贝构造
String(const String& str)
{
String tmp(str._str);//构造一个新的
Swap(tmp);//进行交换
}
void Swap(String& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
char* c_str()
{
return _str;
}
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
//现代写法赋值
String& operator=(String str)//这里是传值
{
Swap(str);
return *this;
}
private:
size_t _size;
size_t _capacity;
char* _str;
};
ostream& operator<<(ostream& on, String& str)
{
for (auto e : str)
{
on << e;
}
return on;
}
}
小知识
注意:类的静态成员是不算类的大小的,
当我们在Linux下和在window下计算string的大小是不一样的
window64位下:
可以看到在VS下是40,
Linux下:
大小是8,
在Visual Studio中,string的大小为40是因为Visual Studio的STL(标准模板库)实现中,string通常包含一个指向字符数据的指针,还有一些额外的元数据,比如长度、容量等信息。此外,STL的实现可能还包括一些用于优化和内存对齐的额外空间。因此,即使string中只包含很少的字符,它的大小也可能会比预期的要大。
另外,这个大小可能会根据编译器版本、操作系统和编译配置等因素而有所不同。
在Linux中string的成员只计算一个指向字符数据的指针,
引用计数:是为了实现写时拷贝的,用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
写时拷贝主要是为了解决内存的,前面深拷贝很好用,但是有时候深拷贝也会造成空间的浪费的缺陷,写时拷贝也是有自己的缺陷,
写时拷贝
写时拷贝的缺陷