C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合面向对象的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
所以在C++的STL库中提供了更加方便的string类。
string
string的常见构造
默认构造函数:string()
- 创建一个空字符串对象。
- 示例:
string str;
拷贝构造函数:string(const string& str)
- 创建一个字符串对象,内容为另一个字符串对象
str的拷贝。 - 示例:
string str1("hello");string str2(str1); // "hello"
重载构造函数:string(const string& str, size_t pos, size_t len = npos)
- 创建一个字符串对象,内容为字符串对象
str的pos位置开始的len个字符。 - 示例:
-
string str1("hello"); string str2(str1, 1, 3); // "ell:
- 注意:当不传入第三个参数时,其默认为
npos,这个值是size_t的最大储存值,可以理解为是无穷大,也就是说,当不传入第三个参数,其会从pos位置开始拷贝到结束。
重载构造函数:string(const char* s)
- 创建一个字符串对象,内容为字符串
s。 - 示例:
-
string str("hello"); - 由于单参数构造函数支持类型转换,也可以写为:
string str = "hello";
重载构造函数:string(const char* s, size_t n)
- 创建一个字符串对象,内容为字符串
s的前n个字符。 - 示例:
string str("hello", 3); //"hel"
重载构造函数:string(size_t n, char c)
- 创建一个由
n个字符c组成的字符串对象。 - 示例:
string str(5, 'a'); // "aaaaa"
string的输入输出
operator<<
string对<<进行了重载,我们可以直接对string对象使用<<,此时会输出这个string对象的字符串值。
示例:
string s1 = "hello world";
string s2 = " csdn";cout << s1 << endl; //"hello world"
cout << s1 << s2 << endl; //"hello world csdn"
c_str
string还提供了一个函数,用于读取string对象中的字符串。
string s = "hello world";
cout << s.c_str() << endl; // "hello world"
c_str的返回值是一个字符串,而<<对于字符串的输出机制是遇到'\0'中止输出。而string类重载的<<输出机制则是完整输出整个字符串,如果遇到'\0',会把'\0'一起输出。
看一个案例:
string s = "hello world \0 csdn";
cout << s.c_str() << endl; // "hello world "
cout << s << endl; // "hello world 0 csdn"
operator>>
同样的,string也对>>进行了重载,我们可以使用>>输入字符串。
string s1;
string s2;cin >> s1 >> s2; // 输入"hello world"
cout << s1 << endl; // "hello"
cout << s2 << endl; // "world"
但是如果我们输入的字符串中包含空格,那么>>就不是很好用。
由于输入字符串时,遇到空格会中止读取,所以包含空格的字符串不能用这个方式输入,此时就要用getline了.
getline
getline可以获取一整行的字符串,包括空格,只有遇到换行符的时候才会中止。
不过要注意getline不是string类的成员函数,但是是string头文件的普通函数。
所以使用时不需要使用对象.getline()这样的格式,而是直接使用。
其第一个参数是istream的对象,比如cin。
第二个参数是输入到的string对象。
第三个参数决定遇到什么字符的时候停止读取,默认值为'\n',即换行时停止读取。
示例:
string s;getline(cin, s); // "hello world csdn"
cout << s << endl; // "hello world csdn"
string访问及遍历
operator[ ] & at
operator[ ] 和 at都是通过下标访问的成员函数,两者作用几乎一致。
示例:
string s = "hello world";cout << s[2] << endl; // 'l'
cout << s[8] << endl; // 'r'
示例:
string s = "hello world";cout << s.at(2) << endl; // 'l'
cout << s.at(8) << endl; // 'r'
两者的参数完全一致,即pos下标,传入这个参数后,输出下标位置的字符。此外,他们各自都提供了const版本的重构,用于const对象。
两者唯一的区别就是越界的情况:
当operator[ ]越界时,会通过assert报错,直接中止程序。
而at越界时,会抛出异常。
font & back
font 和 back用于返回字符串的首尾字符。
要注意,其返回值为引用返回char&,所以对其修改时,是会影响到原先的字符串的。
如果我们想遍历一个字符串,可以通过operator[ ] 或者 at,利用for循环来遍历,但是STL库还提供了迭代器,用于遍历对象。
迭代器
begin & end
在C++的string类中,begin和end是两个成员函数,用于返回字符串的起始和结束迭代器。
begin函数返回一个迭代器,该迭代器指向字符串的第一个字符。可以使用解引用运算符*获取迭代器指向的字符。例如,*str.begin()将返回字符串中的第一个字符。
end函数返回一个迭代器,该迭代器指向字符串结尾的下一个位置。因此end()函数返回的迭代器不指向字符串中的任何字符。可以将end函数返回的迭代器与其他迭代器进行比较,以确定迭代器是否已达到字符串的末尾。
演示如何使用begin和end函数遍历字符串中的字符并输出它们:
string str = "Hello World";// 使用begin()和end()函数遍历字符串中的字符
string::iterrtor it = str.begin()
while(it != str.end())
{cout << *it;++it;
}
平常可以直接用auto来代替这个类型。
所以以上代码可以写为:
string str = "Hello World";// 使用begin()和end()函数遍历字符串中的字符
auto it = str.begin()
while(it != str.end())
{cout << *it;++it;
}
rbegin & rend
迭代器不仅支持正向遍历,还支持反向遍历,此时就需要反向迭代器rbegin,rend了。其中r代表reverse,反转的意思。

rbegin是一个反向迭代器,用于指向容器的最后一个元素,并向前迭代。rend是rbegin的逆操作。rbegin返回容器的最后一个元素的迭代器,而rend返回容器的第一个元素之前的位置的迭代器,这是一个越界的位置,同样不允许修改。
string s1 = "54321";string:reverse_iterator rit = s.begin();while (rit != str.rend()) {cout << *rit << " ";++rit;
}
范围for
范围for循环也适用于string,其本质是基于迭代器的循环。
string s = "hello world";for(auto ch : s)
{cout << ch;
}
string的容量操作
size & length&capacity
size返回当前字符串的长度。
length返回当前字符串的长度。
capacity函数用于返回字符串的当前容量(当前可以存储的最大字符数)。
两者的作用完全一致,没有区别,那么为什么STL要设计两种方式来返回string对象的长度呢?
这是因为早期的string还不属于STL,使用length来表达长度,当string进入STL后,为了与其他的数据结构保持一致,都使用size来表示长度,于是又增加了一个size。
clear&empty
在string类中,clear函数用于清空字符串内容,并将字符串的长度设置为0。
empty函数用于检查一个字符串是否为空,即字符串中是否没有任何字符。该函数返回一个bool类型的值。
reserve
reserve是一个用来预留字符串容量的方法。
reserve方法的目的是为了避免频繁的内存分配和释放操作,提高程序的效率。当我们使用string对象时,由于字符串的长度是可变的,所以在进行增删改等操作时,可能会导致内存重新分配,这会带来一定的时间开销。
为了避免这种情况,我们可以使用reserve方法来预先分配足够的内存空间。
resize
resize函数用于改变字符串的大小。它可以增加或减少字符串的长度。
resize函数有两个参数,第一个参数是新的字符串长度,第二个参数是填充字符(可选)。
如果新的长度小于当前长度,则字符串会被截断。如果新的长度大于当前长度,则字符串会自动增加大小,并用指定的填充字符填充空白部分,如果没有选定字符,则默认用\0填充。
要注意的是,就算使用resize将size缩小,capacity容量大小不会减小,而是保持原本的大小。
string的修改操作
push_back
push_back函数用于将字符添加到字符串的末尾。它接受一个字符作为参数,并将其追加到字符串的末尾。
注意:push_back函数只接受一个字符作为参数,如果传递一个字符串或其他类型的参数,将会引发编译错误。
append
string类中的append方法用于将一个字符串追加到另一个字符串的末尾。它有多个重载版本,可以接受不同类型的参数作为输入。
基本使用:
string str1 = "Hello";
string str2 = " World!";// 使用append方法将str2追加到str1的末尾
str1.append(str2);cout << str1 << endl; // 输出 "Hello World!"
append方法还可以接受其他类型的参数作为输入,例如const char*、char、std::string、std::initializer_list<char>等。
operator+=
operator+=是用于将一个字符串追加到另一个字符串的末尾的运算符重载。
根据其三个重载可以看出来,operator+=可以追加string对象,字符串,字符三种类型,功能全面,语法简单,比append和push_back好用的多。
insert
insert函数可以用于在字符串中插入字符、子串或其他类型的数据。
通过函数重载可以看出,我们可以插入字符,字符串,string对象等。
插入字符串:
string str = "Hello, world!";// 在指定位置插入字符串
str.insert(5, "!!!"); // 在位置5插入"!!!"
cout << str << endl;
输出结果为:Hello!!!, world!
插入字符:
string str = "Hello, world!";// 在指定位置插入字符串
str.insert(5, 3, '!'); // 在位置5插入3个'!'
cout << str << endl;
输出结果为:Hello!!!, world!
注意:插入字符时,第二个参数是插入字符的个数,哪怕只插入一个字符,这个值也不能缺省。
erase
erase函数可以用来删除字符串中的字符或子字符串。
首先,我们来看一个简单的示例,删除字符串中的一个字符:
string str = "Hello World";
str.erase(4); // 删除第5个字符,即空格
cout << str << endl; // 输出 "HelloWorld"
删除字符串中的一个子字符串:
string str = "Hello World";
str.erase(6, 5); // 从第7个字符开始,删除5个字符,即"World"
cout << str << endl; // 输出 "Hello"
除了上述两种用法外,erase函数还可以接受迭代器作为参数,用来删除指定范围内的字符或子字符串。
string str = "Hello World";
string::iterator start = str.begin() + 6; // 第7个字符的迭代器
string::iterator end = str.begin() + 11; // 第12个字符的迭代器
str.erase(start, end); // 删除从第7个字符到第12个字符的子字符串,即"World"
cout << str << endl; // 输出 "Hello "
replace
replace函数用于在字符串中替换指定的子字符串。
string str = "Hello, World!";cout << "原始字符串: " << str << endl;// 使用replace函数替换字符串
str.replace(str.find("World"), 5, "Universe");cout << "替换后的字符串: " << str << endl;
输出结果应为:
原始字符串: Hello, World!
替换后的字符串: Hello, Universe!
swap
swap函数用于交换两个string对象之间的内容。
string str1 = "Hello";
string str2 = "World";str1.swap(str2);cout << "After swap:" << endl;
cout << "str1: " << str1 << endl;
cout << "str2: " << str2 << endl;
std中的swap()函数:
string str1 = "Hello";
string str2 = "World";swap(str1, str2);cout << "After swap:" << std::endl;
cout << "str1: " << str1 << endl;
cout << "str2: " << str2 << endl;
在这个例子中,我们使用了std命名空间中的swap函数,将str1和str2的内容进行了交换。输出的结果与前一个例子是一样的。
但是std中的swap将两个对象完全交换了,这个过程创建了string类型的中间变量。
而string中的swap并没有完全交换对象,只是交换了两个对象的指针,以及值,并没有创建string类型的中间变量。所以对于string对象,使用string自带的swap效率会更高。
string的查找操作
find
find函数用于在字符串中查找子字符串的第一次出现。
find函数接受两个参数:
待查找的子字符串 str 或者字符c
开始搜索的位置 pos(可选,默认值为0)
返回值:
- 如果找到了指定子串,返回它在原字符串中第一次出现的位置。
- 如果未找到指定子串,返回
string::npos。
string str = "Hello World";
string subStr = "World";// 使用find函数查找子字符串的位置
size_t index = str.find(subStr);cout << "子字符串在位置 " << index << " 处被找到" << endl;
refind
rfind用于在字符串中寻找指定子串的最后一次出现的位置,或者说是倒着查找。
rfind函数接受两个参数:
待查找的子字符串 str 或者字符c
开始搜索的位置 pos(可选,默认为string::npos)
返回值:
- 如果找到了指定子串,返回它在原字符串中最后一次出现的位置。
- 如果未找到指定子串,返回
string::npos。
sub_str
substr用于提取字符串的子串。该函数可以接受两个参数,起始索引位置和子串的长度。
string str = "Hello, world!";// 提取从索引位置3开始的子串
string sub1 = str.substr(3);// 输出子串
cout << sub1 << endl; // 输出为 "lo, world!"// 提取从索引位置7开始长度为5的子串
std::string sub2 = str.substr(7, 5);// 输出子串
cout << sub2 << endl; // 输出为 "world"
输出结果为:
lo, world!
world
可以看到,substr() 函数成功提取了指定位置和长度的子串,并将其存储在新的字符串变量中。
find_first_of & find_first_not_of
find_first_of函数用于在字符串中查找第一个与指定字符序列中的任意字符匹配的字符的位置。
find_first_of函数有多个重载形式,其中最常用的形式如下:
size_t find_first_of (const string& str, size_t pos = 0) const;
这个函数接受一个字符串参数str,并从指定的位置pos开始搜索。find_first_of函数会在调用对象的字符串中查找第一个与str中的任意字符匹配的字符,并返回其位置。如果找不到匹配的字符,则返回string::npos。
string str = "Hello World";
string chars = "aeiou";// 在str中查找第一个出现在"aeiou"中的字符
size_t pos = str.find_first_of(chars);cout << "第一个元音字母出现在位置:" << pos << endl;
cout << "字符为:" << str[pos] << endl;
输出结果为:
第一个元音字母出现在位置:1
字符为:e