【手撕】string

系列文章目录


文章目录


前言

模拟实现string类


string类的模拟实现

cpp 复制代码
class string
	{
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static const size_t npos = -1;
		//空间大小限定符
	};

member functions

构造函数

cpp 复制代码
string(const char* str = "")
    :_size(strlen(str))
    //size = 0
{
	_capacity = 3 > _size ? 3 : _size;
    //capacity = 3
	_str = new char[_capacity + 1];
    //""
	strcpy(_str, str);
}

构造函数传参必须是const char*类型的,字符串是存放在代码段中的常量区的是不可修改的,因此如果想修改其中string类的对象时,需要将在堆上开辟一段空间用来存放字符串,这样可以实现string类的增、删、查、改

析构函数

cpp 复制代码
~string()
{
  _size = 0;
  _capacity = 0;
  delete[] _str;
}

在堆上开辟的空间,记得一定要释放,否则会出现内存泄漏

拷贝构造(深拷贝)

cpp 复制代码
//深拷贝的传统写法
string(const string& s)
    :_size(s._size),
    _capacity(s._capacity)
{
    _str = new char[_capacity + 1];
    strcpy(_str, s._str);
}

//深拷贝的现代写法
string(const string& s)
    :_str(nullptr)
{
    string tmp(s._str);
    swap(tmp);
}

注:
string类的拷贝构造是深拷贝
深拷贝:拷贝对象,新开一块空间跟原来对象一样大的空间,再把原来对象空间上的值拷贝过来
浅拷贝:指向同一块空间,析构两次,其中一个修改另外一个也会受影响
深拷贝的传统写法:首先在初始化列表中开辟出和s一样大小的空间,其次再将s中的内容拷贝到新创建的对象中
深拷贝的现代写法:首先创建一个string类的临时对象tmp并将s的内容拷贝到tmp中,然后将tmp和新创建的对象进行交换,最后tmp会自动调用析构函

赋值重载

cpp 复制代码
string& operator=(string& s)
{
    if (this != &s)
    {
        _size = s._size;
        _capacity = s._capacity;

        _str = new char[_capacity + 1];
        strcpy(_str, s._str);
    }

    return *this;
}

capacity(容量)

size

cpp 复制代码
size_t size() const
{
    return _size;
}

size()返回字符串有效字符长度

capacity

cpp 复制代码
size_t capacity() const
{
    return _capacity;
}

capacity()返回当前字符串分配的存储空间的大小

reserve

cpp 复制代码
void reserve(size_t n)
{
    if (n > _capacity)
    {//防止缩容
        char* tmp = new char(n + 1);
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;

        _capacity = n;
  }
				
}

reserve为字符串预留空间

reserve(size_t n=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

resize

cpp 复制代码
void resize(size_t n, char ch)
{//开空间+初始化
    if (n <= _size)
    {//删除数据、保留前n个数据
        _str[n] = '\0';
        _size = n;
    }
    else
    {
        if (n > _capacity)
        {//字符有效长度大于空间容量,扩容
            reserve(n);
        }
        int i = _size;
        while (i < n)
        {//扩大字符串大小,往后填入ch
            _str[i] = ch;
        }
        _size = n;
        _str[_size] = '\0';
    }

}

resize将有效字符的个数该成n个,多出的空间用字符ch填充

resize(size_t n, char ch='\0')是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用'\0'来填充多出的元素空间,resize(size_t n, char ch='\0')用字符ch来填充多出的元素空间。

注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

clear

cpp 复制代码
void clear()
{
    _str[0] = '\0';
    _size = 0;
}

clear 清空有效字符

Modifiers(修改器)

push_back

cpp 复制代码
void push_back(char ch)
{
        if (_size = _capacity)
        {
            reserve(2 * _capacity);
        }
	    _str[_size] = ch;
        ++_size;
        _str[_size] = '\0';
}

push_back 在字符串后尾插字符ch

append

cpp 复制代码
void append(char* str)
{
    size_t len = sizeof(str);
    if (len + _size > _capacity)
    {
        reserve(_size + len);//扩容
    }
    strcpy(_str + _size, str);
    _size += len;
}

append 在字符串后追加一个字符串

insert

cpp 复制代码
string& insert(size_t pos, char ch)
{
    assert(pos > _size);
    if (_size + 1 > _capacity)
    {
        reserve(2 * _capacity);
    }
    size_t end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end - 1];
        --end;
    }
    _str[pos] = ch;
    ++_size;
}

insert函数用于插入字符时,首先需要判断pos的合法性,若不合法则无法进行操作,紧接着还需判断当前对象能否容纳插入字符后的字符串,若不能则还需调用reserve函数进行扩容。插入字符的过程也是比较简单的,先将pos位置及其后面的字符统一向后挪动一位,给待插入的字符留出位置,然后将字符插入字符串即可。

cpp 复制代码
string& insert(size_t pos, char* str)
{
    assert(pos <= _size);
    int len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }
    size_t end = _size;
    while (end > pos)
    {
        _str[end + len] = _str[end];
        --end;
    }
    strncpy(_str + pos, str,len);
    _size += len;

    return *this;
}

insert函数用于插入字符串时,首先也是判断pos的合法性,若不合法则无法进行操作,再判断当前对象能否容纳插入该字符串后的字符串,若不能则还需调用reserve函数进行扩容。插入字符串时,先将pos位置及其后面的字符统一向后挪动len位(len为待插入字符串的长度),给待插入的字符串留出位置,然后将其插入字符串即可。

operator +=

cpp 复制代码
string& operator+=(char ch)
{
    push_back(ch);
    return *this;
}

string& operator+=(const char* str)
{
    append(str);
    return *this;
}

string& operator+=(const string& s)
{
    *this += s._str;
    return *this;
}

operator+= 在字符串后追加字符串str或者在字符串后追加字符ch

在string尾部追加字符时,s.push_back(ch) / s.append(1, ch) / s += 'ch'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

swap

cpp 复制代码
void swap(string& s)
{
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}

swap 交换字符串值

通过s的内容交换自身的内容,s是另一个字符串对象。 长度可能不同。在调用这个成员函数之后,这个对象的值就是调用之前s的值,s的值就是这个对象调用之前的值。

erase

cpp 复制代码
string& erase(size_t pos, size_t len = npos)
{
    assert(pos <= _size);
    if (pos + len >= _size || len == npos)
    {
        _str[pos] = '\0';
        _size = pos + 1;
    }
    else
    {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
    }

    return *this;

}

erase函数的作用是删除字符串任意位置开始的n个字符。删除字符前也需要判断pos的合法性,进行删除操作的时候分两种情况:

1、pos位置及其之后的有效字符都需要被删除。

这时我们只需在pos位置放上'\0',然后将对象的size更新即可。

2、pos位置及其之后的有效字符只需删除一部分。

这时我们可以用后方需要保留的有效字符覆盖前方需要删除的有效字符,此时不用在字符串后方加'\0',因为在此之前字符串末尾就有'\0'了。

Element access(元素存取)

operator[]

cpp 复制代码
char& operator[](size_t pos)
{
    assert(pos < _size);
    return *(_str + pos);
}

const char& operator[](size_t pos) const
{
    assert(pos < _size);
    return *(_str + pos);
}

operator[] 返回pos位置的字符,适用于const string类对象或 string类对象调用

Iterators(迭代器)

cpp 复制代码
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;
}

begin获取第一个字符的迭代器

end获取最后一个字符下一个位置的迭代器

String operations

c_str

cpp 复制代码
char* c_str(void)
{
    return _str;
}

c_str 返回C格式字符串

c_str不管字符串的长度,遇到'\0'就终止

find

cpp 复制代码
size_t 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 find(char* str, size_t pos)
{
    assert(pos < _size);
    char* p = strstr(_str + pos, str);
    if (p == nullptr)
    {
        return npos;
    }
    else
    {
        return p - _str;
    } 
}

正向查找第一个匹配的字符。

首先判断所给pos的合法性,然后通过遍历的方式从pos位置开始向后寻找目标字符,若找到,则返回其下标;若没有找到,则返回npos。(npos是string类的一个静态成员变量,其值为整型最大值)

正向查找第一个匹配的字符串。

首先也是先判断所给pos的合法性,然后我们可以通过调用strstr函数进行查找。strstr函数若是找到了目标字符串会返回字符串的起始位置,若是没有找到会返回一个空指针。若是找到了目标字符串,我们可以通过计算目标字符串的起始位置和对象sub字符串的起始位置的差值,进而得到目标字符串起始位置的下标。

Member constants(成员常数)

npos

cpp 复制代码
static const size_t npos;//声明
const size_t string::npos = -1;//定义

Non-member function overloads

operator+

cpp 复制代码
string operator +(const string& s1, char ch)
{
    string ret = s1;
    ret += ch;
    return ret;
}
string operator +(const string& s1, const char* str)
{
    string ret = s1;
    ret += str;
    return ret;
}

operator+ 尽量少用,因为传值返回,导致深拷贝效率低

operator>>

cpp 复制代码
	istream& operator>>(istream& in, string& s)
	{//缓冲区里会放入空格和换行,但是cin/scanf流提取会忽略

		s.clear();
		char ch = in.get();
		char buff[128];
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{//数据满了127个就返回
				buff[127] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}

		if (i != 0)
		{//剩余数据添加
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

operator>> 输入运算符重载

operator<<

cpp 复制代码
	ostream& operator<<(ostream& out, string& s)
	{
		for (auto ch : s)
		{
			cout << ch;
		}
		return out;
	}

operator<< 输出运算符重载

operator<< 不管字符数组中的内容是什么,_size是多少,就输出多少个有效字符

getline

cpp 复制代码
	istream& getline(istream& in, string& s)
	{
		s.clear();
		char ch;
		ch = in.get();
		while (ch != '\n' )
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}

getline 获取一行字符串

relational operators

cpp 复制代码
		bool operator >(const string& s) const
		{
			return strcmp(_str, s._str) > 0;
		}

		bool operator ==(const string& s) const
		{
			return strcmp(_str, s._str) == 0;
		}

		bool operator>=(const string& s) const
		{
			return *this > s || *this == s;
		}

		bool operator<=(const string& s) const
		{
			return !(*this > s);
		}

		bool operator<(const string& s) const
		{
			return !(*this >= s);
		}

		bool operator!=(const string& s) const
		{
			return !(*this == s);
		}

大小比较

相关推荐
FeboReigns3 分钟前
C++简明教程(4)(Hello World)
c语言·c++
FeboReigns4 分钟前
C++简明教程(10)(初识类)
c语言·开发语言·c++
zh路西法13 分钟前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(二):从FSM开始的2D游戏角色操控底层源码编写
c++·游戏·unity·设计模式·状态模式
.Vcoistnt39 分钟前
Codeforces Round 994 (Div. 2)(A-D)
数据结构·c++·算法·贪心算法·动态规划
小k_不小1 小时前
C++面试八股文:指针与引用的区别
c++·面试
沐泽Mu1 小时前
嵌入式学习-QT-Day07
c++·qt·学习·命令模式
ALISHENGYA1 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战训练三)
数据结构·c++·算法·图论
GOATLong2 小时前
c++智能指针
开发语言·c++
F-2H3 小时前
C语言:指针3(函数指针与指针函数)
linux·c语言·开发语言·c++