目录
引言
标准库中的 string 类
功能概述
常见操作示例
自我实现简易 string 类
代码结构概述
1. String11.h 头文件
类的成员变量
迭代器相关
构造函数和析构函数
基本访问和修改方法
赋值运算符重载
内存管理和扩容
以下代码在.cpp文件中解析:
2. String11.cpp 实现文件
字符串修改操作
插入和删除操作
查找和截取操作
比较运算符重载
输入输出运算符重载
3. Teststring.cpp 测试文件
主要功能点
4.特色总结
总结
在 C++ 编程中,string
类是一个非常常用且重要的类,它位于标准库
中,为我们处理字符串提供了便捷的方式。不过,为了更好地理解 string
类的工作原理,我们可以尝试自己实现一个简易的 string
类。本文将详细解析 C++ 中的 string
类,并结合提供的代码文件,介绍如何自我实现一个简易的 string
类。
string
类C++ 标准库中的 string
类是一个模板类 std::basic_string
的特化版本,它封装了字符数组,提供了一系列操作字符串的方法,如字符串的拼接、查找、替换、插入、删除等。使用 string
类,我们可以避免手动管理内存,减少内存泄漏的风险。
#include
#include
int main() {
std::string s1 = "Hello";
std::string s2 = " World";
std::string s3 = s1 + s2; // 字符串拼接
std::cout << s3 << std::endl;
size_t pos = s3.find("World"); // 查找子字符串
if (pos != std::string::npos) {
std::cout << "Found at position: " << pos << std::endl;
}
s3.insert(5, ","); // 插入字符
std::cout << s3 << std::endl;
return 0;
}
string
类提供的代码文件包含了三个主要部分:String11.h
头文件、String11.cpp 实现文件和 Teststring.cpp 测试文件。下面我们将详细分析每个部分。
一些短小且常用的函数都可以直接在头文件内实现,调用时直接内联展开。
private:
char* _str = nullptr;
size_t _size;
size_t _capacity;
static const size_t npos;
_str
:一个指向字符数组的指针,用于存储字符串的实际内容。_size
:表示当前字符串的长度,即字符串中字符的个数(不包括字符串结束符 '\0'
)。_capacity
:表示当前分配给字符串的内存容量,即 _str
指向的字符数组的大小(包括字符串结束符 '\0'
)。npos
:一个静态常量,表示无效的位置,通常用于表示查找操作未找到目标时的返回值。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;
}
iterator
和 const_iterator
,并提供了 begin()
和 end()
方法,使得该 string
类可以像标准容器一样使用迭代器进行遍历。for
循环和其他基于迭代器的算法,增强了代码的通用性和可维护性。string(const char* str = "")
{
_size = strlen(str);
_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;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
_str
中。string
对象作为参数,创建一个新的 string
对象,其内容和容量与原对象相同。_str
指向的动态分配内存,避免内存泄漏,并将 _size
和 _capacity
置为 0。const char* c_str() const
{
return _str;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
const size_t size() const
{
return _size;
}
const 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];
}
c_str()
:返回指向字符串实际内容的 C 风格字符串指针。clear()
:清空字符串,将字符串的第一个字符置为 '\0'
,并将 _size
置为 0。size()
:返回字符串的当前长度。capacity()
:返回字符串当前分配的内存容量。operator[]
:重载了下标运算符,允许通过下标访问字符串中的字符。提供了常量和非常量版本,以支持对常量和非常量对象的访问。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;
}
void reserve(size_t n)
{
cout << "reserve:" << n << endl;
if(n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
n
个字符的内存空间。如果 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);
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);
istream& getline(istream& in, string& s);
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
先给定static变量npos值:
const size_t string::npos = -1;
接下来实现剩下的代码:
void string::push_back(char ch)
{
if(_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
if(len + _size > _capacity)
{
// 大于2倍,需要多少开多少,小于2倍按2倍扩
reserve(len + _size > _capacity * 2 ? len + _size : _capacity * 2);
}
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;
}
push_back(char ch)
:在字符串末尾添加一个字符。如果当前容量不足,则进行扩容。append(const char* str)
:在字符串末尾添加一个 C 风格字符串。如果需要,会进行扩容。operator+=(char ch)
和 operator+=(const char* str)
:重载了 +=
运算符,分别用于添加单个字符和 C 风格字符串,调用 push_back
和 append
方法实现。 void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if(_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
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(len + _size > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : _capacity * 2);
}
size_t end = _size + len;
while(end > pos + len - 1)
{
_str[end] = _str[end - len];
end--;
}
for(size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
void string::erase(size_t pos, size_t len)
{
assert(_size > pos);
if(len >= _size - pos) // 不能写pos + len , 因为len == npos时为INT_MAX越界
{
_size = pos;
_str[pos] = '\0';
}
else
{
size_t res = pos + len;
while(res <= _size)
{
_str[pos++] = _str[res++];
}
_size -= len;
}
}
insert(size_t pos, char ch)
:在指定位置 pos
插入一个字符。如果容量不足,会进行扩容。insert(size_t pos, const char* str)
:在指定位置 pos
插入一个 C 风格字符串。同样,若需要会进行扩容。erase(size_t pos, size_t len = npos)
:从指定位置 pos
开始删除 len
个字符。如果 len
大于等于从 pos
到字符串末尾的字符数,则删除从 pos
开始的所有字符。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* res = strstr(_str + pos, str);
if(res == nullptr)
{
return npos;
}
return res - _str;
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
//len大于字符串长度,更新一下len
if(len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for(size_t i = 0; i < len; i++)
{
sub += _str[i + pos];
}
return sub;
}
find(char ch, size_t pos = 0)
:从指定位置 pos
开始查找字符 ch
,返回其第一次出现的位置。如果未找到,则返回 npos
。find(const char* str, size_t pos = 0)
:从指定位置 pos
开始查找 C 风格字符串 str
,返回其第一次出现的位置。若未找到,返回 npos
。substr(size_t pos = 0, size_t len = npos)
:从指定位置 pos
开始截取长度为 len
的子字符串。如果 len
大于从 pos
到字符串末尾的字符数,则截取到字符串末尾。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 strcmp(s1.c_str(), s2.c_str()) <= 0;
}
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);
}
string
对象的大小。使用 strcmp
函数进行比较。string
对象可以像标准类型一样进行比较,方便进行排序和条件判断。ostream& operator<<(ostream& out, const string& s)
{
for(auto ch : s)
{
out << ch;
}
return out;
}
// istream& operator>>(istream& in, string& s)
// {
// s.clear();
// char ch;
// ch = in.get();
// while(ch != ' ' && ch != '\n')
// {
// s += ch;
// ch = in.get();
// }
// return in;
// }
优化istream
istream& operator>>(istream& in, string& s)
{
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
ch = in.get();
while(ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if(i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if(i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
istream& getline(istream& in, string& s)
{
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
ch = in.get();
while(ch != '\n')
{
buff[i++] = ch;
if(i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if(i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
getline(istream& in, string& s)
:从输入流中读取一行字符串,直到遇到换行符 '\n'
,并将其存储到 s
中。operator<<(ostream& out, const string& s)
:重载了输出运算符,将 string
对象的内容输出到输出流中。operator>>(istream& in, string& s)
:重载了输入运算符,从输入流中读取一个单词(以空格或换行符分隔),并将其存储到 s
中。#include "String11.h"
namespace lmr
{
void string_test()
{
string s("haha");
cout << s.c_str() << endl;
s += "sss";
s += 's';
cout << s.c_str() << endl;
cout << s.size() << " " << s.capacity() << endl;
s.insert(2, "sss");
cout << s.c_str() << endl;
s.insert(5, '*');
cout << s.c_str() << endl;
s.erase(2);
cout << s.c_str() << endl;
string s1("test.cpp");
size_t pos = s1.find('.');
string buffix = s1.substr(pos);
cout << buffix.c_str() << endl;
}
void string_test1()
{
string s("Hello World");
string s2(s);
bool sign = s2 == s;
if(sign == true)
{
// cin >> s;
getline(cin, s);
cout << s << endl;
string s3;
cin >> s3;
cout << s3 << endl;
}
cout << endl;
}
}
int main()
{
lmr::string_test1();
return 0;
}
new
和 delete
运算符动态分配和释放内存,避免了固定大小数组的限制。std::string
类似的接口,如迭代器、构造函数、赋值运算符等,方便用户使用。reserve
方法进行内存预分配,减少了频繁的内存重新分配,提高了性能。assert
进行边界检查,确保操作的安全性。但在实际应用中,建议使用更健壮的异常处理机制。通过自我实现一个简易的 string
类,我们可以更深入地理解 C++ 中 string
类的工作原理和内存管理机制。同时,我们也学习了如何使用类和对象的概念,以及如何重载运算符和使用迭代器。在实际编程中,建议使用标准库中的 string
类,因为它经过了严格的测试和优化,具有更高的性能和可靠性。