前提:
1:须知知识点
两个函数都会有使用,不会点开链接
2:实现的文件结构:
++①:全在一个.h文件里面进行实现++
++②:将①分离成一个.h 和 一个.cpp文件++
3:实现函数一览
本文只模拟实现string类的常用重点的函数
A:默认成员函数
①:构造函数(无参 和 字符串作为参数)
②:拷贝函数(传统写法和现代写法)
③:赋值运算符重载(传统写法和现代写法)
④:析构函数
B:功能性函数
①:swap()函数
②:reserve()函数
③:erase()函数
④:clear()函数
⑤:find()函数
⑥:c_str()函数
C:重要函数
①:push_back()函数
②:append()函数
③:+=运算符重载
④:[ ]运算符重载
⑤:insert()函数
⑥:substr()函数
⑦:<< 和 >> 运算符重载
一:默认成员函数的实现
初始状态:
cpp
namespace key
{
class string
{
public:
private:
char* _str = nullptr;
size_t size = 0;
size_t capacity = 0;
static const size_t npos;
};
const size_t string::npos = -1;
}
解释:
**1:**在库中的string类中的npos是一个静态成员变量,按照规则,声明初始化规则如图所示
**2:**为了避免和库中的string冲突 选择用一个key的域保护起来
①:构造函数
cpp
//构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//缺省函数
//既能string s1;
//也能string s1("hello");
解释:
**1:**缺省函数,既能string s1,也能string s1("hello")
**2:**string库中的_capacity 和 _size都是不计入'\0'的,模拟实现就要跟库统一,所以开空间的时候多开了一个放置'\0'
**3:**strlen的复杂度不低,建议只使用一次strlen,用得到的值再去进行成员变量的初始化
②:拷贝构造函数
a 传统写法:
cpp
//拷贝函数(传统写法)
//string s2(s1) 或 string s2 = s1
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
解释:
++**1:**传统写法就是老老实实地去完成深拷贝。++
b 现代写法:
cpp
//拷贝函数(现代写法)
//string s2(s1)或 string s2 = s1;
string(const string& s)
{
//用s(s2的引用)的_str去构造一个tmp对象
string tmp(s._str);
//然后用swap函数把tmp对象 和 *this 进行交换
swap(tmp);
}
解释:
**1:**不再自己开辟空间,而是通过 string tmp(s._str); 这一步,复用构造函数,得到一个tmp对象,然后再自己和tmp对象交换
**2:**而且tmp对象的所有成员变量及_str指向的空间,出了函数都会被释放
**3:**总的来说,就是一种复用构造函数和swap函数去简化拷贝函数的方法
**4:**我们在成员变量初始化的时候,给了缺省值(_str = nullptr),在这里至关重要,有些编译器不会自动置空,此时交换后,tmp内的_str指向的是随机空间,tmp 出了作用域要调用析构函数,对随机值指向的空间进行释放,此时再去delete[ ] _str,就可能会引发崩溃。但是 delete / free 一个空,是不会报错的,所以成员变量_str在声明的时候给的nullptr至关重要
③:赋值运算符重载
a 传统写法:
cpp
//赋值重载(传统写法)
//s1 = s3
string& operator=(const string& s) {
if (this != &s) { // 防止自己给自己赋值
char* tmp = new char[s._capacity + 1]; // Step1:先在tmp上开辟新的空间
strcpy(tmp, s._str); // Step2:把s3的值赋给tmp
delete[] _str; // Step3:释放原有的空间
_str = tmp; // Step4:把tmp的值赋给s1
// 把容量和大小赋过去
_size = s._size;
_capacity = s._capacity;
}
return *this; // 结果返回*this
}
**解释:**传统写法,就是自己开空间自己拷贝数据
b 现代写法:
cpp
//赋值函数(现代写法)
//s1 = s3;
string& operator=(string s)
{
//该函数参数s去接受s3的时候,s3拷贝生成了s
//直接swap 交换s 和 *this(s1)
swap(s);
return *this;
}
解释:
**1:**参数从 string& s 变成了 string s ,这意味着 s3 到 s 这一步,是拷贝构造,生成s这个对象,而我们赋值的意义就在于要一个和s3一样的东西,但是空间的地址又不一样,此时s3拷贝构造生成的s这个对象是经过我们自己写好的深拷贝生成的,所以符合要求
**2:**所以直接swap交换了 s1 和 s 即可
**3:**返回值是string& 的意义在于,可以连续的使用赋值操作符,这也是和库一致的
④:析构函数
cpp
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
**解释:**过于简单 不作赘述
二:功能性函数的实现
①:swap()函数
cpp
//swap()函数
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
**解释:**交换两个对象的一切东西,不生产中间拷贝
②:reserve()函数
cpp
//扩容函数
//s1.reverse(10)
void reserve(size_t n)
{
//vs的string类的reserve函数不支持缩容
//所以我们为了保持一致,只对n>capacity进行扩容处理
if (n > _capacity)
{
//开辟新的空间(n+1是因为多开一个给'/0')
char* tmp = new char[n + 1];
//把旧空间的_str strcpy 到 tmp空间中
strcpy(tmp, _str);
//释放旧空间
delete[] _str;
//_str指向tmp
_str = tmp;
//容量重置成n
_capacity = n;
}
}
解释:
**1:**vs的string类的reserve函数不支持缩容,所以我们为了保持一致,只对n>capacity进行扩容处理
**2:**因为n是和_capcacity比较,_capcacity是不计入'\0'的,所以我们开空间的时候自然是要多开一个的
**3:**只是重置_capacity为n即可,因为_capacity就是代表容量,而_size则不用管,其实代表存储的有效字符的大小,扩容并没有影响存储的有效字符
③:erase()函数
cpp
//erase()函数
//s1.erase(2,5);从下标为2开始,清理5个字符
void erase(size_t pos, size_t len = npos)
{
//断言
assert(pos < _size);
//不传len值,代表从pos位置开始全部清理
//len + pos >= _size 也是从pos位置开始全部清理
if (len == npos || len + pos >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else//代表从下标pos位置开始清理一部分
{
//将_str + pos + len往后的内容 strcpy到 _str + pos往后
strcpy(_str + pos, _str + pos + len);
//置_size-=len
_size -= len;
}
}
解释:
**1:**断言是必要的,因为下标是_size代表,是'\0'的位置,从这里及以后的位置开始清理,无任何的意义
**2:**参数len给了缺省值,这是与库一致,不给len即代表len等于npos,npos在前文已经说过,一个size_t的类型,值为-1,即整形的最大值 ,所以len不给,代表从下标为pos位置开始全部清理
**3:**经过第2点,可以len == npos 代表一种从下标为pos位置开始全清理,初次之外,当len + pos >= _size 的时候,也是从下标为pos位置开始全清理
**4:**2和3的判断条件必须 len == npos在前,len + pos >= _size在后,因为如果len等于npos,你是先判断len + pos >= _size时,会整形溢出,会出各种各样的bug,所以len == npos必须在前
**5:**也不能仅仅要len + pos >= _size这个条件,因为理由还是可能会整形溢出
④:clear()函数
cpp
//clear()函数
//s1.clear();
void clear()
{
//_size置0 且 在第一个位置放入'\0'
_size = 0;
_str[0] = '\0';
}
解释:
**1:**此函数一般不直接使用,但在operator>> 的实现中有着重要作用
**2:**不仅要_size = 0 还要 _str[0] = '\0',因为有一些遍历不看size的大小,而是看什么时候遇到'\0',它才会停止
⑤:find()函数
find()找字符
cpp
//find()函数 找字符
//s1.('h',2)从下标为2开始查找 'h'字符
size_t find(char c, size_t pos = 0)const
{
//assert(pos < _size);
//遍历查找
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
//来到这里 代表找不到 返回npos(与库一致)
return npos;
}
解释:
**1:**find()函数找字符串,即遍历查找即可,找到返回下标,找不到返回npos(与库一致)
2: pos不给,则为0(与库的参数用形式一致)
**3:**const修饰的是*this,因为find函数不会对对象的内容改变,所以写成const,这样正常对象和const对象都能够使用
find()找字符串
cpp
//find()函数 找字符串
//s1.("hello",2)从下标为2开始查找 "hello"字符串
size_t find(const char* str, size_t pos = 0)
{
//assert(pos < _size);
//用strstr函数来从_str+pos位置开始查找字符串
const char* ptr = strstr(_str + pos, str);
//ptr为空 代表没找到 返回npos(于库一致)
if (ptr == nullptr)
{
return npos;
}
else
{
//指针相减得到下标
return ptr - _str;
}
}
解释:
**1:**pos不给,则为0(与库的参数用形式一致)
**2:**strstr函数来进行查找字符串的功能,其次需要注意的是,在被查找的字符串中_str + pos指定查找的起始位置,pos为0则从头查找
**3:**strstr函数返回的如果是空,则代表没找到,此时返回npos即可(与库一致)
**4:**找到了返回的是一个被查找的字符串首次出现位置(指针),那我们要得到的是下标,所以ptr - _str 即可,指针相减得到的是下标
Q:为什么find函数都没有对pos进行断言
A:因为pos不管为什么值,都有对应的返回值,所以不需要断言
⑥:c_str()函数
cpp
//c_str()函数
const char* c_str() const//后面加const 对于是非const的对象都能调用
{
return _str;
}
解释:
**1:**c_str() 返回的是当前字符串的首字符地址,是可读不写的,所以返回值类型是const char *
**2:**直接 return _str 即可实现。
⑦:迭代器
cpp
typedef char* iterator;
//只可读不可写的迭代器
typedef const char* const_iterator;
//iterator用的begin()
iterator begin()
{
return _str;
}
//iterator用的end()
iterator end()
{
return _str + _size;
}
//const_iterator用的begin()
const_iterator begin()const
{
return _str;
}
//const_iterator用的end()
const_iterator end()const
{
return _str + _size;
}
解释:
**1:**与库一致,针对const对象和非const对象都有对应的迭代器
2:
string的底层是连续地物理空间,给原生指针++解引用能正好贴合迭代器的行为,就能做到遍历。
但是对于链表和树型结构来说,迭代器的实现就没有这么简单了。
但是,强大的迭代器通过统一的封装,无论是树、链表还是数组......
它都能用统一的方式遍历,这就是迭代器的优势,也是它的强大之处
三:string类重要的函数的实现
①:push_back()函数
cpp
//尾插
void push_back(char c)
{
//if判满
if (_size == _capacity)
{
//两种满的给不同容量newcapacity
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
//根据newcapacity扩容
reserve(newcapacity);
}
//原先的_str[_size]是'\0',现在成为新尾插的字符
_str[_size] = c;
//_size++
_size++;
//'\0'往后移动一个
_str[_size] = '\0';
}
解释:
**1:**push_back意为尾插,既然是插入吗,肯定要先判断容量,在针对不同的容量为满的类型进行扩容
2:_str[_size] = c; 原本的'\0'的位置,插入了尾插的字符c,然后_size++,再补'\0'
②:append()函数
cpp
//append
//s1.append("hello")
void append(const char* str)
{
//计算出要追加的字符串的长度
size_t len = strlen(str);
//已有的字符串长度(_size)+len 超过容量即扩容
if (_size + len > _capacity)
{
reserve(_size + len);
}
//把追加的str strcpy 到 _str + _size 往后
strcpy(_str + _size, str);
//置_size+=len
_size += len;
//reserve里面已经置capacity
}
解释:
**1:**照样是判断容量,和push_back不同的是,是 _size + len 去判断,满了也是扩_size + len这么大容量的容
**2:**尾插字符串,则用strcpy函数即可,起始位置为_str + _size,即原本的'\0'的位置开始,然后更新_size即可,_capacity在扩容函数中就已重置了,所以不用再置
总结:push_back 和 append 都不常用,因为有更好的 +=运算符重载,其内部复用了push_back 和 append
③:+=运算符重载
cpp
//+=运算符重载函数
//s1+='w' 给s1+=一个字符
string& operator+=(char c)
{
//复用push_back()
push_back(c);
return *this;
}
cpp
//+=运算符重载函数
//s1+="hello" 给s1+=一个字符串
string& operator+=(const char* str)
{
//复用append()
append(str);
return *this;
}
解释:
**1:**返回值是string& 是因为可以减少拷贝 ,二者分别复用了push_back 和 append
④:[ ]运算符重载
cpp
//[]运算符重载(只可读)
const char& operator[](size_t pos)const
{
//'\0'也能读
assert(pos <= _size);
return _str[pos];
}
cpp
//[]运算符重载(可读可写)
char& operator[](size_t pos)
{
assert(pos <= _size);
return _str[pos];
}
解释:
**1:**和库一致,[ ]针对 const对象和正常对象都写了一个
**2:**返回值是char& 引用的意义是可以对其直接++
⑤:insert()函数
insert插入字符
cpp
//insert()函数 在下标为pos处插入字符
//s1.insert(2,'w')在下标为2的位置,插入一个字符
void insert(size_t pos, char c)
{
//断言 最多能在'\0'的位置插入数据
assert(pos <= _size);
//判满 与push_back类似
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
//把下标从pos开始的所有字符向后移动一个
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
//腾出来的下标为pos的位置 给上要插入的字符
_str[pos] = c;
//_size++
_size++;
}
解释:
**1:**断言 最多能在'\0'的位置插入数据
**2:**判满,与push_back一致
**3:**把下标从pos开始的所有字符向后移动一个,统一往后移动一个,至于为什么end 初始化为_szie+1,后面会讲
**4:**腾出来的下标为pos的位置 给上要插入的字符
insert插入字符串
cpp
//insert()函数 在下标为pos处插入字符串
//s1.insert(2,"xxxx") 在下标为2处插入字符串"xxxx"
void insert(size_t pos, const char* str)
{
//断言 最多能在'\0'的位置插入数据
assert(pos <= _size);
//判满 与append类似
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//把下标从pos开始的所有字符向后移动len个
size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - len];
end--;
}
//把str的全部字符 复制到 s1的_str+pos处及往后的位置
//strncpy 不计入'\0'
strncpy(_str + pos, str, len);
//置_size+=len
_size += len;
}
解释:
**1:**大部分都类似,移动的时候,变成了移动len个
**2:**用了strncpy函数,因为strcpy会默认把被复制的字符串的'\0'也复制过去,因为这里是局部插入,所以用strcpy的话,则会有两个'\0',必然是错的
3: strncpy函数 可以对被复制的字符串进行指定字节个数的复制,所以可以避免复制到'\0',_str + pos即起始位置,len即我们算出来的str的大小,strlen函数本身就是不计算'\0'的,所以这里刚好可以
**4:**重置_size
⑥:substr()函数
cpp
//substr()函数 截取_str的从下标为pos位置开始的len个字符去创建一个新的对象
//s1.substr(2,5)
string substr(size_t pos = 0, size_t len = npos)
{
//断言 不能从'\0'及以后的位置进行截取
assert(pos < _size);
//截取字符串末尾下标为 end
size_t end = len + pos;
//不传len值,代表从pos位置开始全部清理
//len + pos >= _size 也是从pos位置开始全部清理
if (len == npos || len + pos >= _size)
{
//end就变成了最后的位置('\0'的位置)
end = _size;
}
//end-pos 代表 从pos位置开始到最后一个字符的长度(不是'\0')
//reserve 会为'\0'多开一个空间
//建立一个新的对象
string s2;
//新对象的空间为end-pos 大小不包含'\0'但是reserve里面会多开辟一个给'\0'
s2.reserve(end - pos);
//把截取的字符串 让新对象str不断地+=字符
for (size_t i = pos; i < end; i++)
{
s2 += _str[i];
}
return s2;
}
解释:
**1:**断言,'\0'位置及以后进行截取无意义
**2:**pos不给为0,代表从头开始截取,len不给,代表从下标为pos的位置开始全部截取
**3:**对len和len+pos的判断与erase一致,在判断之前就size_t end = len + pos;这样判断不通过也会维持原样,判断通过了才会重置
4: 得到正确的end后,end-pos得到新对象的_capacity的大小,然后再对该部分的字符串进行循环
得放入到新对象的_str(遍历从下标pos开始,到end-1)
5:+=的时候,就会自动置_size,所以不要再置
⑦:<< 和 >> 运算符重载
cpp
// cout << s1 → operator<<(cout, s1)
ostream& operator<<(ostream& out, const string& s) {
//for (auto ch : s) {
// out << ch;
//}
for (size_t i = 0; i < s.size(); i++) {
out << s[i];
}
return out;
}
cpp
// cin >>
istream& operator<<(istream& in, string& s) {
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
**解释:**都很简单,需要注意的是istream类的对象对我们输入的字符串进行读的时候,遇到空格和换行不会进行读取,而是默认跳过,所以我们需要istream的成员函数get()才能读取到空格和换行
四:代码的测试
cpp
void test()
{
string s1;
string s2("world");
cout << "构造出的空s1:" << s1;
cout << "构造出的s2:" << s2;
cout << "使用+=,连续给s1+=字符" << endl;
s1 += 'h';
cout << s1;
s1 += 'e';
cout << s1;
cout << "使用+=,给s1+=一个字符串llo " << endl;
s1 += "llo";
cout << "最终的的s1:" << s1;
cout << endl;
cout << endl;
string s3 = s1;
cout << "由s1拷贝得到的s3:" << s3;
string s4 = s2;
cout << "由s2拷贝得到的s4:" << s4;
cout << endl;
string s5;
s5 = s1;
string s6;
s6 = s2;
cout << "由s1赋值得到的s5:" << s5;
cout << "由s2赋值得到的s6:" << s6;
cout << endl;
string s7("hello");
cout << "打印正常非const对象s7:" << s7;
const string s8("world");
cout << "打印const对象s8:" << s8;
cout << endl;
//迭代器验证
cout << "正常对象s7使用迭代器进行遍历打印:";
string::iterator it = s7.begin();
while (it != s7.end())
{
cout << *it;
++it;
}
cout << endl;
cout << "正常对象s7使用迭代器进行更改再打印:";
string::iterator it2 = s7.begin();
while (it2 != s7.end())
{
*it2 = 'x';
cout << *it2;
++it2;
}
cout << endl;
cout << endl;
cout << "const对象s8使用const迭代器进行遍历打印:";
string::const_iterator it3 = s8.begin();
while (it3 != s8.end())
{
cout << *it3;
++it3;
}
cout << endl;
//const对象s8无法进行写
/*cout << "const对象s8使用const迭代器进行遍历打印:";
string::const_iterator it4 = s8.begin();
while (it4 != s8.end())
{
*it4 = 'x';
cout << *it;
++it;
}*/
s1.swap(s2);
cout << "用swap()函数交换s1和s2" << endl;
cout << "交换后的s1:" << s1;
cout << "交换后的s2:" << s2;
cout << endl;
cout << "在s1中找字符串rld" << endl;
int pos = s1.find("rld");//不给pos 即从头开始找
cout << "将下标给substr得到一个新的对象s9" << endl;
string s9 = s1.substr(pos);//不传len 即从pos开始全部截取
cout << "s9:" << s9;
cout << "inset()函数在s9头插字符串wo:" << endl;
s9.insert(0, "wo");
cout << "s9:" << s9;
}
测试结果如下:
解释: 对多个函数进行了测试,准确无误
五:一些细节
①:构造/拷贝/赋值函数
解释:
**1:**string类的成员变量都可以直接在构造函数的体内进行初始化,那就不用也不要去初始化列表进行初始化了,因为和成员变量声明的顺序不一致可以会导致出错
**2:**string类的成员变量不是全部都是内置类型,其中一个变量指向了一块空间,所以构造函数 拷贝函数 赋值函数我们都是要进行深拷贝的,不管是传统写法还是现代写法,本质都是进行深拷贝
②:insert()函数
**1:**为什么end 初始化为_szie+1?因为这样当pos为0的时候么也就是头插的时候,end最小也只会小到0,不会为-1,为-1会怎么样?size_t为-1代表整形的最大值,所以根本无法跳出循环,如下图所示:
cpp
//把下标从pos开始的所有字符向后移动一个
size_t end = _size;
while (end >= pos)
{
_str[end+1] = _str[end ];
end--;
}
**解释:**当我们end等于_size时,循环条件必须是end>=pos,才能把该向后移动的字符全部进行移动,此时就会出现死循环的情况
**2:**除开 end 初始化为_szie+1 然后循环条件end>pos,还有强转的方法:
cpp
int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}
**解释:**end和pos都是int了,就不会出错,切记不要仅仅把end写作int,因为这样循环判断里面的end>=pos,pos是size_t,二者比较会发生隐式类型转换(int向size_t转换),此时连监视窗口看不出来错误(end依旧是-1,pos依旧是0),这是万万不可的,如图:
cpp
//错误!!!
int end = _size;
while (end >= pos)
{
_str[end + 1] = _str[end];
--end;
}
六:源码
①:全写在一个.h里面的源码
cpp
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace key
{
class string
{
public:
//构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//缺省函数
//既能string s1;
//也能string s1("hello");
//swap()函数
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//拷贝函数(现代写法)
//string s1(s2);
string(const string& s)
{
//用s(s2的引用)的_str去构造一个tmp对象
string tmp(s._str);
//然后用swap函数把tmp对象 和 *this 进行交换
swap(tmp);
}
拷贝函数(传统写法)
//string(const string& s)
//{
// _str = new char[s._capacity + 1];
// strcpy(_str, s._str);
// _size = s._size;
// _capacity = s._capacity;
//}
//赋值函数(现代写法)
//s1 = s2;
string& operator=(string s)
{
//该函数参数s去接受s2的时候,s2拷贝生成了s
//直接swap 交换s 和 *this(s1)
swap(s);
return *this;
}
赋值函数(传统写法)
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// char* tmp = new char[s._capacity + 1];
// strcpy(tmp, s._str);
// delete[] _str;
// _str = tmp;
// _size = s._size;
// _capacity = s._capacity;
// }
// return *this;
//}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//以上是默认成员函数
//---------------------------------------------------------
//迭代器
//可读可写的迭代器
typedef char* iterator;
//只可读不可写的迭代器
typedef const char* const_iterator;
//iterator用的begin()
iterator begin()
{
return _str;
}
//iterator用的end()
iterator end()
{
return _str + _size;
}
//const_iterator用的begin()
const_iterator begin()const
{
return _str;
}
//const_iterator用的end()
const_iterator end()const
{
return _str + _size;
}
//扩容函数
//s1.reverse(10)
void reserve(size_t n)
{
//vs的string类的reserve函数不支持缩容
//所以我们为了保持一致,只对n>capacity进行扩容处理
if (n > _capacity)
{
//开辟新的空间(n+1是因为多开一个给'/0')
char* tmp = new char[n + 1];
//把旧空间的_str strcpy 到 tmp空间中
strcpy(tmp, _str);
//释放旧空间
delete[] _str;
//_str指向tmp
_str = tmp;
//容量重置成n
_capacity = n;
}
}
//c_str()函数
const char* c_str() const//后面加const 对于是非const的对象都能调用
{
return _str;
}
//size函数()
size_t size() const
{
//返回成员变量_size的值
return _size;
}
//[]运算符重载(只可读)
const char& operator[](size_t pos)const
{
//'\0'也能读
assert(pos <= _size);
return _str[pos];
}
//对const类型的对象 返回值也必须是const 这叫权限一致
//[]运算符重载(可读可写)
char& operator[](size_t pos)
{
assert(pos <= _size);
return _str[pos];
}
//find()函数 找字符
//s1.('h',2)从下标为2开始查找 'h'字符
size_t find(char c, size_t pos = 0)const
{
//assert(pos < _size);
//遍历查找
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
//来到这里 代表找不到 返回npos(与库一致)
return npos;
}
//find()函数 找字符串
//s1.("hello",2)从下标为2开始查找 "hello"字符串
size_t find(const char* str, size_t pos = 0)
{
//assert(pos < _size);
//用strstr函数来从_str+pos位置开始查找字符串
const char* ptr = strstr(_str + pos, str);
//ptr为空 代表没找到 返回npos(于库一致)
if (ptr == nullptr)
{
return npos;
}
else
{
//指针相减得到下标
return ptr - _str;
}
}
//erase()函数
//s1.erase(2,5);从下标为2开始,清理5个字符
void erase(size_t pos, size_t len = npos)
{
//断言
assert(pos < _size);
//不传len值,代表从pos位置开始全部清理
//len + pos >= _size 也是从pos位置开始全部清理
if (len == npos || len + pos >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else//代表从下标pos位置开始清理一部分
{
//将_str + pos + len往后的内容 strcpy到 _str + pos往后
strcpy(_str + pos, _str + pos + len);
//置_size-=len
_size -= len;
}
}
//clear()函数
//s1.clear();
void clear()
{
//_size置0 且 在第一个位置放入'\0'
_size = 0;
_str[0] = '\0';
}
//以上是一些工具函数
//-----------------------------------------------------------------
//尾插
void push_back(char c)
{
//if判满
if (_size == _capacity)
{
//两种满的给不同容量newcapacity
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
//根据newcapacity扩容
reserve(newcapacity);
}
//原先的_str[_size]是'\0',现在成为新尾插的字符
_str[_size] = c;
//_size++
_size++;
//'\0'往后移动一个
_str[_size] = '\0';
}
//append
//s1.append("hello")
void append(const char* str)
{
//计算出要追加的字符串的长度
size_t len = strlen(str);
//已有的字符串长度(_size)+len 超过容量即扩容
if (_size + len > _capacity)
{
reserve(_size + len);
}
//把追加的str strcpy 到 _str + _size 往后
strcpy(_str + _size, str);
//置_size+=len
_size += len;
//reserve里面已经置capacity
}
//+=运算符重载函数
//s1+='w' 给s1+=一个字符
string& operator+=(char c)
{
//复用push_back()
push_back(c);
return *this;
}
//+=运算符重载函数
//s1+="hello" 给s1+=一个字符串
string& operator+=(const char* str)
{
//复用append()
append(str);
return *this;
}
//insert()函数 在下标为pos处插入字符
//s1.insert(2,'w')在下标为2的位置,插入一个字符
void insert(size_t pos, char c)
{
//断言 最多能在'\0'的位置插入数据
assert(pos <= _size);
//判满 与push_back类似
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
//把下标从pos开始的所有字符向后移动一个
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
//腾出来的下标为pos的位置 给上要插入的字符
_str[pos] = c;
//_size++
_size++;
}
//insert()函数 在下标为pos处插入字符串
//s1.insert(2,"xxxx") 在下标为2处插入字符串"xxxx"
void insert(size_t pos, const char* str)
{
//断言 最多能在'\0'的位置插入数据
assert(pos <= _size);
//判满 与append类似
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//把下标从pos开始的所有字符向后移动len个
size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - len];
end--;
}
//把str的全部字符 复制到 s1的_str+pos处及往后的位置
//strncpy 不计入'\0'
strncpy(_str + pos, str, len);
//置_size+=len
_size += len;
}
//substr()函数 截取_str的从下标为pos位置开始的len个字符作为一个新的对象
//s1.substr(2,5)
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
size_t end = len + pos;
if (len == npos || len + pos >= _size)
{
end = _size;
}
string s2;
s2.reserve(end - pos);
for (size_t i = pos; i < end; i++)
{
s2 += _str[i];
}
return s2;
}
//以上是string的重要函数
//--------------------------------------
private:
size_t _size = 0;
size_t _capacity = 0;
char* _str = nullptr;
static const size_t npos;
};
const size_t string::npos = -1;
//<<运算符重载
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
cout << '\n';
return out;
}
//>>运算符重载
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128];
char ch = in.get();
int i = 0;
while (ch != ' ' && 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;
}
//流插入 流提取的运算符重载
//-----------------------------------------------
void test()
{
string s1;
string s2("world");
cout << "构造出的空s1:" << s1;
cout << "构造出的s2:" << s2;
cout << "使用+=,连续给s1+=字符" << endl;
s1 += 'h';
cout << s1;
s1 += 'e';
cout << s1;
cout << "使用+=,给s1+=一个字符串llo " << endl;
s1 += "llo";
cout << "最终的的s1:" << s1;
cout << endl;
cout << endl;
string s3 = s1;
cout << "由s1拷贝得到的s3:" << s3;
string s4 = s2;
cout << "由s2拷贝得到的s4:" << s4;
cout << endl;
string s5;
s5 = s1;
string s6;
s6 = s2;
cout << "由s1赋值得到的s5:" << s5;
cout << "由s2赋值得到的s6:" << s6;
cout << endl;
string s7("hello");
cout << "打印正常非const对象s7:" << s7;
const string s8("world");
cout << "打印const对象s8:" << s8;
cout << endl;
//迭代器验证
cout << "正常对象s7使用迭代器进行遍历打印:";
string::iterator it = s7.begin();
while (it != s7.end())
{
cout << *it;
++it;
}
cout << endl;
cout << "正常对象s7使用迭代器进行更改再打印:";
string::iterator it2 = s7.begin();
while (it2 != s7.end())
{
*it2 = 'x';
cout << *it2;
++it2;
}
cout << endl;
cout << endl;
cout << "const对象s8使用const迭代器进行遍历打印:";
string::const_iterator it3 = s8.begin();
while (it3 != s8.end())
{
cout << *it3;
++it3;
}
cout << endl;
//const对象s8无法进行写
/*cout << "const对象s8使用const迭代器进行遍历打印:";
string::const_iterator it4 = s8.begin();
while (it4 != s8.end())
{
*it4 = 'x';
cout << *it;
++it;
}*/
s1.swap(s2);
cout << "用swap()函数交换s1和s2" << endl;
cout << "交换后的s1:" << s1;
cout << "交换后的s2:" << s2;
cout << endl;
cout << "在s1中找字符串rld" << endl;
int pos = s1.find("rld");//不给pos 即从头开始找
cout << "将下标给substr得到一个新的对象s9" << endl;
string s9 = s1.substr(pos);//不传len 即从pos开始全部截取
cout << "s9:" << s9;
cout << "inset()函数在s9头插字符串wo:" << endl;
s9.insert(0, "wo");
cout << "s9:" << s9;
}
}
②:分离开的.h和.cpp
.h
cpp
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<assert.h>
#include<iostream>
using namespace std;
namespace bit
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
string(const char* str = "");
// ִд
string(const string& s);
string& operator=(string s);
~string();
const char& operator[](size_t pos) const;
char& operator[](size_t pos);
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos);
void swap(string& s);
size_t find(char ch, size_t pos = 0);
//21:10
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos = 0, size_t len = npos);
void clear();
private:
size_t _capacity = 0;
size_t _size = 0;
char* _str = nullptr;
const static size_t npos = -1;
};
istream& operator>>(istream& in, string& s);
ostream& operator<<(ostream& out, const string& s);
}
.cpp
cpp
#include"string.h"
namespace bit
{
string::string(const char* str)
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// ִд
string::string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
string& string::operator=(string s)
{
swap(s);
return *this;
}
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
const char& string::operator[](size_t pos) const
{
assert(pos <= _size);
return _str[pos];
}
char& string::operator[](size_t pos)
{
assert(pos <= _size);
return _str[pos];
}
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
/*int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}*/
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
}
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
size_t string::find(char ch, size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
size_t end = pos + len;
if (len == npos || pos + len >= _size)
{
end = _size;
}
string str;
str.reserve(end - pos);
for (size_t i = pos; i < end; i++)
{
str += _str[i];
}
return str;
}
void string::clear()
{
_size = 0;
_str[0] = '\0';
}
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128];
char ch = in.get();
int i = 0;
while (ch != ' ' && 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;
}
}