目录
前言
本次模拟实现主要是为了更深入地了解string类的各种接口和函数的功能。所以并不是写一个能和库媲美的模拟实现,只是实现其中主要和常用的功能。
一、框架
首先拟好要实现的函数和功能。
建一个头文件。用命名空间给它命名。把类放在命名空间里。
注意:string类所有新开辟的空间的申请都要比capacity多加个1,存储\0。
namespace ting
{
class string
{
public:
friend ostream& operator<<(ostream& _cout, const string& s);
friend istream& operator>>(istream& _cin, string& s);
public:
typedef char* iterator;
public:
string(const char* str = "");
string(const string& s);
string& operator=(const string& s);
~string();
iterator begin();
iterator end();
void push_back(char c);
string& operator+=(char c);
void append(const char* str);
string& operator+=(const char* str);
void clear();
void swap(string& s);
const char* c_str()const;
size_t size()const;
size_t capacity()const;
bool empty()const;
void resize(size_t n, char c = '\0');
void reserve(size_t n);
char& operator[](size_t index);
const char& operator[](size_t index)const;
private:
char* _str;
size_t _capacity;
size_t _size;
const static int npos;
};
const int string::npos = -1;
std::ostream& operator<<(ostream& _cout, const string& s);
std::istream& operator>>(istream& _cin, string& s);
}
二、模拟实现
具体不细说,只说一些需要注意的细节。不注意可能会有大坑。因为string像顺序表,里面的实现逻辑在数据结构就写过,所以写起来其实不那么复杂,主要是和c++的类一起结合。
1.构造函数
string的构造函数要写两个,一个是用const字符串常量进行构造的,一个是用string类进行构造的。用const字符串进行构造要给缺省值,不然如果用空字符构造还要另外写。所以缺省值给空字符串就可以,不需要加空格。
构造函数要写初始化列表,初始化列表初始化的顺序是根据下面私设的成员变量声明的顺序来进行的。为了防止别人乱改自己的代码顺序(比如把私设的声明顺序改一下)导致后面使用起来莫名崩溃。深拷贝,也就是char* _str要另外new一个空间,最好不要写在初始化列表。写在函数里面。
string(const char* str = "")
:_capacity(strlen(str))
,_size(strlen(str))
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
string(const string& s)
:_capacity(strlen(s._str))
,_size(strlen(s._str))
{
_str = new char[_capacity+ 1];
strcpy(_str, s._str);
}
2.赋值运算符重载
赋值运算符重载这里实现要加条件,this和传来的string的地址不相同的时候,才进行赋值,否则直接返回*this。表示这两个如果是同一个string对象,就不要赋值了。
赋值的话,如果对象比自己的空间要大,那要扩容。但是c++里没有realloc函数,所以要手动实现扩容操作,而且基本都是异地扩容。这里不考虑空间大小,一律进行异地申请一个空间,把对象数据拷贝到这个开的空间,然后释放_str,再把_str指向这个新开好的拷贝了数据的空间。
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
delete[](_str);
strcpy(tmp, s._str);
_str = tmp;
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
这样就完成了赋值操作。
和realloc的效率差不多。
3.迭代器iterator
写迭代器的时候,需要写支持变量的也要支持const的迭代器,后面友元函数会用到。在string里,迭代器就是一个指针,所以typedef char* iterator。有了迭代器就可以支持范围for的使用。
迭代器就写个begin和end指向的位置就可以。
iterator begin()
{
return _str;
}
const iterator begin() const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator end() const
{
return _str + _size;
}
4.push_back尾插
这里检查扩容要写成2倍,可以用reserve扩容。如果扩容写1.5倍,多插入几次程序会崩。
+=运算符重载直接调用push_back就可以。
void push_back(char c)
{
if (_size == _capacity)
{
reserve(2* _capacity + 1);//扩容写成1.5倍会出问题
}
_str[_size++] = c;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
5.clear
clear是清楚空间的数据但不改变空间大小,_size要变成0,_str[0]='\0'就可以。数据读取不到了。读取数据是解引用读到'\0'为止。第一个元素是'\0'后面都读不到的。
void clear()
{
_str[0] = '\0';
_size = 0;
}
看不见也读取不了,等于清除。
6.swap交换
用库提供的swap函数,把两个string对象的_capacity,_size,_str交换就可以。
void swap(string& s)
{
std::swap(_str, s._str);//库里提供了swap的模板
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
7.resize
resize会改变_size的大小,不管大于还是小于。其他根据resize功能的逻辑写就可以。
void resize(size_t n, char c = '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
for (size_t i = _size; i < n; ++i)
{
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
}
}
8.reserve
reserve只有在扩容的时候才起作用,所以要加上这个条件判断。
void reserve(size_t n)
{
if (n > _capacity)//reserve只有在扩容的时候才会起作用
{
char* ptr = new char[n + 1];
strcpy(ptr, _str);
delete[] _str;
_str = ptr;
_capacity = n;
}
}
9.[]操作符重载
这个要加assert断言,下标要小于_size
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
10.友元函数>> 和<<运算符重载
这里用范围for就好,遍历流插入,遍历流提取。
std::ostream& operator<<(ostream& _cout, const string& s)//这里传的是const string
{
for (auto a : s)//这个需要迭代器支持才能用,所以迭代器也要写const迭代器,卡了好久
{
_cout << a;
}
return _cout;
}
std::istream& operator>>(istream& _cin, string& s)
{
for (auto& a : s)
{
_cin >> a;
}
return _cin;
}
三、string模拟实现全部代码示例
头文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;
namespace ting
{
class string
{
friend ostream& operator<<(ostream& _cout, const string& s);
friend istream& operator>>(istream& _cin, string& s);
public:
typedef char* iterator;
public:
string(const char* str = "")
:_capacity(strlen(str))
,_size(strlen(str))
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
string(const string& s)
:_capacity(strlen(s._str))
,_size(strlen(s._str))
{
_str = new char[_capacity+ 1];
strcpy(_str, s._str);
}
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
delete[](_str);
strcpy(tmp, s._str);
_str = tmp;
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
~string()
{
if (_str)
{
delete[](_str);
_str = nullptr;
_capacity = _size = 0;
}
}
//
// iterator
iterator begin()
{
return _str;
}
const iterator begin() const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator end() const
{
return _str + _size;
}
/
// modify
void push_back(char c)
{
if (_size == _capacity)
{
reserve(2* _capacity + 1);//扩容写成1.5倍会出问题
}
_str[_size++] = c;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
void append(const char* str)
{
size_t len = strlen(str) + _size;
if (len > _capacity)
{
reserve(len);//扩容
}
strcpy(_str + _size, str);
_size = len;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
void swap(string& s)
{
std::swap(_str, s._str);//库里提供了swap的模板
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
const char* c_str()const
{
return _str;
}
/
// capacity
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
return _size == 0;
}
void resize(size_t n, char c = '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
_capacity = n;
}
else
{
if (n > _capacity)
{
reserve(n);
}
for (size_t i = _size; i < n; ++i)
{
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
_capacity = n;//resize是可以改变_capacity的大小的,所以这一步也要加
}
}
void reserve(size_t n)
{
if (n > _capacity)//reserve只有在扩容的时候才会起作用
{
char* ptr = new char[n + 1];
strcpy(ptr, _str);
delete[] _str;
_str = ptr;
_capacity = n;
}
}
/
// access
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static int npos;
};
const int string::npos = -1;
std::ostream& operator<<(ostream& _cout, const string& s)//这里传的是const string
{
for (auto a : s)//这个需要迭代器支持才能用,所以迭代器也要写const迭代器,卡了好久
{
_cout << a;
}
return _cout;
}
std::istream& operator>>(istream& _cin, string& s)
{
for (auto& a : s)
{
_cin >> a;
}
return _cin;
}
}
部分功能测试文件
#include "string.h"
void test1()
{
//测试下标引用操作符和迭代器,拷贝构造
cout << "测试下标引用操作符和迭代器,拷贝构造" << endl;
ting::string s1("abcdefg");
for (int i = 0; i < s1.size();++i)
{
cout << s1[i] << " ";
}
cout << endl;
for (auto e : s1)
{
e++;//e不是指针,是解引用后的值
cout << e << " ";
}
cout << endl;
ting::string::iterator m = s1.begin();
while (m != s1.end())
{
cout << *m << " ";
++m;
}
cout << endl<<endl;
}
void test2()
{
//测试尾插、追加、+=运算符重载
cout << "测试尾插、追加、+=运算符重载" << endl;
ting::string s2("hello world");
s2.push_back('a');
s2.push_back('a');
s2.push_back('a');
s2.append(" ah");
s2.append(" ahhh");
s2 += 'm';
s2 += "mmmmmmmm";
for (auto e : s2)
{
cout << e;
}
cout << endl<<endl;
}
void test3()
{
cout << "测试clear、reserve、resize、capacity" << endl;
ting::string s3("aaaaaaaa");
for (auto e : s3)
{
cout << e;
}
cout << endl;
s3.clear();
for (auto e : s3)
{
cout << e;
}
cout << endl;
cout << s3.capacity() << endl;
s3.reserve(15);
s3.reserve(3);
cout << s3.capacity() << endl;
s3.resize(3);
cout << s3.capacity() << endl;
s3.resize(20);
cout << s3.capacity() << endl<<endl;
}
void test4()
{
cout << "测试流插入和流提取" << endl;
ting::string s4("abcdef");
cout << s4 << endl;
cin >> s4;
cout << s4 << endl;
}
int main()
{
test1();
test2();
test3();
test4();
return 0;
}