C++ ——string的模拟实现

迷迭所归处 2024-10-02 10:05:03 阅读 58

目录

前言

浅记

 1. 构造函数

2. 拷贝构造函数

2.1 拷贝构造传统写法

2.2 拷贝构造现代写法

3. swap 

4. 赋值重载

4.1 赋值重载的传统写法

4.2 赋值重载的现代写法1

 4.3 赋值重载的现代写法2

5. 析构函数

6. reserve(扩容) 

7. push_back(尾插) 

8. iterator(迭代器)

9. append(尾插一个字符串)

10. insert 

10.1 按pos位插入一个字符

10.2 按pos位插入一个字符串

11. erase 

12. find

12.1 查找字符

12.2 查找字符串

13. substr 

 14.代码汇总

string.h

string.cpp


前言

        C++ —— 关于string类-CSDN博客

icon-default.png?t=O83A

https://blog.csdn.net/hedhjd/article/details/142023625?spm=1001.2014.3001.5501


浅记

char* _str;字符串存储空间首地址指针

size_t _size; 当前存储的有效数据个数

size_t _capacity;可用容量

*

_str:指向字符串存放的空间的指针

_size:当前存储的有效数据个数 ,指向最后一个字符的下一个位置

_capacity:代表当前可存储的最大容量

nops:此值设置为 -1,无符号整型转换就是42亿,且此值为const和静态参数具有全局效应,某些函数设置其为缺省参数

 


 1. 构造函数

<code>//构造函数

string(const char* str = "")

{

_size = strlen(str);

// _capacity不包含\0

_capacity = _size;

_str = new char[_capacity + 1];

strcpy(_str, str);

}


2. 拷贝构造函数

2.1 拷贝构造传统写法

// 深拷贝问题

//拷贝构造传统写法

string(const string& s)

{

_str = new char[s._capacity + 1];

strcpy(_str, s._str);

_size = s._size;

_capacity = s._capacity;

}

2.2 拷贝构造现代写法

//拷贝构造现代写法

string(const string& s)

{

string tmp(s._str);

swap(tmp);

}

 


3. swap 

void swap(string& s)

{

//指定std,调用算法库里的值

std::swap(_str, s._str);

std::swap(_size, s._size);

std::swap(_capacity, s._capacity);

}


4. 赋值重载

4.1 赋值重载的传统写法

// s2 = s1

// s1 = s1

//赋值重载的传统写法

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;

}

 

4.2 赋值重载的现代写法1

//赋值重载的现代写法1

// s1 = s3;

string& operator=(const string& s)

if (this != &s)

{

//string tmp(s._str);

string tmp(s);

swap(tmp);

}

return *this;

}

 4.3 赋值重载的现代写法2

//赋值重载的现代写法2

string& operator=(string tmp)

{

swap(tmp);

return *this;

}


5. 析构函数

//析构函数

~string()

{

if (_str)

{

delete[] _str;

_str = nullptr;

_size = _capacity = 0;

}

}

 

6. reserve(扩容) 

void string::reserve(size_t n)

{

//如果字符串存储的数据大于容量

if (n > _capacity)

{

//cout << "reserve:" << n << endl;

//申请新空间赋给临时变量tmp + 1是位\0申请的空间

char* tmp = new char[n + 1];

//把数据拷贝到临时变量里

strcpy(tmp, _str);

//释放旧空间

delete[] _str;

//指向新空间

_str = tmp;

//字符串存储的数据等于容量

_capacity = n;

}

}


7. push_back(尾插) 

//尾插

void string::push_back(char ch)

{

//如果当前的有效数据个数和最大容量相等

if (_size == _capacity)

{

//那么扩容

reserve(_capacity == 0 ? 4 : _capacity * 2);

}

//因为size指向最后一个字符的下一个位置,所以直接插入

_str[_size] = ch;

++_size;

//最后要加一个\0,不然会乱码

_str[_size] = '\0';

}

先判断是否还有剩余空间如果满了就进行扩容操作。扩容后在_size位置放上我们要插入的字符,然后++_size,最后在_size的后一个位置补上'\0',防止乱码


8. iterator(迭代器)

const size_t string::npos = -1;

string::iterator string::begin()

{

return _str;

}

string::iterator string::end()

{

return _str + _size;

}

string::const_iterator string::begin() const

{

return _str;

}

string::const_iterator string::end() const

{

return _str + _size;

}


9. append(尾插一个字符串)

//尾插一个字符串

//_str:指向字符串存放的空间的指针

//_size:当前存储的有效数据个数, 指向最后一个字符的下一个位置

void string::append(const char* str)

{

//先计算要插入的字符长度,将值赋给临时变量len

size_t len = strlen(str);

//如果当前存储的有效数据个数 加上 要插入的字符 大于 容量

if (_size + len > _capacity)

{

// 大于2倍,需要多少开多少,小于2倍按2倍扩

reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);

}

//将目标地址到\0的位置中间的值拷贝过去

strcpy(_str + _size, str);

//尾插

_size += len;

}

如果尾插后的字符串所需空间大于现有空间就扩容,然后使用strcpy函数将需要尾插的字符串从_str + _size的位置开始拷贝,最后插入len即可


10. insert 

10.1 按pos位插入一个字符

//按pos位插入一个字符

void string::insert(size_t pos, char ch)

{

assert(pos <= _size);

//如果当前的有效数据个数和最大容量相等

if (_size == _capacity)

{

//那么扩容

reserve(_capacity == 0 ? 4 : _capacity * 2);

}

// 挪动数据

//_size + 1:\0的后一位

//把end指向\0的后一位

size_t end = _size + 1;

//循环将pos之后的数据都向后挪动一位

while (end > pos)

{

_str[end] = _str[end - 1];

--end;

}

//将字符ch放入pos位

_str[pos] = ch;

++_size;

}

先判断是否需要扩容,然后定义一个end指向'\0'的下一个位置,然后将pos之后的数据都向后挪动一位,最后将字符ch放入pos位

10.2 按pos位插入一个字符串

//按pos位插入一个字符串

void string::insert(size_t pos, const char* str)

{

assert(pos <= _size);

//先计算要插入的字符长度,将值赋给临时变量len

size_t len = strlen(str);

//如果当前存储的有效数据个数 加上 要插入的字符 大于 容量

if (_size + len > _capacity)

{

// 大于2倍,需要多少开多少,小于2倍按2倍扩

reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);

}

//定义一个end指向_size + len的位置上

size_t end = _size + len;

//通过循环把pos后的len个数据向后挪动len个位置

while (end > pos + len - 1)

{

_str[end] = _str[end - len];

--end;

}

// 这里不能用 strcpy 会把 '\0' 拷贝过来

//使用memcpy函数进行拷贝的原因是memcpy函数不会自动补上'\0'

// 而strcpy函数会在拷贝后自动补上'\0’,

//把str里的len个数据拷贝到_str + pos里

memcpy(_str + pos, str, len);

//插入字符串

_size += len;

}

 先计算要插入的字符长度,将值赋给临时变量len,判断是否需要扩容,然后定义一个end指向_size + len 的位置。通过循环来控制将pos后的len个数据向后挪动len个位置,然后使用memcpy函数把str里的len个数据拷贝到_str + pos里


11. erase 

//从pos位置开始删除长度为的len的字符串

//_str:指向字符串存放的空间的指针

//_size:当前存储的有效数据个数, 指向最后一个字符的下一个位置

void string::erase(size_t pos, size_t len)

{

//判断是否会越界

assert(pos < _size);

//如果要删除的字符 大于等于 当前存储的有效数据个数 减去 当前指向数据的位置

if (len >= _size - pos)

{

//直接将pos位置置为\0

_str[pos] = '\0';

_size = pos;

}

//如果要删除的字符 小于 当前存储的有效数据个数减去当前指向数据的位置

else

{

//将_str + pos + len(要保留的数据)位置的字符串直接拷贝到_str + pos(要删除的数据)位置直接覆盖掉

strcpy(_str + pos, _str + pos + len);

_size -= len;

}

}

 先进行判断len是否大于等于pos后面的元素个数,如果大于等于的话,就将pos位及其之后的元素全部删除,直接将pos位置置为'\0',然后将有效数据个数置为pos

如果len小于pos后面的元素个数的话,就将_str + pos + len位置的字符串直接拷贝到_str + pos位置,直接将要删除的那len个元素覆盖,最后有效数据更新即可


12. find

12.1 查找字符

//查找字符

//查找对应的字符,返回对应的下标

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;

}


12.2 查找字符串

strstr:在一个字符串中查找另一个字符串 ,返回子串在原串里第一个出现的位置

str1:原串       str2:子串

const char * strstr ( const char * str1, const char * str2 );

size_t string::find(const char* str, size_t pos)

{

assert(pos < _size);

//通过strstr函数来将str作为子串找到它的地址

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

//匹配失败

if (ptr == nullptr)

{

return npos;

}

else//匹配成功

{

//ptr的地址减去_str的地址就是我们要找的字符串的起始位置的下标

return ptr - _str;

}

}

通过strstr函数来将str作为子串找到它的地址,然后sub的地址减去_str的地址就是我们要找的字符串的起始位置的下标


13. substr 

//从pos位置开始截取长度为len的字符再构造一个子串,子串返回

string string::substr(size_t pos, size_t len)

{

assert(pos < _size);

// 如果len大于剩余字符长度

if (len > _size - pos)

{

//把len更新成为一个有效的长度,有多少长度取多少空间

len = _size - pos;

}

//构造一个子串sub

string sub;

//先给sub预留len个空间

sub.reserve(len);

//for循环遍历要截取的字符

for (size_t i = 0; i < len; i++)

{

//将从pos位置开始长度为len的字符尾插到sub中

sub += _str[pos + i];

}

return sub;

}

先判断len是否大于剩余字符的长度,如果len大于剩余字符的长度就把len更新成为一个有效的长度,有多少长度取多少空间,然后再构造一个子串sub,先给sub预留len个空间,然后通过遍历将从pos位置开始长度为len的字符尾插到sub中最后直接返回sub 


 14.代码汇总

string.h

#define _CRT_SECURE_NO_WARNINGS 1

#pragma once

#include<iostream>

#include<string>

#include<assert.h>

using namespace std;

namespace bit

{

class string

{

public:

typedef char* iterator;

typedef const char* const_iterator;

iterator begin()

{

return _str;

}

iterator end()

{

return _str + _size;

}

const_iterator begin() const

{

return _str;

}

const_iterator end() const

{

return _str + _size;

}

/*string()

:_str(new char[1]{'\0'})

,_size(0)

,_capacity(0)

{}*/

// 短小频繁调用的函数,可以直接定义到类里面,默认是inline

//构造函数

string(const char* str = "")

{

_size = strlen(str);

// _capacity不包含\0

_capacity = _size;

_str = new char[_capacity + 1];

strcpy(_str, str);

}

// 深拷贝问题

//拷贝构造传统写法

/*string(const string& s)

{

_str = new char[s._capacity + 1];

strcpy(_str, s._str);

_size = s._size;

_capacity = s._capacity;

}*/

void swap(string& s)

{

//指定std,调用算法库里的值

std::swap(_str, s._str);

std::swap(_size, s._size);

std::swap(_capacity, s._capacity);

}

//拷贝构造现代写法

string(const string& s)

{

string tmp(s._str);

swap(tmp);

}

// s2 = s1

// s1 = s1

//赋值重载的传统写法

/*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;

}*/

//赋值重载的现代写法1

// s1 = s3;

/*string& operator=(const string& s)

{

if (this != &s)

{

//string tmp(s._str);

string tmp(s);

swap(tmp);

}

return *this;

}*/

//赋值重载的现代写法2

string& operator=(string tmp)

{

swap(tmp);

return *this;

}

//析构函数

~string()

{

if (_str)

{

delete[] _str;

_str = nullptr;

_size = _capacity = 0;

}

}

const char* c_str() const

{

return _str;

}

//将字符串的内容清空

void clear()

{

_str[0] = '\0';

_size = 0;

}

size_t size() const

{

return _size;

}

size_t capacity() const

{

return _capacity;

}

char& operator[](size_t pos)

{

assert(pos < _size);

return _str[pos];

}

const char& operator[](size_t pos) const

{

assert(pos < _size);

return _str[pos];

}

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 erase(size_t pos, size_t len = npos);

size_t find(char ch, size_t pos = 0);

size_t find(const char* str, size_t pos = 0);

string substr(size_t pos = 0, size_t len = npos);

private: //设置私有,不允许随便访问底层数据

char* _str = nullptr;//字符串存储空间首地址指针

size_t _size = 0; //当前存储的有效数据个数

size_t _capacity = 0;//可用容量

//static const size_t npos = -1;

static const size_t npos;

/*_str:指向字符串存放的空间的指针

_size:当前存储的有效数据个数 ,指向最后一个字符的下一个位置

_capacity:代表当前可存储的最大容量

nops:此值设置为 -1,无符号整型转换就是42亿,且此值为const和静态参数具有全局效应,某些函数设置其为缺省参数*/

};

bool operator<(const string& s1, const string& s2);

bool operator<=(const string& s1, const string& s2);

bool operator>(const string& s1, const string& s2);

bool operator>=(const string& s1, const string& s2);

bool operator==(const string& s1, const string& s2);

bool operator!=(const string& s1, const string& s2);

ostream& operator<<(ostream& out, const string& s);

istream& operator>>(istream& in, string& s);

}


string.cpp

#include"string.h"

//char* _str;字符串存储空间首地址指针

//size_t _size; 当前存储的有效数据个数

//size_t _capacity;可用容量

//static const size_t npos = -1;

/*_str:指向字符串存放的空间的指针

_size:当前存储的有效数据个数 ,指向最后一个字符的下一个位置

_capacity:代表当前可存储的最大容量

nops:此值设置为 -1,无符号整型转换就是42亿,且此值为const和静态参数具有全局效应,某些函数设置其为缺省参数*/

namespace bit

{

const size_t string::npos = -1;

//扩容

void string::reserve(size_t n)

{

//如果字符串存储的数据大于容量

if (n > _capacity)

{

//cout << "reserve:" << n << endl;

//申请新空间赋给临时变量tmp + 1是位\0申请的空间

char* tmp = new char[n + 1];

//把数据拷贝到临时变量里

strcpy(tmp, _str);

//释放旧空间

delete[] _str;

//指向新空间

_str = tmp;

//字符串存储的数据等于容量

_capacity = n;

}

}

//尾插

void string::push_back(char ch)

{

//如果当前的有效数据个数和最大容量相等

if (_size == _capacity)

{

//那么扩容

reserve(_capacity == 0 ? 4 : _capacity * 2);

}

//因为size指向最后一个字符的下一个位置,所以直接插入

_str[_size] = ch;

++_size;

//最后要加一个\0,不然会乱码

_str[_size] = '\0';

}

//尾插一个字符串

//_str:指向字符串存放的空间的指针

//_size:当前存储的有效数据个数, 指向最后一个字符的下一个位置

void string::append(const char* str)

{

//先计算要插入的字符长度,将值赋给临时变量len

size_t len = strlen(str);

//如果当前存储的有效数据个数 加上 要插入的字符 大于 容量

if (_size + len > _capacity)

{

// 大于2倍,需要多少开多少,小于2倍按2倍扩

reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);

}

//将目标地址到\0的位置中间的值拷贝过去

strcpy(_str + _size, str);

//尾插

_size += len;

}

//尾插一个字符

string& string:: operator+=(char ch)

{

push_back(ch);

return *this;

}

//尾插一个字符串

string& string::operator+=(const char* str)

{

append(str);

return *this;

}

//按pos位插入一个字符

void string::insert(size_t pos, char ch)

{

assert(pos <= _size);

//如果当前的有效数据个数和最大容量相等

if (_size == _capacity)

{

//那么扩容

reserve(_capacity == 0 ? 4 : _capacity * 2);

}

// 挪动数据

//_size + 1:\0的后一位

//把end指向\0的后一位

size_t end = _size + 1;

//循环将pos之后的数据都向后挪动一位

while (end > pos)

{

_str[end] = _str[end - 1];

--end;

}

//将字符ch放入pos位

_str[pos] = ch;

++_size;

}

//按pos位插入一个字符串

void string::insert(size_t pos, const char* str)

{

assert(pos <= _size);

//先计算要插入的字符长度,将值赋给临时变量len

size_t len = strlen(str);

//如果当前存储的有效数据个数 加上 要插入的字符 大于 容量

if (_size + len > _capacity)

{

// 大于2倍,需要多少开多少,小于2倍按2倍扩

reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);

}

//定义一个end指向_size + len的位置上

size_t end = _size + len;

//通过循环把pos后的len个数据向后挪动len个位置

while (end > pos + len - 1)

{

_str[end] = _str[end - len];

--end;

}

// 这里不能用 strcpy 会把 '\0' 拷贝过来

//使用memcpy函数进行拷贝的原因是memcpy函数不会自动补上'\0'

// 而strcpy函数会在拷贝后自动补上'\0’,

//把str里的len个数据拷贝到_str + pos里

memcpy(_str + pos, str, len);

//插入字符串

_size += len;

}

//从pos位置开始删除长度为的len的字符串

//_str:指向字符串存放的空间的指针

//_size:当前存储的有效数据个数, 指向最后一个字符的下一个位置

void string::erase(size_t pos, size_t len)

{

//判断是否会越界

assert(pos < _size);

//如果要删除的字符 大于等于 当前存储的有效数据个数 减去 当前指向数据的位置

if (len >= _size - pos)

{

//直接将pos位置置为\0

_str[pos] = '\0';

_size = pos;

}

//如果要删除的字符 小于 当前存储的有效数据个数减去当前指向数据的位置

else

{

//将_str + pos + len(要保留的数据)位置的字符串直接拷贝到_str + pos(要删除的数据)位置直接覆盖掉

strcpy(_str + pos, _str + pos + len);

_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;

}

//查找字符串

//strstr:在一个字符串中查找另一个字符串

// 返回子串在原串里第一个出现的位置

//str1:原串 str2:子串

//const char * strstr ( const char * str1, const char * str2 );

size_t string::find(const char* str, size_t pos)

{

assert(pos < _size);

//通过strstr函数来将str作为子串找到它的地址

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

//匹配失败

if (ptr == nullptr)

{

return npos;

}

else//匹配成功

{

//ptr的地址减去_str的地址就是我们要找的字符串的起始位置的下标

return ptr - _str;

}

}

//从pos位置开始截取长度为len的字符再构造一个子串,子串返回

string string::substr(size_t pos, size_t len)

{

assert(pos < _size);

// 如果len大于剩余字符长度

if (len > _size - pos)

{

//把len更新成为一个有效的长度,有多少长度取多少空间

len = _size - pos;

}

//构造一个子串sub

string sub;

//先给sub预留len个空间

sub.reserve(len);

//for循环遍历要截取的字符

for (size_t i = 0; i < len; i++)

{

//将从pos位置开始长度为len的字符尾插到sub中

sub += _str[pos + i];

}

return sub;

}

//string比较大小按照ascii码比

//strcmp:如果第一个数大于/小于/等于第二个数,那么返回>0/<0/0

//小于

bool operator<(const string& s1, const string& s2)

{

return strcmp(s1.c_str(), s2.c_str()) < 0;

}

//小于等于

bool operator<=(const string& s1, const string& s2)

{

return s1 < s2 || s1 == s2;

}

//大于

bool operator>(const string& s1, const string& s2)

{

return !(s1 <= s2);

}

//大于等于

bool operator>=(const string& s1, const string& s2)

{

return !(s1 < s2);

}

//等于

bool operator==(const string& s1, const string& s2)

{

return strcmp(s1.c_str(), s2.c_str()) == 0;

}

//不等于

bool operator!=(const string& s1, const string& s2)

{

return !(s1 == s2);

}

//流插入

ostream& operator<<(ostream& out, const string& s)

{

for (auto ch : s)

{

out << ch;

}

return out;

}

//流提取

istream& operator>>(istream& in, string& s)

{

s.clear();

const int N = 256;

char buff[N];

int i = 0;

char ch;

//in >> ch;

ch = in.get();

while (ch != ' ' && ch != '\n')

{

buff[i++] = ch;

if (i == N - 1)

{

buff[i] = '\0';

s += buff;

i = 0;

}

//in >> ch;

ch = in.get();

}

if (i > 0)

{

buff[i] = '\0';

s += buff;

}

return in;

}


感谢观看~



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。