自学内容网 自学内容网

C++:string类模拟实现


前言

今天我们来模拟实现一下string类~

总结实现源码附上~

在这里插入图片描述


一、构造相关

1. 类的定义

为了区分和库里面的string我们需要包个自己的命名空间,里面写我们的string类

namespace jyf
{
class string
{
private:
char* _str;        //数组
size_t _size;      //大小
size_t _capacity;  //容量

    public:
static const size_t npos;  //之后要用的静态成员变量,npos = -1
    };
}

2. 构造,拷贝构造,赋值重载,析构

//string();构造相关
string(const char* str = "");
~string();
string(const string& s);

//传统写法
//string& operator=(const string& s);

//现代写法
string& operator=(string s);
//string();构造相关

//构造函数
string::string(const char* str)
:_size(strlen(str))
{
_str = new char[_size + 1];
strcpy(_str, str);
_capacity = _size;
}

//析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}

//拷贝构造
//传统写法
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}

//赋值运算符重载
//传统写法
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);

_size = s._size;
_capacity = s._capacity;
}

return *this;
}


3. 注意的点

1)构造函数初始化列表顺序问题

在这里插入图片描述


2)赋值运算符重载判断自我赋值

在这里插入图片描述


4. 拷贝构造和赋值运算符重载现代写法

1)拷贝构造

//现代写法
string::string(const string& s)
{
string tmp(s._str);
swap(tmp);
}

在这里插入图片描述

相当于资本主义,疯狂压榨构造函数和析构的剩余价值~


2)赋值运算符重载

//新版本
string& string::operator=(string s)
{
swap(s);
return *this;
}

在这里插入图片描述
构造函数不能只传值,不然会引发无穷递归~


二、遍历相关

1. operator[ ]

char& string::operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& string::operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}

这两个版本是一样的,一个正常调用,一个const修饰的来调用。


2. 迭代器

注意:使用之前要进行typedef
//typedef char* iterator;
using iterator = char*;
using const_iterator = const char*;

iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}

这里的参数名字要写对,不然类似范围for这样的会出问题


三、容量相关

在这里插入图片描述

1. size

size_t string::size()const
{
return _size;
}

2. capacity

size_t string::capacity()const
{
return _capacity;
}

3. empty

bool string::empty()const
{
return _size == 0;
}

4. 改变大小resize

这里memset是这样的
在这里插入图片描述

//改size,赋值c
void string::resize(size_t newSize, char c)
{
if (newSize > _size)
{
// 如果newSize大于底层空间大小,则需要重新开辟空间
if (newSize > _capacity)
{
reserve(newSize);
}
memset(_str + _size, c, newSize - _size);
}
_size = newSize;
_str[newSize] = '\0';
}

5. 预设空间reserve

这里只有n比_capcacity大才开空间

//预开空间
void string::reserve(size_t n)
{
// n比_capcacity小就不开了
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;

_capacity = n;
}
}

四、插入与清除

1. 任意位置插入insert

1)插一个字符

  1. 断言pos位置pos <= _size
  2. 判断是否需要扩容
  3. 循环pos及以后数据后移
    注意这里后移逻辑,size_t end = _size + 1;
    也就是说要从_size + 1的位置开始把_str[end] = _str[end - 1],如果不这样干,一旦让end变为负数,又因为end为_size_t类型,他就会无限循环

size_t -1可以简单理解为最大的整形数据,因此会无限循环
而且只改end为int类型也不行,因为pos是size_t,end会引发整型提升,提升为无符号整型,除非强转了pos为int

  1. 插入
//任意位置插入
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);

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++;
}

2)插字符串

  1. 断言pos位置pos <= _size
  2. 判断是否需要扩容,不一样的是判断_size + len > _capacity,扩容也需要它和2 * _capacity比
  3. 循环pos及以后数据后移,同样注意end不要小于零
  4. 插入
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);

size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}

size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}

for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}

_size += len;
}

2. 尾插字符push_back

可以考虑复用insert,或者也可以像注释这样写

//尾插字符
void string::push_back(char ch)
{
/*if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}

_str[_size] = ch;
_size++;*/

insert(_size, ch);
}

3. 尾插字符串append

可以考虑复用insert,或者也可以像注释这样写

//尾插字符串
void string::append(const char* str)
{
/*size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > _capacity ? _size + len : 2 * _capacity);
}

strcpy(_str + _size, str);
_size += len;*/

insert(_size, str);
}

4. operator+=

可以考虑复用push_back 和 append

//重载加等
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}

5. 清除数据clear

不动空间,直接让_str[0]的位置变为’\0’就好

void string::clear()
{
_str[0] = '\0';
_size = 0;
}

6. 删除pos后的n个数据erase

n太大,str太小pos后面全删完
n太小,只删除pos后一部分,要挪数据

//删除
void string::earse(size_t pos, size_t len)
{
assert(pos < _size);

// n太大, str太小pos后面全删完
if (pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else//只删除pos后一部分,要挪数据
{
size_t end = pos + len;
while (end <= _size)
{
_str[end - len] = _str[end];
end++;
}

_size -= len;
}
}

7. 转C风格

char* c_str() const
{
return _str;
}

8. 类内交换

直接调用算法库的swap交换_str, _size, _capacity;

//类内的交换
void string::swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}

五、查找相关

1. find

1)返回c在string中第一次出现的位置

同样这里的npos需要定义在类内public作用于下,定义成静态成员变量,类内定义,类外声明

但这里对于size_t npos 编译器做了特殊处理,直接在类内定义都可以,但最好还是按照规则来
在这里插入图片描述
在这里插入图片描述

size_t string::find(char ch, size_t pos)
{
assert(pos < _size);

for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}

return npos;
}

2)返回子串s在string中第一次出现的位置

这里借助了C语言找字串的函数strstr,左值是从哪个位置开始找,右值是找到的字串
在这里插入图片描述
它的返回值是第一次找到的位置,因此我们这个函数要返回的下标就是两个指针相减,得出下标。

size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);

const char* ptr = strstr(_str + pos, str);

if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}

六、重载比较运算符

借助在日期类那里学到的思路,只需重载 == 和 < ,其他复用这两个就好

1. operator==

bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}

2. operator<

bool operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}

3. 复用 == 与 <

bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}

bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}

bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}

bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}

七、重载流插入,流输出

1. operator<<

ostream& operator<< (ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}

2. operator>>

  1. 赋值要先清理干净原来的内容
  2. 这里用get()是因为要获取到’ ‘(空格) 和’ \n ',将他们作为判断结束的标志

在这里插入图片描述

  1. 不断地循环 += ch
istream& operator>> (istream& is, string& str)
{
str.clear();

char ch = is.get();

while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}

return is;
}

八、getline

getline 和 operator>> 的区别就是,getline默认遇到’ ‘不结束,遇到’ \n’才结束,而且可以自定义结束终止符。

  1. 赋值就要先清理str
  2. 这里没有直接 += ,做了一个优化

定义一个 char buff[256] ,假定可以存256个值,每次获取到的 ch 都先放到 buff 里,等到 buff 存到255个数据用 i 来记录,就添加一个’\0’,然后 += 到str上面,再将 i 重新置 0 ,继续循环
这样减少了每次的 += 效率的损失,提高了效率

  1. 出循环的时候 i 不为零,补一个’\0’,再 += 到 str 上就好
//getline获取字符串
istream& getline(istream& is, string& str, char delim)
{
str.clear();

int i = 0;
char buff[256];

char ch;
ch = is.get();
while (ch != delim)
{
buff[i++] = ch;

if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}

ch = is.get();
}

if (i > 0)
{
buff[i] = '\0';
str += buff;
}

return is;
}
}

总结

string模拟实现源码:

//string.h
#define _CRT_SECURE_NO_WARNINGS 1

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace jyf
{
class string
{

public:
//typedef char* iterator;
using iterator = char*;
using const_iterator = const char*;

//string();构造相关
string(const char* str = "");
~string();
string(const string& s);

//传统写法
//string& operator=(const string& s);

//现代写法
string& operator=(string s);

char operator[](size_t i);
const char operator[](size_t i) const;

iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}

size_t size() const
{
return _size;
}

char* c_str() const
{
return _str;
}

bool empty()const

{
return _size == 0;
}

void clear()
{
_str[0] = '\0';
_size = 0;
}

void resize(size_t newSize, char c = '\0');
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 earse(size_t pos, size_t len = npos);

size_t find(char ch, size_t pos);
size_t find(const char* str, size_t pos);

void swap(string& str);

string substr(size_t pos, size_t len);

private:
char* _str;
size_t _size;
size_t _capacity;

public:
static const size_t npos;
};

//类外交换
void swap(string& s1, string& s2);

//类外比较的重载
bool operator== (const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string & lhs, const string & rhs);
bool operator>= (const string & lhs, const string & rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);

//类外重载流插入,流提取
ostream& operator<< (ostream& os, const string& str);
istream& operator>> (istream& is, string& str);

//getline获取字符串
istream& getline(istream& is, string& str, char delim = '\n');
}
//string.cpp
#include"string.h"

namespace jyf
{
const size_t string::npos = -1;

//string();构造相关

//构造函数
string::string(const char* str)
:_size(strlen(str))
{
_str = new char[_size + 1];
strcpy(_str, str);
_capacity = _size;
}

//析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}

//拷贝构造
//传统写法
/*string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}*/

//现代写法
string::string(const string& s)
{
string tmp(s._str);
swap(tmp);
}

//赋值运算符重载
//传统写法
/*string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);

_size = s._size;
_capacity = s._capacity;
}

return *this;
}*/

//新版本
string& string::operator=(string s)
{
swap(s);
return *this;
}

//改size,赋值c
void string::resize(size_t newSize, char c)
{
if (newSize > _size)
{
// 如果newSize大于底层空间大小,则需要重新开辟空间
if (newSize > _capacity)
{
reserve(newSize);
}
memset(_str + _size, c, newSize - _size);
}
_size = newSize;
_str[newSize] = '\0';
}

//预开空间
void string::reserve(size_t n)
{
// n比_capcacity小就不开了
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;

_capacity = n;
}
}

//[]遍历
char string::operator[](size_t i)
{
assert(i < _size);
return _str[i];
}

const char string::operator[](size_t i) const
{
assert(i < _size);
return _str[i];
}

//尾插字符
void string::push_back(char ch)
{
/*if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}

_str[_size] = ch;
_size++;*/

insert(_size, ch);
}

//尾插字符串
void string::append(const char* str)
{
/*size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > _capacity ? _size + len : 2 * _capacity);
}

strcpy(_str + _size, str);
_size += len;*/

insert(_size, str);
}

//重载加等
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)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}

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 > 2 * _capacity ? _size + len : 2 * _capacity);
}

size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}

for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}

_size += len;
}

//删除
void string::earse(size_t pos, size_t len)
{
assert(pos < _size);

// n太大, str太小pos后面全删完
if (pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else//只删除pos后一部分,要挪数据
{
size_t end = pos + len;
while (end <= _size)
{
_str[end - len] = _str[end];
end++;
}

_size -= len;
}
}

//查找
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);

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)
{
assert(pos < _size);

const char* ptr = strstr(_str + pos, str);

if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}

//类内的交换
void string::swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}

//获取字串
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);

//如果,len的长度大于pos位置开始之后的长度,就从pos取到结尾
if (len > _size - pos)
{
len = _size - pos;
}

jyf::string sub;
sub.reserve(len);

for (int i = 0; i < len; i++)
{
sub += _str[pos + i];
}

return sub;
}


//类外交换
void swap(string& s1, string& s2)
{
s1.swap(s2);
}

//类外比较的重载
bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}

bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}

bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}

bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}

bool operator< (const string& lhs, const string& rhs) 
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}

bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}

//类外重载流插入,流提取
ostream& operator<< (ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}

istream& operator>> (istream& is, string& str)
{
str.clear();

char ch = is.get();

while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}

return is;
}

//getline获取字符串
istream& getline(istream& is, string& str, char delim)
{
str.clear();

int i = 0;
char buff[256];

char ch;
ch = is.get();
while (ch != delim)
{
buff[i++] = ch;

if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}

ch = is.get();
}

if (i > 0)
{
buff[i] = '\0';
str += buff;
}

return is;
}
}
//test.cpp
#include "string.h"
#include <iostream>
using namespace std;

void TestString()
{
    // 1. 构造函数测试
    cout << "=== 构造函数测试 ===" << endl;
    jyf::string s1;
    cout << "默认构造: " << s1.c_str() << " (size: " << s1.size() << ")" << endl;

    jyf::string s2("hello");
    cout << "带参数构造: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    jyf::string s3(s2);
    cout << "拷贝构造: " << s3.c_str() << " (size: " << s3.size() << ")" << endl;

    // 2. 赋值运算符测试
    cout << "\n=== 赋值运算符测试 ===" << endl;
    s2 = "world";
    cout << "赋值: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    // 3. 运算符[] 测试
    cout << "\n=== 运算符[] 测试 ===" << endl;
    cout << "s2[0]: " << s2[0] << ", s2[4]: " << s2[4] << endl;

    // 4. push_back 测试
    cout << "\n=== push_back 测试 ===" << endl;
    s2.push_back('!');
    cout << "push_back: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    // 5. append 测试
    cout << "\n=== append 测试 ===" << endl;
    s2.append(" test");
    cout << "append: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    // 6. insert 测试
    cout << "\n=== insert 测试 ===" << endl;
    s2.insert(6, ' ');
    cout << "insert char: " << s2.c_str() << endl;

    s2.insert(12, " case");
    cout << "insert string: " << s2.c_str() << endl;

    // 7. earse 测试
    cout << "\n=== earse 测试 ===" << endl;
    s2.earse(6, 5);
    cout << "earse部分删除: " << s2.c_str() << endl;

    s2.earse(6); // 删除从位置6开始到结尾的所有字符
    cout << "earse到末尾: " << s2.c_str() << endl;

    // 8. find 测试
    cout << "\n=== find 测试 ===" << endl;
    size_t pos = s2.find('!', 0);
    cout << "find '!': " << pos << endl;
    pos = s2.find("ld", 0);
    cout << "find 'ld': " << pos << endl;

    // 9. substr 测试
    cout << "\n=== substr 测试 ===" << endl;
    jyf::string s5 = s2.substr(0, 5);
    cout << "substr(0, 5): " << s5.c_str() << endl;

    // 10. 类外比较运算符测试
    cout << "\n=== 类外比较运算符测试 ===" << endl;
    cout << "s2 == s2: " << (s2 == s2) << endl;
    cout << "s2 != s3: " << (s2 != s3) << endl;
    cout << "s3 < s2: " << (s3 < s2) << endl;
    cout << "s2 > s3: " << (s2 > s3) << endl;

    // 11. 类内 swap 测试
    cout << "\n=== swap 测试 ===" << endl;
    s2.swap(s3);
    cout << "swap后 s2: " << s2.c_str() << ", s3: " << s3.c_str() << endl;

    // 12. clear 测试
    cout << "\n=== clear 测试 ===" << endl;
    s2.clear();
    cout << "clear后 s2: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    // 13. 类外 swap 测试
    cout << "\n=== 类外 swap 测试 ===" << endl;
    jyf::swap(s2, s3);
    cout << "类外swap后 s2: " << s2.c_str() << ", s3: " << s3.c_str() << endl;

    // 14. 类外流插入、提取运算符测试
    cout << "\n=== 流插入与提取测试 ===" << endl;
    jyf::string s6("stream test");
    cout << "输出运算符: " << s6 << endl;

    jyf::string s7;
    cout << "输入一段字符串: ";
    cin >> s7;
    cout << "输入的字符串是: " << s7 << endl;

    // 15. getline 测试
    cout << "\n=== getline 测试 ===" << endl;
    jyf::string s8;
    cout << "输入一行文字 (以逗号结束): ";
    jyf::getline(cin, s8, ',');
    cout << "输入的内容是: " << s8 << endl;
}

int main()
{
    TestString();
    return 0;
}

到这里就结束啦~
谢谢大家!~

在这里插入图片描述


原文地址:https://blog.csdn.net/Jdxxwu/article/details/142747613

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!