#pragma once
#include
namespace hqj
{
class string
{
public:
friend ostream& operator<<(ostream& _cout, const hqj::string& s);
friend istream& operator>>(istream& _cin, hqj::string& s);
string(const char* str = "")//构造函数
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& s)//拷贝构造函数
:_capacity(0)
, _size(0)
, _str(nullptr)
{
string tmp(s._str);//这里不能传s,原因是要调用构造函数而不是拷贝构造函数
swap(tmp);
}
~string()//析构函数
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char c)//尾插一个字符
{
if (_size >= _capacity)//不要去改变_capacity
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
//+=运算符重载
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
return _size == 0;
}
void clear()//清空string对象
{
_str[0] = '\0';
_size = 0;
}
void swap(string& s)//string的交换函数,是现代写法的基础(包工头写法
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const size_t size()const//string有效数据个数
{
return _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];
}
//迭代器部分
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator begin() const
{
return _str;
}
const iterator end() const
{
return _str + _size;
}
string& operator=(const string& s)
{
string tmp(s);
swap(tmp);
return *this;
}
//提供私有成员_str
const char* c_str()const
{
return _str;
}
// 返回c在String中第一次出现的位置
size_t find(char c, size_t pos = 0) const
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
// 返回子串s在String中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const
{
assert(pos < _size);
char* tmp = strstr(_str + pos, s);
if (tmp == nullptr)
{
return npos;
}
return find(*tmp);
}
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c)
{
assert(pos <= _size);//pos=size相当于尾插
if (_size >= _capacity)//不要去改变_capacity
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end != pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size+len >= _capacity)//不要去改变_capacity
{
reserve(_size+len);
}
size_t end_new = _capacity;
size_t end_old = _size;
while (end_old > pos)
{
_str[end_new] = _str[end_old];
end_old--;
end_new--;
}
_str[end_new] = _str[pos];
for (int i = 0; i < len; i++)
{
_str[pos++] = str[i];
}
_size+=len;
return *this;
}
// 删除pos位置上的元素,并返回该元素的下一个位置
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
++begin;
}
_size -= len;
}
}
void resize(size_t n, char c = '\0')
{
size_t len = strlen(_str);
if (n > _capacity)
{
reserve(n);
for (int i = len + 1; i < _capacity; i++)
{
(*this) += c;
}
}
else
{
_str[n] = '\0';
}
_size = n;
}
bool operator<(const string& s)
{
if (strcmp(_str, s._str) < 0)
{
return true;
}
return false;
}
bool operator<=(const string& s)
{
if ((*this) < s || (*this) == s)
{
return true;
}
return false;
}
bool operator>(const string& s)
{
return !(*this <= s);
}
bool operator>=(const string& s)
{
return !(*this < s);
}
bool operator==(const string& s)
{
if (strcmp(_str, s._str) == 0)
{
return true;
}
return false;
}
bool operator!=(const string& s)
{
return!((*this) == s);
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
const static size_t npos;
};
const size_t string::npos = -1;
//流插入流提取的运算符重载
ostream& operator<<(ostream& out, const hqj::string& s)
{
for (int i = 0; i < s.size(); i++)
{
out << s._str[i];
}
return out;
}
/* istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[129];
size_t i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 128)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}*/
//自己的版本
istream& operator>>(istream& in, string& s)
{
char ch;
ch = in.get();
while (ch != '\n' && ch != '\0')
{
s += ch;
ch = in.get();
}
return in;
}
_str 是一个指向字符数组(C-style string)的指针,用于存储字符串的字符序列。
_size 是一个用于记录字符串当前长度的变量,即字符串中实际包含的字符数量。
_capacity 是一个用于记录字符串当前内存容量的变量,即分配给字符串的字符数组的大小。
这样的设计是为了支持动态字符串的存储和操作。当字符串长度超过当前内存容量时,可以通过重新分配更大的内存空间来保证字符串的存储能力。而 _str 指针则可以指向新分配的内存空间,以容纳更多的字符序列。
private:
char* _str;
size_t _size;
size_t _capacity;
构造函数中的语法 const char* str = "" 定义了一个默认参数。如果在创建对象时不传入参数,那么默认会将一个空字符串赋值给 str。如果传入参数,则使用传入的字符串来初始化新对象。
在构造函数中,首先使用 C 标准库函数 strlen 来计算 str 字符串的长度,并将其保存到成员变量 _size 中。然后,根据字符串长度计算出需要分配的存储空间,并将其保存到成员变量 _capacity 中。
接下来使用 new 运算符在堆上动态分配内存,分配的空间大小为 _capacity + 1,因为字符串末尾要以字符 '\0' 结束。然后使用 C 标准库函数 strcpy 将 str 字符串的内容复制到新分配的内存空间中。
最后,将指针变量 _str 指向新分配的内存空间,完成 C++ 字符串的构造过程。
string(const char* str = "")//构造函数
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
在拷贝构造函数中,首先初始化成员变量 _capacity、_size 和 _str,它们分别表示字符串的容量、大小和存储内容的指针。
然后,在构造函数内部创建了一个名为 tmp 的临时字符串对象,并通过传入的参数 s 的 _str 成员变量来初始化它。这里不能直接传入 s,而是使用 s._str,是因为直接传入 s 会导致递归调用拷贝构造函数,而我们需要调用的是构造函数。
接下来,通过调用 swap 函数,将临时字符串对象 tmp 和当前对象的内容进行交换。这一步中,实际上是交换了 _str 指针指向的内存空间,从而实现了将原始字符串对象的内容复制给新对象的目的。
最终,根据交换后的内容,临时字符串对象 tmp 会自动被销毁,同时也会自动释放内存空间。
string(const string& s)//拷贝构造函数
:_capacity(0)
, _size(0)
, _str(nullptr)
{
string tmp(s._str);//这里不能传s,原因是要调用构造函数而不是拷贝构造函数
swap(tmp);
}
在赋值运算符重载函数中,首先创建了一个名为 tmp 的临时字符串对象,并通过传入的参数 s 调用拷贝构造函数初始化它。
接下来,调用 swap 函数,将临时字符串对象 tmp 和当前字符串对象的内容进行交换。这一步中,实际上是交换了 _str 指针指向的内存空间,从而实现了将原始字符串对象的内容复制给当前对象的目的。
最后,返回当前字符串对象的引用,以支持链式赋值操作。
string& operator=(const string& s)
{
string tmp(s);
swap(tmp);
return *this;
}
在析构函数中,首先使用 delete[] 关键字释放之前在堆上动态分配的 _str 成员变量指向的内存空间。这是为了避免发生内存泄漏,释放资源以确保不再使用该内存空间。
接下来,将指针 _str 赋值为 nullptr,以防止在使用已释放的内存时发生未定义的行为。
最后,将成员变量 _size 和 _capacity 的值都设为 0,以标记字符串的大小和容量为 0,表示该字符串对象已经被析构销毁。
~string()//析构函数
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
resize 函数接受两个参数:新的字符串大小 n 和要填充的字符 c(可选,默认为结束符 '\0')。该函数首先使用 strlen 函数获取当前字符串的长度 len。然后根据不同情况进行处理:
如果新的字符串大小 n 大于字符串的容量 _capacity,表示需要增加字符串的容量以容纳更多的字符。这里通过调用 reserve 函数来增加字符串的容量,保证足够存放 n 个字符,并且在剩余空间中填充字符 c。循环从 len + 1 开始,一直到容量 _capacity,将字符 c 追加到字符串中。
如果新的字符串大小 n 不大于字符串的容量 _capacity,表示不需要增加字符串的容量。此时,如果 n 小于当前字符串的长度 len,需要将字符串截断到指定大小,即将第 n 个字符设置为结束符 '\0'。如果 n 大于等于当前字符串的长度 len,则不需要进行额外操作。
最后,更新字符串的大小 _size 为新的字符串大小 n。
void resize(size_t n, char c = '\0')
{
size_t len = strlen(_str);
if (n > _capacity)
{
reserve(n);
for (int i = len + 1; i < _capacity; i++)
{
(*this) += c;
}
}
else
{
_str[n] = '\0';
}
_size = n;
}
这是一个 C++ 字符串类中的 reserve 函数,用于预留足够的容量来存储字符串。
在 reserve 函数中,首先判断传入的参数 n 是否大于当前字符串的容量 _capacity。如果大于,则需要进行扩容操作。
创建了一个名为 tmp 的字符数组指针,通过使用 new 运算符在堆上分配了 n+1 个字符的内存空间。这里 n+1 是为了包含字符串的结尾字符 \0。
然后,使用 strcpy 函数将原始字符串 _str 的内容复制到新分配的内存空间 tmp 中。
接下来,使用 delete[] 运算符释放原始字符串 _str 指向的内存空间,以防止出现内存泄漏。
将指针 _str 更新为指向新分配的内存空间 tmp,使其指向新的字符串内容。
最后,将字符串的容量 _capacity 更新为 n,表示字符串已经具备了预留的容量。
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
在 push_back 函数中,首先判断当前字符串的大小 _size 是否已经达到了容量 _capacity,如果已经达到,则通过调用字符串类内部的 reserve 函数进行扩容,以腾出更多的空间来存储新的字符。
当确保有足够的容量后,将字符 c 插入到字符串的末尾 _str[_size],并将 _size 的值加一。最后,在末尾添加一个空字符 \0,保证字符串的完整性。
void push_back(char c)//尾插一个字符
{
if (_size >= _capacity)//不要去改变_capacity
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
在 append 函数中,首先获取待追加字符数组 str 的长度 len,使用 strlen 函数计算得到。
接着,判断当前字符串的大小 _size 加上待追加字符数组的长度 len 是否超过了容量 _capacity。如果超过了容量,则通过调用字符串类内部的 reserve 函数进行扩容,以腾出足够的空间来存储新的字符。
当确保有足够的容量后,使用 strcpy 函数将字符数组 str 的内容复制到字符串的末尾 _str + _size,即从 _str 的第 _size 个位置开始复制。
最后,将 _size 的值增加 len,表示字符串的大小已经扩展到了追加后的长度。
需要注意的是,为了保证字符串的完整性,最后需要手动添加一个空字符 \0,以结束字符串。
与 push_back 函数相比,append 函数可以一次性追加多个字符,效率较高。适用于需要一次性追加多个字符或者字符数组的情况。而 push_back 则适用于单个字符的追加。
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
在函数体内,通过使用 assert 宏来判断指定的下标是否越界,如果越界则会触发一个断言错误并终止程序的执行,从而避免了对无效位置进行访问。
然后,根据运算符的重载类型(即 const 和非 const),返回私有成员变量 _str 中对应下标位置的字符的引用或常量引用,从而实现对字符串中字符的读写操作。
使用 [] 运算符可以方便地访问字符串对象中的任意字符,类似于数组的使用方式。
//[]运算符重载
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
第一个重载函数 operator+=(char c) 接受一个字符 c 作为参数。在函数体内,调用了字符串类的 push_back 函数,将字符 c 追加到字符串的末尾。然后,返回当前字符串对象的引用 *this。
第二个重载函数 operator+=(const char* str) 接受一个字符数组 str 作为参数。在函数体内,调用了字符串类的 append 函数,将字符数组 str 追加到字符串的末尾。然后,返回当前字符串对象的引用 *this。
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
这是 C++ 字符串类中的 capacity 函数,用于获取字符串对象的容量大小。
在函数体内,直接返回私有成员变量 _capacity 的值。该变量表示字符串对象已经预分配的空间大小,即在不扩容的情况下,该字符串对象最多可以存储多少个字符。
使用 capacity 函数可以帮助我们了解当前字符串对象的容量大小,以便更好地管理内存分配和释放,避免浪费或者不足的情况发生。
size_t capacity()const
{
return _capacity;
}
这是 C++ 字符串类中的 empty 函数,用于判断字符串对象是否为空。
在函数体内,通过比较私有成员变量 _size 是否等于 0,来判断字符串是否为空。如果 _size 等于 0,表示字符串中没有任何字符,即为空;否则,表示字符串中至少包含一个字符,即不为空。
bool empty()const
{
return _size == 0;
}
在函数体内,首先将字符串对象 _str 的第一个字符 _str[0] 设置为 '\0',即空字符,表示字符串为空。
然后,将私有成员变量 _size 设置为 0,表示字符串中没有任何字符。
通过调用 clear 函数可以将字符串对象清空,使其不包含任何字符。
void clear()//清空string对象
{
_str[0] = '\0';
_size = 0;
}
在函数体内,通过使用 std::swap 函数来交换私有成员变量 _str、_size 和 _capacity 与另一个字符串对象 s 的对应成员变量的值。
std::swap 是 C++ 标准库中的一个通用函数模板,用于交换两个对象的值。它会根据对象的类型自动选择最高效的交换方式,从而避免了手动实现交换逻辑的繁琐和潜在的错误。
通过调用 swap 函数,可以快速且安全地交换两个字符串对象的内容。
void swap(string& s)//string的交换函数,是现代写法的基础(包工头写法
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
在函数体内,直接返回私有成员变量 _size 的值,表示字符串中包含 _size 个字符,即为有效数据的个数。
使用 size 函数可以方便地获取字符串中字符的数量,可以用于数组越界和其他处理中。
const size_t size()const//string有效数据个数
{
return _size;
}
在代码中,使用 typedef 关键字定义了一个名为 iterator 的别名,它表示指向字符的指针类型。
然后,重载了 begin() 和 end() 函数,分别用于返回字符串对象的起始位置和结束位置的迭代器。
对于非常量对象,begin() 函数直接返回私有成员变量 _str,即指向字符串开头的指针。而 end() 函数返回 _str + _size,即指向字符串结尾下一个位置的指针。
对于常量对象,也重载了 begin() 和 end() 函数,返回相同的迭代器,但是声明为常量迭代器,表示在常量对象上不允许修改字符值。
通过返回迭代器,可以方便地遍历字符串对象中的字符,并进行各种操作。
//迭代器部分
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator begin() const
{
return _str;
}
const iterator end() const
{
return _str + _size;
}
c_str() 函数通常用于将字符串对象转换为 C 风格的字符串,以便与需要接受 C 风格字符串作为参数的函数进行交互。
//提供私有成员_str
const char* c_str()const
{
return _str;
}
函数的参数包括一个指向字符数组的指针 s 和一个可选的起始搜索位置 pos(默认为0)。
首先,使用 assert 断言确保 pos 的值小于字符串的长度 _size,以避免越界访问。
接下来,使用标准库函数 strstr 在字符串 _str 的 pos 位置之后搜索指定的子串 s。如果找到了匹配的子串,则 strstr 函数返回指向该子串的指针;如果没有找到,则返回空指针。
如果 tmp 是空指针(即找不到匹配的子串),则表示查找失败,函数返回特殊值 npos(通常定义为 size_t(-1),表示无效的位置)。
如果找到了匹配的子串,函数会继续调用 find 函数,传入子串的第一个字符 *tmp,以在整个字符串中找到该字符第一次出现的位置,并将其作为结果返回。
需要注意的是,find 函数返回的是子串第一次出现的位置,而不是子串本身。如果需要获取子串本身,可以使用字符串类的 substr 函数进行切片操作。
此外,该代码中的 find 函数是一个 const 成员函数,表示它不会修改字符串对象的内容。
size_t find(const char* s, size_t pos = 0) const
{
assert(pos < _size);
char* tmp = strstr(_str + pos, s);
if (tmp == nullptr)
{
return npos;
}
return find(*tmp);
}
第一个 insert 函数接受两个参数:一个是要插入的字符 c,另一个是插入位置 pos。该函数首先使用断言 assert 确保插入位置不超出字符串的范围。然后,如果字符串当前的长度 _size 已经达到了其容量 _capacity,则通过调用 reserve 函数增加字符串的空间以容纳新字符。接下来,将 pos 之后的所有字符依次向后挪一位,为新字符腾出空间。最后,将新字符插入到指定位置,更新字符串的长度并返回字符串对象本身的引用。
第二个 insert 函数与第一个函数类似,但它接受的参数是一个字符指针 str,表示要插入的字符串。该函数的实现类似于第一个函数,只不过需要多次插入字符,并且需要移动更多的字符位置。在插入前,该函数还会先计算待插入字符串的长度 len。最后,更新字符串的长度并返回字符串对象本身的引用。
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c)
{
assert(pos <= _size);//pos=size相当于尾插
if (_size >= _capacity)//不要去改变_capacity
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end != pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size+len >= _capacity)//不要去改变_capacity
{
reserve(_size+len);
}
size_t end_new = _capacity;
size_t end_old = _size;
while (end_old > pos)
{
_str[end_new] = _str[end_old];
end_old--;
end_new--;
}
_str[end_new] = _str[pos];
for (int i = 0; i < len; i++)
{
_str[pos++] = str[i];
}
_size+=len;
return *this;
}
erase 函数接受两个参数:要删除的元素的起始位置 pos,和要删除的元素的长度 len(可选,默认为 npos)。该函数首先使用断言 assert 确保删除位置不超出字符串的范围。然后根据 len 的值进行不同的处理:
如果 len 的值为默认值 npos 或者 pos + len 大于等于字符串的长度 _size,表示要删除 pos 位置之后的所有元素,则将 pos 位置上的元素设置为结束符 '\0',并更新字符串的长度 _size 为 pos。
否则,需要删除指定位置 pos 后面的 len 个元素。首先定义一个变量 begin,表示要删除的元素之后的第一个元素的位置。然后通过循环将 begin 位置及其后面的所有元素向前挪动 len 个位置,以覆盖要删除的元素。最后,更新字符串的长度 _size 减去 len。
这样,删除成功后,字符串中就会被删除的元素占据的位置填补上相应的元素,并且字符串的长度也会相应调整。
// 删除pos位置上的元素,并返回该元素的下一个位置
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
++begin;
}
_size -= len;
}
}
首先,这些比较运算符都接受一个字符串对象 s 作为参数,并以引用形式传递。接下来,按照运算符的定义进行逐个实现:
小于运算符(<)通过调用 strcmp 函数比较当前字符串 _str 和参数字符串 s._str 的大小。如果 _str 小于 s._str,则返回 true;否则返回 false。
小于等于运算符(<=)通过在小于运算符之后添加一个等于运算符组成。如果当前字符串小于 s 或者当前字符串等于 s,则返回 true;否则返回 false。
大于运算符(>)通过在小于等于运算符之前添加逻辑非运算符(!)来实现。如果当前字符串不小于等于 s,即大于 s,则返回 true;否则返回 false。
大于等于运算符(>=)通过在大于运算符之前添加逻辑非运算符来实现。如果当前字符串不大于 s,即小于 s,则返回 true;否则返回 false。
等于运算符(==)通过调用 strcmp 函数比较当前字符串和参数字符串的大小。如果相等,则返回 true;否则返回 false。
不等于运算符(!=)通过在等于运算符之前添加逻辑非运算符来实现。如果当前字符串不等于 s,则返回 true;否则返回 false。
这样,就可以使用比较运算符对字符串对象进行大小比较操作。
bool operator<(const string& s)
{
if (strcmp(_str, s._str) < 0)
{
return true;
}
return false;
}
bool operator<=(const string& s)
{
if ((*this) < s || (*this) == s)
{
return true;
}
return false;
}
bool operator>(const string& s)
{
return !(*this <= s);
}
bool operator>=(const string& s)
{
return !(*this < s);
}
bool operator==(const string& s)
{
if (strcmp(_str, s._str) == 0)
{
return true;
}
return false;
}
bool operator!=(const string& s)
{
return!((*this) == s);
}
首先,流插入运算符重载函数接受一个输出流对象 out 和一个字符串对象 s 作为参数,并以引用形式传递。实现过程中遍历字符串 _str 的每个字符并输出到 out 流中,最后返回输出流对象 out。
比较而言,流提取运算符重载函数接受一个输入流对象 in 和一个自定义的字符串对象 s 作为参数。实现过程中逐个读取输入流中的字符,如果字符不是换行符或者字符串终止符,则将其添加到字符串对象 s 中,最后返回输入流对象 in。
//流插入流提取的运算符重载
ostream& operator<<(ostream& out, const hqj::string& s)
{
for (int i = 0; i < s.size(); i++)
{
out << s._str[i];
}
return out;
}
//自己的版本
istream& operator>>(istream& in, string& s)
{
char ch;
ch = in.get();
while (ch != '\n' && ch != '\0')
{
s += ch;
ch = in.get();
}
return in;
}