【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,"*");//流提取,直到遇到*才停止
相关推荐
无尽的大道4 分钟前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
爱吃生蚝的于勒8 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~11 分钟前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
binishuaio17 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE19 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻23 分钟前
WPF中的依赖属性
开发语言·wpf
洋24031 分钟前
C语言常用标准库函数
c语言·开发语言
进击的六角龙33 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
wrx繁星点点34 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
NoneCoder1 小时前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发