序言:
在本文中,我们将基于C++实现string类中的一些基本功能,并且完成string类功能的测试。
一、基本框架
1.在实现string之前,我们需要创建三个文件:string.h用于存放类的声明,string.cpp来实现类的基本.功能,test.cpp来对模拟类进行测试。
2.为了避免与std中的string类冲突,我们模拟的string类应在我们定义的命名空间mystring中去声明定义。
3.string类的成员变量应该包括字符串_str,容量_capacity,大小_size。
二、类的声明
以下是我们需要模拟实现string类的结构以及一些功能:
#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
namespace mystring {
class string {
friend ostream& operator<<(ostream& _cout, const mystring::string& s);
friend istream& operator>>(istream& _cin, mystring::string& s);
public:
typedef char* iterator;
//构造与析构
string(const char* str = "");
//拷贝构造
string(const string& s);
//赋值重载
string& operator=(const string& s);
~string();
//迭代器函数
iterator begin();
iterator end();
const iterator begin()const;
const iterator end()const;
// 修改函数
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;
//关系运算符重载
bool operator<(const string& s);
bool operator<=(const string& s);
bool operator>(const string& s);
bool operator>=(const string& s);
bool operator==(const string& s);
bool operator!=(const string& s);
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const;
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const;
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c);
string& insert(size_t pos, const char* str);
// 删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos, size_t len);
};
}
三、构造与析构
我们需要实现构造函数,拷贝构造,赋值重载,析构函数共四个函数。
1.构造函数
string(const char* str = "")
: _size(strlen(str))
, _str(new char[strlen(str) + 1])
, _capacity(_size)
{
strcpy(this->_str, str);
}
考虑到在创建对象的时候,可能不会指定对象内容,所以我们使用全缺省函数进行构造,默认内容为空。再初始化其_size和_capacity;
2.拷贝构造函数
string(const string& s)
: _str(new char[s.size()+1])
,_size(s.size())
,_capacity(_size){
strcpy(this->_str, s._str);
}
3.赋值重载
//赋值重载
string& operator=(const string& s) {
this->_str = new char[s.size() + 1];
this->_size = s._size;
this->_capacity = s._capacity;
strcpy(this->_str, s._str);
return *this;
}
注意:由于string类涉及内存的开辟,所以其拷贝构造和赋值重载必须手动写,不能使用系统默认生成的,否则只能完成浅拷贝,无法完成深拷贝。
4.析构函数
~string() {
delete[]_str;
_size = 0;
_capacity = 0;
}
同理,析构函数也必须手动书写,否则无法完成空间释放工作。
5.测试
我们根据写的三种构造函数,可以使用四个对象来对其及逆行测试。
void test1() {
mystring::string s1("abcd");
mystring::string s2(s1);
mystring::string s3=s2;
mystring::string s4="abcd";
cout << "s1:" << s1 << endl;
cout << "s2:" << s2 << endl;
cout << "s3:" << s3 << endl;
cout << "s4:" << s4 << endl;
}
测试通过
四、迭代器函数
1.函数实现
在string类中,我们可以使用字符指针来模拟迭代器,其实现如下:
typedef char* iterator;
string::iterator string::begin() {
return _str;
}
string::iterator string::end() {
return _str + _size;
}
const string::iterator string::begin()const {
return _str;
}
const string::iterator string::end()const {
return _str + _size;
}
2.测试
void test2() {
mystring::string s1 = "hello world!";
mystring::string::iterator it1 = s1.begin();
while (it1 != s1.end()) {
cout << *it1 << " ";
it1++;
}
cout << endl;
for (auto e : s1) {
cout << e << " ";
}
cout << endl;
const mystring::string s2 = "this is a sentance!";
mystring::string::iterator it2 = s2.begin();
while (it2 != s2.end()) {
cout << *it2 << " ";
it2++;
}
cout << endl;
for (auto e : s2) {
cout << e << " ";
}
cout << endl;
}
通过!
五、容量大小操作函数
1.size函数
作用:获取字符串大小
实现:直接返回_size即可
size_t size()const {
return _size;
}
2.capacity函数
作用:获取string的当前容量
实现:直接返回_capacity即可
size_t capacity() const {
return this->_capacity;
}
3.reserve函数
作用:将容量扩至指定值n,若n小于等于当前容量,则不进行操作,否则进行扩容。
实现:将原字符串的内容拷贝新字符串中,释放原来的空间。
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n;
}
}
4.resize函数
作用:改变字符串的大小至n,并且用字符ch填充字符串后面的内容,若n大于_capacity,则需要对字符串进行扩容操作。
实现:如果n<=_size,则将字符串的第n个位置改为'\0'即可,若n>_size,则将后面填充为ch即可。
void resize(size_t n, const char ch='\0') {
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;
}
}
5.empty函数
作用:判断字符串是否为空
实现:判断_size是否为0即可
bool empty()const {
return _size == 0;
}
6.测试
void test3() {
mystring::string s1 = "abcdefg";
cout << "s1: " << s1.size()<<" "<<s1.capacity() << endl;
cout << "test capacity:";
//n<_capacity
s1.reserve(3);
cout << s1.capacity() << endl;
s1.reserve(20);
cout << s1.capacity() << endl;
cout << "test resize:";
//n<_size
s1.resize(3);
cout << s1 << endl;
//n>_size
s1.resize(10, '#');
cout << s1 << endl;
cout << "test empty:";
cout << s1.empty() << endl;
}
通过!
六、修改类函数
1.push_back函数
作用:在末尾加一个元素ch
实现:将_str[_size]位置改为ch,再将后面加上一个'\0'即可,但是要注意扩容问题,以及记得需要修改_size的值
void push_back(const char ch) {
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
2.append函数
作用:在末尾追加一个字符串
实现:在字符串s的字符依次追加到后面即可,注意扩容问题,以及记得需要修改_size的值
void append(const char* s) {
size_t len = strlen(s);
if (_size + len > _capacity) {
reserve(_size + len + 1);
}
strcpy(_str + _size, s);
_size += len;
}
3.+=操作符重载
作用:在末尾追加一个字符或者是字符串
实现:根据追加的内容类型,选择调用append或者是push_back即可
void operator+=(const char ch) {
push_back(ch);
}
void operator+=(const char* s) {
append(s);
}
4.clear函数
作用:摸出字符串中的内容,使字符串的大小为0
实现:将字符串的第一个位置改为'\0',再将_str该为0即可
void clear() {
if (_size != 0) {
_str[0] = '\0';
_size = 0;
}
}
5.swap函数
作用:交换两个字符串的内容,包括_str,_szie,_capacity
实现:对_str,_size,_capacity直接调用std中的swap函数即可
void swap(string& s) {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
6.c_str函数
作用:返回字符串中_str的首地址,作用类似于begin函数
实现:直接返回其地址即可
const char* c_str() {
return _str;
}
7.insert函数(const char ch)
作用:在pos位置插入一个字符
实现:将pos位置之后的元素全部往后移动一个位置,然后将pos位置改为ch
void insert(size_t pos, char ch){
assert(pos <= _size);
// 扩容2倍
if (_size == _capacity){
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + 1;
while (end > pos){
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
8.insert函数(const char*s)
作用:在pos位置插入一个字符串
实现:将pos位置之后的元素全部往后移动strlen(s)个位置,然后将pos位置改为ch
void insert(size_t pos, const char* str){
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity){
// 扩容
reserve(_size + len);
}
size_t end = _size + len;
while (end > pos + len - 1){
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
}
9.erase函数
作用:删除从pos位置开始的len个元素,如果pos+len>_size,则删除pos位置之后的所有元素
实现:如果pos+len>_size,则直接将pos位置改为'\0',再修改_size的值,否则直接将pos+len后面位置的元素直接拷贝到pos位置。再修改_size的值
void erase(size_t pos, size_t len = std::string::npos) {
assert(pos < _size);
if (len == std::string::npos || len >= _size - pos) {
_str[pos] = '\0';
_size = pos;
}
else {
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
10.测试
void test4() {
mystring::string s1 = "abcd";
//测试push_back
s1.push_back('x');
cout << s1 << endl;
//测试qppend
s1.append("hello worldz!");
cout << s1 << endl;
//测试+=
s1 += '1';
cout << s1 << endl;
s1 += "234567";
cout << s1 << endl;
//测试clear
s1.clear();
cout << s1 << " s1._capacity:" << s1.capacity() <<"s1._size: "<< s1.size() << endl;
//测试swap函数
cout << "测试swap" << endl;
mystring::string s2 = "hello world 12345";
cout << s2 << endl;
s1.swap(s2);
cout << "s1: " << s1 << "s2: " << s2 << endl;
cout << "测试C_str" << endl;
//测试c_str
cout << s1.c_str() << endl;
//测试insert
cout <<endl<< "测试insert" << endl;
s1.insert(3, "haha");
cout << s1 << endl;
s1.insert(2, 's');
cout << s1 << endl;
//测试erase
cout << "测试erase:" << endl;
s1.erase(3, 5);
cout << s1 << endl;
}
七、操作符重载
1.[ ]操作符
作用:取pos位置的内容返回
实现:直接返回pos位置的元素
char& operator[](int pos) {
assert(pos < _size);
return _str[pos];
}
const char& operator[](int pos)const {
assert(pos < _size);
return _str[pos];
}
2.< <= > >= == !=
作用:用于比较两个字符串
实现:利用mencpy函数来进行比较
bool operator<(const string& s) {
memcmp(this->_str, s._str,max(this->_size,s._size)) < 0;
}
bool operator<=(const string& s) {
memcmp(this->_str, s._str, max(this->_size, s._size)) <= 0;
}
bool operator>(const string& s) {
memcmp(this->_str, s._str, max(this->_size, s._size)) > 0;
}
bool operator>=(const string& s) {
memcmp(this->_str, s._str, max(this->_size, s._size)) >= 0;
}
bool operator==(const string& s) {
if (this->_size != s._size)
return false;
return
memcmp(this->_str, s._str, max(this->_size, s._size)) == 0;
}
bool operator!=(const string& s) {
return !((*this) == s);
}
3.测试
void test5() {
mystring::string s1 = "hello world!";
for (int i = 0; i < s1.size(); i++) {
s1[i]++;
cout << s1[i] << " ";
}
cout << endl;
}
测试通过
八、流插入和流提取重载
注意:流插入和流提取重载不能放在类内,必须在类外进行重载
1.流插入<<
作用:将字符串打印打印出来
实现:循环,遇到'\0'退出,并且返回流对象
ostream& operator<<(ostream& _cout, const mystring::string& s) {
for (size_t i = 0; i < s.size(); ++i){
if (s[i] == '\0')
break;
_cout << s[i];
}
return _cout;
}
2.流提取>>
作用:从流中读取数据到字符串中
实现:注意一点,为了避免频繁地扩容,可以将提取的字符暂存在一个数组中,等数组满了或者是读取结束后再转移到字符串中。
istream& operator>>(istream& _cin, mystring::string& s) {
char ch;
ch = _cin.get();
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\n') {
buff[i++] = ch;
if (i == 127) {
buff[127] = '\0';
s += buff;
i = 0;
}
ch = _cin.get();
}
if (i > 0) {
buff[i] = '\0';
s += buff;
}
return _cin;
}
九、其他
find函数
作用:从pos位置检查字符ch或字符串s是否存在,存在则返回其第一次出现的位置
实现:直接进行比对,找到返回即可
size_t find(char c, size_t pos = 0) const{
for (size_t i = pos; i < _size; ++i){
if (_str[i] == c)
return i;
}
return -1;//未找到
}
size_t find(const char* s, size_t pos = 0) const{
assert(s);
assert(pos < _size);
const char* src = _str + pos;
while (*src){
const char* match = s;//如果不匹配,返回子串起始处重新查找
const char* cur = src;
while (*match && *match == *cur){
++match;
++cur;
}
if (*match == '\0'){
return src - _str;//返回下标
}
else{
++src;
}
}
return -1;
}
void test6() {
mystring::string s1 = "hello world!";
cout<<s1.find('5')<<endl;
cout << s1.find('l')<<endl;
cout << s1.find("hello") << endl;
}
通过!