STL模板库——string容器

一、string基本概念

string是C++STL中的字符串容器。本质上是一个封装了char*的动态字符串类 ,管理堆区分配的字符串数组,提供一系列字符串操作方法。

在底层,std::string维护了几个重要成员:

  • char*指针(指向存储字符串的动态数组)
  • 长度size_t size(字符串的当前大小)
  • 容量 size_t capacity(字符串当前分配的最大空间)
  • 字符存储区(在堆区分配的存储空间)
特点
  1. 自动管理内存
  2. 动态扩展
  3. 提供丰富的成员函数
  4. 兼容C风格字符串
  5. STL兼容性

二、string构造函数

1. 默认构造(空字符串)

创建一个空字符串,长度为0,但仍可以动态扩展。

cpp 复制代码
std::string str;  
std::cout << "空字符串:\"" << str << "\"" << std::endl;

输出内容:

2. C风格字符串构造

直接使用C语言的const char*(char[])初始化std::string

cpp 复制代码
std::string  str1("Hello,World!");
std::cout << "字符串:" << str1 << std::endl;

输出内容:

3. 拷贝构造

用已有的std::string创建新字符串,内容完全相同。

cpp 复制代码
std::string  str1("Hello,World!");
string str2(str1);
cout << "拷贝构造:" << str2 << endl;  

输出内容:

4.指定长度的字符串构造

使用char* 创建字符串,但只取前n个字符。

cpp 复制代码
string str3("Hello,World!", 5);
cout << "部分截取:" << str3 << endl;  

输出内容:

5. 重复字符构造

使用n个相同字符初始化string

cpp 复制代码
string str4(10, 'A');
cout << "重复字符:" << str4 << endl; 

输出内容:

6. 迭代器构造(从容器中取一部分字符串)

使用迭代器从original中截取部分内容。

cpp 复制代码
string original = "ABCDEFG";
string str5(original.begin() + 2, original.begin() + 5);
cout << "部分字符串:" << str5 << endl;  

输出内容:

7. 移动构造

std::move允许std::string 进行资源转移。

cpp 复制代码
string tempVar = "Hello";
string str6 = move(tempVar);
cout << "移动后的tempVar:" << tempVar << endl;
cout << "移动到str6:" << str6 << endl; 

输出内容:

三、string赋值操作

常见的赋值操作包括:

  • operator =赋值运算符
  • assign() 赋值函数

1. 赋值运算符operator=

可以将字符串赋值给string对象,有几种不同的情况:

(1) 直接赋值C风格字符(char*)

将C风格字符串"Hello,World!"赋值给string

cpp 复制代码
string str;
str = "Hello,World!";
cout << str << endl; 

输出内容:

(2) 赋值给另一个string

str2拷贝str1的内容,不会影响str1

cpp 复制代码
string str1 = "Hello";
string str2;
str2 = str1;
cout << str2 << endl;  

输出内容:

(3) 赋值单个字符

string支持单个字符赋值(char a='A'),它会被自动转换string("A")

cpp 复制代码
string str3;
str3 = 'A';
cout << str3 << endl;  

输出内容:

(4) 使用move()进行移动赋值

str4的数据转移到str5,避免拷贝,提高效率。

cpp 复制代码
string str4 = "Hello";
string str5;
str5 = move(str4);
cout << "str4: " << str4 << endl;  // str1 变为空
cout << "str5: " << str5 << endl;  // str2 获得 "Hello"   

输出内容:

2. assign()赋值函数

assign()string专门提供的赋值函数,可以灵活地从字符串字符迭代器范围等方式进行赋值。

(1) 赋值C风格字符串

assign()作用和operator = 一样,但更灵活。

cpp 复制代码
string str;
str.assign("Hello,World!");
cout << str << endl;  

输出内容:

(2) 赋值部分C风格字符串

char* 开头取n个字符赋值。

cpp 复制代码
string str1;
str.assign("Hello,World!", 5);
cout << str1 << endl;   

输出内容:

(3) 赋值另一个string

等价于str4=str3

cpp 复制代码
string str3 = "Hello";
string str4;
str4.assign(str3);
cout << str4 << endl;  
输出内容:  
    
![image.png](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/7e9756c9abad4191890e30d39c7f6d1d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgRXRob25f546L:q75.awebp?rk3s=f64ab15b&x-expires=1756042197&x-signature=Dw33jW9lZKnfSKU%2FSbIkV0TELAM%3D)  
#### (4) 赋值重复字符
创建5个'A'组成的字符串。  
```cpp
string str5;
str5.assign(5, 'A');
cout << str5 << endl;

输出内容:

(5) 赋值迭代器范围

使用迭代器范围截取部分字符串赋值。

cpp 复制代码
string str6 = "ABCDEFG";
string str7;
str7.assign(str6.begin() + 2, str6.begin() + 5);
cout << str7 << endl;

输出内容:

assign()函数原型

cpp 复制代码
string& assign(const string & str);           // 赋值另一个 string
string& assign(const string & str, size_t pos, size_t len = npos); // 截取部分赋值
string& assign(const char* s);               // 赋值 C 风格字符串
string& assign(const char* s, size_t n);     // 取 C 字符串的前 n 个字符
string& assign(size_t n, char c);            // 赋值 n 个字符 c
template <class InputIterator>
string& assign(InputIterator first, InputIterator last); // 迭代器范围赋值  

四、 string字符串拼接

在C++string容器中,字符串拼接有多种方式,主要包括:

  1. 使用+运算符
  2. 使用+=运算符
  3. 使用append()函数

1.使用+运算符

+用于拼接两个字符串,可以拼接stringchar*,但不能charstring直接相加。

示例:

cpp 复制代码
string str1 = "I";
string str2 = " love";
string result1 = str1 + str2; //拼接两个字符串
cout << result1 << endl;

string result2 = result1 + " you!";//拼接string和C字符串
cout << result2 << endl;   

输出结果:

注意:string不能直接拼接单个字符。

错误示例:

cpp 复制代码
string result3 = "Hello" + 'A';
cout << result3 << endl;  

输出结果:

为什么?这是因为代码中存在类型不匹配和指针运算(指针偏移)问题。

问题分析:

  1. "Hello"的类型:"Hello"是一个C风格字符串(const char*类型)。
  2. 'A'的类型:'A'是一个字符(char类型),其ASCll值为65。
  3. "Hello"+'A'实际上等价于:const char* ptr ="Hello"+65;

也就是说,它让"Hello"的指针向后移动了65个字节,但"Hello"实际上只有6个字节(包括\0),因此访问到了随机的内存地址,导致了未定义行为(可能输出Fancyptr或者是其他的值,也可能崩溃)

正确做法:
string result3 =string("Hello")+'A'; //显示转换为string再拼接

或者
string result3 = "Hello"+string(1,'A');//先把'A'变成string再拼接

2.使用+=运算符

+=直接在原字符串上追加内容,可以拼接stringchar*char

示例:

cpp 复制代码
string str = "Closed off";
str += " from love"; //拼接C风格字符串
cout << str << endl;
str += '!';//追加单个字符
cout << str << endl;   

输出内容:

注意事项:不能拼接数字类型。

错误示范:str += 123;

那么会输出什么?

为什么会输出一个'{'?

关键问题:

123是整数(int),但stringoperator+=没有定义直接接收int作为参数。因此,C++试图找到一个兼容的重载,但没有匹配的string::operator+=(int)。隐式转换发生了,int被转换成char,而char(123)的ASCll码对应的字符是'{'。

正确做法:
str += to_string(123); //先转换为字符串

输出内容:


不要忘记头文件#include<string>

3.使用append()函数

append()适用于更复杂的拼接需求,支持拼接部分字符串、指定数量字符等。

追加整个字符串

cpp 复制代码
string str = "I didn't";
str.append(" need the pain");
cout << str << endl;

输出内容:

  1. 追加部分字符串
cpp 复制代码
string str1 = "Once or twice";
str1.append(" was enough and", 11);//只追加前11个字符
cout << str1 << endl;  

输出内容:

  1. 追加重复字符
cpp 复制代码
string str2 = "and it was all vain";
str2.append(3, '!');//追加3个'!'
cout << str2 << endl;    

输出内容:

  1. 追加另一个string的部分内容
cpp 复制代码
string str3 = "Time starts to";
string str4 = " pass before";
str3.append(str4, 0, 5);//追加str4的前5个字符
cout << str3 << endl;

输出内容:

string::append()函数原型

cpp 复制代码
string& append(const string & str); // 追加整个字符串
string& append(const string & str, size_t subpos, size_t sublen); // 追加 str 的一部分
string& append(const char* s); // 追加 C 风格字符串
string& append(const char* s, size_t n); // 追加 C 字符串前 n 个字符
string& append(size_t n, char c); // 追加 n 个字符 c
template <class InputIterator>
string& append(InputIterator first, InputIterator last); // 追加迭代器范围内容 

五、 string查找和替换

在C++string容器中,我们可以使用一系列成员函数来查找子字符串的位置,并进行替换。

1.find()--查找子字符串

find()用于查找子字符串在当前string对象中的起始位置。

函数原型

cpp 复制代码
size_t find(const string & str, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
size_t find(char ch, size_t pos = 0) const;    

str/s:要查找的字符串或字符
pos:从字符串的pos位置 开始查找(默认为0)

返回值:

  • 找到:返回子串第一个匹配字符的索引
  • 未找到:返回string::npos
    示例
cpp 复制代码
string str = "Before you know it you're frozen";
//查找子字符串"know"
size_t pos = str.find("know");
if (pos != string::npos)
 cout << "找到\"know\",位置:" << pos << endl;
else
 cout << "未找到\"know\"" << endl;
//查找字符'o',从索引5开始查找
pos = str.find('o', 5);
cout << "找到'o',位置:" << pos << endl;  

输出结果

在上述例子有看到使用的类型是size_t,那么这个是否可以替换为int?那就要先说一说它们之间的区别:

  1. size_t是无符号整数类型,通常表示内存大小、数值索引等非负值。
  2. int是有符合整数,可以存储负数、范围更小。

那么可以替换吗?

也不建议。虽然在小型程序中,如果没有太大的字符串(最大索引不超过int范围),那么可能不会导致错误。如果找到了,那没什么问题,但若没找到,find()函数返回的是std::string::npos,是一个特殊的常量,它的类型是size_t,通常是一个非常大的无符号整数,在32为系统通常是(2^32-1),其二进制表示与-1的二进制补码相同。那么如果用int回被表示成-1。在64位系统,如果赋值给int,会导致溢出,结果是未定义的。

2. rfind()--逆向查找

find()相同,但从字符串的末尾向前查找,返回最后一次出现的位置。

示例

cpp 复制代码
string str1 = "But something";
size_t pos1 = str1.rfind('n');
cout << "最后一次'n'出现的位置:" << pos1 << endl;

输出

3.find_first_of()--查找多个字符中的第一个匹配项

查找字符串中任意一个字符首次出现的位置

示例

cpp 复制代码
string str2 = "happened for";
size_t pos2 = str2.find_first_of("pe");
cout << "字符 'p' 或 'e' 第一次出现的位置:" << pos2 << endl; 

输出

4.find_last_of()--逆向查找多个字符

查找字符串中任意一个字符最后一次出现的位置。

示例

cpp 复制代码
string str3 = "the very firt time with you";
size_t pos3 = str.find_first_of("ry");
cout<<"字符 'r' 或 'y' 最后一次出现的位置: " << pos3 << endl;  

输出

5. replace()--替换子字符串

可以用新字符串替换已有字符串的某部分。
函数原型

cpp 复制代码
string& replace(size_t pos, size_t len, const string & str);
string& replace(size_t pos, size_t len, const char* s); 
  • pos:要替换的起始位置
  • len:要替换的长度
  • str/s:用于替换的新字符串

示例

cpp 复制代码
string str4 = "My liver melted to the ground";
//替换"liver"为"heart"
str4.replace(str4.find("liver"), 5, "heart");
cout << str4 << endl; 

输出

6. erase()--删除子字符串

可以删除string中的某部分内容
函数原型

cpp 复制代码
string& erase(size_t pos = 0, size_t len = npos);
  • pos:删除的起始位置
  • len:删除的字符数(默认为npos,即删除到末尾)

示例

cpp 复制代码
string str5 = "Founddd something true";
str5.erase(5, 2);
cout << str5 << endl;   

输出

六、string字符串比较

在C++中,string提供了多种比较方式。主要比较方式包括:

  1. 使用==、!=、<、>等运算符
  2. 使用compare()函数进行字符串比较

1.运算符比较

C++string类重载了"==、!=、<、>、<=、>="这些运算符,比较方式如下:

  • 按ASCll码顺序逐字符比较
  • 若某个字符不同,则比较ASCll码的大小
  • 若前缀相同,则短字符串小于长字符串。

示例

cpp 复制代码
//运算符比较
string str1 = "apple";
string str2 = "banana";
string str3 = "apple";
//比较相等
cout << (str1 == str2) << endl; //0(false)
cout << (str1 == str3) << endl; //1(true)
//比较不相等
cout << (str1 != str2) << endl; //1(true)
//大小比较(按ASCll码表顺序)
cout << (str1 < str2) << endl; //1(true)
cout << (str1 > str2) << endl; //0(false)    

输出

2. compare()方法

string::compare()方法提供了更灵活的字符串比较,它返回一个整数值:

  • 0:两个字符串相等
  • <0:当前字符串小于目标字符串
  • >0:当前字符串大于目标字符串

compare()语法:

cpp 复制代码
int compare(const string & str) const;
int compare(size_t pos, size_t len, const string & str) const;
int compare(size_t pos, size_t len, const string & str, size_t subpos, size_t sublen) const;

1. 直接比较两个字符串:

cpp 复制代码
string str4 = "stand";
string str5 = "apart";
string str6 = "stand";
cout << str4.compare(str5) << endl;//正数
cout << str4.compare(str6) << endl;//0
cout << str5.compare(str4) << endl;//负数  

输出

2.只比较字符串的一部分

cpp 复制代码
string str7 = "abcdef";
string str8 = "cde";
// 比较 str7[2] 开始的3个字符 ("cde") 和 str8 ("cde")
cout << str7.compare(2, 3, str8) << endl;  // 0,相等
// 比较 str7[2] 开始的3个字符 ("cde") 和 "cdf"
cout << str7.compare(2, 3, "cdf") << endl; // 负数,因为 'e' < 'f' 

输出

七、string字符串访问和修改

1.访问字符串中的字符

1.使用[ ]下标访问

string允许像数组一样使用[ ]访问字符:

cpp 复制代码
string str = "parse";
cout << str[0] << endl; //p
cout << str[4] << endl; //e                                              

输出


[]不会进行范围检查,如果访问超出范围,可能会导致未定义行为。

2.使用at()方法

at()方法与[]类似,但它会检查索引是否超出范围,如果超出,则抛出out_of_range异常。

cpp 复制代码
string str = "parse";
cout << str.at(1) << endl;//a
cout << str.at(10) << endl;//抛出异常

输出

异常:_Xout_of_range("invalid string position");

3.访问首尾字符

cpp 复制代码
string str = "parse";
cout << "首字符:" << str.front() << endl;
cout << "尾字符:" << str.back() << endl;  

输出

2.修改字符串

使用[]at()修改字符。

cpp 复制代码
string str = "parse";
str[0] = 'H';//修改第一个字符
str.at(4) = 'h';//使用at()修改
cout << str << endl;

输出

八、 string插入和删除

1.插入字符

insert(pos,string)
insert(pos,string)方法用于在pos位置插入字符串:

cpp 复制代码
string str = "thoug";
str.insert(5, "ht");//在索引5处插入"ht"
cout << str << endl;

输出


insert(pos,n,char)

pos位置插入n个相同字符:

cpp 复制代码
string str = "thought";
str.insert(7, 3, '!');
cout << str << endl; 

输出

2.删除字符

erase(pos,len)

pos开始删除len个字符:

cpp 复制代码
string str1 = "Just do it";
str1.erase(4, 6);//从索引4开始删除6个字符
cout << str1 << endl;

输出


pop_back()

删除字符串的最后一个字符:

cpp 复制代码
string str2 = "Just do it!";
str.pop_back();//删除最后一个字符
cout << str << endl;  

输出

九、获取string子串

1.substr()方法

函数原型:

cpp 复制代码
string substr(size_t pos = 0, size_t len = npos) const;
  • pos:子串的起始位置
  • len:要截取的字符数量(默认npos表示截取到末尾)
  • 返回值:返回指定范围的新字符串,不会修改原字符串

2.substr()基本用法

示例1:获取指定范围的子串

cpp 复制代码
string str = "Hello, World!";
string sub1 = str.substr(7, 5); //从索引7开始,截取5个字符
cout << sub1 << endl; //World

输出

示例2:截取从某个位置到结尾的子串

如果不提供len,则默认从pos开始一直截取到字符串末尾。

cpp 复制代码
string str = "C++ STL is powerful";
string sub2 = str.substr(6); //从索引6开始,一直到末尾
cout << sub2 << endl; //STL is powerful 

输出

示例3:获取整个字符串(等于拷贝)

如果pos =0,且len = npos,等价于复制整个字符串:

cpp 复制代码
string str = "Hello,C++!";
string copy_str = str.substr(); //复制整个字符串
cout << copy_str << endl;

输出

3.substr()find()结合使用

可以先使用find()定位子串的位置,再用substr()提取。

cpp 复制代码
string str = "Welcome to C++ programming";
//找到C++在字符串中的位置
size_t pos = str.find("C++");
//提取"C++"及后面的内容
string sub = str.substr(pos);
cout << sub << endl;

输出

十、原型

cpp 复制代码
// <string> Forward declarations -*- C++ -*-

// Copyright (C) 2001-2023 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.

// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
// <http://www.gnu.org/licenses/>.

/** @file bits/stringfwd.h
 *  This is an internal header file, included by other library headers.
 *  Do not attempt to use it directly. @headername{string}
 */

//
// ISO C++ 14882: 21 Strings library
//

#ifndef _STRINGFWD_H
#define _STRINGFWD_H 1

#pragma GCC system_header

#include <bits/c++config.h>
#include <bits/memoryfwd.h>

namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION

  /**
   *  @defgroup strings Strings
   *
   *  @{
  */

  template<class _CharT>
    struct char_traits;

  template<> struct char_traits<char>;

  template<> struct char_traits<wchar_t>;

#ifdef _GLIBCXX_USE_CHAR8_T
  template<> struct char_traits<char8_t>;
#endif

#if __cplusplus >= 201103L
  template<> struct char_traits<char16_t>;
  template<> struct char_traits<char32_t>;
#endif

_GLIBCXX_BEGIN_NAMESPACE_CXX11

  template<typename _CharT, typename _Traits = char_traits<_CharT>,
           typename _Alloc = allocator<_CharT> >
    class basic_string;

_GLIBCXX_END_NAMESPACE_CXX11

  /// A string of @c char
  typedef basic_string<char>    string;   

  /// A string of @c wchar_t
  typedef basic_string<wchar_t> wstring;   

#ifdef _GLIBCXX_USE_CHAR8_T
  /// A string of @c char8_t
  typedef basic_string<char8_t> u8string;
#endif

#if __cplusplus >= 201103L
  /// A string of @c char16_t
  typedef basic_string<char16_t> u16string; 

  /// A string of @c char32_t
  typedef basic_string<char32_t> u32string; 
#endif

  /** @}  */

_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std

#endif	// _STRINGFWD_H