C++ string类的初步了解

news2024/11/27 14:36:48

目录

一.   为什么学习string类?

1.C语言中的字符串

2.string类

二.   string类的常用接口说明

1.构造

 2.容量

size和length

 capacity

clear

empty

reserve

resize

3.元素访问 

operator[]

at

front、back

4.迭代器

​编辑begin、end

rbegin、rend 

cbegin、cend、crbegin、crend

5.增添、删除、修改

 operator+=

append 

push_back

assign

insert

replace

swap

pop_back 

 6.字符串操作

 c_str

find 

rfind

substr 

7.string类非成员函数

​operator+

relational operators 

operator>>

getline


一.   为什么学习string类?

1.C语言中的字符串

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。


2.string类

string类介绍

1. 字符串是表示字符序列的类

2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。

3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。

4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。

5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结:

1. string是表示字符串的字符串类

2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;

4. 不能操作多字节或者变长字符的序列。



二.   string类的常用接口说明

标红的是常用的

1.构造

在string类的成员函数中,最开始讲的是constructor (构造) 、destructor (析构) 以及operator (赋值)

我们主要来看一下构造

 在C++98中,给与了7种方式,我们可以对照着后面给的注释分别来看一下

首先,第一种,不用多说,构造一个空的string类(即一个空的字符串)

第二种的参数是str,即另外一个string类 ,功能实质上就是一个拷贝构造

第三种的参数就变成了三个,分别为str、pos、以及len,其中pos指的是拷贝开始的位置(类似于数组的下标,同样是从0开始),而len指的是所拷贝的长度,而后面的npos则是缺省值

通过索引,我们可以知道,npos的值默认为无符号的-1,即2^31-1,由于我们在创建string类时,长度不可能这么大,所以我们可以当做在不写参数len时,默认为将后面所有拷贝进新的string类

第四种所传的参数则是一个类似于c语言中的字符串

第五种在第四种的基础上,增加了一个参数n,意为将字符串的前n个字符进行拷贝

第六种的两个参数n与c,指的是构造一个size为n的string类并初始化为字符c

第七种需要先掌握迭代器,暂时先放一放

我们可以来实践一下

void Test1()
{
	string s1;
	cin >> s1;
	string s2(s1);
	string s3(s1, 2, 3);
	string s4("abcdef");
	string s5("abcdef", 3);
	string s6(5, 'a');
	cout << "s1:" << s1 << endl;
	cout << "s2:" << s2 << endl;
	cout << "s3:" << s3 << endl;
	cout << "s4:" << s4 << endl;
	cout << "s5:" << s5 << endl;
	cout << "s6:" << s6 << endl;
}

我们也可以通过监视来看一下string类中的成员变量的情况

当然,在allocator[6]的位置也是存有'\0'的

再往后的destructor(析构),没啥好说的,就固定的一种方式

而operator=的使用方式与拷贝构造类似,这里也就不多做说明


 2.容量

后面的Iterators(迭代器)我们先放一放,先来讲一下Capacity(容量)

size和length

都是用来返回字符串的有效长度(即成员变量size)的,那么这两个接口有什么不同呢?没什么不同,那么为什么会存在两个同样方式的接口呢?这是因为,不只是string,其他容器同样也有大小,而就像树一样,它的大小不能使用length(长度)来表示,只能使用size,因此,为了与其他容器保持一致,string的接口就新增了size,而以前用于表示大小的length当然不能舍弃

 capacity

返回容量大小(字符串总长度)(即成员变量capacity)

 

 

clear

说的也很清楚,清空string,而清空的是字符串中的有效部分

void Test2()
{
	string s1("abcdef");
	s1.clear();
}

clear前

clear后

 

empty

即判断字符串有效部分是否为空,空返回1,非空返回0

void Test2()
{
	string s1("abcdef");
	cout << s1.empty() << endl;
	s1.clear();
	cout << s1.empty() << endl;
}

reserve

通过解释,我们可以知道该接口是将对象的capacity变为n,当capactiy小于n时,直接扩增到n或者更大,当大于时,其实是一个未定义的行为,会根据编译器进行优化,而同时,不管怎么优化,都不能对字符串的有效部分进行改变。

void Test2()
{
	string s1("abcdef");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.reserve(14);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.reserve(18);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
}

很显然,我所使用的vs2022是没有对大于的情况进行优化的 

而在s1.reserve(18)中,实际将capacity扩增到了31(加上'\0'为32),这是为什么呢?我们先来探究一下扩增的规律(涉及到后面的增添数据,可以之后返回来看)

void Test3()
{
	string s1;
	size_t sz = s1.size();
	for (int i = 0; i < 1000; i++)
	{
		s1 += 'a';
		if (sz != s1.capacity())
		{
			sz = s1.capacity();
			cout << sz << ' ';
		}
	}
	cout << endl;
}

 在上面的代码中,我们将size在1000范围内所能扩增到的capacity的大小打印了出来

当然,capacity是不包含'\0'的,因此,我们可以将sz+1打印出来作为真正的大小

 

可以看到,除了第一次扩增了2倍以外,后面的扩增都大概遵循1.5倍的关系

而在reserve进行扩增时,也会从n向上找一个接近的值进行扩增,这也就是为什么我们上面会扩增到31

resize

简而言之,首先将字符串的长度(size)扩增到n,若是n<=capacity,就往原本字符串的末尾位置到位置n之间存放字符c(若是没有该参数,默认存放‘\0’),若是n>capacity,则先扩容在存放。

而要注意的是,当n<size时,size依旧会改变为n

void Test2()
{
	string s1("abcdef");
	cout << s1.size() << ' ' << s1.capacity() << endl;
	s1.resize(5);
	cout << s1.size() << ' ' << s1.capacity() << endl;
	s1.resize(12);
	cout << s1.size() << ' ' << s1.capacity() << endl;
	s1.resize(18);
	cout << s1.size() << ' ' << s1.capacity() << endl;
}

 


3.元素访问 

operator[]

 实质上就是下标访问操作符的重载,用法上也是类似

void Test4()
{
	string s1("abcdef");
	size_t i = 0;
	while (s1[i] != '\0')
	{
		cout << s1[i++] << ' ';
	}
	cout << endl;
}

当然也可以改变一下循环

void Test4()
{
	string s1("abcdef");
	size_t i = 0;
	for(i=0;i<=s1.size();i++)
	{
		cout << s1[i] << ' ';
	}
	cout << endl;
}

当然,由于该函数是传引用返回,我们也可以对其进行修改 

void Test4()
{
	string s1("abcdef");
	size_t i = 0;
	while (s1[i] != '\0')
	{
		s1[i] = s1[i] - 'a' + '1';
		cout << s1[i++] << ' ';
	}
	cout << endl;
}

而我们可以看到,还有第二种方式,即当对象被const修饰时,返回类型也就变为const char&,这时就只能完成访问,而无法做到改变

 

而为了越界,operator[] 采用的方式是断言

void Test4()
{
	string s1("abcdef");
	size_t i = 0;
	for(i=0;i<20;i++)
	{
		cout << s1[i] << ' ';
	}
	cout << endl;
}

 

at

用法其实和operator[]一样

void Test4()
{
	string s1("abcdef");
	size_t i = 0;
	for(i=0;i<s1.size(); i++)
	{
		cout << s1.at(i) << ' ';
	}
	cout << endl;
}

 

 而不同的点在于,at检查越界的方式是抛异常

void Test4()
{
	string s1("abcdef");
	size_t i = 0;
	for(i=0;i<20; i++)
	{
		cout << s1.at(i) << ' ';
	}
	cout << endl;
}

 

 

front、back

 一个是返回第一个字符,一个是返回最后一个字符,同样都可以访问,对没有const修饰的对象都可以进行改变,都无法对空的string对象进行使用,而它们与begin和end的不同就放在后面的迭代器里讲吧

void Test4()
{
	string s1("abcdef");
	cout << s1.front() << " ";
	s1.front() = 'A';
	cout << s1.back() << endl;
	s1.back() = 'F';
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << ' ';
	}
	cout << endl;
}

 


4.迭代器

begin、end

 简单来说,begin是返回的字符串第一个字符的迭代器,end是返回的字符串最后一个字符的下一个的迭代器。

我们可以使用它们来完成遍历

void Test5()
{
	string s1("abcdef");
	string::iterator it = s1.begin();
	for (it; it != s1.end(); it++)
	{
		cout << *it << ' ';
	}
	cout << endl;
}

同样,若是对象没有使用const进行修饰,我们也可以进行修改

void Test5()
{
	string s1("abcdef");
	string::iterator it = s1.begin();
	for (it; it != s1.end(); it++)
	{
		cout << *it << ' ';
		*it = *it - 'a' + '1';
	}
	cout << endl;
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << ' ';
	}
	cout << endl;
}

 

 

这里再插一点,除了operator[]、at以及迭代器,我们也可以使用范围for来进行遍历

我们之前已经学过范围for来遍历数组,而在string类中也可以范围for

void Test5()
{
	string s1("abcdef");
	for (auto& e:s1)
	{
		cout << e << ' ';
		e = e - 'a' + '1';
	}
	cout << endl;
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << ' ';
	}
	cout << endl;
}

 

rbegin、rend 

 

 与begin、rend类似,不同的是rbegin返回的是最后一个字符的反向迭代器,而rend返回的是第一个字符前一个的反向迭代器

void Test5()
{
	string s1("abcdef");
	string::reverse_iterator it = s1.rbegin();
	for (it; it != s1.rend(); it++)
	{
		cout << *it << ' ';
		*it = *it - 'a' + '1';
	}
	cout << endl;
	for (it = s1.rbegin(); it != s1.rend(); it++)
	{
		cout << *it << ' ';
	}
	cout << endl;
}

而我们或许会觉得,使用operator[]进行遍历就足够了,迭代器没有什么必要。

的确,在string中,operator[]的确更方便,但, 这并不是在所有容器中通用的,而迭代器是通用的

cbegin、cend、crbegin、crend

就是把begin、end、rbegin、rend中const的方式给单独摘出来了,没啥其他不同


5.增添、删除、修改

 operator+= 

实际上就是向后增添字符串,三种方式分别是增添string对象、 增添C形式字符串、增添字符

void Test6()
{
	string s1("abc");
	string s2("de");
	s1 += s2;
	cout << s1 << endl;
	s1 += "fg";
	cout << s1 << endl;
	s1 += 'h';
	cout << s1 << endl;
}

 

append 

 

 相较于operator,append使用的方式更多一些,大多数的使用方式其实与开始学的构造类似,可以推断出来,而第二种方式简单来说就是将参数str从下标为subpos的位置的长度为sublen的字符串增添到后面,其实参数就是在pos和len的基础上加了一个sub

void Test6()
{
	string s1("abc"),s2("abc"),s3("abc"),s4("abc"),s5("abc");
	string s6("def");
	s1.append(s6);
	s2.append(s6, 0, 2);
	s3.append("defg");
	s4.append("defg",2);
	s5.append(3, 'e');
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
}

 

 

push_back

相较而言,push_back就比较简单了

void Test6()
{
	string s1("abc");
	s1.push_back('a');
	cout << s1 << endl;
}

 

assign

大概就是重新分配字符串,类似于赋值操作符,只是用法多一些

第一种直接赋值,第二种从下标subpos开始赋值sublen个字符,第三种C类型字符串,第四种C类型字符串前n个,第五种n个字符c

void Test7()
{
	string s1("abcdef");
	string s2, s3, s4, s5, s6;
	s2.assign(s1);
	s3.assign(s1, 2, 3);
	s4.assign("abcedf");
	s5.assign("abcedf",3);
	s6.assign(3,'a');
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
}

 

insert 

就是插入,也很容易理解

除去迭代器相关的,从上到下一次为pos位置插入str、pos位置插入str的subpos往后sublen长度、pos位置插入C类型字符串、pos位置插入C类型字符串前n项、pos位置插入n个字符‘c’

这些方法和前面的众多接口都是相似的,后面类似的就不举例子了

replace

大部分都关联到迭代器,就先不说了

swap

交换字符串,就一种方法

此外,swap还有非成员函数的重载

 

pop_back 

删除尾部字符


 6.字符串操作

 c_str

说白了,就是将string对象转换为C类型的字符串,返回首元素地址,而要注意的是,不能改变

void Test8()
{
	string s1("abcdef");
	cout << s1.c_str() << endl;
}

 

find 

查找,查找string对象、C类型字符串、字符

 找到即返回第一个元素的位置,找不到返回npos 

void Test8()
{
	string s1("abcdef");
	string s2("bc");
	cout << s1.find(s2) << endl;
	cout << s1.find("ef") << endl;
	cout << s1.find("mn") << endl;
	cout << s1.find("ef", 5) << endl;
	cout << s1.find('f') << endl;
}

 

rfind

功能类似,逆序查找

void Test8()
{
	string s1("abcdef");
	string s2("bc");
	cout << s1.rfind(s2,4) << endl;
	cout << s1.rfind("ef") << endl;
	cout << s1.rfind("mn") << endl;
	cout << s1.rfind("ef", 4) << endl;
	cout << s1.rfind('f') << endl;
}

 

我们可以看到第4个,不同于find需要所有字符串在0-pos的范围内,rfind只需要所查找的第一个字符在0-pos内即可找到

substr 

从pos位置截断len长度的字符串作为string对象返回

void Test9()
{
	string s1("abcdef");
	string s2(s1.substr(2, 2));
	cout << s2 << endl;
}

 


7.string类非成员函数


operator+

string对象能和C类型字符串相加

void Test9()
{
	string s1("abc");
	string s2("def");
	cout << s1+s2 << endl;
	cout << s1 + "def" << endl;
	cout << 'a' + s2 << endl;
}

 

relational operators 

比较运算符重载,和我们之前学的C类型字符串之间的比较一样。

operator>>

 

经典的流提取与流插入操作符的重载,我们在前面也或多或少的用到了

getline  

 在流插入中,与scanf有同样的问题,可以以空格为间隔符,这样就无法输入带有空格的字符串

void Test9()
{
	string s1;
	cin >> s1;
	cout << s1 << endl;
}

 

而getline就能解决这个问题

void Test9()
{
	string s2;
	getline(cin, s2);
	cout << s2 << endl;
}

 

end

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

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

相关文章

数据结构初阶:排序

本期博客我们来到了初阶数据结构最后一个知识点&#xff1a;排序 排序&#xff0c;我们从小到大就一直在接触&#xff0c;按身高、成绩、学号等等不同的排序我们已经历许多&#xff0c;那么各位是按怎样的方法进行排序的呢&#xff1f; 废话不多说这期博客我们对各种排序方法…

测试开发 | 测试平台开发-前端开发之数据展示与分析

本文节选自霍格沃兹测试学院内部教材测试平台的数据展示与分析&#xff0c;我们主要使用开源工具ECharts来进行数据的展示与分析。ECharts简介与安装ECharts是一款基于JavaScript的数据可视化图表库&#xff0c;提供直观&#xff0c;生动&#xff0c;可交互&#xff0c;可个性化…

Unity 使用OpenXR和XR Interaction Toolkit 开发 HTCVive(Vive Cosmos)

Unity 使用OpenXR和XR Interaction Toolkit 开发 HTCVive&#xff08;Vive Cosmos&#xff09; 提示&#xff1a;作者是 Unity 2020.3 以上版本做的开发。开发VR程序需要安装 Steam&#xff0c;SteamVR, (Vive Cosmos,需要再安装VIVEPORT,VIVEConsole) OpenXR 控制设备 &#x…

OpenCV(12)-OpenCV的机器学习

OpenCV的机器学习 基本概念 计算机视觉是机器学习的一种应用&#xff0c;而且是最有价的应用 人脸识别 哈尔(Haar)级联方法深度学习方法(DNN) Haar人脸识别方法 哈尔(Haar)级联方法是专门为解决人脸识别而推出的&#xff0c;在深度学习还不流行时&#xff0c;哈尔已可以商…

Android 深入系统完全讲解(21)

关键性 EGLSurface 代码位置 继续再看看&#xff0c;代码跑到 C 里面去了。 然后关键点&#xff1a; 获取本地窗口&#xff0c;创建 Surface&#xff0c;然后 toEGLHandle 进行包裹&#xff0c;变成 EGL 上下文。 EGLSurface 。 绘制的设计本质逻辑 在这里就回归一点&#xff…

Unity学习笔记--File.ReadAllLines和File.ReadAllText的使用以及注意事项(一定要看到最后!!!)

目录前言一、File.ReadAllLines参数返回例子二、File.ReadAllText参数返回例子注意事项可能出现的问题总结前言 最近在做文件存储以及读取的时候&#xff0c;需要用到C#给我们提供的类&#xff1a;File 具体使用方法可以看官方文档&#xff1a;C# File 类 这篇文章只会说File.…

深度学习基础理念(一)

文章目录1. 机器学习 Machine Learing机器学习类别2. 机器如何找函数深度学习输入类型和输出类型机器如何找函数的1. 机器学习 Machine Learing 什么是机器学习&#xff0c;顾名思义 机器 拥有会学习的能力&#xff0c;机器学习就是让机器具备能够找函数的能力 机器学习就是找…

【C语言课程设计】通讯录(1.0版本)

前言 相信各位对于通讯录都不是很陌生吧。通讯录我们在学校的大作业&#xff0c;课程设计经常会去使用它。那么今天我们将使用C语言来实现一个简单的通讯录。 目录 前言 一、通讯录的需求 二、工程文件的创建 三、通讯录的声明和定义 四、通讯录各函数的声明和定义 五、通…

Mysql入门技能树-数据查询-练习篇

SELECT 下列 SQL 语句&#xff0c;哪一项不合法&#xff1f; 答案是&#xff1a;C select now(),3.14 now() |3.14| ----------------------- 2023-01-16 16:47:04|3.14|MySQL查询表中所有的数据可以通过“SELECT * 通配符”或者“SELECT 所有字段”实现。 SE…

hadoop3.x源码编译及cmake的问题解决:CMake failed with error code 1

一、准备工作 基础环境&#xff1a;centos7 &#xff08;1&#xff09;官方源码中编译之前对基础环境及版本的要求&#xff08;重点是红色部分&#xff09; Requirements: * Unix System* JDK 1.8 * Maven 3.3 or later * ProtocolBuffer 2.5.0 * CMake 3.1 or newer (if com…

OSCP-Vulnhub靶机记录-Hacker_Kid-v1.0.1

Vulnhub靶机记录-Hacker_Kid-v1.0.1介绍&安装信息收集页面源代码DIG信息收集xxe漏洞探测9999端口SSTI模板注入发现具有Capabilities特殊操作权限的程序原理介绍&安装 靶机名称&#xff1a;Hacker_Kid-v1.0.1 靶机难度&#xff1a;中等 虚拟机环境&#xff1a;此靶机推…

【Linux】线程互斥

目录&#x1f308;前言&#x1f338;1、Linux线程互斥&#x1f367;1.1、线程间互斥相关背景概念&#x1f368;1.2、互斥量(锁)相关背景&#x1f36f;1.3、互斥量(锁)相关API&#x1f36f;1.3.1、初始化和销毁互斥锁&#x1f370;1.3.2、互斥量加锁和解锁&#x1f372;1.3.3、互…

Python爬虫403错误的解决方案

前言程序使用一段时间后会遇到HTTP Error 403: Forbidden错误。 因为在短时间内直接使用Get获取大量数据&#xff0c;会被服务器认为在对它进行攻击&#xff0c;所以拒绝我们的请求&#xff0c;自动把电脑IP封了。 解决这个问题有两种方法。一是将请求加以包装&#xff0c;变成…

1.浮动float

提示&#xff1a;如果多一个盒子&#xff08;都设置浮动&#xff0c;则它们会按照属性值一行内显示并且顶端对齐排列&#xff09; 注意&#xff1a; 浮动的元素是互相贴靠在一起的&#xff0c;&#xff08;没有缝隙&#xff09;&#xff0c;如果父级宽度装下这些浮动盒子&#…

MyBatis 详解 (2) -- 增删改操作

MyBatis 详解 2 -- 增删改操作前言一、准备工作1.1 创建数据库和表1.2 添加实体类1.3 添加 mapper 接口 (数据持久层)1.4 创建与接口对应的 xml 文件二、增加操作2.1 默认返回受影响的行数2.2 特殊的新增&#xff1a;返回自增 id三、删除操作四、修改操作五、实现完整交互5.1 添…

爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)

&#x1f4da; 前言 &#x1f4d1;博客主页&#xff1a;丘比特惩罚陆 &#x1f496;欢迎关注&#xff1a;点赞收藏⭐留言✒ &#x1f4ac;系列专栏&#xff1a;web前端、嵌入式、笔记专栏 &#x1f3ae; 加入社区&#xff1a; 丘比特惩罚陆 &#x1f947;人生格言&#xff1a;选…

【教学赛】金融数据分析赛题1:银行客户认购产品预测(0.9676)

本文是对天池教学赛&#xff0c;银行客户认购产品预测的记录&#xff0c;教学赛网址如下&#xff1a; 【教学赛】金融数据分析赛题1&#xff1a;银行客户认购产品预测_学习赛_天池大赛-阿里云天池 1. 读取数据 import pandas as pd# 加载数据 train pd.read_csv(train.csv) …

P5587 打字练习————C++

题目 打字练习 题目描述 R 君在练习打字。 有这样一个打字练习网站&#xff0c;给定一个范文和输入框&#xff0c;会根据你的输入计算准确率和打字速度。可以输入的字符有小写字母、空格和 .&#xff08;英文句号&#xff09;&#xff0c;输入字符后&#xff0c;光标也会跟…

c语言小练pintia11-20

11.计算平均分已知某位学生的数学、英语和计算机课程的成绩分别是87分、72分和93分&#xff0c;求该生3门课程的平均成绩&#xff08;结果按整型输出&#xff09;。输入格式&#xff1a;本题无输入输出格式&#xff1a;按照下列格式输出结果&#xff1a;math 87, eng 72, com…

深耕地市区县市场,新华三智行中国走新路

2022年就这样结束了&#xff0c;但是企业数字化的进程从未结束。回顾这一年&#xff0c;对于任何企业而言&#xff0c;数字化优先的战略仍然在继续。不仅如此&#xff0c;数字化走向地市区县市场&#xff0c;带来了更多的机遇和发展&#xff0c;让我们看到了中国的数字经济还有…