当前位置: 首页 > news >正文

C++: STL简介与string类核心技术解析及其模拟实现

目录:

      • 一.STL
      • 二.string类
        • 一、创建对象的6种构造方式
        • 二、常用接口解析
          • 1. 容量操作
          • 2. 元素访问
          • 3. 修改操作
          • 4. 字符串操作
      • 三.string模拟实现
        • 一、设计基础:类结构与资源管理
        • 二、拷贝控制:深拷贝的三种实现
          • 1. 传统深拷贝
          • 2. 现代写法(推荐)
        • 三、关键功能实现
          • 1. 容量管理
          • 2. 访问操作
          • 3. 数据修改操作
          • 4. 字符串操作
        • 四、运算符重载与IO
          • 1. 关系运算符
          • 2. 流操作符
      • 四.总结

一.STL

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
1. STL核心架构
STL(标准模板库)由三大核心组件构成:

  • 容器(数据结构):存储数据的模板类(如vector/list/map)
  • 算法:通用算法(如sort/find),可适配不同容器
  • 迭代器:连接容器与算法的桥梁,支持统一遍历方式

2. 重要版本对比

  • SGI版:GCC采用,可读性最佳,适合源码学习
  • P.J.版:VS采用,闭源但性能优异
  • HP原始版:所有版本始祖,开源自由(没错就是你知道的那个的惠普)

3. 六大组件应用
在这里插入图片描述

在这里推荐一本书: 侯捷老师的《STL源码剖析》,里面系统的讲解STL的源码


二.string类

一、创建对象的6种构造方式
构造方式语法示例功能描述
默认构造std::string s1;创建空字符串(长度为0)
拷贝构造std::string s2(s1);复制另一个字符串的内容
C字符串构造std::string s3("Hello");用C风格字符串初始化(需以\0结尾)
子串构造std::string s4(s3, 1, 3);s3的第1个字符开始取3个字符("ell"
部分构造std::string s5("Hello", 4);取C字符串的前4个字符("Hell"
填充构造std::string s6(5, 'A');生成5个重复字符("AAAAA"

注意

  • 子串构造中 s4(s3, pos, len)
    pos超出范围会抛out_of_range异常,len超长时自动截断。
  • 所有构造支持链式操作:
    std::string s = std::string("Hi") + " there!";
    在这里插入图片描述

二、常用接口解析
1. 容量操作
接口参数返回值功能示例
resize(len)len:新长度void调整字符串长度(默认用\0填充)s.resize(3); // "Hel"
resize(len, c)c:填充字符void指定填充字符扩展/缩短s.resize(5, '!'); // "Hello!!"
reserve(size)size:预留容量void预分配内存避免多次扩容s.reserve(100);
size()size_t返回字符串的有效长度s.size();
capacity()size_t返回空间总大小`s.capacity();

性能提示

  • reserve()减少动态分配次数,适合已知大小时提前调用,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
  • resize()扩展时未指定字符则填充空字符(\0),如果resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变`。
    在这里插入图片描述
    注意在vs底下reserve会可能多开一些空间,而Linux下你要多少它开多少

2. 元素访问
接口参数返回值行为说明
operator[](i)i:索引位置char&不检查越界(访问越界=未定义行为)
at(i) (不常用)i:索引位置char&检查越界(越界抛out_of_range异常)

有了operator;我们就可以像数组一样访问遍历或修改数据了
在这里插入图片描述

范围for

  • for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
  • 范围for可以作用到数组和容器对象上进行遍历
  • 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
	//范围for 底层是迭代器,只能正序遍历for (auto e : str2)//值拷贝{cout << e << " ";}cout << endl;for (auto& e : str2)//引用会修改原字符串{e++;cout << e << " ";}

在这里插入图片描述

auto关键字
作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期
推导而得
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际
只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
auto不能直接用来声明数组
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
typeid().name()可以查看数据的类型

迭代器
迭代器(Iterator)是C++ STL中至关重要的抽象机制,其意义远超出简单的遍历容器元素。

  • 迭代器为不同类型容器(如vector、list、map)提供了统一的元素访问方式,隐藏了底层存储结构的差异。
//迭代器
void func(const string& str)
{string::const_iterator it = str.begin();//从前向后打印,不可修改值while (it != str.end()){cout << *it << " ";++it;}cout << endl;//string::const_reverse_iterator itr = str.rbegin();//从后向前打印,且不可修改值auto itr = str.rbegin();while (itr != str.rend()){cout << *itr << " ";++itr;}}
int main()
{
string str1;
string str2("hello world");cout << endl;
string::iterator it = str2.begin();
while (it != str2.end())
{cout << *it << " ";++it;
}cout << endl;
it = str2.begin();
while (it != str2.end())
{(*it)-- ;cout << (*it) << " ";++it;
}cout << endl;
string::reverse_iterator it1 = str2.rbegin();
while (it1 != str2.rend())
{cout << *it1 << " ";++it1;
}cout << endl;
func(str2);
}

暂且先记住是这样的,以后会讲,而且string类不喜欢使用迭代器
it 可能是指针也可能不是
begin+ end: begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
在这里插入图片描述
rbegin + rend :begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
在这里插入图片描述


3. 修改操作
接口参数返回值功能
append(str)str:追加的字符串string&尾部添加字符串
push_back(c)c:单个字符void尾部添加字符(等价于+=
insert(pos, str)pos:插入位置,str:内容string&pos处插入字符串
erase(pos, len)pos:起始位置,len:长度string&删除子串(默认删到结尾)
operator+=(str)str:追加内容string&追加字符串或字符(最常用)
clear()void清空内容(长度=0,容量不变)

示例

std::string s = "Hello";
s.append(" World");  // "Hello World"
s.insert(5, " C++"); // "Hello C++ World"
s.erase(5, 4);       // "Hello World"
s += '!';            // "Hello World!"

避免循环中反复调用str += ch,改用reserve() + 循环更高效
频繁拼接字符串时,优先用+=append()而非+(减少临时对象)。


4. 字符串操作
接口参数返回值功能
find(str, pos)str:查找内容,pos:起始位置size_t返回首次出现的位置(未找到返回npos
substr(pos, len)pos:起始位置,len:长度string提取子串(默认到结尾)
c_str()const char*返回C风格字符串(只读)

c_str()返回的指针在字符串修改后可能失效,需立即使用。
查询操作(如find)时间复杂度为O(n),避免在大循环中调用。

示例

std::string s = "Hello World";
size_t pos = s.find("World");  // pos = 6
std::string sub = s.substr(6, 5); // "World"
const char* cstr = s.c_str();    // 用于C接口函数

场景:从路径中提取文件名(不含扩展名)

#include <iostream>
#include <string>int main() {std::string path = "/home/user/file.txt";// 1. 从末尾找到最后一个'.'size_t dot_pos = path.rfind('.');// 2. 从开头到'.'之前截取std::filename = path.substr(0, dot_pos);// 3. 从末尾找到最后一个'/'size_t slash_pos = path.rfind('/');// 4. 从'/'后截取到'.'前std::string name = path.substr(slash_pos + 1, dot_pos - slash_pos - 1);std::cout << name; // 输出 "file"
}

完整接口列表见C++ Reference(非官方)。


三.string模拟实现

一、设计基础:类结构与资源管理

核心成员变量

class string {
private:size_t _size;      // 当前字符串长度size_t _capacity;  // 当前内存容量char* _str;       // 动态内存指针
};

构造与析构关键点

// 默认构造(支持空字符串)
string(const char* str = "") {_size = strlen(str);//计算字符串的大小_capacity = _size;_str = new char[_capacity + 1];  // +1存储'\0'memcpy(_str, str, _size + 1);    // 高效内存拷贝
}// 析构函数
~string() {delete[] _str;  // 释放动态内存_str = nullptr;_size = _capacity = 0;
}

设计陷阱:若使用编译器生成的默认拷贝构造,会导致浅拷贝问题——多个对象共享同一内存,析构时重复释放。这也是为什么必须手动实现深拷贝。
在这里插入图片描述

二、拷贝控制:深拷贝的三种实现
1. 传统深拷贝
// 拷贝构造
string(const string& str) {_str = new char[str._capacity + 1];memcpy(_str, str._str, str._size + 1);_size = str._size;_capacity = str._capacity;
}// 赋值重载
string& operator=(const string& s) {if(this != &s) {  // 防止自赋值char* tmp = new char[s._capacity + 1];memcpy(tmp, s._str, s._size + 1);delete[] _str;  // 释放旧内存_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}
2. 现代写法(推荐)
void swap(string& tmp) {std::swap(_str, tmp._str);std::swap(_size, tmp._size);std::swap(_capacity, tmp._capacity);
}// 拷贝构造
string(const string& str): _str(nullptr) {string tmp(str._str); // 调用构造函数string(const char* str),创建一个内容与str完全相同的临时对象tmpswap(tmp);  //调用成员函数swap()交换当前对象和临时对象的内容     //交换后,当前对象获得tmp的资源(即拷贝的数据)//tmp获得当前对象初始时的空状态(nullptr)    //当函数结束时,临时对象tmp离开作用域//调用tmp的析构函数,释放其持有的资源(此时是nullptr,安全释放)
}// 赋值重载
string& operator=(string tmp) 
{ // 传值产生临时副本swap(tmp);  // 交换资源return *this; // tmp离开作用域自动析构旧资源
}

在这里插入图片描述
这里是引用

方法对比

实现方式内存安全异常安全代码简洁度
传统深拷贝⚠️中等
现代写法

现代写法的核心优势:异常安全自赋值安全。传参过程自动完成拷贝,避免手动检查。

三、关键功能实现
1. 容量管理
size_t size()const
{return _size;//返回个数大小
}size_t capacity()const
{return _capacity;//返回容量大小
}// 扩容(不影响数据)
void reserve(size_t n) {if (n > _capacity) {char* tmp = new char[n + 1];memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;  // 更新容量}
}// 调整大小(影响数据)
void resize(size_t n, char ch = '\0') {if (n < _size) {  // 缩小,删数据_size = n;_str[_size] = '\0';//直接在_size位置截断} else {          // 扩大reserve(n);   // 确保容量for (size_t i = _size; i < n; i++)_str[i] = ch;  // 填充字符_size = n;_str[_size] = '\0';}
}

容量操作对比

方法功能时间复杂度是否影响数据
reserve()预分配内存O(N)
resize()调整字符串长度O(N)
2. 访问操作
//[]运算符重载
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const//不可修改
{assert(pos < _size);return _str[pos];
}//迭代器
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;
}

我们自己实现的迭代器可以用原生指针完成,但是并不是所有的迭代器都是指针(虽然底层依旧还是指针)
在这里插入图片描述
这是vs底下string迭代器的类型,你会发现他用一个类封装了(考虑到各种原因)
typeid().name()可以看到变量的类型
在这里插入图片描述
我们屏蔽掉end(),发现编译器报错“此基于范围的"for“语句需要适合的"end"函数,但未找到”,间接证明范围for容器遍历实际就是替换为迭代器

3. 数据修改操作
// 尾部追加字符
void push_back(char c) {if (_size == _capacity)reserve(_capacity ? _capacity*2 : 4);  // 指数扩容_str[_size++] = c;_str[_size] = '\0';
}void  append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}memcpy(_str + _size, str, len + 1);_size += len;}
// 插入字符串
void insert(size_t pos, const char* str) {assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity)reserve(_size + len);  // 精确扩容// 移动现有字符(从后向前)size_t end = _size;//要注意end是size_t类型,当end = -1 ->42以多的数字恒大于pos//把end改为int也不行,关系运算符两边类型不同会出现整型提升while (end >= pos && end != npos)//nops是公共成员变量 = -1{_str[end + len] = _str[end];end--;}// 插入新内容memcpy(_str + pos, str, len); // 避免循环_size += len;
}// 删除操作
void erase(size_t pos = 0, size_t len = npos) {assert(pos < _size);if (len == npos || pos + len >= _size) { _str[pos] = '\0';  // 截断至pos处_size = pos;} else {memmove(_str + pos, _str + pos + len, _size - pos - len + 1);_size -= len;}
}string& operator+=(char ch)
{push_back(ch);return *this;
}string& operator+=(const char* str)
{append(str);return *this;
}

性能技巧:使用memmove而非循环移位,处理内存重叠更安全。

4. 字符串操作
// 查找字符
size_t find(char ch, size_t pos = 0) const {for (; pos < _size; pos++){if (_str[pos] == ch){ return pos;}}	return npos;
}// 查找子串
size_t find(const char* s, size_t pos = 0) const {const char* ptr = strstr(_str + pos, s); // 库函数优化return ptr ? ptr - _str : npos;
}// 获取子串
string substr(size_t pos = 0, size_t len = npos) const {if (len == npos || pos + len > _size)len = _size - pos;  // 自动调整长度string result;result.reserve(len);for (size_t i = 0; i < len; i++)result += _str[pos + i];  // 支持链式追加return result;  // 返回值优化(RVO)
}
四、运算符重载与IO
1. 关系运算符
// 字典序比较
bool operator<(const string& str) const {size_t i = 0;while (i < _size && i < str._size) {if (_str[i] != str[i]) return _str[i] < str[i];i++;}return _size < str._size;  // 前缀相同比长度
}// 基于<和==实现其他运算符
bool operator==(const string& str) const { return _size == str._size && memcmp(_str, str._str, _size) == 0;
}bool operator!=(const string& str) const { return !(*this == str); 
}
// <=, >, >= 类似实现...
bool string::operator<=(const string& str)const
{return (*this < str) || (*this == str);
}bool string::operator>(const string& str)const
{return !(*this <= str);
}
bool string:: operator>=(const string& str)const
{return !(*this < str);
}

operator<()
“hello” “hello” --false
“hellox” “hello” – false
“hello” "hellox’ --true

2. 流操作符
// 输出(支持含'\0'的字符串)ostream& operator<<(ostream& out, const string& str) {for (size_t i = 0; i < str.size(); i++) out << str[i];  // 避免c_str()的'\0'截断return out;
}// 输入(带缓冲区优化)istream& operator>>(istream& in, string& str) {str.clear();        // 清空目标char buff[128];     // 减少扩容次数size_t i = 0;char ch = in.get();while (isspace(ch)) ch = in.get();  // 跳过空白while (!isspace(ch)) {buff[i++] = ch;if (i == 127) {                 // 缓冲区满buff[i] = '\0';str += buff;                 // 批量追加i = 0;                      // 重置}ch = in.get();}if (i > 0) {                        // 处理剩余buff[i] = '\0';str += buff;}return in;
}

在这里插入图片描述
这里是引用

<<和>> 不要在类里重载,因为会有个隐藏的this指针占据第一个位置
输入优化:缓冲区减少+=操作次数,降低扩容开销。


四.总结

通过系统的学习string类,我们学会了使用,在面对字符串或字符的时候就可以用string类,提高效率,并且通过完整的模拟实现可深入理解:动态内存管理深/浅拷贝操作符重载迭代器设计等C++核心概念。需知我们模拟实现不是为了造一个更好的轮子,已经有人给我们造出来了,我们直接使用就行了,关键在于学习人家的思路和想发,以及了解底层是如何工作的。

相关文章:

  • 【芯片学习】555
  • 第二章支线四 ·响应圣坛:媒体查询与移动适配
  • Linux服务器 TensorFlow找不到GPU
  • 内网怎么映射外网ip? 内网的地址快速映射给外网访问用方法
  • 美业新动能:智能体如何赋能行业“维护”升级(3/6)
  • VMware Tools 手动编译安装版
  • 71.用户重新编辑功能实现之后端实现
  • 简历制作要精而不简
  • Redis缓存存储:从基础到高阶的深度解析
  • 16-前端Web实战(Tlias案例-部门管理)
  • UNION 与 UNION ALL 的区别
  • 七、物理.
  • CppCon 2014 学习:ASYNCHRONOUS COMPUTING IN C++
  • MCU如何从向量表到中断服务
  • 爬虫工具链的详细分类解析
  • 历年苏州大学计算机保研上机真题
  • 系统性学习C语言-第十三讲-深入理解指针(3)
  • 【Canvas与色彩】八彩大风车
  • 鸿蒙ArkTS | Badge 信息标记组件自学指南
  • LTSPICE仿真电路:(三十一)HOWLAND电流源
  • 信誉好的扬中网站建设/万词优化
  • 网站设计建设公司/网络营销的策略有哪些
  • 明薇通网站建设/手机建立一个免费网站
  • 福建泉州晋江疫情/萧山seo
  • 网站文章编辑/免费广告推广软件
  • 宁波市内做公司网站的公司/三只松鼠口碑营销案例