目录
2.3.3 clear()、reserve()、rsize()
1 为什么要学习string类
string类是C++中处理字符串的核心工具,在常规工作中,使用string类方便快捷,很少用C语言库中的字符串操作函数。
在C语言中,字符串是用字符数组表示的,以\0结尾,这样的方式存在一些问题:
- 首先是容易出错,需要自己手动管理内存,容易发生缓冲区溢出
- 其次是操作较为复杂,像使用strcpy,strcat,如果不熟练容易出错
- 再者字符数组是固定大小的,无法自动调整
C语言风格的字符串:
//C语言
char str1[20] = "hello world";
char str2[] = "HELLO WORLD";
char str3[50];//确保空间足够大
strcat(str3, str1);
strcat(str3, str2);//有风险,可能会导致溢出
C++风格的字符串:
//C++
#include <string>
std::string str1 = "hello world";
std::string str2 = "HELLO WORLD";
std::str3 = str1 + str2;//简洁安全
1.1 string类的优势
(1)自动内存管理:
string类自动处理内存的分配和释放,无需担心内存泄漏和溢出等问题。
(2)丰富的成员函数:
string类中具有丰富的成员函数,可以调用成员函数来对字符串进行操作,例如:
查找:find(),refind()
修改:append(),insert(),erase(),replace()
子串:substr()
大小操作:resize(),capacity()
(3)支持运算符重载
可以使用+、-、+=、==、<等运算符,使代码更直观。
总而言之,string类在安全性、便利性、可读性等方面相较于C语言有很大优势,这就是我们要学习string类的原因。
2 标准库中的string类
string类中有很多接口,本文只说明一些常用的接口,具体可以参考string类的文档:https://cplusplus.com/reference/string/string/?kw=string
2.1 string类对象的常见构造
| string() | 构造空的string类对象 |
| string(const char* s) | 用C-string来构造string类对象 |
| string(const string&s) | 拷贝构造函数 |
void teststring1()
{
string s1;//构造一个空的string类对象s1
string s2("helllo world");//用C格式字符串构造string类对象s2
string s3 = s2;//拷贝构造上s3
}
2.2 string类对象的访问及遍历操作
| operator[ ] | 返回pos位置的字符,const string类对象调用 |
| begin+end | begin获取第一个字符的迭代器,end获取最后一个字符下一个位置的迭代器 |
| rbegin+rend | rbegin获取字符的最后一个位置,rend获取字符第一个位置的前一个位置 |
| 范围for | C++11中支持更简洁的新遍历方式 |
2.2.1 string类对象的访问
使用[ ]操作符访问某一个字符的位置,可以修改某一个字符:
void teststring1()
{
string s1("hello world");
s1[1] = 'x';//修改下标1的字符
cout << s1 << endl;//输出hxllo world
}
int main()
{
teststring1();
return 0;
}
2.2.2 三种遍历方式
2.2.2.1 size()获取长度
void teststring1()
{
string s1("hello world");
s1[1] = 'x';//修改下标1的字符
//size()获取长度 []访问字符
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i];
}
cout << endl;
}
2.2.2.2 利用迭代器
迭代器
string的迭代器是STL中一种类似指针的对象,用于遍历和访问字符串中的字符,提供了一种统一的方式来访问容器元素。
迭代器就像是一个"智能指针",可以指向 string 中的某个字符,并支持移动操作来遍历整个字符串。所有容器都有自己的迭代器,可以用类似的方式访问。
void teststring2()
{
string s("hello world");
string::iterator it = s.begin();
while (it != s.end())
{
//*it += 2;//可以修改
cout << *it;
++it;
}
cout << endl;
}
这里的string::iterator可以用auto来代替,结果是一样的
void teststring2()
{
string s("hello world");
auto it = s.begin();
while (it != s.end())
{
//*it += 2;//可以修改
cout << *it;
++it;
}
cout << endl;
}
auto是一个关键字,在C++11中,auto是一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译期间推导而得。
- 用auto声明指针类型时,用auto和auto*没有区别,但是auto声明引用类型时必须用auto&
- 在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错。
- auto不能作为函数的参数,可以做返回值
- auto不能用来声明数组
反向迭代器
使用反向迭代器可以倒着访问字符串
void teststring2()
{
string s("hello world");
string::reverse_iterator rit = s.rbegin();//rbegin返回字符串的最后一个位置
while (rit != s.rend())//rend返回字符串第一个字符的前一个位置
{
//*rit += 2;//可以修改
cout << *rit;
++rit;//从最后一个位置向前走,倒着走,反向迭代器重载了++操作符
}
cout << endl;
}
const迭代器
详情见注释
void teststring2()
{
string s("hello world");
//const正向迭代器 本身可以修改 指向的内容不可以修改
string::const_iterator cit = s.begin();
while (cit != s.end())
{
//*cit += 2; 错误!const迭代器不可以修改指向的内容
cout << *cit;
++cit;
}
cout << endl;
}
const反向迭代器
void teststring2()
{
string s("hello world");
//const反向迭代器 本身可以修改 指向的内容不可以修改 从后向前遍历
string::const_reverse_iterator rcit = s.rbegin();
while (rcit != s.rend())
{
//*rcit += 2; 错误!const反向迭代器不可以修改指向的内容
cout << *rcit;
++rcit;
}
}
像string::const_reverse_iterator 这种规定好的长类型变量就可以用auto代替,结果都是一样的,这就体现出了auto的优势,缺点就是牺牲了可读性。
2.2.2.3 范围for
C++11中引入了基于范围的for循环,for循环后的括号由冒号:分为两部分:冒号前部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
- 范围for可以作用到数组和容器对象上进行遍历
- 范围for的底层其实就是替换为迭代器
//范围for底层就是迭代器
//自动从s中寻找值给ch auto是自动推导
//自动迭代,自动判断结束
for (auto& ch : s)
{
ch += 2;
cout << ch;
}
cout << endl;
cout << s << endl;//返回auto引用才能修改s2,ch是s2中字符的别名,不返回引用不会修改s
以上就是本节遍历字符串的三种方式,其中范围for和迭代器是重点。
2.3 string类对象的容量操作
| size | 返回字符串有效字符长度 |
| length | 返回字符串有效字符长度 |
| capacity | 返回空间总大小 |
| empty | 检测字符串是否为空串,是返回true,否则返回false |
| clear | 清空有效字符 |
| reserve | 为字符串预留空间 |
| resize | 将有效字符的个数改成n个,多出的空间用字符c填充 |
2.3.1 计算字符串长度
计算字符串长度时主要有两个成员函数,分别是size()和length()。它们返回实际存储的字符数,不依赖\0做标志,字符串中可以包含\0作为有效字符。
void teststring3()
{
string s1 = "hello world";
cout << s1.size() << endl;//输出11
string s2 = "hello w\0orld";
cout << s2.length() << endl;//输出7
}
2.3.2 空间扩容
本小节利用字符串中尾插新的数据来说明剩余的string类中对容量改变的成员函数。
#include <iostream>
#include <string>
using namespace std;
void TestPushBack()
{
string s;
size_t sz = s.capacity();//看空间
cout << "capacity changed: " << sz << endl;
for (size_t i = 0; i < 100; i++)
{
s.push_back('c');//插入字符的成员函数
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << endl;
}
}
}
int main()
{
TestPushBack();
return 0;
}
输出结果:

可以看到在Visual Studio中,内存中先开15个字节的空间,避免短字符串的动态分配,然后2倍扩容,快速扩大容量,减少分配次数,接下来的扩容都是依据原来的容量进行1.5倍扩容,使得内存利用率更高,节省一些空间。
2.3.3 clear()、reserve()、rsize()
reserve(),括号内加数字,表示预留多少的空间,这样提前开空间,避免扩容,提高效率。
#include <iostream>
#include <string>
using namespace std;
void TestPushBack()
{
string s;
s.reserve(100);
size_t sz = s.capacity();//看空间
cout << "capacity changed: " << sz << endl;
for (size_t i = 0; i < 100; i++)
{
s.push_back('c');//插入字符的成员函数
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << endl;
}
}
}
int main()
{
TestPushBack();
return 0;
}
输出结果:

可以看到提前预留空间只进行了一次扩容,提高了性能。但是使用reserve()要注意如果过度预留空间会造成资源浪费,不要在不了解数据特征时盲目使用。
clear()是清理字符的内容,但是不清空间。
#include <iostream>
#include <string>
using namespace std;
void teststring4()
{
string s("hello world");
s.reserve(100);
cout << s.capacity() << endl;
s.clear();
cout << s << endl;
cout << s.capacity() << endl;
}
int main()
{
teststring4();
return 0;
}
输出结果:

resize()是将有效字符个数改成n个,多出的字符空间用指定字符填充,如果不指定,用\0填充
#include <iostream>
#include <string>
using namespace std;
void teststring5()
{
string s("hello world");
s.resize(7);
cout << s << endl;
s.resize(15);//不指定字符,默认用\0
cout << s << endl;
s.resize(20,'c');
cout << s << endl;
}
int main()
{
teststring5();
return 0;
}
输出结果:

resize(7):截断字符串,保留前7个字符
resize(15):扩大到15,用 \0 填充(输出时不可见)
resize(20, 'c'):继续扩大到20,用'c' 填充额外的5个位置
string的resize函数中,小于size时,会删除数据,容量不会缩小,大于size时,插入数据,如果空间不够就扩容
2.4 string类对象的修改操作
| push_back | 在字符串中尾插字符 |
| append | 在字符串后追加一个字符串 |
| operator+= | 在字符串后追加字符串str |
| c_str | 返回C格式字符串 |
| find+npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
| rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
| substr | 从str中从pos位置开始,截取n个字符,然后将其返回 |
2.4.1 插入字符
可以使用push_back、append、+=来尾插,其中+=最常用
void teststring6()
{
string s("hello world");
s.push_back('x');
s.append("ttt");
//+=常用
s += 'c';
s += "yyy";
cout << s << endl;
}
int main()
{
teststring6();
return 0;
}
头插用insert()来在字符串头部或某一位置插入字符或者字符串,头删和指定位置删除用erase(),操作如下:
void teststring7()
{
string s("hello world");
s.insert(0,"x");//头插x
s.insert(0,"xxx");//头插字符串xxx
s.insert(14, "x");//在下标n处插入x
s.insert(0,1,'c');//头插1个字符c
s.insert(0,2,'c');//头插两个字符cc
s.insert(10, 2, 'c');//下标n处插入两个字符cc
s.erase(0, 1);//头删一个字符
s.erase(s.begin());//利用迭代器。头删
s.erase(--s.end());//尾删
cout << s << endl;
}
头插头删操作建议谨慎使用,在指定位置插入字符时,后面的数据先向后挪动,头删时后面的数据向前挪动,这样会降低效率。
2.4.2 查找函数
find()函数从pos位置查找字符串中的字符或指定字符串,如果找到,返回第一个匹配的字符的下标位置(可能存在多个匹配字符),如果没有找到,返回npos。rfind()是从后向前找。
void teststring8()
{
string s("hello wo rld");
size_t pos = s.find(' ');
while (pos != string::npos)//找空格
{
s.replace(pos, 1, "%%");
pos = s.find(' ');
}
cout << s << endl;//输出hello%%wo%%rld
}
2.4.3 返回子串
substr是从str中从pos位置开始,截取n个字符,然后将其返回。
void teststring9()
{
string s("test.cpp.rar");
size_t pos = s.rfind('.');//从后向前找
string suffix = s.substr(pos);//获取.和它之后的字串
cout << suffix << endl;//输出.rar
size_t pos2 = s.find('.');//从前向后找
string suffix2 = s.substr(pos2);//获取.和它之后的字串
cout << suffix2 << endl;//输出.cpp.rar
}
以上就是有关string类的常用成员函数说明,具体了解全部的成员函数可以查看文档,链接在本文开头给出,如果这篇文章对你有用,可以点点赞哦,你的支持就是我写下去的动力,后续会继续更新其他知识。
C++ string类常用函数详解
1330

被折叠的 条评论
为什么被折叠?



