C++string模拟实现
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、
- 二、使用步骤
- 1.引入库
- 2.读入数据
- 总结
前言
C++中的string相当于是一个类模板,包含在头文件string中,具体实现在这个头文件中名为std的namespace中,所以一般在直接使用string时,展开std;
那么模拟实现string时,首先要将实现的内容放在一个自己定义的命名空间里面,在这个命名空间里面再定义自己的string类;
这里实现string吗,模板的主要接口功能。
一、string头文件
实现时用到的头文件;
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
注意:默认定义在类里面的函数都是inline内联函数,在头文件中去实现频繁调用的短小函数提高了程序的效率;所以在头文件中实现的函数都是频繁调用并且短小的函数。
###构造和析构;
A是我们模拟的命名空间,里面实现了一个自定义的string类;
后续头文件中的函数都在这个类里面,所以后面只展示函数的实现;
namespace A
{
class string
{
public:
//默认构造函数
string(const char* s = "")
{
size_t len = strlen(s);
_str = new char[len + 1];
strcpy(_str, s);//strcpy拷贝包含'\0'
_capacity = len;
_size = len;
}
private:
size_t _size;
size_t _capacity;
char* _str;
};
}
设置一个默认参数,若是没有传参,用这个""的字符串的优点:只含一个'\0',strcpy时直接把'\0'拷贝过来,更加便捷,s的长度为0,strlen不会计算'\0',new len+1个长度,留一个字节的空间给'\0'
拷贝构造
//拷贝构造传统写法
string(const string& str)
{
size_t len = strlen(str._str);
_str = new char[len + 1];
strcpy(_str, str._str);
_size = str._size;
_capacity = str._capacity;
}
//拷贝构造现代写法
string(const string& str)
{
string tmp;
tmp._str = new char[strlen(str._str) + 1];
strcpy(tmp._str, str._str);
tmp._size = str._size;
tmp._capacity = str._capacity;
swap(tmp);
}
传统写法:让string的对象的_str申请和str一样的长度,再用strcpy将str里面的内容拷贝过去,同时让string对象的_size和_capacity都和str的相同;
现代写法:重新定义一个临时的string对象,让这个临时对象是str的拷贝,再用swap交换要构造的对象和临时对象(swap的实现在后面,它的作用是将两个对象完全交换);
析构
~string()
{
if (_str)
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
释放对象的指针的指向的内容,让指针指为空;让string的大小和容量都为0;
赋值重载
//赋值重载传统写法
string& operator=(const string& str)
{
if (_str)
delete[] _str;
_str = new char[strlen(str._str) + 1];
strcpy(_str, str._str);
_size = str._size;
_capacity = str._capacity;
return *this;
}
//赋值重载现代写法
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
传统写法:释放被赋值对象的指针指向内容,申请和str一样的空间容量,再用strcpy将str里面的内容拷过来,同时改变大小_size、_capacity;
现代写法:这里就体现出来了现代写法的优势;将函数形参处写上string类型的临时变量,没有传引用,那么tmp就要拷贝构造,拷贝赋值对象的内容;拷贝完之后,tmp就可以看作赋值对象,此时再和要被赋值对象进行交换,就实现了赋值重载。
###容量
//容量
size_t capacity()const
{
return _capacity;
}
size_t size()const
{
return _size;
}
void reserve(size_t n);
void clear()
{
_str[0] = '\0';
_size = 0;
}
bool empty()
{
return _size == 0;
}
获取string对象的容量大小、数据个数、以及clear清空数据和判空;
reverse是用来改变容量大小的,在string里面增加数据空间不够时要用到,此函数定义在cpp文件中;就算定义在cpp文件中,也要是在同名的命名空间里面定义的,因为即使在不同的文件中同名的命名空间会整合到一起;
void string::reserve(size_t n)
{
//默认往大的扩容
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
VS里面的reverse只能变大capacity,不能缩小;
具体实现:先定义一个临时变量,让这个临时变量的容量大小为n+1(指定的加上'\0'的),再将要被扩容的对象里面的值拷贝到tmp里面,再释放要被扩容的对象里面的内容,再让_str等于tmp;同时记得把_capacity置为n,_size不变,只是扩容而没有增加或者减少数据;
###迭代器
//迭代器
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin()const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator end()const
{
return _str + _size;
}
在普遍的迭代器定义中,都是对象内容重命名为迭代器iteartor;所以此处模拟时也将char*或者const char*重命名为相应的迭代器;
###元素获取
//元素获取
char& operator[](size_t pos)
{
assert(pos < size());
return _str[pos];
}
重载[],直接用string对象加上[]就能访问_str里面存储的数据;
二、string源文件
此处的函数都是比较长的函数,也在和头文件中的函数相同的命名空间内,形成一个整体;
###修改
头文件中的声明部分:
//修改
void swap(string& x);
string& operator+=(const char* s);
string& operator+=(const char c);
string& push_back(const char c);
string& append(const char* s);
string& append(const char c);
string& Insert(size_t pos, const char c);
string& Insert(size_t pos, const char* s);
static const size_t npos = -1;
string& Erase(size_t pos = 0, size_t len = npos);
static const size_t npos = -1;
这是一个特殊存在,一般在类里面的static要在类外面初始化,但是此处是特例,声明时就直接定义了;
npos是无符号整数的最大值;
实现:
swap交换各个小部分时要调用std里面的swap
向对象里面增加数据时要先判断是否要扩容;其次要注意在结束处置'\0'
实现了一个加字符或者加字符串后面就可以复用;
void string::swap(string& x)
{
std::swap(_size, x._size);
std::swap(_capacity, x._capacity);
std::swap(_str, x._str);
}
string& string:: operator+=(const char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity)
reserve(_capacity * 2 > _size + len ? 2 * _capacity : _size + len);
for (size_t i = 0; i < len; i++)
{
_str[_size++] = s[i];
}
_str[_size] = '\0';
return *this;
}
string& string:: operator+=(const char c)
{
if (_size == _capacity)
reserve(_capacity + 1);
_str[_size++] = c;
_str[_size] = '\0';
return *this;
}
string& string::push_back(const char c)
{
return operator+=(c);
}
string& string::append(const char* s)
{
return operator+=(s);
}
string& string::append(const char c)
{
return operator+=(c);
}
任意处加、删除数据
注意扩容或者数据的移动
string& string::Insert(size_t pos, const char c)
{
assert(pos <= size());
if (_size == _capacity)
reserve(_capacity + 1);
for (size_t i = size(); i > pos; i--)
{
_str[i] = _str[i - 1];
}
_str[pos] = c;
_str[++_size] = '\0';
return *this;
}
string& string::Insert(size_t pos, const char* s)
{
assert(pos <= _size);
if (pos == _size)
*this += s;
else
{
size_t len = strlen(s);
if (_size + len > _capacity)
reserve(_capacity * 2 > _size + len ? 2 * _capacity : _size + len);
for (size_t i = size()+ len; i >= pos + len; i--)
{
_str[i] = _str[i - len];
}
for (size_t i = 0; i < len; i++)
{
_str[i + pos] = s[i];
}
_size += len;
}
return *this;
}
string& string:: Erase(size_t pos, size_t len)
{
assert(pos < size());
if (len + pos >= size() || len == string::npos)
{
_size = pos;
_str[pos] = '\0';
}
else
{
for (size_t i = 0; i < size()-pos-len; i++)
{
_str[i + pos] = _str[i + pos+ len] ;
}
_size -= len;
_str[_size] = '\0';
}
return *this;
}
###查找数据
size_t string::find(size_t pos, const char c)
{
assert(pos < size());
for (size_t i = pos; i < size(); i++)
{
if (_str[i] == c)
return i;
}
return string::npos;
}
size_t string::find(size_t pos, const char* s)
{
assert(pos < size());
char* p = strstr(_str + pos, s);
if (p != nullptr)
return p - _str;
return string::npos;
}
从pos处开始查找,找到了就返回此处下标,没有找到就返回npos,对于字符串,使用strstr函数,匹配整个字符串,记录返回匹配处的指针,用此处指针减去开始的指针就是下标位置;
返回子串
定义一个临时对象,拷贝从pos处开始之后的len个长度的字符串形成一个子串;
string string::substr(size_t pos, size_t len)const
{
assert(pos < size());
string tmp;
tmp.reserve(size());
strcpy(tmp._str, _str + pos);
tmp._str[ len] = '\0';
tmp._size = len;
return tmp;
}
###对象的比较 (复用)
bool operator==( const string& x, const string& y)
{
return strcmp(x._str, y._str) == 0
&& x._size == y._size
&& x._capacity == y._capacity;
}
bool operator>(const string& x, const string& y)
{
return strcmp(x._str, y._str) > 0;
}
bool operator<(const string& x, const string& y)
{
return !(x > y)&& !(x==y);
}
bool operator>=(const string& x, const string& y)
{
return !(x < y);
}
bool operator<=(const string& x, const string& y)
{
return !(x > y);
}
bool operator!=(const string& x, const string& y)
{
return !(x == y);
}
###输入输出
ostream& operator<<(ostream& out, const string& str)
{
for (auto ch : str)
{
out << ch;
++ch;
}
return out;
}
istream& operator>>(istream& in, string& str)
{
str.clear();
const size_t N = 256;
char buffer[N];
char ch = in.get();
size_t i = 0;
while (ch!=' ' && ch!='\n')
{
buffer[i++] = ch;
if (i == N - 1)
{
buffer[i] = '\0';
str += buffer;
i = 0;
}
ch = in.get();
}
if (i > 0)//未到256但是中途有' '或者'\n';
{
buffer[i] = '\0';
str += buffer;
}
return in;
}
输出一个一个输出;
输入为了避免开多空间,先定义一个充当缓冲区的字符数组,存放输入的字符,达到字符数组的限度之后再在随后加上'\0'作为结束,接着用string对象加上这个字符串;
在输入的过程中可能还没到字符数组的最大限度就碰见空字符或者换行了,此时就跳出循环,让i处为'\0',再加上buffer
注意i最大为N-1,至少要留一个给'\0'。