【C++】C++中的String类详解及模拟实现示例
文章目录
string类简介
string类简介在C++编程中,字符串是一种非常常见的数据类型,用于存储文本信息。C++标准库提供了string类来处理字符串,它提供了许多方法和功能,使得字符串操作更加方便和高效。
string类的基本用法
要使用C++中的string类,需要包含头文件。下面是一个简单的示例,演示了如何创建和操作string对象:
#include <iostream>
#include <string>
int main() {
// 创建一个空的string对象
std::string str;
// 初始化string对象
std::string greeting = "Hello, world!";
// 获取字符串长度
int len = greeting.length();
// 字符串拼接
std::string message = greeting + " Have a nice day!";
// 输出字符串
std::cout << message << std::endl;
return 0;
}
string类的常用方法
string类提供了丰富的方法来操作字符串,包括查找子串、比较字符串、截取子串等功能。下面是一些常用的方法示例:
std::string str = "This is a C++ string";
// 查找子串
size_t found = str.find("C++");
// 比较字符串
if (str.compare("Another string") == 0) {
std::cout << "Strings are equal" << std::endl;
}
// 截取子串
std::string sub = str.substr(8, 2); // 从索引8开始,截取2个字符
string类的优势
与C风格字符串相比,C++的string类具有许多优势。它自动管理内存,避免了内存泄漏和越界访问的问题;提供了丰富的方法和操作符重载,使得字符串操作更加方便和直观;而且在C++11标准中,string类还支持移动语义,进一步提高了性能。
总的来说,C++中的string类是一个强大而灵活的工具,用于处理字符串数据。无论是简单的字符串拼接,还是复杂的字符串处理,string类都能够满足需求并提供高效的解决方案。
string类的模拟实现
除了对string类的简单使用外,为了更好的了解string类的实现原理,我们可以试着实现string类的部分功能,以便我们可以更加深入了string的原理,以便可以更好的使用它。
存储结构
string本质上是一个char类型的顺序表,实现起来与顺序表相似,所以结构上也相似。
using namespace std;
#include <iostream>
#include <assert.h>
// 将自定义字符串类封装在命名空间hd内
namespace hd {
// 定义string类
class string {
public:
// 成员常量
const static size_t npos = -1; // 表示未找到的位置,它是无符号整型的最大值,
//特殊用法,这个值给初始化列表,但是它又不走初始化列表
//const static double x = 1.1; 这种特使用法只支持整数,像浮点数这些类型并不支持
//这种特殊处理还有一个用法:
//const static int N = 10;
//int a[N];
private:
// 私有数据成员
size_t _size = 0; // 字符串大小
size_t _capacity = 0; // 字符串容量
char* _str = nullptr; // 指向动态分配的数组
};
}
头文件string.h
// string.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1 // 禁用安全警告,与Visual Studio相关
using namespace std;
#include <iostream>
#include <assert.h>
// 将自定义字符串类封装在命名空间hd内
namespace hd {
// 定义string类
class string {
public:
// 构造函数、复制构造函数、赋值运算符和析构函数
string(const char* str = ""); // 默认构造函数,可接受C风格字符串
string(const string& s); // 复制构造函数
string(const char* s, size_t n); // 从C风格字符串构造,并指定长度
string& operator= (string s); // 赋值运算符,使用拷贝交换技术
~string(); // 析构函数,释放内存
// 迭代器相关
typedef char* iterator; // 迭代器类型定义
typedef const char* const_iterator; // 常量迭代器类型定义
iterator begin() const { return _str; } // 返回指向第一个字符的迭代器
iterator end() const { return _str + _size; } // 返回指向最后一个字符之后的迭代器
// 容量相关函数
size_t size() const { return _size; } // 返回字符串的大小
void resize(size_t n, char c = '\0'); // 改变字符串的大小,如果变大则填充c字符
void reserve(size_t n = 0); // 预留空间,避免频繁分配内存
void clear();// 清空字符串
size_t capacity() const { return _capacity; } // 返回字符串对象的容量
bool empty() const { return _size == 0; } // 判断字符串对象是否为空
// 元素访问
char& operator[] (size_t pos) { assert(pos <= _size); return _str[pos]; } // 下标运算符
const char& operator[] (size_t pos) const { assert(pos <= _size); return _str[pos]; } // 下标运算符常量版本
char& at(size_t pos) { assert(pos <= _size); return _str[pos]; } // 安全访问元素
const char& at(size_t pos) const { assert(pos <= _size); return _str[pos]; } // 安全访问元素常量版本
// 修改器
string& operator+= (const string& s) { return append(s); } // 重载+=运算符,用于追加字符串
string& operator+= (const char* str) { return append(str); } // 重载+=运算符,用于追加C风格字符串
string& operator+= (char c) { return append(1, c); } // 重载+=运算符,用于追加字符
string& append(const string& s); // 追加字符串
string& append(const char* str); // 追加C风格字符串
string& append(size_t n, char c); // 追加n个字符c
string& append(const string& s, size_t subpos, size_t sublen); // 追加字符串的子串
void push_back(char c); // 在字符串末尾添加一个字符
string& insert(size_t pos, const string& str); // 在指定位置插入字符串
string& insert(size_t pos, const char* s); // 在指定位置插入C风格字符串
string& insert(size_t pos, size_t n, char c); // 在指定位置插入n个字符c
string& erase(size_t pos = 0, size_t len = npos); // 删除从pos开始的len个字符
void swap(string& s); // 交换两个字符串的内容
void pop_back() { erase(_size-1, 1); } // 删除最后一个字符
// 字符串操作
const char* c_str() const { return _str; } // 返回C风格字符串
size_t copy(char* s, size_t len, size_t pos = 0); // 复制字符串到s
size_t find(const string& str, size_t pos = 0) const; // 查找子字符串
size_t find(const char* s, size_t pos = 0) const; // 查找C风格子字符串
size_t find(char c, size_t pos = 0) const; // 查找字符
string substr(size_t pos = 0, size_t len = npos) const; // 返回子串
//+号运算符重载
friend string operator+(const string& lhs, const string& rhs);
// 成员常量
const static size_t npos = -1; // 表示未找到的位置
private:
// 私有数据成员
size_t _size = 0; // 字符串大小
size_t _capacity = 0; // 字符串容量
char* _str = nullptr; // 指向动态分配的数组
};
// 非成员函数重载
istream& operator>> (istream& is, string& str); // 输入运算符重载
ostream& operator<< (ostream& os, const string& str); // 输出运算符重载
istream& getline(istream& is, string& str, char delim); // 从输入流读取一行
istream& getline(istream& is, string& str); // 从输入流读取一行,直到换行符
}
};
// 非成员函数重载
istream& operator>> (istream& is, string& str); // 输入运算符重载
ostream& operator<< (ostream& os, const string& str); // 输出运算符重载
istream& getline(istream& is, string& str, char delim); // 从输入流读取一行
istream& getline(istream& is, string& str); // 从输入流读取一行,直到换行符
}
源文件string.cpp
#include "string.h"
namespace hd
{
// 构造函数:根据传入的C风格字符串构造字符串对象
string::string(const char* str)
{
_size = strlen(str); // 计算字符串长度
_capacity = _size; // 初始容量为字符串长度
_str = new char[_capacity + 1]; // 分配内存空间,+1是为了存放字符串结束符'\0'
strcpy(_str, str); // 将传入的字符串拷贝到_str中
}
// 拷贝构造函数:使用另一个字符串对象构造当前字符串对象
string::string(const string& s)
{
string tmp(s.c_str()); // 利用传入的字符串对象构造临时字符串对象
swap(tmp); // 交换当前字符串对象和临时字符串对象的成员变量值
}
// 构造函数:根据传入的C风格字符串和长度构造字符串对象
string::string(const char* s, size_t n) {
string tmp(s); // 利用传入的C风格字符串构造临时字符串对象
tmp[n] = '\0'; // 限制临时字符串对象的长度为n
tmp._size = n; // 更新临时字符串对象的大小为n
swap(tmp); // 交换当前字符串对象和临时字符串对象的成员变量值
}
// 赋值运算符重载:将字符串对象s赋值给当前字符串对象
string& string::operator=(string s)
{
swap(s); // 交换当前字符串对象和s的成员变量值
return *this; // 返回当前字符串对象
}
// 析构函数:释放字符串对象所占用的内存空间
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
// 修改字符串大小,使其包含n个字符,如果n小于当前大小,则截断字符串,如果n大于当前大小,则在末尾填充字符c
void string::resize(size_t n, char c)
{
if (this->size() > n) {
_str[n] = '\0'; // 截断字符串
_size = n;
}
else {
reserve(n); // 扩容字符串,使其能够容纳n个字符
append(n - this->size(), c); // 在末尾填充字符c,直到达到长度n
}
}
// 扩展字符串容量,以容纳至少n个字符
void string::reserve(size_t n)
{
if (n > _capacity) {
char* tmp = new char[n + 1]; // 分配新的内存空间,+1是为了存放字符串结束符'\0'
strcpy(tmp, _str); // 将原字符串拷贝到新的内存空间中
delete[] _str; // 释放原有内存空间
_str = tmp; // 更新字符串指针指向新的内存空间
_capacity = n; // 更新字符串容量
}
}
// 在字符串末尾追加另一个字符串对象
string& string::append(const string& s)
{
return append(s.c_str());
}
// 在字符串末尾追加C风格字符串
string& string::append(const char* str)
{
size_t len = strlen(str); // 计算要追加的字符串长度
if (_size + len > _capacity) { // 如果追加后的长度大于当前容量,则扩容
reserve(_size + len);
}
strcpy(_str + _size, str); // 将要追加的字符串拷贝到当前字符串的末尾
_size += len; // 更新字符串大小
return *this;
}
// 在字符串末尾追加n个字符c
string& string::append(size_t n, char c)
{
for (size_t i = 0; i < n; ++i) {
this->push_back(c);
}
return *this;
}
// 在字符串末尾追加另一个字符串对象的子串,子串起始位置为subpos,长度为sublen
string& string::append(const string& s, size_t subpos, size_t sublen)
{
size_t len = s.size(); // 获取字符串的大小
if (_size + len > _capacity) { // 如果追加后的长度大于当前容量,则扩容
reserve(_size + len);
}
const char* cp_str = s.c_str() + subpos; // 根据起始位置计算子串的指针
strncpy(_str + _size, cp_str, sublen); // 将子串拷贝到当前字符串的末尾
_size += sublen; // 更新字符串大小
_str[_size] = '\0'; // 添加字符串结束符
return *this;
}
// 在字符串末尾添加一个字符c
void string::push_back(char c)
{
if (_size == _capacity) { // 如果字符串已满,则进行扩容
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = c; // 在末尾添加字符c
++_size; // 更新字符串大小
_str[_size] = '\0'; // 添加字符串结束符
}
// 在字符串的指定位置插入另一个字符串对象
string& string::insert(size_t pos, const string& str)
{
assert(pos <= _size); // 确保插入位置在合法范围内
return this->insert(pos, str.c_str());
}
// 在字符串的指定位置插入C风格字符串
string& string::insert(size_t pos, const char* s)
{
assert(pos <= _size); // 确保插入位置在合法范围内
size_t len = strlen(s); // 计算要插入的字符串长度
if (_size + len > _capacity) { // 如果插入后的长度大于当前容量,则扩容
reserve(_size + len);
}
memmove(_str + pos + len, _str + pos, _size - pos); // 将插入位置及之后的字符后移
memcpy(_str + pos, s, len); // 将要插入的字符串拷贝到指定位置
_size += len; // 更新字符串大小
_str[_size] = '\0'; // 添加字符串结束符
return *this;
}
// 在字符串的指定位置插入n个字符c
string& string::insert(size_t pos, size_t n, char c)
{
assert(pos <= _size); // 确保插入位置在合法范围内
string str;
while (n--) {
str += c;
}
return this->insert(pos, str);
}
// 删除字符串中从指定位置开始的指定长度的字符
string& string::erase(size_t pos, size_t len)
{
if (len == npos || pos + len >= _size) { // 如果要删除的长度超过了字符串的长度,则截断字符串
_str[pos] = '\0';
_size = pos;
return *this;
}
else {
strcpy(_str + pos, _str + pos + len); // 将要删除的部分后面的字符前移
_size -= len; // 更新字符串大小
}
return *this;
}
// 交换两个字符串对象的成员变量值
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// 将指定长度的字符从字符串中复制到字符数组s中,返回实际复制的字符数
size_t string::copy(char* s, size_t len, size_t pos) {
assert(pos <= _size); // 确保复制位置在合法范围内
size_t actual_len = min(len, _size - pos); // 计算实际要复制的字符数
strncpy(s, _str + pos, actual_len); // 将要复制的字符拷贝到数组中
return actual_len;
}
// 查找字符串中指定子串的起始位置,从指定的位置pos开始查找
size_t string::find(const string& str, size_t pos) const
{
assert(pos <= _size); // 确保查找位置在合法范围内
return find(str.c_str(), pos);
}
// 查找字符串中指定C风格子串的起始位置,从指定的位置pos开始查找
size_t string::find(const char* s, size_t pos) const
{
assert(pos <= _size); // 确保查找位置在合法范围内
char* ptr = strstr(_str + pos, s); // 在字符串中查找子串
if (ptr == nullptr) {
return npos; // 如果找不到子串,则返回特殊值 npos,表示找不到
}
else {
return ptr - _str; // 如果找到子串,则返回子串在字符串中的起始位置
}
}
// 返回字符串对象的子串,从指定位置pos开始,长度为len
string string::substr(size_t pos, size_t len) const
{
assert(pos <= _size); // 确保起始位置在合法范围内
size_t actual_len = min(len, _size - pos); // 计算实际要复制的字符数
return string(_str + pos, actual_len); // 返回从起始位置pos开始,长度为actual_len的子串
}
// 清空字符串对象,将大小和容量都重置为0
void string::clear()
{
delete[] _str; // 释放之前分配的内存空间
_str = new char[1]; // 分配新的内存空间,大小为1,用于存放字符串结束符'\0'
_str[0] = '\0'; // 设置字符串结束符
_size = 0; // 更新字符串大小
_capacity = 0; // 更新字符串容量
}
string operator+(const string& lhs, const string& rhs)
{
std::size_t newLen = lhs._size + rhs._size; // 计算拼接后字符串的长度
char* newData = new char[newLen + 1]; // 分配足够的内存来存储新的字符串,+1 用于存储字符串末尾的 '\0'
strcpy(newData, lhs._str); // 将左操作数字符串复制到新字符串的开头位置
strcpy(newData + lhs._size, rhs._str); // 将右操作数字符串复制到新字符串的左操作数字符串之后
return string(newData); // 返回一个包含新字符串的 string 对象
}
istream& operator>> (istream& is, string& str)
{
return getline(is, str, ' '); // 使用 getline 函数从 istream 中读取字符串,以空格作为分隔符
}
ostream& operator<< (ostream& os, const string& str)
{
for (auto& ch : str)
{
os << ch; // 逐个字符地写入 ostream
}
return os;
}
istream& getline(istream& is, string& str, char delim)
{
str.clear(); // 清空待填充的字符串
char buffer[128]; // 临时缓冲区,为了减少扩容次数,用于存储每次从 istream 中读取的字符
char ch = is.get(); // 从 istream 中获取一个字符
//in >> ch; //会导致一直连续输入无法结束
//遇到空格或者换行时会被忽略掉,用get可以收到空格和换行的内容
int i = 0; // 缓冲区索引
while (ch != '\n' && ch != delim) // 依次读取字符,直到遇到换行符或指定分隔符
{
buffer[i++] = ch;
if (i == 127) { // 当临时缓冲区满时,将缓冲区的内容添加到字符串中
buffer[i] = '\0'; // 将缓冲区末尾添加 '\0',形成 C 风格字符串
str += buffer; // 将缓冲区的内容追加到字符串中
i = 0; // 重置缓冲区索引
}
ch = is.get(); // 继续从 istream 中获取下一个字符
}
if (i > 0) // 如果缓冲区中还有剩余的字符,则将它们添加到字符串中
{
buffer[i] = '\0';
str += buffer;
i = 0;
}
return is; // 返回 istream 对象的引用,以支持链式操作
}
istream& getline(istream& is, string& str)
{
str.clear(); // 清空待填充的字符串
char buffer[128]; // 临时缓冲区,为了减少扩容次数,用于存储每次从 istream 中读取的字符
char ch = is.get(); // 从 istream 中获取一个字符
//in >> ch; //会导致一直连续输入无法结束
//遇到空格或者换行时会被忽略掉,用get可以收到空格和换行的内容
int i = 0; // 缓冲区索引
while (ch != '\n') // 依次读取字符,直到遇到换行符
{
buffer[i++] = ch;
if (i == 127) { // 当临时缓冲区满时,将缓冲区的内容添加到字符串中
buffer[i] = '\0'; // 将缓冲区末尾添加 '\0',形成 C 风格字符串
str += buffer; // 将缓冲区的内容追加到字符串中
i = 0; // 重置缓冲区索引
}
ch = is.get(); // 继续从 istream 中获取下一个字符
}
if (i > 0) // 如果缓冲区中还有剩余的字符,则将它们添加到字符串中
{
buffer[i] = '\0';
str += buffer;
i = 0;
}
return is; // 返回 istream 对象的引用,以支持链式操作
}
}
源文件test.cpp
//test.cpp
#include "string.h"
int main() {
hd::string s1; // 默认构造函数
cout << "s1: " << s1 << endl;
hd::string s2("Hello"); // 构造函数,接受C风格字符串
cout << "s2: " << s2 << endl;
hd::string s3(s2); // 复制构造函数
cout << "s3: " << s3 << endl;
hd::string s4("World", 3); // 构造函数,接受C风格字符串和长度
cout << "s4: " << s4 << endl;
// 迭代器测试
cout << "s2: ";
for (auto it = s2.begin(); it != s2.end(); ++it) {
cout << *it;
}
cout << endl;
// 容量相关函数测试
cout << "s2 size: " << s2.size() << endl;
cout << "s2 capacity: " << s2.capacity() << endl;
s2.resize(10, 'a');
cout << "s2 after resize: " << s2 << endl;
cout << "s2 capacity after resize: " << s2.capacity() << endl;
s2.reserve(20);
cout << "s2 capacity after reserve: " << s2.capacity() << endl;
s2.clear();
cout << "s2 size after clear: " << s2.size() << endl;
cout << "s2 capacity after clear: " << s2.capacity() << endl;
cout << "s2 is empty: " << (s2.empty() ? "true" : "false") << endl;
// 元素访问测试
cout << "s3[0]: " << s3[0] << endl;
cout << "s3.at(1): " << s3.at(1) << endl;
// 修改器测试
s3 += " World"; // 追加字符串
cout << "s3 after append: " << s3 << endl;
s3.insert(5, " C++"); // 在指定位置插入字符串
cout << "s3 after insert: " << s3 << endl;
s3.erase(5, 4); // 删除指定位置的字符
cout << "s3 after erase: " << s3 << endl;
s3.pop_back(); // 删除最后一个字符
cout << "s3 after pop_back: " << s3 << endl;
// 字符串操作测试
const char* cStr = s3.c_str();
cout << "C-style string: " << cStr << endl;
char buffer[10];
size_t copiedLen = s3.copy(buffer, 5, 0); // 复制字符串到buffer
buffer[copiedLen] = '\0';
cout << "Copied string: " << buffer << endl;
s3 += "World !";
size_t pos = s3.find("World"); // 查找子字符串
if (pos != hd::string::npos) {
cout << "Position of 'World': " << pos << endl;
}
else {
cout << "Position of 'World': " << -1 << endl;
}
hd::string subStr = s3.substr(6, 3); // 返回子串
cout << "Substring: " << subStr << endl;
return 0;
}
分别测试库中,和自己实现得出的结果:
这里的resize与reserve的不同,是因为在实现过程中的扩容策略的不同造成的。
通过全面介绍了C++中string类的基本用法、常用方法和优势,以及string类的模拟实现示例。通过详细的讲解和示例代码,可以深入了解如何在C++中使用string类进行字符串操作,并了解其相对于C风格字符串的优势所在。同时,通过展示一个简单的string类的模拟实现,还可以加深对string类内部原理的理解。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!