深入解析:c++的STL:string类与string类的手动基础实现

news/2025/10/20 10:00:09/文章来源:https://www.cnblogs.com/slgkaifa/p/19151869

深入解析:c++的STL:string类与string类的手动基础实现

点击跳转快捷目录

  • 简介
  • std::string的基础与使用
    • string的实现
    • 基本使用示例
  • 底层实现my::string(不涉及SSO/内存池优化)
    • 代码整体(直接复制可用的代码)
      • string.h
      • string.cpp
    • 代码讲解
      • 为什么不写默认构造函数string()?
      • 带参构造string(const char* str)为什么不用初始化列表?
      • 没看懂拷贝构造函数和赋值运算符重载?
      • string(const char* str)为什么使用strcpy而不使用my::string::swap?
      • assert()是什么?
      • reserve()中为什么使用memcpy而不是strcpy?
      • 流运算符重载
  • 总结:为什么 std::string 如此重要?

简介

在 C++ 的标准模板库(STL)中,std::string 是最常用、最重要的类之一。
它封装了字符数组的复杂操作,为开发者提供了安全、便捷、功能强大的字符串处理能力。相比于传统的 C 风格字符串(即以 \0 结尾的 char* 数组),std::string自动管理内存支持动态扩展提供丰富的成员函数,极大地提升了开发效率和代码安全性。
本文将从 std::string 的基本使用入手,逐步深入其底层实现原理,帮助读者会用、理解。

提示:由于std::string的底层实现并非简单地开辟空间,而是涉及内存池、SSO等优化技术,为了让有一定c++基础但是没有深入了解的读者也能看懂,本文的代码实现部分不涉及内存池等优化技术!

std::string的基础与使用

string的实现

std::string 是 C++ 标准库中定义的一个类模板实例化类型,其完整定义为:
typedef std::basic_string<char> string;
它位于头文件 <string> 中,是一个模板类 basic_stringchar 类型的特化。除了 string,标准库还提供了:

typedef std::basic_string<wchar_t> wstring;       // 宽字符字符串typedef std::basic_string<char16_t> u16string;    // UTF-16typedef std::basic_string<char32_t> u32string;    // UTF-32
  • 这里提及的宽字符、UTF等,对我们了解string没有太大帮助,不了解字符编码可以无视。如果没有兴趣可以不了解,感兴趣可以进行网络搜索或询问AI等
std::string 本质上是一个动态数组容器,专门用于存储操作字符序列。它具备以下核心特性:
动态大小:可根据需要自动增长或缩减。
自动内存管理:无需手动分配或释放内存。
丰富的接口:支持拼接、查找、替换、比较、子串等操作。
与 C 风格字符串兼容:可通过 c_str() 获取 const char*
迭代器支持:可与 STL 算法无缝结合。

基本使用示例

以下为简单使用示例 这些简单的部分就不过多赘述了

#include <iostream>#include <string>using namespace std;int main() {// 构造string s1 = "Hello";string s2("World");// 拼接string s3 = s1 + ", " + s2 + "!";// 长度与访问cout << "Length: " << s3.size() << endl;cout << "First char: " << s3[0] << endl;// 查找与子串size_t pos = s3.find("World");if (pos != string::npos) {string sub = s3.substr(pos, 5);cout << "Found: " << sub << endl;}// 比较if (s1 < s2) {cout << s1 << " < " << s2 << endl;}return 0;}

底层实现my::string(不涉及SSO/内存池优化)

代码整体(直接复制可用的代码)

string.h

头文件: string.h

#define _CRT_SECURE_NO_WARNINGS 1   //Visual Studio防报错用
#pragma once
#include<iostream>#include<string.h>#include<assert.h>using namespace std;namespace my{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;string(const char* str = "");~string();string(const string& s);string& operator=(string tmp);const char* c_str() const;void swap(string& s);size_t size() const;char& operator[](size_t i);const char& operator[](size_t i) const;void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);string& insert(size_t pos, char ch);string& insert(size_t pos, const char* str);string& erase(size_t pos = 0, size_t len = npos);void pop_back();size_t find(char ch, size_t pos = 0) const;size_t find(const char* str, size_t pos = 0) const;string substr(size_t pos, size_t len = npos) const;void clear();bool operator<(const string& s) const;bool operator<=(const string& s) const;bool operator>(const string& s) const;bool operator>=(const string& s) const;bool operator==(const string& s) const;bool operator!=(const string& s) const;private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;public:static const size_t npos;};ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);istream& getline(istream& in, string& s, char delim = '\n');void swap(string& x, string& y);}

string.cpp

实现文件: string.cpp

#include "string.h"
namespace my
{
const size_t string::npos = -1;
string::string(const char* str)
: _size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string::string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
string& string::operator=(string tmp)
{
swap(tmp);
return *this;
}
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;
}
const char* string::c_str() const
{
return _str;
}
size_t string::size() const
{
return _size;
}
char& string::operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
const char& string::operator[](size_t i) const
{
assert(i < _size);
return _str[i];
}
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* str = new char[n + 1];
memcpy(str, _str, _size + 1);
delete _str;
_str = str;
_capacity = n;
}
}
void string::push_back(char ch)
{
if (_size >= _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newCapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity) {
size_t newCapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
reserve(newCapacity);
}
memcpy(_str + _size, str, len + 1);
_size += len;
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
string& string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size >= _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newCapacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
string& string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity) {
size_t newCapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
reserve(newCapacity);
}
int end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
memcpy(_str + pos, str, len);
_size += len;
return *this;
}
string& string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len == npos || len >= (_size - pos))
{
_size = pos;
_str[_size] = '\0';
}
else
{
size_t i = pos + len;
memmove(_str + pos, _str + i, _size + 1 - i);
_size -= len;
}
return *this;
}
void string::pop_back()
{
assert(_size > 0);
--_size;
_str[_size] = '\0';
}
size_t string::find(char ch, size_t pos) const
{
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) const
{
const char* p1 = strstr(_str + pos, str);
if (p1 == nullptr)
return npos;
else
return p1 - _str;
}
string string::substr(size_t pos, size_t len) const
{
if (len == npos || len >= _size - pos)
{
len = _size - pos;
}
string ret;
ret.reserve(len);
for (size_t i = 0; i < len; i++)
{
ret += _str[pos + i];
}
return ret;
}
void string::clear()
{
_size = 0;
_str[0] = '\0';
}
bool string::operator<(const string& s) const
{
size_t i1 = 0, i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] < s[i2])
return true;
else if (_str[i1] > s[i2])
return false;
else {
++i1; ++i2;
}
}
return i2 < s._size;
}
bool string::operator<=(const string& s) const
{
return *this < s || *this == s;
}
bool string::operator>(const string& s) const
{
return !(*this <= s);
}
bool string::operator>=(const string& s) const
{
return !(*this < s);
}
bool string::operator==(const string& s) const
{
size_t i1 = 0, i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] != s[i2])
return false;
else {
++i1; ++i2;
}
}
return i1 == _size && i2 == s._size;
}
bool string::operator!=(const string& s) const
{
return !(*this == s);
}
ostream& operator<<(ostream& out, const string& s)
{
//out << s.c_str();	//无法访问'\0'后的数据
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128];
int i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i] = ch;
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
++i;
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
istream& getline(istream& in, string& s, char delim)
{
s.clear();
char buff[128];
int i = 0;
char ch = in.get();
while (ch != delim)
{
buff[i] = ch;
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
++i;
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
void swap(string& x, string& y)
{
x.swap(y);
}
}

代码讲解

为什么不写默认构造函数string()?

如果我们手动写默认构造,那么会写成下面这样:

string::string()
: _str(new char[1] {'\0'})
, _size(0)
, _capacity(0)
{
}

但是我们已经实现了带参构造函数,因此我们可以通过给带参构造函数缺省值的方式,实现两个构造函数的合并

string::string(const char* str = "")
: _size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}

这样,如果是无参构造,就可以直接使用""来进行构造

带参构造string(const char* str)为什么不用初始化列表?

可能有人会疑惑,为什么这里不直接写为全初始化列表:

string::string(const char* str)
:_str(new char[strlen(str) + 1])
,_size(strlen(str))
,_capacity(strlen(str))
{
strcpy(_str, str);
}

我们不难发现,如果使用这种方法,会调用3次strlen函数,但是strlen函数的时间复杂度是n,这意味着每次调用都要遍历一次字符串str,这会造成极大的时间浪费。
但是我们可以发现,strlen的值保存在了_size之中,因此我们可以将_size作为参数进行传递。
这时,可能就会有同学这样想了:调换初始化列表的顺序不就好了吗?

string::string(const char* str)
:_size(strlen(str))
,_str(new char[_size + 1])
,_capacity(_size)
{
strcpy(_str, str);
}

但是很疑惑,如果试着运行一下,会发现代码是错误的!
因为对于初始化列表而言,初始化顺序与声明顺序相同,我们的声明顺序是

private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;

因此初始化顺序依次为_str_size_capacity
(如果不太理解可以看看这里~)
CSDN文章:C++初始化列表是什么?有什么“隐藏坑”?初始化列表的顺序问题
当然,改变声明顺序也是解决办法之一,即将_size的声明放到最前面,变成

private:
size_t _size = 0;
char* _str = nullptr;
size_t _capacity = 0;

但是这样会大大增加代码的耦合性,这是编程之大忌!
因此,这个时候就只能放弃全部使用初始化列表,只将_size放在初始化列表之中,其余部分手动实现

string::string(const char* str)
: _size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}

(可能有同学会疑惑,初始化列表、缺省值等,谁的优先级更高呢?可以看看我写的另一篇文章哦~)
CSDN文章:C++类中成员变量走初始化列表的逻辑

没看懂拷贝构造函数和赋值运算符重载?

文章里提供的拷贝构造函数和赋值运算符重载是经过优化后的版本,虽然不会提高运行效率,但是代码简洁性会大大提高!
具体如何优化的在下面这里,里面涉及的例子就是本文的my::string
CSDN文章:C++拷贝构造函数与operator=()的现代简洁写法,如何“优雅”地拷贝构造与赋值

string(const char* str)为什么使用strcpy而不使用my::string::swap?

在上一个问题中,我们已经写了my::string::swap函数来实现对一个新对象的赋值(点击上一个问题的蓝色链接即可看到),那么在这里为什么不调用swap来进行更"优雅"的操作呢?

原因有两个

1.类型不同:
_str的类型是char*,而str的类型是const char*,二者类型不同

2.存储位置不同
即使二者类型相同,二者的存储空间位置也不同
str指向的内存空间是常量区,值是不能修改
_str指向的内存空间是,值是可以修改

assert()是什么?

assert是c/c++都有的断言报错,可以判断代码运行到此处时是否符合括号内的条件语句,是debug时的好帮手!
不清楚的话可以看看这里~
CSDN文章:assert断言(如何让程序在不满足条件时报错)

reserve()中为什么使用memcpy而不是strcpy?

在正常的使用情况下,使用strcpy()其实并没有什么问题,因为一个字符串正常插入数据,结尾以\0结束,我们的代码逻辑可以很好地处理这种情况。
但是,为了代码的健壮性/鲁棒性,我们不得不考虑一种情况:
用户会不会插入\0这个字符?
例如:

void test()
{
my::string s("hello");
s.push_back('\0');
s += "world";
cout << s <<endl;
}

调用这个函数后,如果使用strcpy,我们会发现,打印结果为:

hello

但是在模板库std::string,会发现能正常打印出结果:

helloworld

原因分析
对于strcpy函数而言,看到\0就意味着字符串的结束,并不会考虑这个\0是否属于字符串内容的一部分,因此我们不应当使用strcpy,而是使用memcpy来手动设置拷贝_size + 1个字符,这样才能确保用户插入的\0以及后面的内容不会在拷贝中丢失!

补充
append()方法中,使用strcpymemcpy均可

流运算符重载

可能部分同学会疑惑:流运算符为何会被重载到全局?
我写了两篇关于这个问题的文章,第一篇比较全面但是较长,想要快速理解可以直接看第二篇~
C++流运算符重载为何不能作为类的成员函数?
C++流运算符重载为何不能作为类的成员函数?快速理解!

总结:为什么 std::string 如此重要?

特性说明
安全性避免缓冲区溢出,自动管理内存
易用性支持 +=、== 等运算符重载
功能丰富提供 findreplacesubstr 等 50+ 成员函数
性能优化SSO、移动语义、预留容量 (reserve) 等机制
标准兼容与 STL 容器、算法、迭代器无缝集成

(文章如有错漏,欢迎在评论区/私信指出!)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/940935.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS中的值传递和引用传递

JS中的值传递和引用传递 JS没有引用传递 (arguments除外)值传递:内存独立,互不影响引用传递:共享一块内存空间,指向同一个地址var a = {} var b = a b.n = 3 a // {n:3}var a = {} var b = a b = {n:3} a // {…

乐理和蜂鸣器的实现

在学习计算机无源蜂鸣器的发声过程中,想到可以借此机会掌握乐理知识。 B站有一个从零基础讲解的非常好的视频: 20分钟乐理通俗讲解 这里想写一篇博客记录一下学习历程1.首先用Aduino+无源蜂鸣器实现《小星星》的旋律…

CF1288C Two Arrays 分析

题目概述 题目链接:https://www.luogu.com.cn/problem/CF1288C。 长度为 \(m\) 的序列 \(a,b\),值域为 \([1,n]\),求 \((a,b)\) 的数量满足:\(a\) 单调不降。 \(b\) 单调不升。 对于每个 \(i\),满足 \(a_i\leq b_…

基于Java+Springboot+Vue开发的母婴商城管理系统源码+运行步骤

项目简介该项目是基于Java+Springboot+Vue开发的母婴商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过…

2025智能客服管理系统哪个好?对比国产主流5款工具中怎么选? - RAIN

2025智能客服管理系统哪个好?对比国产主流5款工具中怎么选?前言:智能客服成企业服务竞争核心,选型难题待解 在电商、金融、教育、SaaS等行业,客户咨询量激增、服务时段延长、问题类型多样化,导致企业面临人力成本…

一文详解 | 纷享销客CRM如何助力快消巨头蒙牛实现全场景数字化转型

当蒙牛乳业这样的千亿级快消巨头启动全场景、全链路数字化改革,需要怎样的合作伙伴? 过去3年,纷享销客通过构建「访」「销」「费」「促」「商」「店」数字化体系,助力蒙牛落地三流合一、战场扫描、终端费用直投、b…

基于进化算法的自动神经架构搜索

阅读文献: A Survey on Evolutionary Neural Architecture Search 是由 Yuqiao Liu 、Yanan Sun 等人发表在 IEEE TRANSACTIONS ON NEURAL NETWORKS AND LEARNING SYSTEMS (JCR-Q1)上的一篇综述 abstract 首先指出深…

基于MATLAB的谐波分析实现方案

基于MATLAB的谐波分析实现方案,包含信号生成、FFT处理、谐波参数提取及可视化模块:一、核心代码 function [harmonics] = analyze_harmonics(signal, fs, fundamental) % 输入参数: % signal: 输入时域信号 (列向量…

AI生成代码系列:开源代码片段检测的有效方法

AI生成代码系列:开源代码片段检测的有效方法随着代码片段通过AI增强的IDE自动补全功能及外部AI提示进入专有代码库,企业必须识别可能涉及许可义证务、安全风险或来源问题的开源软件(OSS)片段。此时,具备开源代码片…

稀疏大规模多目标优化问题

阅读文献: An Enhanced Competitive Swarm Optimizer With Strongly Convex Sparse Operator for Large-Scale Multiobjective Optimization 是由王翔宇、王健等人发表在 IEEE TRANSACTIONS ON EVOLUTIONARY COMPUTAT…

2025 年高端月子会所中心推荐:西安女王臻瑷月子会所 —— 专注母婴护理 10 年,打造高品质母婴护理服务标杆

行业背景随着居民生活水平提升与育儿观念升级,高端月子护理需求持续增长。现代家庭对产后母婴护理的专业性、安全性、舒适性要求愈发严苛,不仅期待科学的护理方案,更注重整体居住环境与配套服务的完整性。然而,当前…

流水线

I hope all of us can learn to progress!

2025年10月豆包关键词排名优化服务推荐排行榜:十大服务商深度对比与评测指南

一、引言 在人工智能技术快速发展的今天,豆包等AI平台已成为企业获取流量和提升品牌影响力的重要渠道。豆包关键词排名优化作为生成式引擎优化的重要分支,直接关系到企业在AI搜索生态中的可见度与商业价值。本文主要…

2025年10月豆包关键词排名优化服务推荐排行榜单:十大服务商深度对比与评测分析

一、引言 在当前数字化营销快速发展的背景下,豆包关键词排名优化已成为企业提升品牌曝光、获取精准流量的重要手段。对于广大创业者、市场营销负责人以及企业管理者而言,选择一家专业可靠的优化服务商,能够有效控制…

2025年10月豆包关键词排名优化服务排行榜:十家优质服务商综合评测与选择指南

一、引言 在人工智能技术快速发展的今天,豆包等AI平台已成为企业获取信息的重要渠道。豆包关键词排名优化作为生成式引擎优化的重要分支,直接影响着企业在AI搜索生态中的可见度和业务机会。对于正在寻求数字化转型的…

【tinyusb】首次使用

参考文档 OpenDeepWiki - AI-Powered Knowledge Management Platform下载tnyusb代码 git clone https://github.com/hathach/tinyusb.git cd tinyusb git checkout master 编译工程 cd examples/device/cdc_msc mkdir …

2025 年西安标志标识厂家最新推荐排行榜:聚焦西北优质服务商,精选实力企业助您精准选型

引言当前标识行业在红色党建、乡村振兴、医疗教育等多领域需求激增,但市场中部分厂家存在产业链断裂、设备落后、跨领域经验不足等问题,导致客户选型难、项目落地质量差。为解决这一痛点,本榜单结合 2025 年西安及西…

2025 年国内电容厂家最新推荐排行榜:聚焦固态 / 高压 / 安规等多品类,精选优质厂商助力采购选型

引言当前,电容作为电子设备核心元件,在消费电子、工业控制、新能源等领域的需求持续攀升,固态、高压、安规等细分品类产品应用场景不断拓展。但市场上电容品牌数量繁多,资质与性能差异悬殊,企业采购时常常面临选型…

2025年最强ChatGPT客户端TOP5!Windows/Mac通用AI神器推荐

2025年最强ChatGPT客户端TOP5!Windows/Mac通用AI神器推荐在 ChatGPT、Claude、Gemini 等大型语言模型日趋成熟的 2025 年,AI 已经成为我们日常工作、写作、学习、创作的「第二大脑」。 但不少用户仍在纠结:我该用哪…

ccrc 应审会议记录

ccrc 应审会议记录1、注意所有日期,需要跟计划表里进行对应 2、注意验收报告完成试运行时间(a-b)【5.14-6.14】 3、申请验收时间》=b 【6.19】 6、合同日期 完工日期 核定好 代码检查 (内容、目的没看出来) 项目…