一、String 类初相识
在 C 语言的世界里,字符串是以'\0'结尾的字符集合,为了方便操作,C 标准库提供了一系列str系列的库函数,如strcpy、strcat、strlen等。虽然这些库函数在一定程度上满足了我们对字符串的操作需求,但是它们与字符串是分离开的,不太符合面向对象编程(OOP)的思想。而且,C 语言中字符串的底层空间需要用户自己管理,这就像让你自己搭建帐篷去露营,虽然很有挑战性,但也容易出错。稍不留神,就可能会出现越界访问的情况,比如在使用strcpy函数时,如果目标缓冲区的大小不足以容纳源字符串,就会导致缓冲区溢出,这可是一个很严重的问题,就像你把太多的东西塞进一个小袋子里,袋子可能会被撑破。
下面是一个简单的 C 语言字符串操作示例:
#include <stdio.h>
#include <string.h>int main() {char str1[20] = "Hello";char str2[] = " World";// 拼接字符串strcat(str1, str2);printf("拼接后的字符串: %s\n", str1);// 计算字符串长度int len = strlen(str1);printf("字符串长度: %d\n", len);return 0;
}
在这个示例中,我们使用了strcat函数来拼接两个字符串,使用strlen函数来计算字符串的长度。虽然这些操作看起来很简单,但是需要我们自己管理字符串的存储空间,并且要小心避免缓冲区溢出等问题。而在 C++ 的标准库中,string类就像是一个专门为你搭建好的豪华帐篷,为我们提供了更加安全、方便和高效的字符串处理方式。它将字符串的操作和数据封装在一起,完全符合 OOP 的思想,让我们可以像操作普通对象一样操作字符串。string类还会自动管理字符串的内存空间,这就像帐篷会自动根据你的需求调整大小,你不用担心空间不够用或者空间浪费的问题。而且,string类提供了丰富的成员函数,让我们可以轻松地完成字符串的各种操作,如拼接、查找、替换等,就像豪华帐篷里配备了各种高级设施,让你的露营体验更加舒适和便捷。
在 OJ(Online Judge)中,有关字符串的题目基本都是以string类的形式出现。在实际工作中,为了 追求简单、方便和快捷,大家也基本都使用string类,很少有人再去使用 C 库中的字符串操作函数。 所以,掌握string类的使用是非常必要的,它将大大提高我们的编程效率和代码的安全性。
二、String 类的基础操作
2.1 构造函数大揭秘
string类提供了多种构造函数,以满足不同的初始化需求。下面是一些常见的构造函数及其用法:
无参构造函数:创建一个空的string对象。
string s1;
有参构造函数:使用 C 风格字符串或其他string对象来初始化string。
string s2("Hello, C++");
string s3(s2);
指定字符和数量构造函数:创建一个包含指定数量相同字符的string对象。
string s4(5, 'a');// s4:"aaaaa"
部分复制构造函数:从 C 风格字符串的指定位置开始,复制指定长度的字符来初始化string。
char c[] = "0123456789";
string s5(c, 5); // s5:"01234"
迭代器构造函数:使用迭代器指定的范围来初始化string。
char c2[] = "0123456789";
string s6(c2 + 1, c2 + 7); // s6:"123456"
子字符串构造函数:从另一个string对象的指定位置开始,复制指定长度的子字符串来初始化string。
string temp("0123456789");
string s7(temp, 0, 5); // s7:"01234"
2.2 容量操作
string类提供了一系列函数来获取和管理字符串的容量和长度,这些函数对于优化字符串操作和避免不必要的内存分配非常重要。
size () 和 length ():这两个函数都返回字符串中有效字符的长度,它们的功能完全相同,只是为了与 其他容器的接口保持一致,才同时提供了这两个函数。一般情况下,我们更常用size()。
string s("Hello, World!");
cout<< s.size() << s.length()<<endl;
capacity():返回当前为字符串分配的总容量,这个容量可能大于字符串的实际长度,用于减少频繁的内存重新分配。
string s("Hello");
cout << s.capacity() << endl;
empty():检查字符串是否为空,为空则返回true,否则返回false。
string s1;
string s2("Hello");cout << s1.empty()<< endl;
cout << s2.empty() << endl;
clear():清空字符串中的有效字符,但不会释放底层分配的内存空间,即容量不变。
string s("Hello, World!");
s.clear();
cout << s.size()<< endl;
cout << s .capacity() << endl;
reserve():为字符串预留指定大小的容量,避免在后续操作中频繁的内存重新分配,提高效率。如果指定的容量小于当前容量,则该操作通常不会改变容量。
string s;
s.reserve(100);
cout << s.capacity() << endl;
resize():将字符串的有效字符长度调整为指定的大小。如果新长度大于原长度,会在字符串末尾填充指定字符(默认为空字符'\0');如果新长度小于原长度,会截断字符串。
string s("Hello");
s.resize(8, 'a');
s.resize(3);
2.3 访问与遍历方法
operator[]:通过下标访问字符串中的字符,就像访问数组一样。需要注意的是,下标从 0 开始,并 且要确保下标不超出字符串的有效长度,否则会导致未定义行为。
string s("Hello");
cout << "第一个字符: " << s[0] << endl;
cout << "最后一个字符: " << s[s.size() - 1] << endl;
迭代器(begin、end 等):string类提供了begin()和end()函数,分别返回指向字符串第一个字符和 最后一个字符下一个位置的迭代器。通过迭代器,我们可以遍历字符串中的每个字符。此外,还 有rbegin()和rend()函数,用于反向遍历字符串。
string s("Hello");
// 正向遍历
for (string::iterator it = s.begin(); it!= s.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 反向遍历
for (string::reverse_iterator rit = s.rbegin(); rit!= s.rend(); ++rit) {
cout << *rit << " ";
}
cout << endl;
范围 for:C++11 引入的范围 for 循环,为遍历字符串提供了一种更简洁的方式。它会自动遍历字符串中的每个字符,无需手动管理迭代器。
string s("Hello");
for (char ch : s) {
cout << ch << " ";
}
cout << endl;
2.4 修改操作大集合
push_back():在字符串的末尾添加一个字符。
string s("Hello");
s.push_back('!');
cout << "添加字符后的字符串: " << s << endl;
append():在字符串的末尾追加另一个字符串,可以是 C 风格字符串、string对象或字符序列。
string s1("Hello");
string s2(" World");
s1.append(s2);
cout << "追加字符串后的字符串: " << s1 << endl;
operator+=:这是一个更常用的追加操作符,它可以在字符串的末尾追加单个字符、C 风格字符串或string对象,功能与push_back()和append()类似,但使用起来更加简洁。
string s("Hello");
s += " World";
s += '!';
cout << "使用operator+=后的字符串: " << s << endl;
insert():在字符串的指定位置插入字符或字符串。可以插入单个字符、C 风格字符串、string对象或字符序列,还可以使用迭代器指定插入的位置和范围。
string s("Hello");
s.insert(5, ", World");
cout << "插入字符串后的字符串: " << s << endl;
erase():删除字符串中的部分字符,可以指定删除的起始位置和长度,也可以使用迭代器指定删除的范围。
string s("Hello, World!");
s.erase(5, 7);
cout << "删除部分字符后的字符串: " << s << endl;
replace():用新的字符串替换字符串中的部分内容,可以指定替换的起始位置和长度,以及用于替换的新字符串。
string s("Hello, World!");
s.replace(0, 5, "Hi");
cout << "替换部分字符串后的字符串: " << s << endl;
find () 和 rfind ():find()函数从字符串的指定位置开始,向后查找指定的字符或子字符串,返回第一次出现的位置;rfind()函数则从指定位置开始,向前查找,返回最后一次出现的位置。如果找不到,这两个函数都会返回string::npos,这是一个表示无效位置的常量。
#include<iostream>
using namespace std;
int main()
{string s("Hello, World!");size_t pos1 = s.find("World");if (pos1 != string::npos) {cout << "找到子字符串'World',位置为: " << pos1 << endl;}else {cout << "未找到子字符串'World'" << endl;}size_t pos2 = s.rfind('o');if (pos2 != string::npos) {cout << "找到字符'o'最后一次出现的位置为: " << pos2 << endl;}else {cout << "未找到字符'o'" << endl;}
}
substr():从字符串的指定位置开始,截取指定长度的子字符串并返回。如果不指定长度,则截取从指定位置到字符串末尾的所有字符。
#include<iostream>
using namespace std;
int main()
{string s("Hello, World!");string sub1 = s.substr(7);cout << "从位置7开始截取的子字符串: " << sub1 << endl;string sub2 = s.substr(0, 5);cout << "从位置0开始截取长度为5的子字符串: " << sub2 << endl;
}
好啦,到这里我们对 C++ `String` 类全面解析就到这里啦,希望今天的内容能给你带来收获,让你对字符串处理有新的认识。编程的世界丰富多彩,还有很多知识等你解锁,加油哦,期待你在代码世界里创造更多精彩!下一期我会带来string类的模拟实现。