C++STL的string(超详解)

文章目录

  • 前言
    • C语言的字符串
  • string
  • string类的常用接口
    • string类的常见构造
      • string (const string& str);
      • string (const string& str, size_t pos, size_t len = npos);
    • capacity
      • size和length
      • reserve
      • resize
        • resize可以删除数据
    • modify
      • 尾插
        • 插入字符
        • 插入字符串
      • insert
      • erase
      • replace
    • 迭代器
      • swap
      • c_str
      • find
    • 反向迭代器
    • const迭代器
    • auto
  • 自己实现的string

前言

无论c++还是c语言,字符串都是最常见的类之一。我们日常当中写的程序必然要存储数据。
我们的内置类型只能表示基础的信息,无法表示一些复杂的信息,比如int, double.但我们要表示身份证、住址那就表示不了了。

C语言的字符串

C语言用字符数组来表示字符串,但是这里有一个巨大的缺陷。

1.不够好用
2.不能够很好的管理
比如:用字符数组来存储地址,但是地址的长度要修改呢?这就很麻烦。

所以c++提供了一个管理字符串的一个类,string,你可以把它想象成存储字符的顺序表。

string

string是一个类模板,它是typedef出来的。
在这里插入图片描述

string的底层是一个字符数组,但是你可以把它想象成可以增删查改的数组。

string类的常用接口

string类的常见构造

string (const string& str);

#include <string>
int main()
{string s2("hello world");for (size_t i = 0; i < s2.size(); ++i){s2[i]++;}cout << s2 << endl;for (size_t i = 0; i < s2.size(); ++i){cout << s2[i] << " ";}cout << endl;return 0;
}

在这里插入图片描述

为什么可以这样构造?

string s1="hello world";

本质是类型转换,把const char* 转换成string.
它是先构造再拷贝构造,然后优化成了构造。

string (const string& str, size_t pos, size_t len = npos);

int main()
{string s3 = "hello world";string s4(s3, 6, 3);cout << s4 << endl;string s5(s3, 6, 13);//如果字符不够,有多少取多好cout << s5 << endl;return 0;
}

在这里插入图片描述
** 如果第三个参数不给呢?**

string s6(s3, 6);

这里给了一个参数,npos,并且npos=-1;
-1代表什么,这里其实是无符号,所以-1表示42亿九千万。
npos很大意味着取到结束

capacity

容量没有把’\0’算进去。

size和length

size和length有什么差异?

int main()
{string s3 = "hello world";cout << s3.size() << endl;cout << s3.length() << endl;return 0;
}

没有什么差异。

** 那为什么同时会有这两个东西呢?**
跟STL的发展历史有关。平时用size就可以了。

reserve

** 观察vs编译器的扩容情况**
在这里插入图片描述

** 那假如我知道要开多少空间呢?**
我们可以调用这样一个接口,reserve
知道需要开多少空间,提前开空间,减少扩容,提高效率。

s.reserve(100)

注意,你要100的空间,它不一定给你100的空间,它可能为了一些对齐等等的原因,可能开的比100大一些。

resize

resize和reserve功能相似,但也有很大区别。
resize除了开空间它还帮助初始化

int main()
{string s1;s1.reserve(100);cout << sizeof(s1) << endl;cout << s1.size() << endl;cout << "--------------" << endl;string s2;s2.resize(100);cout << sizeof(s2) << endl;cout << s2.size() << endl;return 0;
}

在这里插入图片描述

那初始化填了什么值呢?
填的是0;

那我想填其他的值怎么办呢?
比size小,删除数据,保留前5个

s2.resize(100, 'x');

但是它不会缩容。

这里提一个点,为什么编译器不会轻易的缩容?
缩容其实是不支持原地缩的,原地缩荣的话,那以为这要delete一部分空间,这增加了内存管理的难度。
真正缩荣都是开好另一块空间,然后将需要保留的数据拷贝过去。这也意味着缩容肯定会带来性能上的消耗。一般来说不要轻易的缩容。

resize可以删除数据
s2.resize(5);

modify

string 最好用的地方就是不用去管空间。

尾插

插入字符
int main()
{string s3 = "hello world";s3.push_back(' ');s3.push_back('!');return 0;
}
插入字符串
s3.append("bit");

但是我们不管是插入字符还是插入字符串都不喜欢这样写,我们喜欢用运算符重载+=;

s3+=' ';
s3+='!';s3+='bit';

不过+=底层还是调用了push_back和append.

insert

如果我们再头部或者中间插入一个数据,我们就可以用insert;

int main()
{string s1("world");s1.insert(0, "hello");cout << s1 << endl;return 0;
}

在这里插入图片描述

中间插入

int main()
{string s1("world");s1.insert(0, "hello");//s1.insert(5, 1, ' ');//s1.insert(5, " ");s1.insert(s1.begin() + 5, ' ');//迭代器位置cout << s1 << endl;return 0;
}

在这里插入图片描述

不推荐经常使用,能不用就不用。因为要挪动数据,影响性能。

erase

删除一个字符

int main()
{string s2("hello world");//s2.erase(5, 1);s2.erase(s2.begin() + 5);//迭代器位置cout << s2 << endl;return 0;
}

在这里插入图片描述

删除多个字符

int main()
{string s2("hello world");//s2.erase(5, 30);//如果要删除的数据大于字符串的剩余长度,那就相当于全部删完s2.erase(5);cout << s2 << endl;return 0;
}

在这里插入图片描述

不推荐经常使用,能不用就不用。因为要挪动数据,影响性能。

replace

将hello world 中间空格,替换成%%d

string s1("hello world");
s1.replace(5, 1, "%%d");

replace 能不用就不用,为什么?

1.空间不够就要扩容
2.需要挪动数据

有个题目,把hello world i love you 中的所有空格替换成%20

int main()
{string s1("hello world i love you");size_t pos = s1.find(' ');while (pos != string::npos){s1.replace(pos, 1,"%20");pos = s1.find(' ');}cout << s1 << endl;return 0;
}

在这里插入图片描述

那上面的代码能不能优化一下呢?
每次都是从0的位置开始找,其实没必要。
还有一个点就是,replace可能会扩容。

int main()
{string s1("hello world i love you");size_t num = 0;//计算有多少个' ',为开空间做准备for (auto ch : s1){if (ch == ' ')++num;}// 提前开空间,避免repalce时扩容s1.reserve(s1.size() + 2 * num);size_t pos = s1.find(' ');while (pos != string::npos){s1.replace(pos, 1, "%20");pos = s1.find(' ', pos + 3);}cout << s1 << endl;return 0;
}

再给大家看个好玩的东西

int main()
{string s1("hello world i love you");string newStr;size_t num = 0;for (auto ch : s1){if (ch == ' ')++num;}// 提前开空间,避免repalce时扩容newStr.reserve(s1.size() + 2 * num);for (auto ch : s1){if (ch != ' ')newStr += ch;elsenewStr += "%20";}s1 = newStr;cout << newStr << endl;return 0;
}

在这里插入图片描述

这个是以空间换时间的方式,不需要挪动数据。

迭代器

如果不用[]加下标怎么访问string对象呢,这里要用到迭代器。

int main()
{string s1("hello world");string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

这个代码看起来有点懵,可以暂时先把迭代器理解为指针。
在这里插入图片描述

bein()表示第一个字符的地址,end()表示最后一个字符下一个位置的地址。
它是左闭右开。

其实还可以用范围for来访问,不过范围for的底层原理还是迭代器

for (auto ch : s1)
{cout << ch << " ";
}
cout << endl;

swap

看下面的代码有什么区别?

int main()
{string s1("hello world");string s2("xxxxx");s1.swap(s2);cout << s1 << endl;cout << s2 << endl;cout << "------------" << endl;swap(s1, s2);cout << s1 << endl;cout << s2 << endl;return 0;
}

s1.swap()和swap()有什么区别?
我们知道swap()是类模板,所有类型都可以交换,是泛型模板。

s1.swap()和swap()谁的效率高?
很明显s1.swap()的效率更高,s1和s2两段空间,只需要交换两段空间指针的指向就可以了。
而swap()要产生一个临时对象,需要调用拷贝构造,还是深拷贝,然后又需要两次赋值。

c_str

int main()
{string s1("hello world");cout << s1 << endl;cout << s1.c_str() << endl;return 0;
}

两者都可以打印数据,那它们的区别是什么?
s1.c_str()是遇到‘\0’结束,而cout << s1 << endl;则是根据s1.size()来打印的。

c_str的主要作用还是,为c的接口提供兼容

find

find其实前面已经见过了,再看一个例子,怎样取文件名的后缀。

int main()
{string file("string.cpp");size_t pos = file.find('.');if (pos != string::npos)//npos是静态成员变量,所以可以这样写,直接加上类域{string suffix = file.substr(pos, file.size() - pos);//substr表示从某个位置开始,取len字符长度的字符串string suffix = file.substr(pos);cout << suffix << endl;}return 0;
}

在这里插入图片描述

如果文件有多个点怎么样找后缀?
倒着找

size_t pos = file.rfind('.');

反向迭代器

除了正着访问string,还可以反着访问,这里要用到反向迭代器。

int main()
{string s1("hello world");string::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()){cout << *rit << " ";++rit;}return 0;
}

在这里插入图片描述

在这里插入图片描述

const迭代器

写成这样为什么会报错?

void Func(const string& s)
{// 遍历和读容器的数据,不能写string::iterator it = s.begin();//报错这里赋值不过去while (it != s.end()){//*it += 1;cout << *it << " ";++it;}cout << endl;
}
int main()
{	string s1("hello world");Func(s1);return 0;
}

在这里插入图片描述
有两个版本,const对象返回const对象,普通对象返回普通对象。
所以很显然,应该改成const迭代器

string::const_iterator it = s.begin();//只能遍历和读取容器的数据,不能写

为什么要有返回const的迭代器呢?
不允许被修改。

普通迭代器和 const迭代器的区别?
能不能写的问题。

auto

正向的普通迭代器和const迭代器一共就有4种,我们可以用auto来优化一下迭代器的写法。

auto it = s.begin();

不过它也有一个弊端,降低了程序的可读性。

自己实现的string

有个问题,下面结果是多少?下面是在vs2022实现的。
在这里插入图片描述

	cout << sizeof(s3) << endl;

s3存了字符串会不会变得更大一点?
不会,字符串是存在堆上的空间的,不用算进去。


为什么是std的string是28?
std里的string里还有一些额外的东西。

在g++编译器里就不演示了,感兴趣的老铁可以自行去测试。

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

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

相关文章

在vscode下将ipynb文件转成markdown(.md文件)的方法

在vscode下将ipynb文件转成markdown&#xff08;.md文件&#xff09;的方法 写在最前面安装nbconvert工具vscode界面 or cmd终端基本命令将ipynb文件转换成md文件 总结 写在最前面 VSCode作为一款强大的代码编辑器&#xff0c;提供了广泛的功能。它支持多种文件格式的编辑和查…

【精选】设计模式——工厂设计模式

工厂设计模式是一种创建型设计模式&#xff0c;其主要目的是通过将对象的创建过程封装在一个工厂类中来实现对象的创建。这样可以降低客户端与具体产品类之间的耦合度&#xff0c;也便于代码的扩展和维护。 工厂设计模式&#xff1a; 以下是Java中两个常见的工厂设计模式示例…

【数据结构 — 排序 — 插入排序】

数据结构 — 排序 — 插入排序 一.排序1.1.排序的概念及其运用1.1.1排序的概念1.1.2排序运用1.1.3 常见的排序算法 二.插入排序2.1.直接插入排序2.1.1.算法讲解2.1.2.代码实现2.1.2.1.函数定义2.1.2.2.算法接口实现2.1.2.3.测试代码实现2.1.2.4.测试展示 2.2.希尔排序2.2.1.算法…

vue路由导航守卫(全局守卫、路由独享守卫、组件内守卫)

目录 一、什么是Vue路由导航守卫&#xff1f; 二、全局守卫 1、beforeEach 下面是一个beforeEach的示例代码&#xff1a; 2、beforeResolve 下面是一个beforeResolve的示例代码&#xff1a; 3、afterEach 下面是一个afterEach的示例代码&#xff1a; 三、路由独享守卫…

scikit-learn实现线性回归

要学习scikit-learn,我们必须要到scikit-clearn的官网中去查看公式和原理 scikit-learn 官网 scikit-learn 中文社区 进入官网一以后我们找到回归&#xff0c;然后再有监督学习中找到线性模型 scikit-learn实现简单的线性回归 公式&#xff1a; L2范数是指向量中每个元素的平…

NLP项目实战01之电影评论分类

介绍&#xff1a; 欢迎来到本篇文章&#xff01;在这里&#xff0c;我们将探讨一个常见而重要的自然语言处理任务——文本分类。具体而言&#xff0c;我们将关注情感分析任务&#xff0c;即通过分析电影评论的情感来判断评论是正面的、负面的。 展示&#xff1a; 训练展示如下…

软件设计中如何画各类图之八深入解析部署图:物理布局与系统架构的视觉化呈现

目录 1 前言2 部署图的符号及说明3 画部署图的步骤3.1 **识别节点**3.2 **定义组件**3.3 **标识部署关系**3.4 **添加细节** 4 部署图的用途4.1 **系统设计与规划**4.2 **系统架构分析**4.3 **系统维护与升级** 5 实际场景举例5.1 Web应用部署图5.2 云端服务部署图 6 结语 1 前…

【GAMES101】观测变换

图形学不等于 OpenGL&#xff0c;不等于光线追踪&#xff0c;而是一套生成整个虚拟世界的方法 记得有个概念叫光栅化&#xff0c;就是把三维虚拟世界的事物显示在二维的屏幕上&#xff0c;这里就涉及到观察变换 观察变换&#xff0c;叫viewing transformation&#xff0c;包括…

二分查找|前缀和|滑动窗口|2302:统计得分小于 K 的子数组数目

作者推荐 贪心算法LeetCode2071:你可以安排的最多任务数目 本文涉及的基础知识点 二分查找算法合集 题目 一个数组的 分数 定义为数组之和 乘以 数组的长度。 比方说&#xff0c;[1, 2, 3, 4, 5] 的分数为 (1 2 3 4 5) * 5 75 。 给你一个正整数数组 nums 和一个整数…

【带头学C++】----- 九、类和对象 ---- 9.10 C++设计模式之单例模式设计

❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️麻烦您点个关注&#xff0c;不迷路❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️ 目 录 9.10 C设计模式之单例模式设计 举例说明&#xff1a; 9.10 C设计模式之单例模式设计 看过我之前的文章的&#xff0c;简单讲解过C/Q…

Python:核心知识点整理大全8-笔记

目录 ​编辑 4.5 元组 4.5.1 定义元组 dimensions.py 4.5.2 遍历元组中的所有值 4.5.3 修改元组变量 4.6 设置代码格式 4.6.1 格式设置指南 4.6.2 缩进 4.6.3 行长 4.6.4 空行 4.6.5 其他格式设置指南 4.7 小结 第5章 if语句 5.1 一个简单示例 cars.py 5.2 条…

Kafka快速实战以及基本原理详解

文章目录 一、Kafka介绍为什么要用Kafka 二、Kafka快速上手实验环境单机服务体验 三、理解Kakfa的消息传递机制四、Kafka集群服务五、理解服务端的Topic、Partition和Broker七、Kafka集群的整体结构八、Kraft集群Kraft集群简介配置Kraft集群 一、Kafka介绍 ChatGPT对于Apache …