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

C++中的STL标准模板库和string

一.STL简介

1.什么是STL

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

2.STL的版本

原始版本

Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本--所有STL实现版本的始祖。

P. J. 版本

由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。

RW版本

由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

SGI版本

由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本

3.STL的六大组件

1. 容器(Containers)

  • 作用:存储和管理数据的通用数据结构。

  • 分类

    • 序列容器:元素按线性顺序排列

      • vector:动态数组(随机访问高效)

      • list:双向链表(插入/删除高效)

      • deque:双端队列

      • forward_list(C++11):单向链表

      • array(C++11):固定大小数组

    • 关联容器:基于键(Key)排序的元素集合

      • set/multiset:唯一/重复键集合(红黑树实现)

      • map/multimap:键值对映射

    • 无序容器(C++11):哈希表实现

      • unordered_set/unordered_multiset

      • unordered_map/unordered_multimap

2. 算法(Algorithms)

  • 作用:操作容器中元素的通用函数模板(独立于容器类型)。

  • 分类

    • 非修改序列操作find()count()for_each()

    • 修改序列操作copy()replace()shuffle()

    • 排序/二分搜索sort()binary_search()

    • 数值运算accumulate()(在 <numeric> 中)

  • 关键特性:通过迭代器访问容器,实现容器与算法的解耦。

3. 迭代器(Iterators)

  • 作用:容器与算法之间的"粘合剂",提供统一的元素访问接口(类似指针)。

  • 分类(按功能递增):

4. 仿函数(Functors)/ 函数对象

  • 作用:行为类似函数的对象(重载了operator()的类)。

  • 优势:可携带状态(成员变量),比函数指针更灵活。

  • STL内置仿函数(在 <functional> 中):

    • 算术运算:plus<T>minus<T>

    • 比较运算:less<T>greater<T>

    • 逻辑运算:logical_and<T>

5. 适配器(Adapters)

  • 作用:修改其他组件的接口以提供新功能。

  • 常见类型

    • 容器适配器

      • stack:基于deque/list实现后进先出(LIFO)

      • queue:先进先出(FIFO)

      • priority_queue:优先队列(基于vector+堆算法)

    • 迭代器适配器

      • reverse_iterator:逆向遍历

      • insert_iterator:插入元素而非覆盖

    • 函数适配器

      • bind()(C++11):绑定参数

      • 过时的bind1st/bind2nd(C++17移除)

6. 分配器(Allocators)

  • 作用:封装容器内存管理的底层细节(内存分配/释放)。

  • 默认行为:使用std::allocator(调用newdelete)。

  • 自定义场景

    • 内存池优化

    • 共享内存管理

    • 调试内存分配

7.六大组件之间的协作关系

二.string

1.介绍

string类严格来说并非STL中的部分,但是字符串在现实生活中十分常见,而C语言中的一些字符操作函数难以满足人的需求,C++中的string类方便快捷,十分具有学习价值

2.叙述模式

之后所有的STL库容器都会以这样的方式进行介绍:基本操作的使用,原理,迭代器,模拟实现

3.标准库中的string

1.string类

http://www.cplusplus.com/reference/string/string/?kw=string

2.auto和范围for

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

auto不能直接用来声明数组

#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange",
"橙子" }, {"pear","梨"} };
// auto的使用,极大简化
//std::map<std::string, std::string>::iterator it = dict.begin();
auto it = dict.begin();
while (it != dict.end())
{
cout << it->first << ":" << it->second << endl;
++it;
}
return 0;
}

范围for

范围for本质上还是利用迭代器进行容器的遍历,对于一个有范围的容器而言,使用范围for更加方便也不容易犯错。值得注意的是,范围for是否会改变被遍历容器的值,在于是否加引用符号&

#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
// C++98的遍历
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2;
} f
or (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i] << endl;
} 
// C++11的遍历
for (auto& e : array)
e *= 2;
for (auto e : array)
cout << e << " " << endl;
string str("hello world");
for (auto ch : str)
{
cout << ch << " ";
} 
cout << endl;
return 0;
}
3.string类中常用接口和方法

1.常用构造

(constructor)函数名称功能说明
string() (重点)构造空的string类对象,即空字符串
string(const char* s) (重点)用C-string来构造string类对象
string(size_t n, char c)string类对象中包含n个字符c
string(const string&s) (重点)拷贝构造函数
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}

2.string类对象的容器操作

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty (重点)检测字符串释放为空串,是返回true,否则返回false
clear (重点)清空有效字符
reserve (重点)为字符串预留空间
resize (重点)将有效字符的个数改成n个,多出的空间用字符c填充

注意:

1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接

口保持一致,一般情况下基本都是用size()。

2. clear()只是将string中有效字符清空,不改变底层空间大小。

3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不

同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char

c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数

增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参

数小于string的底层空间总大小时,reserver不会改变容量大小。

3.string的遍历

函数名称功能说明
operator[] (重点)返回pos位置的字符,const string类对象调用
begin+ endbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rendbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
范围forC++11支持更简洁的范围for的新遍历方式

4,string类对象的修改操作

函数名称功能说明
push_back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+= (重点)在字符串后追加字符串str
c_str(重点)返回C格式字符串
find + npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符,然后将其返回

5.string类的非成员函数

函数功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点)输入运算符重载
operator<< (重点)输出运算符重载
getline (重点)获取一行字符串
relational operators (重点)大小比较

4.vs和g++下的string(32位环境)

vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:

当字符串长度小于16时,使用内部固定的字符数组来存放当字符串长度大于等于16时,从堆上开辟空间.

union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量

最后:还有一个指针做一些其他事情,一共16+4+4+4=28字节

g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

空间总大小字符串有效长度引用计数

struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};

两种编译器扩容策略对比

vs第一次扩容是两倍正是在vs中有一个长度为16字节的buff数组,超出这个范围将会在堆上开空间,因此第一次扩容较为特殊,之后均为扩1.5倍。而在g++中,则是稳定按照2倍进行扩容。

三.模拟实现string

1.string.h

用于定义string类的基本框架和一些简短的函数

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once#include<iostream>
#include<string>
#include<assert.h>
using namespace std;namespace wjh {class string {private:char* _str;size_t _size;size_t _capacity;//这里的npos定义为-1,而类型为size_t//原因是他认为字符串长度不会超过非负数的全1(约42亿字节)static const size_t npos;public://迭代器typedef char* iterator;typedef const char* const_iterator;// 构造函数string(const char* str = ""){_size = strlen(str);// _capacity不包含\0_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;}// 赋值运算符重载//这里要做深拷贝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;}iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//取到字符串的首地址const char* c_str() const{return _str;}//上面提到的string各种各样的操作void clear() {_str[0]='\0';_size = 0;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}//重载operator []//传char&的原因是为了修改//这样可以通过operator[]对s[i]处的值进行修改//类似于使用数组,很方便//并且加入assert可以顺便实现检查越界char &operator[](size_t index) {assert(index<_size);return _str[index];}const char &operator[](size_t index) const{assert(index<_size)return _str[index];}//需要在类外实现void reserve(size_t 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);ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);
}

2.string.cpp

#include"string.h"
namespace wjh {const size_t string::npos = -1;//为字符串预留空间void string::reserve(size_t n) {if (n > _capacity) {char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}//尾插字符void string::push_back(char ch) {//判断越界if (_size == _capacity) {reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}string& string::operator+=(char ch) {push_back(ch);return *this;}//追加字符串void string::append(const char* str) {int len = strlen(str);if (_size + len >= _capacity) {reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}strcpy(_str + _size, str);_size += len;}string& string::operator+=(const char* str) {append(str);return *this;}//插入字符void string::insert(size_t pos, char ch) { //pos是否越界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);//判断越界int len = strlen(str);if (_size+len>=  _capacity){reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//挪动数据int end = _size + len;while (end > pos + len - 1) {_str[end] = _str[end - len];}//插入数据for (int i = 0; i <  len; ++i){_str[pos + i] = str[i];}_size += len;}void string::erase(size_t pos, size_t len){ assert(pos < _size);//如果是pos之后的所有数据if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{for (size_t i = pos + len; i <= _size; i++){_str[i - len] = _str[i];}_size -= len;}}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);//从pos开始找const char* p = strstr(_str + pos, str);if (p==nullptr){return npos;}else {return p - _str;}}//返回一个子串string string::substr(size_t pos, size_t len) { assert(pos < _size);if (len > _size - pos) {len=_size - pos;}string sub;sub.reserve(len);for (size_t i = pos; i < pos + len; i++){sub+=_str[pos+i];}return sub;}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 || s1 == s2;}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);}ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear();const int N = 256;char buff[N];int i = 0;char ch;//in >> ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1){buff[i] = '\0';s += buff;i = 0;}//in >> ch;ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}
}

3.知识补充

1.对于迭代器,它从用法上有四种:正向,反向,const和普通两两组合。其中对于const迭代器,它用于访问那些仅可读的容器或对象,而对于普通的迭代器,不仅可读而且可写。

const string s3("hello world");
string::const_iterator cit = s3.begin();
while (cit != s3.end())
{//*cit += 2;显然这里已经不能修改容器的元素,因为使用了const迭代器cout << *cit << " ";++cit;
}
cout << endl;//string::const_reverse_iterator rcit = s3.rbegin();
auto rcit = s3.rbegin();
while (rcit != s3.rend())
{// *rcit += 2;cout << *rcit << " ";++rcit;
}
cout << endl;

http://www.dtcms.com/a/330287.html

相关文章:

  • Heterophily-aware Representation Learning on Heterogeneous Graphs
  • AI - 工具调用
  • AI智能体记忆策略
  • 10 ABP 模块系统
  • [转]SURREAL数据集国内下载链接
  • Deep Agents:用于复杂任务自动化的 AI 代理框架
  • nm命令和nm -D命令参数
  • 19. 重载的方法能否根据返回值类型进行区分
  • Java之String类
  • 3.Cursor提效应用场景实战
  • UEdior富文本编辑器接入AI
  • 算法篇----分治(归并排序)
  • 云电竞盒子对游戏性能有影响吗?
  • 手游业务怎么做防护
  • 智慧城市数字孪生:城市管理的“平行宇宙”
  • 补环境基础(四) Hook插件
  • 黎阳之光立体物业透明管理:开启智慧物业新时代
  • 设计原则之【抽象层次一致性(SLAP)】,方法也分三六九等
  • 安装Win10怎样跳过欢迎界面
  • ant-design a-from-model的校验
  • poetry
  • 《深入解析C++中的Map容器:键值对存储的终极指南》
  • 基于51单片机zigbee的病房呼叫系统
  • Datawhale AI夏令营 「2025全球AI攻防挑战赛-赛道一:图片全要素交互认证-生成赛」的赛事项目实践
  • springboot接口请求参数校验
  • 双椒派E2000D系统盘制作全攻略
  • 在腾讯云CodeBuddy上实现一个AI聊天助手
  • 实盘回测一体的期货策略开发:tqsdk获取历史数据并回测,附python代码
  • java循环分页查询数据,任何把查询到的数据,分批处理,多线程提交到数据库清洗数据
  • 第十二节:粒子系统:海量点渲染