【C++】——string类的使用

目录

一.为什么学习string类?

[1.1 C语言的字符串](#1.1 C语言的字符串)

[二. 标准库中的string类](#二. 标准库中的string类)

[2.1 string类(了解)](#2.1 string类(了解))

[2.2 string类成员函数](#2.2 string类成员函数)

[● string类对象的常见构造](#● string类对象的常见构造)

[● string类析构函数](#● string类析构函数)

[● 赋值重载](#● 赋值重载)

[2.3 string的迭代器](#2.3 string的迭代器)

[<1>正向迭代器 Iterator](#<1>正向迭代器 Iterator)

[<2> 反向迭代器 reverse_iterator](#<2> 反向迭代器 reverse_iterator)

[<3> 迭代器访问const类型容器](#<3> 迭代器访问const类型容器)

[2.4 string类的容量操作](#2.4 string类的容量操作)

[2.5 string类的访问修改](#2.5 string类的访问修改)

<1>元素访问

<2>元素修改

<3>子字符串

[2.6 string类的查找](#2.6 string类的查找)

[2.7 string类的非成员函数](#2.7 string类的非成员函数)

[● 运算符重载+](#● 运算符重载+)

[● 比较运算符重载](#● 比较运算符重载)

[● getline(将线从流转换为字符串)](#● getline(将线从流转换为字符串))


一.为什么学习string类?

1.1 C语言的字符串

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

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、 快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

string类相比C语言的字符串更安全、更方便、更高效的使用,是面向对象编程的思想体现

二. 标准库中的string类

2.1 string类(了解)

string类的文档介绍

在使用string类时,必须包含头文件#include<string>以及using namespace std;

2.2 string类成员函数

● string类对象的常见构造

<1>string(); 无参构造,空的 string 只有 "\0"

<2>string (const string& str); 拷贝构造函数

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

(其他负数也可以起到一样的效果)

拷贝构造,在指定下标 pos 位置拷贝 len 个字符 默认值为 npos 类型为无符号整数类型,因此 npos 为无符号整数最大值,库规定 len 大于字符串长度,则拷贝整个 str 到结束

<4>string (const char* s); 将字符串拷贝给 string 类

<5> string (const char* s, size_t n); 将字符串的前 n 个拷贝给 string 类

<6>string (size_t n, char c); 将 n 个字符 c 拷贝给 string 类

cpp 复制代码
int main()
{
	string s1;//无参构造 只有\0
	string s11{};//同上  string s1(); 编译器可能认为声明
	string s2("hello world");//将括号内字符串拷贝给s2
	string s3(s2, 0, 2);//将s2从第0个位置拷贝2个元素给s3
	string s4(s2);//拷贝构造 将s2类拷贝给s4
	string s5(s2, 5);//拷贝构造 将s2类的前5个字符拷贝给s5
	string s6(8, 'a');//拷贝n个字符a给s6

	cout << s1 << endl;
	cout << s11 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;

	return 0;
}

● string类析构函数

当字符串生命周期结束,会自动调用析构函数。

● 赋值重载

2.3 string的迭代器

<1>正向迭代器 Iterator

提供了一种通用的(所用)访问容器的方式

cpp 复制代码
string s1("hello");
// it 作用类似指针
string::iterator it = s1.begin();
while (it != s1.end())//end 返回最后一个字符下一个位置 左闭右开
{
	*it += 2;//可以修改内容
	cout << *it << ' ';
	++it;
}
cout << endl;

● begin 返回 指向 string 第一个字符

● end 返回 指向 string 最后一个有效字符下一个位置 \0

<2> 反向迭代器 reverse_iterator
cpp 复制代码
string s1("hello");
// it 作用类似指针
string::reverse_iterator it = s1.rbegin();//反向指向string的最后一个有效字符 视为开始端
auto end = s1.rend();//指向 string的第一个有效字符前一个位置 视为结尾端

while (it != s1.rend())
{
	cout << *it << ' ';
	++it;
}
cout << endl;

● rbegin

● end

<3> 迭代器访问const类型容器

普通的迭代器是可读可写的,const迭代器访问const修饰的容器只能读不能写(指向的内容)

Tip:const迭代迭代器不是指const 修饰迭代器,因为迭代器本身可以修改,而它指向的内容不能修改!

cpp 复制代码
string s1("hello");
// it 作用类似指针
string::const_iterator it = s1.cbegin();  //也可以写成 begin 相当于权限缩小
while (it != s1.end())
{
	/**it += 2;*/
	cout << *it << ' ';
	++it;
}
cout << endl;

三种访问方式:

cpp 复制代码
string s1("hello world");
cout << s1 << endl;
for (int i = 0; i < s1.size(); i++)
{
	cout << s1[i] << ' ';
}
cout << endl;

string::iterator it = s1.begin();
auto it2 = s1.begin();
while (it2 != s1.end())
{
	cout << *it2 << ' ';
	it2++;
}
cout << endl;

//自动推导类型 自动赋值 自动迭代结束 底层迭代器
//本质拷贝赋值 不能修改原数据 将*it值 赋值给 c
for (auto c : s1)
{
	cout << c << ' ';
}
cout << endl;

//引用 可以修改
for (auto& c : s1)
{
	cout << c << ' ';
}
cout << endl;

2.4 string类的容量操作

std::string 里内部结构可能优化了,比如 char[] _buff 在这种情况下,短字符串可能直接存储在 std::string 对象的内存空间内,而不是在堆上分配。但是,当字符串增长超出了这个内部空间时,就会触发堆上的内存分配和扩容。

连续尾插测试扩容机制:

cpp 复制代码
int main()
{
	string s;
	size_t sz = s.capacity();
	cout << "capacity changed:" << sz << '\n';

	cout << "making a grow:\n";
	for (int i = 0; i < 100; i++)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed:" << sz << '\n';
		}
	}
	return 0;
}

可以看到 由于 _buff 的存在 初始容量为15个字节,再继续尾插之后该编译器出现了 1.5倍的扩容机制以减少将来可能的内存重新分配次数,提高性能。

容量只显示有效字符的个数,内存会比容量多一个空间(存储' \0 ')

● 总结:

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

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

**<2> capacity()**返回有效数据空间大小,不包含 \0 实际需要多开一个空间

**<3>**clear() 将string中有效字符清空,不改变底层空间大小。

<4> resize(size_t n) 与 resize(size_t n, char c) 都是将字符串中有效字符个数改变到n个 ,不
同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char
c) 用字符c来填充多出的元素空间 。

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

**<5>**reserve(size_t res_arg=0) 为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

vs下,有效元素个数小于预留空间大小时,不缩容。

g++下, 有效元素个数小于预留空间大小时,缩容。

cpp 复制代码
int main()
{
	string s2("hello xxxxxxxxxxxxxxxxxx");
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

	//给一个小于 size的值
	s2.reserve(20); //结果不变
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

	//给一个 size与 capacit 中间值
	s2.reserve(28); //结果不变
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

	//给一个 大于 capacit 值
	s2.reserve(40); //扩容
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

	s2.clear();//清楚数据 不清容量
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;
	return 0;
}

2.5 string类的访问修改

<1>元素访问

● 运算符重载[ ] 返回pos位置字符的引用

模拟实现:

cpp 复制代码
char& operator[](size_t pos)
{
	assert( pos>=0 &&pos < _size);
	return _str[pos];
}

const char& operator[](size_t pos)const
{
	assert(pos >= 0 && pos < _size);
	return _str[pos];
}

● at 获取字符串中的字符

at 函数自动检查 pos 是否是字符串中字符的有效位置(即 pos 是否小于字符串长度),如果不是,则抛出 out_of_range 异常。

<2>元素修改

● push_back 尾部插入一个字符c

● append 在字符串后追加字符串str

● 赋值重载+= 在字符串后追加字符串str

cpp 复制代码
int main()
{
	string s2("world");
	string s("hello worold");
	s.push_back(' ');//单个字符
	s.push_back('x');
	s.append(" ");
	s.append(s2);//字符串
	cout << s << endl;
	s += ' ';
	s += "ccccccc";
	cout << s << endl;

	return 0;
}

注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c' 三种的实现方式差不多,一般情况下string类的 += 操作用的比较多,+= 操作不仅可以连接单个字符,还可以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

● insert(插入) 在pos或p位置之前插入字符串

● erase(删除)pos位置删除字符串

● replace(替换)

cpp 复制代码
int main()
{

	string s("hello worold");
	//模拟头插 需要移动数据 效率相对低
	s.insert(0, "hello xc ");
	cout << s << endl;
	//模拟头删 需要移动数据
	s.erase(0, 1);
	cout << s << endl;
	s.erase(s.begin());
	cout << s << endl;
	//模拟尾删
	s.erase(--s.end());
	cout << s << endl;
	s.erase(s.size() - 1, 1);
	cout << s << endl;
	//将空格替换
	string ss("hello world");
	cout << ss << endl;
	ss.replace(5, 1, "%%");
	cout << ss << endl;
	cout << endl;
	return 0;
}

<3>子字符串

● c_str(兼容C)返回一个指向字符数组的指针 数组内容即 string 对象

cpp 复制代码
// string 不能直接与 文件操作对接
string file;
cin >> file;
FILE* fout = fopen(file.c_str(),"r");
char ch = fgetc(fout);
while (ch != EOF)
{
	cout << ch;
	ch= fgetc(fout);
}
fclose(fout);

● data(类似于c_str)

● substr(子串)在str中从pos位置开始,截取n个字符,然后将其返回

2.6 string类的查找

● find(正向) 在字符串里查找字符数据 pos位置开始 n个数据

cpp 复制代码
//将字符串的空格替换成百分号

//法一 移动数据频繁 效率低
string sss("hello world hello bit");
cout << sss << endl;
size_t pos = sss.find(' ');
while (pos != string::npos)
{
	sss.replace(pos, 1, "%%");
	pos = sss.find(' ',pos+2);
}
cout << sss << endl<<endl;
//法二 构造新串 空间换时间
string tmp;
tmp.reserve(sss.size());
for (auto ch : sss)
{
	if (ch == ' ')
		tmp += "%%";
	else
		tmp += ch;
}
cout << tmp << endl;
sss.swap(tmp);// 交换 字符指针
cout << sss << endl << endl;

● rfind(倒着找) 与find 参数一致意义 倒着查找

cpp 复制代码
//找出文件后缀
string s2("test.cpp.zip");
size_t pos2 = s2.rfind('.');
string suffix2 = s2.substr(pos2);
cout << suffix2 << endl;

● find_first_of 在字符串中搜索与其参数中指定的任何字符匹配的第一个字符。

cpp 复制代码
int main()
{
	string s3("hello world hello xc");
	cout << s3 << endl;
	size_t pos3 = s3.find_first_of("abcd");// find_first_of 给定一个需要查找的字符串,一个一个查找,返回索引 
	while (pos3 != string::npos)			
	{										
		s3[pos3] = '*';
		pos3 = s3.find_first_of("abcd", pos3 + 1);
	}
	cout << s3 << endl;
	return 0;
}

● find_last_of(倒着找)倒序查找

cpp 复制代码
void SplitFilename(const std::string& str)
{
	std::cout << "Splitting: " << str << '\n';
	std::size_t found = str.find_last_of("/\\");
	std::cout << " path: " << str.substr(0, found) << '\n';
	std::cout << " file: " << str.substr(found + 1) << '\n';
}

//文件路径分离
string str1("/usr/bin/man");
string str2("c:\\windows\\winhelp.exe");
SplitFilename(str1);
SplitFilename(str2);

find_first_not_of 查找字符串中与参数中指定的任何字符都不匹配的第一个字符

find_last_not_of 倒叙查找字符串中与参数中指定的任何字符都不匹配的第一个字符

2.7 string类的非成员函数

● 运算符重载+

返回一个新构造的字符串对象,其值是lhs中字符的连接,后跟rhs中的字符。

cpp 复制代码
int main()
{
	string s1("hello");
	string s2 = s1 + " wrold";
	string s3 = s1 + s2;
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	return 0;
}

● 比较运算符重载

在字符串对象lhs和rhs之间执行适当的比较操作

● getline(将线从流转换为字符串)

从is中提取字符并将其存储到str中,直到找到分隔符delim

找最后一个单词长度:

cpp 复制代码
#include <iostream>
using namespace std;
 
int main() {
   string str;
   //cin >> str;// cin 和 scanf 将空格和换行默认成分割
   getline(cin,str);// 无定界默认遇到换行才停止输入
   size_t pos = str.rfind(' ');
   cout << str.size() - (pos + 1) << endl;
}

可以自定义分隔符

cpp 复制代码
getline(cin,str,"*");//流提取,直到遇到*才停止
相关推荐
cherub.1 分钟前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
I_Am_Me_14 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
暮色_年华16 分钟前
Modern Effective C++item 9:优先考虑别名声明而非typedef
c++
重生之我是数学王子24 分钟前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
Ai 编码助手26 分钟前
使用php和Xunsearch提升音乐网站的歌曲搜索效果
开发语言·php
学习前端的小z30 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
神仙别闹37 分钟前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
XINGTECODE38 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
我们的五年1 小时前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
zwjapple1 小时前
typescript里面正则的使用
开发语言·javascript·正则表达式