前言
这一篇博客,我们主要讲一下string的模拟实现和一些练习题
1.基本框架
string.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
#define _CRT_SECURE_NO_WARNINGS 1
namespace bit
{
class string
{
public:
private:
char* _str;
size_t _size;
size_t capacity;
};
}
string.cpp
#include"string.h"
namespace bit
{
}
test.cpp
#include"string.h"
void test1()
{
}
int main()
{
test1();
return 0;
}
相同的命名空间会合并
2.构造函数
2.1有参构造函数
string(char* arr);
char* c_str();
string::string(char* arr)
:_size(strlen(arr))
{
_str = new char[_size + 1];//因为size不包含\0
strcpy(_str,arr);//iostream中有这些字符串函数,所以不用再包含了
_capacity = _size;
}
char* string::c_str()
{
return _str;
}
string s1("123456");
cout << s1.c_str() << endl;//对于字符串的地址,会直接打印字符串,而不是地址
2.2 无参构造函数
string();
string::string()
{
_str = new char[1] {'\0'};
strcpy(_str, arr);
_capacity = _size=0;
}
有参与无参合并
string(const char* arr="");
string::string(const char* arr)//定义不能传缺省值
:_size(strlen(arr))
{
_str = new char[_size + 1];
strcpy(_str, arr);
_capacity = _size;
}
2.3 拷贝构造函数
string::string(string& s)
{
_capacity=_size = strlen(s._str);
_str = new char[_size + 1];
strcpy(_str, s._str);
}
3. 析构函数
string::~string()
{
_size = _capacity = 0;
delete _str;
_str = nullptr;
}
4.赋值运算符重载
string& string::operator=(string& s)
{
//std::swap(_str, s._str);
//std::swap(_size, s._size);
//std::swap(_capacity, s._capacity);//因为我们后面还会自己实现自己的swap,所以区分清楚//不是这样实现的
_size = s._size;
_capacity = s._capacity;
_str = new char[_size + 1];
strcpy(_str, s._str);
return *this;
}
5. 迭代器
因为范围for的底层是迭代器,所以没有begin,end的话,范围for是无法运行的
typedef char* iterator;//因为迭代器类似于指针,所以在string中,可以定义为指针
typedef const char* const_iterator;//因为迭代器类似于指针,所以在string中,可以定义为指针
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
这样就可以使用范围for了,因为这就是迭代器
然后我们测试的时候,为了防止与库里的string冲突,我们测试也在命名空间bit中
string::const_iterator string::begin()const
{
return _str;
}
string::const_iterator string::end()const//说明this指针指向的内容不可修改
{
return _str + _size;
}
6. size
size_t string::size()
{
return _size;
}
7. capacity
size_t string::capacity()
{
return _capacity;
}
8. resize
void string::resize(size_t n)
{
_size = n;
if (_size > _capacity)
{
//扩容
char* tmp = new char[_size + 1];
strcpy(tmp, _str);
delete _str;
_str = tmp;
_capacity = _size;
}
}
9. reserve
void string::reserve(size_t n)
{
if (n > _capacity)
{
//扩容
char* tmp = new char[_size + 1];
strcpy(tmp, _str);
delete _str;
_str = tmp;
_capacity = n;
}
}
10. operator[]
char& string::operator[](size_t n)
{
assert(n <= _size);
return _str[n];
}
11. clear
void string::clear()//clear的话,字符串变成空串,然后capacity不变,size变为0
{
_str[0] = '\0';
_size = 0;
}
12. operator+=
string& string::operator+=(const string& s)//因为隐式类型转换是const类型的,所以要加const
{
if (s._size + _size > _capacity)
{
//扩容
char* tmp = new char[s._size + _size+1];
strcpy(tmp, _str);
delete _str;
_str = tmp;
_capacity = s._size + _size;
}
strcpy(_str + _size, s._str);
return *this;
}
string& string::operator+=(char c)
{
if (_size == _capacity)
{
//扩容
char* tmp = new char[_capacity*2+1];
strcpy(tmp, _str);
delete _str;
_str = tmp;
_capacity *= 2;
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
return *this;
}
13. append和push_back
void string::append(const string& str)
{
(*this) += str;
}
void string::push_back(char c)
{
(*this) += c;
}
14. insert
insert字符
这个只需要定义一个end,然后将end的数据往后移,end–就ok了
int end = _size - 1;
while (end >= pos)
{
}
如果这样定义end的话,如果pos为0,那么就完了,因为pos为size_t,所以int与size_t比较时,int会转换为size_t,那么end就永远>=pos了,循环不会停止,解决办法有两个,其中一个就是将pos强转为int,方法二就是,end一开始就指向_size
法一
if (_size == _capacity)
{
//扩容
char* tmp = new char[_capacity * 2 + 1];
strcpy(tmp, _str);
delete _str;
_str = tmp;
_capacity *= 2;
}
//开始插入
//int pos = _size - 1;
//while (pos >= _size)
//{
//}//如果这样定义end的话,如果pos为0,那么就完了,因为pos为size_t,所以int与size_t比较时,i
//nt会转换为size_t,那么end就永远>=pos了,循环不会停止,
//解决办法有两个,其中一个就是将pos强转为int,方法二就是,end一开始就指向_size
//法1
int end = _size-1;
_str[_size + 1] = '\0';//别忘了\0,因为覆盖了
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = c;
法二
int end = _size;
_str[end + 1] = '\0';
while (end > pos)//这些条件都要自己画图来得到
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
insert串
法一
void string::insert(size_t pos, const string& s)
{
assert(pos <= _size);
if (s._size + _size > _capacity)
{
char* tmp = new char[s._size + _size + 1];
strcpy(tmp, _str);
delete _str;
_str = tmp;
_capacity = s._size + _size;
}
//开始插入
//这次还是两种方法,这次我们把\0也计入移动数据中
//还是要自己画图,不然真的不好整
int len = s._size;
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
end--;
}
for (int i = 0; i < len; i++)
{
_str[i + pos] = s._str[i];
}
}
int len = s._size;
int end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
end--;
}
for (int i = 0; i < len; i++)
{
_str[i + pos] = s._str[i];
}
15. npos
npos是size_t整型的最大值,因为为-1嘛
然后静态成员变量的声明与定义也要分离
在一个文件中声明与定义要分离,声明在类内,定义在类外
在不同文件中声明与定义也要分离,但是定义不能在.h中,不然都重定义了,定义要在string.cpp中才行
然后就是只有静态的const类型的int类型的才可以初始化,一般的static没有缺省值,有了初始化,就不能在定义了
16. erase
void string::erase(size_t pos, size_t len)
{
assert(pos <= _size);
//if (len + pos >= _size)//这里的加法可能存在问题,因为size_t的最大值加1,就变为0了
if (len==npos||len + pos >= _size)
{
//直接将pos后面的全部删掉
_str[pos] = '\0';
_size = pos;
}
else
{
//直接将后面不用删的拷贝到前面去
memcpy(_str + pos, _str + pos + len, _size - pos - len+1);//因为\0也要考过去
}
}
17. replace
void string::replace(size_t pos, size_t len, const string& s)
{
assert(pos <= _size);
//先删
erase(pos, len);
//再插入
insert(pos, s);
}
18. swap
void string::swap(string& s)
{
//这个是我们自己定义的swap
std::swap(_str, s._str);//这个是库里面的
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
void swap(string& s1, string& s2) //这个函数也是要声明的哦
{
s1.swap(s2);
}
要交换的话,就不要能用const修饰了,不然会出错
19. find
size_t string::find(size_t pos, char c)
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return -1;
}
size_t string::find(size_t pos, string s)
{
assert(pos < _size);
char* ret = strstr(_str + pos, s._str);
if (ret != nullptr)
{
return ret - _str;
}
else
{
return -1;
}
}
20. relational operators
21. substr
string string::substr(size_t pos, size_t len) const
//获取一个子串,并返回这个子串
{
string s;
if (len == npos || pos + len >= _size)
{
s._str = new char[_size - pos + 1];
strcpy(s._str, _str + pos);
s._capacity = _size - pos;
s._size = _size - pos;
}
else
{
s._str = new char[len + 1];
memcpy(s._str,_str+pos,len);
s._str[len] = '\0';
s._capacity = len;
s._size = len;
}
return s;
}
22. 流插入和流提取
ostream& operator<<(ostream& out, string& s)
//流提取和流输入都定义在类外面
{
out << s.c_str();
return out;
}
流提取就比较难了
主要是这个你也无法知道要输入的数据有多大,所以无法定义一个数组,只能一个字符一个字符的读取,在插入
istream& operator>>(istream& in, string& s)
{
char c;
in >> c;
while (c != '\0' && c != '\n')
{
s += c;
in >> c;
}
return in;
}
这样子不行,因为cin无法提取空格和\n
char c = in.get();
while (c != ' ' && c != '\n')
{
s += c;
c = in.get();
}
return in;
cin.get()这个函数的话,就可以读取空格和换行符
s.clear();//先清空
char c = in.get();//cin.get()这个函数的话,就可以读取空格和换行符
char arr[128] = { 0 };
int i = 0;
while (c != ' ' && c != '\n')
{
arr[i] = c;
i++;
if (i == 127)//最后一个\0
{
s += arr;
i = 0;
}
c = in.get();
}
arr[i] = '\0';
s += arr;
return in;
这个是最终版本
要注意,有const和没有const是两个不同的类型,有引用和指针时,不改变所指向的的值时,最好加上const
23. 浅拷贝的问题
浅拷贝有两个问题,第一个问题就是同一块空间会被析构两次,第二个问题就是一个改变会引起另一个改变
所以我们引入引用计数的概念,但我们现在不实现,只是讲讲
所谓引用计数,就是对那一块我们开辟的空间计数,看有多少个变量指向它,释放一个对象,那么计数就少1,最后变为0,才是真正的释放
然后就是,如果要修改里面的值,那么这个对象就自己去拷贝,去深拷贝,然后自己的计数为1,原来的计数少1
所以说,这个引用计数,如果不修改值,那么很好,如果要修改值,那么也就一般般了
总结
下一篇我们讲vector