Cpp::STL—string类的使用与理解(上)(8)

news2024/9/28 22:26:22

文章目录

  • 前言
  • 一、string类对象的构造函数
    • string()
    • string(const char* s)
    • string(size_t n, char c)
    • string(const string& s)
    • string(const string& str,size_t pos,size_t len = npos)
  • 二、string类对象的容量操作
    • size与length
    • capacity
      • capacity返回值比size大
      • capacity的扩容机制
    • empty
    • clear
    • shrink_to_fit
    • reserve
      • 关于reserve与扩容的一些问题
    • resize
      • 字符串变短(n>size)
      • 字符串在容量内变长(capacity>=n>size)
      • 字符串修改长度超出容量(n>capcity)
  • 三、string类对象的访问
    • operator[ ]
    • 迭代器(简单介绍)
      • 反向迭代器
  • 四、string类对象的遍历操作
    • for+[ ]
    • 迭代器(begin(),end())
    • 范围for
  • 总结


前言

  C语言中,字符串是以’\0’结尾的一些字符的集合(C-string),为了操作方便,C标准库中提供了一些 str 系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
  这就是我们为什么学习string类的理由,如果你对前面类和对象的内容了解透彻的话,学习STL按道理来说应该不算太难

另外,我还想说,string类其实是当时发布STL时候的前排兵,说白了就是没什么可供参考,所以你在学习它的时候有时候会感觉很挫很冗余,这就对了,这就是正确的感觉


一、string类对象的构造函数

在这里插入图片描述

  事先声明,就像我前言说的一样,string设置的很冗余,所以我挑选几个常见的来讲,甚至于有几个我挑出来的也不多见,下文同理

(constructor)函数名称功能说明
string() (重点)默认构造,创建一个空串,这个空串的长度是0
string(const char* s) (重点)用C-string来构造string类对象
string(size_t n, char c)通过一个字符来构造,一个字符重复n次
string(const string& s) (重点)拷贝构造函数
string(const string& str,size_t pos,size_t len = npos)(重点)从str中pos指向位置先后拷贝len长度字符,两种情况下文给出

string()

功能:构造空string类对象,其长度为0

#include<iostream>
#include<string>
using namespace std;

int main()
{
	string s1;
	cout<<s1.length()<<endl; // 0

	return 0;
}

string(const char* s)

功能:使用C-string构造string类对象。在非空字符串中,从s指向位置拷贝一份字符串

#include<iostream>
#include<string>
using namespace std;

int main()
{
	// 相当简便
	string s1("Hello,world!");
	cout << s1 << endl; // Hello,world!

	// 其实我们还有一种清晰明了的方法
	const char* s = "Hello,world!";
	string s2(s);
	cout << s2 << endl; // Hello,world!

	return 0;
}

string(size_t n, char c)

功能:通过一个字符来构造,一个字符重复n次

#include<iostream>
#include<string>
using namespace std;

int main()
{
	string s1(5,'r');
	cout << s1 << endl; // rrrrr

	return 0;
}

string(const string& s)

功能:拷贝构造,通过已有对象拷贝构造一个新的对象,这个对象和已有对象在逻辑上是相同的

#include<iostream>
#include<string>
using namespace std;

int main()
{
	// 这两种都称为拷贝构造
	string s1("Hello,world!");
	string s2(s1);
	string s3 = s1;

	return 0;
}

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

功能:从str中pos指向位置先后拷贝len长度字符。出现两种结果:拷贝到str最后一个字符或没有达到最后一个字符完成拷贝

  第三个参数len类型为 size_t ,而缺省值 npos == -1 导致了 npos 按补码形式是32个比特位1,而又被当作正数还原为原码,就是 INT_MAX(涉及到编码那块,考验你前面学得扎不扎实的时候到了)。所以对于当没有明确 len 数值,默认是从 pos 位置拷贝字符串到最后一个字符,而如果str已经拷贝到最后一个字符了,那就结束拷贝

这是原文翻译,你英语好的话你自己翻译~
Copies the portion of str that begins at the character position pos and spans len characters (or until the end of str, if either str is too short or if len is string::npos).

#include<iostream>
#include<string>
using namespace std;

int main()
{
	string s1("Hello,world!");
	string s2(s1,2,3);
	cout << s2 << endl;

	return 0;
}

二、string类对象的容量操作

在这里插入图片描述

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

size与length

你可能会想,这两个方法都是返回字符串有效字符的长度,会不会有什么区别?

int main()
{
	string str1("hello world");
	
	cout << str1.size() << endl; // 11
	cout << str1.length() << endl; // 11
	
	return  0;
}

答案是没有

那为什么还会有两个同样作用的方法的存在呢?其实就是我说的,string是STL的前排兵,一开始想着要有个返回长度的方法,命名length也很合理,可是后面vector、map、set都是size,为了统一,只好也跟着加了个size

如果你问为什么不把length给删掉,其实你细想,删掉的话,已经用length这个方法写代码的个人、公司是不是又有意见了,所以一般我们只加不改,这就是向前兼容原则

一言以蔽之,length合理,size统一更规范

capacity

功能:返回当前对象所分配的存储空间的大小,一般情况下capacity返回大小中不包含’\0’

int main()
{
	string str1("hello world");

	cout << str1.size() << endl; // 11
	cout << str1.capacity() << endl; // 15
	
	return 0;
}

capacity返回值比size大

  std::string在底层上是属于动态数组,数组大小是不固定,根据实际需要进行调正,由于经常性出现频繁插入字符的清空,只存在size情况下,会导致频繁地向系统申请空间,性能降低,其实size和capacity的这种用法我们在之前也见识过
在这里插入图片描述

capacity的扩容机制

在这里插入图片描述
  我们会发现string类还是很智能的,能自动扩容,不需要我们操心,但是我们可以注意下扩容的机制,这其实跟编译器和指标因子的不同而有所差异

VS(msvc):扩容机制是第一次扩容到原来空间的两倍左右,之后则扩容当前空间的1.5倍
g++:扩容机制是以当前空间的两倍

empty

功能:检测字符串是否释放为空,是空返回true,否则返回false

int main()
{
	string str1;
	
	if (str1.empty()) // 判断释放为空
		cout << "为空" << endl;
	else
		cout << "非空" << endl;

	return 0;
}

clear

功能:清空string有效字符资源,不改变底层空间大小。影响有效元素size,不会影响空间容量大小capacity

int main()
{
	string str1("hello world");
	cout << str1.size() << endl; // 11
	cout << str1.capacity() << endl; // 15

	str1.clear(); // 清空有效字符
	cout << str1.size() << endl; // 0
	cout << str1.capacity() << endl; // 15

	return 0;
}

shrink_to_fit

功能:向系统请求字符串缩容到适合大小,但是该函数对于字符串的长度和内容是没有影响的,如果使用后容量并没有发生变化,那么可能字符串对象可能已经使用内存管理策略去避免频繁的内存分配和释放

int main()
{
	string str("hello world");
	cout << str.size() << endl; // 11
	cout << str.capacity() << endl; // 15
	cout << endl;

	str.resize(100);
	cout << str.size() << endl; // 100
	cout << str.capacity() << endl; // 111
	cout << endl;

	str.shrink_to_fit();
	cout << str.size() << endl; // 100
	cout << str.capacity() << endl; // 111
	cout << endl;

	return 0;
}

这个方法要少用,甚至其实我觉得都可以不用,因为我们一般认为缩容是释放部分空间从而达到正确大小,可是这是不对的,释放只能整个释放,事实上,正确的流程是,重新开一块空间,拷贝部分内容,进而释放原先的全部空间,损耗极大

reserve

功能:向系统申请预留空间,属于手动扩容

int main()
{
	string str1;
	cout << str1.capacity() << endl; // 15
	str1.reserve(100);
	cout << str1.capacity() << endl; // 111

	string str2(10, 'x');
	cout << str2.capacity() << endl; // 10
	str2.reserve(); // 缺省参数为0
	cout << str2.capacity() << endl; // 10
	
	return 0;
}

关于reserve与扩容的一些问题

  1. 既然我们前面说了编译器会自动扩容,为什么还要我们自己手动扩容呢?
    理由:扩容是需要付出代价的,如果是异地扩容,付出代价更大,需要进行空间开辟和数据拷贝,如果事先知道所需要的空间大小,使用reverse开辟足够使用的空间,减少频繁对内存的重分配,就算后期出现空间不足,也有自动扩容的机制,不需要担心大小是固定的。虽然自动扩容可以解决容量不足的情况,但是手段扩容可以减少频繁自动扩容的代价,属于一种优化手段
  2. reverse要求100个字节空间,但却开辟了111个字节空间呢?
    理由在不同编译器下机制是不同的,但是确保了至少满足所需空间。有些编译器开辟多个空间,是对reserve开辟的空间进行了二次开辟,可以灵活调用内存空间分配,在后继需要小空间,避免扩容
  3. reserve参数部分小于当前空间大小,提出申请空间请求,但是空间大小并没有发生改变
    理由:reserve进行扩容必须参数部分比当前空间大,才会改变string的底层空间总大小,否则就是无效扩容

resize

功能:改变字符串的实际长度
这里我们以"hello world"为例子来讲解三种不同情况
在这里插入图片描述

字符串变短(n>size)

int main()
{
	string str1("hello world"); // 长度为11
	cout << str1.size() << endl; // 11
	cout << str1.capacity() << endl; // 15

	str1.resize(2);
    cout << str1 << endl; // he
	cout << str1.size() << endl; // 2
	cout << str1.capacity() << endl; // 15
	
	return 0;
}

字符串在容量内变长(capacity>=n>size)

int main()
{
	string str1("hello world"); // 长度为11
	cout << str1.size() << endl; // 11
	cout << str1.capacity() << endl; // 15

	str1.resize(13);
	cout << str1 << endl; // hello world
	cout << str1.size() << endl; // 13
	cout << str1.capacity() << endl; // 15
	
	return 0;
}

字符串修改长度超出容量(n>capcity)

int main()
{
	string str1("hello world"); // 长度为11
	cout << str1.size() << endl; // 11
	cout << str1.capacity() << endl; // 15

	str1.resize(50);
	cout << str1 << endl; // hello world
	cout << str1.size() << endl; // 50
	cout << str1.capacity() << endl; // 63

	return 0;
}

  当resize修改长度超过capacity,capacity会进行自动扩容。至于最后capacity的值为什么不是50,在reserve中解释了不同编译器扩容机制是不同的

其中resize有两个重载,功能都是将字符串中有效字符个数改变到n个,不同点在于:
resize(size_t n):用’\0’来填充都出的元素空间
resize(size_t n,char c):用字符c来填充多出的元素空间

  一样的,相比之下我们鼓励用reserve,提前开好空间,避免频繁扩容

三、string类对象的访问

operator[ ]

在这里插入图片描述

我们只要了解第一个operator[ ]就行,其他都是为了规范性
我们不妨来看下这两个重载:
char& operator[ ] (size_t pos); // 读写
const char& operator[ ] (size_t pos) const; // 只读
其实,这里第一个重载体现了引用返回的一大用处:可供修改

// 相当于是自定义类型给数组化了,其实string还是蛮特殊的
// 这点我们在后面的学习会有更深的体会

int main()
{
	string str1("hello world");
	for (int i = 0 ; i < str1.size() ; i++)
	{
		// cout << str1.operator[](i) << endl;
		cout << str1[i] << endl;
	}

	const string str2("hello world");
	for (int i = 0 ; i < str2.size() ; i++)
	{
		// str2[i]++; const修饰的话,没有修改的权限
		cout << str2[i] << endl;
	}
	return 0;
}

迭代器(简单介绍)

  迭代器(Iterator)是一种用于遍历容器(如列表、字典、集合等)元素的对象,它提供了一种统一的访问容器内部元素的方式,而不必暴露容器的具体实现细节。迭代器通常用于循环结构中,让程序员能够逐个访问容器中的元素,在讲解string类对象的遍历前,我想先简要讲解下这一概念

int main()
{
	string str1("hello world");
	string::iterator it = str1.begin();
	while (it != str1.end()) // 左闭右开
	{
		cout << *it << endl;
		it++;
	}
	
	return 0;
}

  在string里,你可以暂时把它当成是类似指针的东西

  我们有很多种遍历类对象的方式,但是迭代器才是主流。对于链表、树等数据结构,迭代器不在乎底层实现,是通用的遍历容器。迭代器是一种像指针的东西,他可以是指针也可以不是指针,具体还是看不同编译器的底层实现,迭代器有两种类型分别:可读可修改,可读不可修改,但是我们要注意存储迭代器的变量类型应该与容器的迭代器类型相匹配,以确保类型的一致性,避免编译器报错或者意外行为

反向迭代器

定义:string::reverse_iterator

int main()
{
	string str1("hello world");
	string::reverse_iterator rit = str1.rbegin();
	
	while (rit != str1.rend())
	{
		cout << *rit << "";
		++rit; // 请注意是++
	}
	cout << endl;
	
	return 0;
}

其实,这个反向迭代器的要求应该很少,我觉得正向的就可以满足所需了

四、string类对象的遍历操作

for+[ ]

前文已讲过这两个方法
// str.operator[ ](size_t pos)
// str[pos]

我前面也说了,这很像数组,所以我们很自然的写出这个遍历

int main()
{
	string str("Hello,world!");

	for (size_t i = 0; i < str.size(); i++) {
		cout << str[i]; // Hello,world!
	}
	cout << endl;

	return 0;
}

迭代器(begin(),end())

前文说,这很像指针,这样一提醒后该怎么遍历,也是很自然而然就能写出来

int main()
{
	string str("Hello,world!");

	string::iterator it1 = str.begin();
	while (it1 != str.end()) {
		cout << *it1;
		it1++;
	}
	cout << endl;

	return 0;
}

范围for

我们说范围for能够自动遍历所有元素,其实哪有什么自动化,范围for的底层还是迭代器,这点结论大家可自行查看汇编代码得出

int main()
{
	string str("Hello,world!");

	for (const auto& ch : str) {
		cout << ch;
	}
	cout << endl;

	return 0;
}

总结

  我真的要花大篇幅来讲string,这是因为其是我们学习STL的第一课,并且就像我在正文一再吐槽的,string设计的是真的冗余

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

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

相关文章

将CSS OKLCH颜色转换为十六进制HEX颜色和十六进制整数格式

我查找了全网都查不到OKLCH&#xff08;&#xff09;方法是颜色转换方法&#xff0c;那今天小编就给大家分享我的方法&#xff0c;可能会有点点误差&#xff0c;但是大体不影响。 程序员必备宝典https://tmxkj.top/#/示例&#xff1a;oklch(0.253267 0.015896 252.418) 得到H…

Go基础学习08-并发安全型类型-通道(chan)深入研究

文章目录 chan基础使用和理解通道模型&#xff1a;单通道、双通道双向通道单向通道单向通道的作用 非缓冲通道 通道基本特性通道何时触发panicChannel和Select结合使用Select语句和通道的关系Select语句的分支选择规则有那些Select和Channel结合使用案例一Select和Channel结合使…

Java底层并发:线程、volatile

在Java的并发编程中&#xff0c;线程、volatile关键字、原子性、临界区以及DelayQueue是一些重要概念。理解这些内容对于编写高效且线程安全的程序至关重要。 1. 线程的基本概念 Java中的线程是程序执行的最小单位。Java提供了多种创建线程的方式&#xff0c;最常用的方式是继…

英特尔终于找到了Raptor Lake处理器崩溃与不稳定问题的根源

技术背景 在过去的几个月里&#xff0c;一些用户报告称他们的第13代和第14代Intel Core“Raptor Lake”处理器遇到了系统崩溃和不稳定的情况。这些问题最初在2024年7月底被英特尔识别出来&#xff0c;并且初步的诊断显示&#xff0c;这些问题与微码有关&#xff0c;该微码使CP…

【JavaEE】——各种“锁”大总结

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c; 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能够帮助到你&#xff01; 目录 一&#xff1a;乐观锁和悲观锁 1&#xff1a;乐观锁 2&#xff1a;悲观锁 3&#xff1a;总结 二&am…

人工智能实战用折线图解读产业GDP发展态势

内容提要 项目分析项目实战 一、项目分析 1、问题提出 我们拿到一大堆关于GDP的数据&#xff0c;如何从这些表面看起来杂乱无章的数据中解读出一些有价值的信息呢? 显然&#xff0c;如果能将这些数据以图形的方式展现出来&#xff0c;例如将这些数据值随时间&#xff08;…

备考中考的制胜法宝 —— 全国历年中考真题试卷大全

在中考这场重要的战役中&#xff0c;每一分都至关重要。为了帮助广大考生更好地备考&#xff0c;我们精心整理了这份全国历年中考真题试卷大全&#xff0c;旨在为大家提供最全面、最权威的备考资料。 文章目录 1. 全科覆盖&#xff0c;无遗漏2. 历年真题&#xff0c;权威可靠3.…

【微服务】springboot 实现动态修改接口返回值

目录 一、前言 二、动态修改接口返回结果实现方案总结 2.1 使用反射动态修改返回结果参数 2.1.1 认识反射 2.1.2 反射的作用 2.1.3 反射相关的类 2.1.4 反射实现接口参数动态修改实现思路 2.2 使用ControllerAdvice 注解动态修改返回结果参数​​​​​​​ 2.2.1 注解…

【C++算法】4.双指针_快乐数

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解&#xff1a; 题目链接&#xff1a; 202.快乐数 题目描述&#xff1a; 解法 根据题目来看&#xff0c;可能是无限循环&#xff0c;也可能是快乐数。因为就相当于下图&#xff1a; 无限循环可…

QT--基础

将默认提供的程序都注释上意义 0101.pro QT core gui #QT表示要引入的类库 core&#xff1a;核心库 gui&#xff1a;图形化界面库 #如果要使用其他库类中的相关函数&#xff0c;则需要加对应的库类后&#xff0c;才能使用 greaterThan(QT_MAJOR_VERSION, 4): QT wid…

AMD 矩阵核心

AMD matrix cores — ROCm Blogs 注意&#xff1a; 本文博客之前是 AMD lab notes 博客系列的一部分。 矩阵乘法是线性代数的一个基本方面&#xff0c;它在高性能计算&#xff08;HPC&#xff09;应用中是一个普遍的计算。自从 AMD 推出 CDNA 架构以来&#xff0c;广义矩阵乘法…

基于SpringBoot+Vue+MySQL的甜品店管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 在数字化浪潮的推动下&#xff0c;甜品店行业也面临着转型与升级的需求。传统的线下经营模式已难以满足现代消费者对于便捷、高效购物体验的追求。为了提升运营效率、优化顾客体验&#xff0c;我们设计了一款基于SpringBoot后端…

Django基础-创建新项目,各文件作用

学习Django的前置知识&#xff1a; python基本语法&#xff1a;需要掌握Python中的变量、循环、条件判断、函数等基本概念。面向对象编程&#xff08;OOP&#xff09;&#xff1a;Django的核心架构基于面向对象编程&#xff0c;许多功能&#xff08;如模型和视图&#xff09;依…

黑神话悟空小西天

游戏里我们一开始就出现一个很可爱的小和尚&#xff0c;当脚步声传来&#xff0c;小和尚化身为一尊弥勒佛&#xff0c;而这尊弥勒佛的大小和位置都在说&#xff0c;这里没有弥勒佛的位置。 随后天命人进入一片雪地&#xff0c;遇到了赤尻马猴&#xff0c;打跑赤尻马猴&#xff…

C++_unordered系列关联式容器(哈希)

unordered系列关联式容器&#xff0c;我们曾在C_map_set详解一文中浅浅的提了几句。今天我们来详细谈谈 本身在C11之前是没有unordered系列关联式容器的&#xff0c;unordered系列与普通的map、set的核心功能重叠度达到了90%&#xff0c;他们最大的不同就是底层结构的不同&…

AVL树(平衡二叉树)的介绍以及相关构建

欢迎光临 &#xff1a; 羑悻的小杀马特-CSDN博客 目录 一AVL树的介绍&#xff1a; 二AVL树的实现&#xff1a; 1结构框架&#xff1a; 2节点的插入&#xff1a; 旋转&#xff1a; 21左单旋&#xff1a; 2.1.1左单旋介绍及步骤&#xff1a; 2.1.2左单旋代码实…

【JavaSE系列】IO流

目录 前言 一、IO流概述 二、IO流体系结构 三、File相关的流 1. FileInputStream 2. FileOutputStream 3. FileReader 4. FileWriter 四、缓冲流 五、转换流 1. InputStreamReader 2. OutputStreamWriter 六、数据流 七、对象流 八、打印流 九、标准输入输出流…

C++学习9.28

1> 创建一个新项目&#xff0c;将默认提供的程序都注释上意义 por QT core gui #QT表示引入的类库 core:核心库例如IO操作在该库中 gui:图形化显示库 #如果要使用其他类库中的相关函数&#xff0c;就需要调用相关类库后&#xff0c;才能加以使用greaterThan(Q…

c++926

1.什么是虚函数&#xff1f;什么是纯虚函数&#xff1f; 虚函数&#xff1a;被virtual关键字修饰的成员函数&#xff0c;用于实现多态性&#xff0c;通过基类访问派生类的函数。纯虚函数&#xff1a;在虚函数后面添加0&#xff0c;只有声明而没有实现&#xff0c;需要派生类提…

天龙八部怀旧单机微改人面桃花+安装教程+GM工具+虚拟机一键端

今天给大家带来一款单机游戏的架设&#xff1a;天龙八部怀旧单机微改人面桃花。 另外&#xff1a;本人承接各种游戏架设&#xff08;单机联网&#xff09; 本人为了学习和研究软件内含的设计思想和原理&#xff0c;带了架设教程仅供娱乐。 教程是本人亲自搭建成功的&#xf…