前言:
为了更好的理解string底层的原理,我们将模拟实现string类中常用的函数接口。为了与std里的string进行区分,所以用命名空间来封装一个自己的strin类。
string.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
#include<string>
using namespace std;
namespace manbo
{
class string
{
public:
//迭代器
typedef char* iterator;
static size_t npos;
//构造函数
string(const char* s);
//默认构造
string();
//拷贝构造
string(const string& s);
//析构函数
~string();
//返回字符串
const char* c_str()const ;
char& operator[](size_t pos);
const char& operator[](size_t pos)const ;
const size_t size()const;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator begin()const
{
return _str;
}
const iterator end()const
{
return _str + _size;
}
void reserve(size_t n = 0);
void push_back(char ch);
string& append(const char*s);
string& operator+=(const char* s);
string& operator+=(char ch);
string& insert(size_t pos, const char* s);
string& erase(size_t pos, size_t len = npos);
size_t find(const char* s, size_t pos = 0) const;
size_t find(char c, size_t pos = 0) const;
string substr(size_t pos, size_t len =npos) const;
void resize(size_t n, char ch = '\0');
void clear();
bool operator<(const string& s) const;
bool operator<=(const string& s) const;
bool operator>(const string& s) const;
bool operator>=(const string& s) const;
bool operator==(const string& s) const;
bool operator!=(const string& s) const;
void swap(string& s2);
string& operator=(string temp);
private:
size_t _size;
size_t _capaticy;
char* _str;
};
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
}
string里有三个私有成员变量 size,capacity,*str,分别表示字符数组的有效数据个数,容量,以及指向字符数组的指针。另外还有一个公有的char类型的指针iterator(迭代器)以及size_t类型的公有静态成员变量npos,并初始化值为-1实则会整型的最大值。
string.cpp
namespace manbo
{
string::string(const char* s)
:_size(strlen(s))
, _capaticy(_size)
, _str(new char[_size + 1])
{
memcpy(_str, s,_size+1);
}
string::string()
:_size(0)
, _capaticy(0)
, _str(new char[1])
{
_str[0] = '\0';
}
string::string(const string& s)
{
_str = new char[s._size + 1];
_size = s._size;
_capaticy = s._capaticy;
memcpy(_str,s._str,s._size+1);
}
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capaticy = 0;
}
const char* string::c_str()const
{
return _str;
}
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& string::operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
const size_t string ::size()const
{
return _size;
}
void string::reserve(size_t n)
{
if (n>_capaticy)
{
char* temp = new char[n + 1];
memcpy(temp, _str,_size+1);
_capaticy = n;
delete[] _str;
_str = temp;
}
}
void string::push_back(char ch)
{
if (_size==_capaticy)
{
reserve(_capaticy == 0 ? 4 : 2 * _capaticy);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
string& string:: append(const char*s)
{
size_t len = strlen(s);
if (_size+len>_capaticy)
{
reserve(2 * (_size + len));
}
memcpy(_str + _size,s,len+1);
_size += len;
return *this;
}
string& string::operator+=(const char* s)
{
append(s);
return *this;
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::insert(size_t pos, const char* s)
{
assert(pos >= 0 && pos <= _size);
size_t n = strlen(s);
size_t len = _size + n;
_size += n;
if (len>_capaticy)
{
reserve(2 *len);
}
size_t end = _size;
while (end>=pos&&end!=npos)
{
_str[end + n] = _str[end];
end--;
}
for (int i = 0; i <n ; i++)
{
_str[pos + i] = s[i];
}
return *this;
}
string& string::erase(size_t pos, size_t len)
{
assert(pos < _size);;
if (len==npos||pos+len>=_size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
int sum = 0;
size_t begin = pos + len;
while (begin<=_size)
{
sum++;
_str[pos] = _str[begin];
pos++, begin++;
}
_size = pos + sum;
}
return *this;
}
size_t string::find(char c, size_t pos) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
return npos;
}
size_t string:: find(const char* s, size_t pos) const
{
char* temp = strstr(_str, s);
if (!temp)
{
return npos;
}
else
{
return temp-_str ;
}
}
string string::substr(size_t pos, size_t len) const
{
assert(pos < _size);
string temp;
if (len == npos || pos + len >= _size)
{
temp.reserve(_capaticy);
for (size_t i = pos; i < _size; i++)
{
temp += _str[i];
}
}
else
{
temp.reserve(len + 1);
for (size_t i = pos; i < len+pos; i++)
{
temp += _str[i];
}
}
return temp;
}
size_t string::npos = -1;
void string::resize(size_t n, char ch)
{
if (n<_size)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
for (int i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
// hello hello false
// hello*** hello false
// hello hello** true
bool string:: operator<(const string& s) const
{
size_t s1, s2;
s1 = s2 = 0;
while (s1<_size&&s2<s._size)
{
if (_str[s1] < s._str[s2])
return true;
else if (_str[s1] > s._str[s2])
return false;
else
{
s1++, s2++;
}
}
if (_size>s.size())
{
return false;
}else if(_size == s.size())
{
return false;
}
return true;
}
bool string::operator==(const string& s) const
{
return _size == s._size && 0 == (memcmp(s._str, _str, _size));
}
bool string:: operator<=(const string& s) const
{
return *this < s || *this == s;
}
bool string::operator>(const string& s) const
{
return !(*this <= s);
}
bool string::operator>=(const string& s) const
{
return *this == s || *this > s;
}
bool string::operator!=(const string& s) const
{
return !(*this == s);
}
void string:: swap( string& s)
{
std::swap(_str, s._str);
std::swap(_capaticy, s._capaticy);
std::swap(_size, s._size);
}
//实现深拷贝
string& string::operator=(string temp)
{
swap(temp);
return *this;
}
}
ostream& manbo:: operator<<(ostream& out, const string& s)
{
for (auto ch:s)
{
out << ch;
}
cout << endl;
return out;
}
istream&manbo:: operator>>(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
while (ch == '\0' || ch == '\n')
{
ch = in.get();
}
char buff[128];
int i = 0;
while (ch!='\0'&&ch!='\n')
{
buff[i++] = ch;
if (i==127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i>0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
string::string(const char* s)
以下是函数的解释:
1.这是string类的构造函数,它接受一个const char*类型的参数s。这个参数代表一个 C语言 风格的字符串(即以 null 终止的字符数组)。
2.初始化列表用于在构造函数体执行之前初始化类的成员变量。这里对三个成员变量进行了初始化:
2.1strlen(s)计算字符串s的长度(不包括 null 终止符)。将这个长度赋值给_size成员变量,表示字符串的实际字符数。
2.2_capaticy是字符串的容量。在这个实现中,容量被设置为与字符串长度相同,即_size
2.3new char[_size + 1]分配了一块动态内存,大小为_size+1。这里+1是为了存储字符串的 null 终止符('\0'
)。将这块内存的指针赋值给_str,_str用于存储实际的字符串内容。
3.构造函数体的内容是将源字符串 s
的内容复制到新分配的内存区域_str中。memcpy函数用于内存块的复制:复制从s开始的_size+1字节到_str。这里的_size+1是因为我们要复制整个字符串,包括 null 终止符。
string::string()
以下是函数解释
1.这是string类的默认构造函数。它不接受任何参数,并用于创建一个空的 string对象。
2.初始化列表用于在构造函数体执行之前初始化类的成员变量。这里对三个成员变量进行了初始化:
2.1初始化_size成员变量为 0
,表示字符串的长度为零。因为这是一个空字符串,所以长度为零。
2.2初始化_capaticy成员变量为 0
,表示字符串的容量也为零。
2.3使用new char[1]分配了一块大小为 1 字节的动态内存。这块内存用于存储字符串及其终止符'\0'。
string::string(const string& s)
以下是关于函数的解释
这段代码是一个拷贝构造函数,用于创建一个string类的新对象,它是现有string对象的副本
string::~string()
以下是函数的解释
1.释放_str指针在堆上申请的空间,并将_str置为空指针
2.将_size与_capaticy重新置为0;
const char* string::c_str()const
以下是函数的解释
1.返回_str所指向的字符串。
char& string::operator[](size_t pos)
以下是函数的解释
1.这段代码定义了string类的一个成员函数operator[ ],用于访问字符串中的字符。这个函数是一个重载的下标运算符,允许通过下标直接访问字符串中的字符。
2.assert是一个宏,用于在调试阶段检查条件是否为真。如果条件不为真,程序会中断并输出错误信息。这里检查pos是否在有效范围内,如果是则程序继续执行。
3.return _str[pos];如果索引pos合法,函数返回_str[pos]。 _str是一个指向字符数组的指针,因此_str[pos] 表示数组中第 pos个位置的字符,并且返回的是引用所以可以对返回的值进行修改会影响到_str[pos]里的值。
const char& string::operator[](size_t pos)const
以下是函数的解释
与上个函数类似,但传入的对象以及返回的引用都被const进行修饰,此函数可以传const对象,因为这是一个权限的平移,而上个函数不能传const对象因为会产生权限的放大。并且对返回的引用只能进行读取而不能进行修改。
const size_t string ::size()const
以下是函数的解释
因为_size是成员变量默认是私有的所以不能直接返回,并且也不能随意修改,通过size() 函数来获取_size的值。
void string::reserve(size_t n)
以下是函数的解释
1.这段代码是一个成员函数reserve,用于调整string类的内部字符数组的容量,以便容纳至少n个字符的数据;
2.判断n > _capaticy,检查n是否大于当前已分配的容量_capaticy,如果大于就扩容;
3.new char[n + 1],在堆上分配一个新的字符数组,大小为n+1。因为额外的 1 个字符位置用于存储字符串的终止符'\0',并temp指针进行接收.
4.memcpy(temp, _str, _size + 1)使用memcpy函数将旧字符数组_str的内容(包括终止符所以要+1)复制到新分配的内存temp中
5.释放原_str的内容
6.将_str重新指向新分配内存的指针temp的地址
void string::push_back(char ch)
以下是函数的解释
1.这个push_back函数用于向string对象的末尾添加一个字符,并处理容量的扩展。
2.if (_size == _capaticy):检查当前字符串的大小是否等于当前容量。如果等于,说明字符串需要扩展容量,那么则调用创建好的reserve函数进行扩容,如果一开始是给空字符串那么_capacity会等于0,0乘任何数都得0,所以要加以进行判断。
3.将字符ch添加到当前字符串的末尾,在赋值后递增_size,更新字符串的当前长度;
4.在字符串末尾添加终止符'\0',标志着字符串的结束
string& string:: append(const char*s)
以下是关于函数的解释
1.append函数用于将一个 C 风格的字符串(const char* s)追加到string对象的末尾
2.strlen(s):计算要追加的字符串s的长度(不包括终止符 '\0'
),并且赋值给len
3.检查当前字符串的总长度(包括要追加的部分)是否超过当前容量,如果超过则调用reserve函数进行扩容
4.拷贝字符串s从原string对象末尾('\0')的位置,并更新_size的值。
string& string::operator+=(const char* s)
以下是关于函数的解释
operatir+=运算符重载,其参数为字符串,使用此operatir+=会复用append函数,在原字符串末尾追加字符串s,并返回*this(原对象)的引用。
string& string::operator+=(char ch)
以下是关于函数的解释
operatir+=运算符重载,其参数为单个字符,使用此operatir+=会复用push_backd函数,在原字符串末尾追加字符ch,并返回*this(原对象)的引用。
string& string::insert(size_t pos, const char* s)
以下是关于函数的解释
1.insert函数用于在string对象中的指定位置插入字符串
2.assert:检查插入位置pos是否在有效范围内(即不小于 0
且不大于当前字符串的_size)
3.strlen(s)计算待插入的字符串s的长度,并将其赋值给n
4.更新当前字符串的大小_size,
将更新后的_size赋值给len,并判断是否需要扩容
5.end:初始化为新的字符串长度_size,这是待会插入操作开始前的字符串末尾。
6.while循环:从字符串的末尾向插入位置pos移动字符,为腾出空间插入新字符串,_str[end + n] = _str[end]:将字符移动到新的位置并将end--.。注意,npos是一个静态成员变量,通常定义为size_t(-1),表示无效位置。在这里,它的作用是防止end变成负值,从而避免无限循环。
7.for循环:将待插入的字符串s 的字符逐个复制到目标位置pos开始的位置
8.返回 *this(当前对象)的引用,以支持链式调用。
string& string::erase(size_t pos, size_t len)
以下是关于函数的介绍
1.erase函数用于从string对象中删除指定位置的字符,删除的长度由 len
参数指定(缺省值为npos)。它的实现包括了处理不同情况的逻辑,以确保删除操作正确地更新字符串的内容和大小。
2.assert:检查pos 是否在有效范围内(即 pos 应小于当前字符串的大小_size)
3.if语句:处理特殊情况
3.1如果len的值是npos(表示删除到字符串的末尾)或者pos + len超过了 _size,则删除从pos位置到字符串末尾的所有字符。
3.2_str[pos] = '\0';
:将删除位置设置为字符串结束符'\0',这会将字符串从pos位置截断,并更新字符串的大小 _size,以表示新的字符串长度。
4.else语句:处理len小于字符串剩余长度的情况
4.1 int sum = 0;:初始化一个计数器sum,用于记录实际移动的字符数。
4.2 size_t begin = pos + len;:确定开始移动的字符位置。
4.3 while (begin <= _size)遍历从begin到字符串末尾的所有字符,并将它们向前移动到删除的区域(这里字符'\0'也会向前移动,所以不需要再添加'\0'),以覆盖掉删除的字符。
5._size = pos + sum;:更新字符串的大小_size,它应该等于新末尾的位置加上实际移动的字符数。
size_t string::find(char c, size_t pos) const
以下是关于函数的介绍
通过遍历来查找传入的字符c,如果找到就返回下标,如果没找到则返回npos。
size_t string:: find(const char* s, size_t pos) const
以下是关于函数的介绍
1.运用c语言的库函数strstr查找子串并用char类型的指针temp进行接受
2.如果temp是NULL那么就没找到返回npos,如果找到了则将temp的地址-_str的首地址,最终会算出子串的第一个字符的下标位置。
string string::substr(size_t pos, size_t len) const
以下是关于函数的介绍
1.substr函数用于生成当前字符串对象的子字符串。它的实现包括对子字符串的提取和处理
2.assert(pos < _size);确保pos 在有效范围内。这里_size表示当前字符串的实际大小
3.string temp;创建一个名为 temp的 string 对象,用于存储提取的子字符串。
4.if (len == npos || pos + len >= _size),如果len是npos(表示提取从pos到字符串末尾)或者pos + len 超过了当前字符串的大小 _size,则处理这两种情况:
4.1预先为temp对象开好_capaticy个空间,目的是避免频繁的扩容
4.2 for循环,从pos位置开始遍历到字符串末尾,将每个字符添加到temp字符串中。
5 当len小于等于字符串的剩余长度时,提取从pos开始的长度为len的子字符串
5.1为temp字符串扩容,len+1是为了确保有足够的空间来存储len个字符和一个'\0'
5.2 for循环从pos位置开始,遍历len个字符,将每个字符添加到 temp
字符串中。
注意:这里并不需要专门再结尾添加'\0',因为temp +=会去调用operator+=的运算符重载,而operator+=里又会调用push_back函数,再push_back函数里会对字符末尾进行添加'\0'的操作
void string::resize(size_t n, char ch)
以下是关于函数的解释
1.resize函数用于调整string对象的大小,并且可以用特定的字符填充新增的部分。
2.如果传入的n是小于_size的那么直接将字符串_str[n]替换为'\0',并将_size更新为n;
3.如果是n大于_size那么先将string对象的内存进行扩容,确保字符串的存储空间足够,然后从_size位置到n 位置填充字符ch。接着在_str[n]插入终结符 '\0',并更新_size为n。
void string::clear()
以下是关于函数的解释
clear顾名思义是清理的意思,那么直接在_str[0]的位置插入终止符'\0',并把_size更新为0,这里没有将_capacity置空是为了怕对象还要进行插入数据,防止多次扩容消耗性能。
bool string:: operator<(const string& s) const
以下是关于函数的解释
.operator<函数定义了一个 <操作符,用于比较当前 string 对象与另一个 string对象的大小。它主要用于ASCII比较(即按字母顺序比较)
其他的operator比较符重载
string& string::operator=(string temp)
以下是关于函数的解释
1.接受一个string对象temp的形参
2.在函数体内调用swap(temp),这将当前对象的内容与temp对象的内容交换。
3.交换后,当前对象就变成了 temp 的内容,temp变成了当前对象的旧内容,并且因为temp是局部参数,在函数调用的时候自动进行析构,防止了内存泄漏
ostream& manbo:: operator<<(ostream& out, const string& s)
以下是关于函数的解释
1.operator<<函数是一个重载的输出流操作符,用于将string对象的内容输出到输出流
2.范围for循环,auto ch自动推导ch的类型为s中的字符类型,循环遍历s中的每一个字符,并将其逐个输出到流out中
3.返回流out以支持链式操作。比如:out<<s1<<s2;
istream&manbo:: operator>>(istream& in, string& s)
以下是关于函数的解释
1.operator>>函数是一个重载的输入流操作符,用于从输入流读取字符串,并将其存储到string对象s中。
2.s.clear();使s变为空字符串,以确保读取的内容覆盖之前的内容.
3.char ch;ch = in.get();使用in.get()从流中读取一个字符并存储在ch中。
4.while循环跳过开头无用的字符如'\0'或'\n',直到读取到第一个有用的字符
5.使用一个字符数组buff作为缓冲区来临时存储字符,i用于确认buff数组的位置。
6.while循环, 只要字符不是'\0'和'\n',就将字符存入缓冲区buff。缓冲区buff里有 127 个字符时(留一个位置给终止符'\0'),将缓冲区内容+=到s中,并重置i。
7.如果i>0表示buff里有剩余的字符,将这些字符+=到s对象中
8. 返回流in的引用,以支持链式输入操作。比如:cin>>s1>>s2;
测试文件:
void test01()
{
manbo::string s1;
manbo::string s2="Hello world";
cout << s2.c_str() << endl;
cout << s1.c_str() << endl;
manbo::string const s3 = "asdddd";
}
void test02()
{
manbo::string const s2 = "Hello world";
manbo::string::iterator it = s2.begin();
while (it!=s2.end())
{
cout << *it;
it++;
}
cout << endl;
}
void test03()
{
manbo::string s2 = "Hello world";
s2.insert(6, "******");
cout << s2.c_str() << endl;
s2.erase(0);
cout << s2.c_str() << endl;
}
void test04()
{
manbo::string s2 = "Hello world";
size_t pos1 = s2.find("world");
size_t pos2 = s2.find('e');
cout << pos2 << endl;
}
void test05()
{
manbo::string s1 = "https://www.bilibili.com/video/BV1fW421X7gD/?spm_id_from=333.1007.tianma.6-1-19.click";
//解析网址,分协议 域名 资源
size_t pos1 = s1.find("://");
if (pos1 == manbo::string::npos)
{
cout << "No Find";
exit(1);
}
manbo::string agreement = s1.substr(0, pos1);
cout << "协议:" << agreement.c_str()<< endl;
size_t pos2 = s1.find('/', pos1 + 3);
if (pos2 == manbo::string::npos)
{
cout << "No Find";
exit(1);
}
manbo::string domain = s1.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << "域名:" << domain.c_str()<< endl;
manbo::string resource = s1.substr(pos2 + 1);
cout << "资源:" << resource.c_str() << endl;
}
void test06()
{
string s1 = "hello world";
manbo::string s2 = "hello world";
//s1.resize(4,'x');
//s2.resize(4,'x');
s1 += '\0';
s1 += "******";
s2 += '\0';
s2 += "******";
cout << s1 << endl;
cout << s2<< endl;
manbo::string s3;
cin >> s3;
cout << s3;
cin >> s3;
cout << s3;
}
void test07()
{
manbo::string s1="hello";
manbo::string s2="hello world";
cout << (s1 < s2) << endl;
cout << (s1 == s2) << endl;
cout << (s1 > s2) << endl << endl;
manbo::string s3 = "hello world";
manbo::string s4 = "hello";
cout << (s3 < s4) << endl;
cout << (s3 == s4) << endl;
cout << (s3 > s4) << endl<<endl;
manbo::string s5 = "hello";
manbo::string s6 = "hello";
cout << (s5 < s6) << endl;
cout << (s5 == s6) << endl;
cout << (s5 > s6) << endl << endl;
}
void test08()
{
manbo::string s1 = "hello";
manbo::string s2 = "hello world";
s1 = s2;
cout << s1;
}