前言: 从这篇博客开始我们就进入了STL标准库的学习,对于这部分是我们以后写项目的重要基础,这部分学习主要分为三个阶段,第一阶段是会用STL中的一些容器,第二阶段是知道它们的底层是如何实现的,第三阶段是我们能对其改造。当然我们前两个阶段达到就已经相当ok了,在面对相当多的场景就已经够用,不奢求能达到第三阶段,接下来就让我们开启对string的学习吧!温馨提示:前面知识有遗忘的要多加回顾哦!
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意 运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使 用。 HP 版本--所有STL实现版本的始祖。
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低, 符号命名比较怪异。
由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好, 可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码, 主要参考的就是这个版本。

可以看到STL中包含了大量的容器和一些算法,学习了这些知识之后,相信你会忘却C语言的存在的。
STL(Standard Template Library)中的 std::string 是 C++ 标准库提供的一个极其重要和常用的类,用于表示和操作字符序列(通常是文本字符串)。它是 basic_string<char> 模板类的类型别名,专为 char 类型(通常用于表示 ASCII 或 UTF-8 字符)设计。
new/delete 或 malloc/free。字符串长度可以动态增长或缩小。
strcpy 不当使用)。
=, +, +=, ==, !=, <, >, [] 等),使得字符串的赋值、连接、比较和访问像操作基本类型一样直观。
vector)一样使用迭代器,方便地与标准算法(<algorithm> 头文件中的 find, sort, transform 等)协同工作。
size() 或 length() 成员函数可以随时获取当前字符串的长度(字符数,不包括结尾的空字符 '\0')。
#include <string> // 必须包含的头文件
using namespace std; // 或者显式使用 std::string构造函数 | 功能说明 |
|---|---|
string()(重点) | 构造空的 string 类对象,即空字符串 |
string(const char* s)(重点) | 用 C-string 来构造 string 类对象 |
string(size_t n, char c) | string 类对象中包含 n 个字符 c |
string(const string& s)(重点) | 拷贝构造函数 |
代码示例如下:
std::string str1; // 默认构造,空字符串 ""
std::string str2("Hello"); // 用 C 风格字符串初始化
std::string str3 = "World"; // 同上
std::string str4(str2); // 拷贝构造 "Hello"
std::string str5(5, 'A'); // 构造包含 5 个 'A' 的字符串 "AAAAA"
std::string str6(str2, 1, 3); // 从 "Hello" 的索引 1 开始取 3 个字符 "ell"
std::string str7(str2, 1); // 如果第三个参数不写的话默认是最大的即npos,"ello"
std::string str8(str2, 1,string::npos); //与上述语句功能完全一致
std::string str9 = str2 + " " + str3; // 连接构造 "Hello World"size_t string::npos = -1;npos就是一个无符号整数的-1
size_t 是无符号整数类型(通常为 unsigned int 或 unsigned long)。
-1 赋值给无符号整数时,会发生整数回绕(wrap-around),结果等于该类型能表示的最大值(即所有位全为 1 的二进制数)。
npos = 4,294,967,295(即 232−1232−1)
npos = 18,446,744,073,709,551,615(即 264−1264−1)
函数名称 | 功能说明 |
|---|---|
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty(重点) | 检测字符串是否为空串,是则返回 true,否则返回 false |
clear(重点) | 清空有效字符 |
reserve(重点) | 为字符串预留空间 |
resize(重点) | 将有效字符的个数改成 n 个,多出的空间用字符 c 填充 |
s.reserve(100);//reserve提前开空间,但要考虑内存对齐,一般都是开给定空间的整数倍
s.resize(100, 'x');//resize是不光开空间而且将开辟的空间给值,不自己设定值默认是'\0'string s("hello world");
s.resize(5);//会发生截断
s.resize(20, 'x');//在hello world后面补充x直至达到20个空间
cout << s << endl;//也就是说resize是管你空间的,你原本就够的给你截断,你原本不够的给你补充函数名称 | 功能说明 |
|---|---|
operator[](重点) | 返回指定位置(pos)的字符,适用于 const string 类对象 |
begin + end | 迭代器:begin 获取第一个字符的迭代器,end 获取最后一个字符下一个位置的迭代器 |
rbegin + rend | 反向迭代器:rbegin 获取最后一个字符的迭代器,rend 获取第一个字符前一个位置的迭代器 |
范围 for | C++11 支持的更简洁的范围 for 循环遍历方式 |
函数名称 | 功能说明 |
|---|---|
push_back | 在字符串后尾插字符 c |
append | 在字符串后追加一个字符串 |
operator+=(重点) | 在字符串后追加字符串 str |
c_str(重点) | 返回 C 格式字符串(即以 \0 结尾的字符数组) |
find + npos(重点) | 从字符串 pos 位置开始往后找字符 c,返回该字符在字符串中的位置 |
rfind | 从字符串 pos 位置开始往前找字符 c,返回该字符在字符串中的位置 |
substr | 在字符串中从 pos 位置开始,截取 n 个字符,然后将其返回 |
cout << s1 << endl;//调用的是string重载的operator<< 会将对象数组中的所有字符都输出
cout << s1.c_str() << endl;//直接输出const char*是C语言的形式 遇到\0就会结束
//就会发现cout << s1 << endl;是会打印完的,而cout << s1.c_str() << endl;只打印到\0处函数名称 | 功能说明 |
|---|---|
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>>(重点) | 输入运算符重载 |
operator<<(重点) | 输出运算符重载 |
getline(重点) | 获取一行字符串 |
relational operators(重点) | 大小比较(如 ==, !=, <, <=, >, >= 等) |
cin:使用 >> 运算符读取输入,以空白字符(空格、制表符、换行符)作为分隔符。遇到空白符时停止读取,剩余输入留在缓冲区。 getline:读取整行输入(包括空格),以换行符 '\n' 为结束标志。换行符被读取但不存储到目标变量中。
int main() {
string s1, s2;
cout << "Enter a string for cin: ";
cin >> s1; // 输入 "Hello World",s1 只得到 "Hello"
cin.ignore(); // 清除缓冲区残留的换行符
cout << "Enter a string for getline: ";
getline(cin, s2); // 输入 "Hello World",s2 得到完整内容
cout << "cin: " << s1 << endl; // 输出 "Hello"
cout << "getline: " << s2; // 输出 "Hello World"
return 0;
}代码示例总结如下:
str1 = "New Value"; // 赋值(重要)
str1.assign("Assigned", 3); // 赋值前 3 个字符 "Ass"char first = str2[0]; // 'H' (下标运算符,不检查边界)
const char* cstr = str2.c_str(); // 获取指向内部 C 风格字符串的指针 (只读,内容可能随 str2 改变而失效)
---------------------下面不常用---------------------------------------------------------
char second = str2.at(1); // 'e' (at 方法,会检查边界,越界抛 std::out_of_range)
char last = str2.back(); // 'o' (C++11)
char first = str2.front(); // 'H' (C++11)
const char* dataptr = str2.data(); // 类似 c_str() (C++11 起,data() 在非 const string 上也返回 const char*,直到 C++17 才有非 const 版本)str2 += " C++"; // 追加 " C++" -> "Hello C++" (operator+=)
str2.insert(5, " dear"); // 在索引 5 处插入 " dear" -> "Hello dear C++!!!"
str2.erase(5, 5); // 从索引 5 开始删除 5 个字符 -> "Hello C++!!!"
str2.clear(); // 清空字符串 -> ""
str2.resize(10, 'X'); // 改变大小为 10,不足部分用 'X' 填充 -> "XXXXXXXXXX"
str2.append("!!!"); // 追加 "!!!" -> "Hello C++!!!"
str2.push_back('!'); // 追加单个字符 '!'
str2.replace(6, 3, "Standard"); // 从索引 6 开始替换 3 个字符 ("C++") 为 "Standard" -> "Hello Standard!!!"
//插入一般喜欢用+=size_t pos1 = str.find("World"); // 查找子串 "World" 首次出现的位置,找不到返回 std::string::npos
size_t pos2 = str.find("lo", 1); // 从索引 1 开始查找 "lo"
size_t pos3 = str.rfind("l"); // 查找 'l' 最后一次出现的位置
size_t pos4 = str.find_first_of("aeiou"); // 查找 "a", "e", "i", "o", "u" 中任意一个首次出现的位置
size_t pos5 = str.find_first_not_of("0123456789"); // 查找第一个非数字字符的位置
// 类似的有 find_last_of, find_last_not_of应用场景,如分离网址:
//取文件后缀名
void test_string1()
{
string s1("string.cpp");
string s2("string.c");
string s3("string.txt");
size_t pos1 = s1.find('.');//如果没找到的话返回的就是无符号整型的最大值
if (pos1 != string::npos)
{
cout << s1.substr(pos1) << endl;
}
size_t pos2 = s2.find('.');
if (pos2 != string::npos)
{
cout << s2.substr(pos1) << endl;
}
}
//分离网址
void test_string2()
{
string habcsdnurl("https://blog.csdn.net/qq_53706413?spm=1011.2124.3001.5343");
string& url = habcsdnurl;
//分离url 协议 域名 资源名称
size_t pos1 = url.find(':');
if (pos1 != string::npos)
{
cout << url.substr(0, pos1) << endl;
}
size_t pos2 = url.find('/', pos1 + 3);
if (pos2 != string::npos)
{
cout << url.substr(pos1 + 3, pos2 - (pos1 + 3)) << endl;
}
cout << url.substr(pos2+1);
}std::string sub1 = str.substr(6); // 从索引 6 开始到结尾的子串 ("Standard!!!")
std::string sub2 = str.substr(6, 8); // 从索引 6 开始长度为 8 的子串 ("Standard")if (str1 == str2) { ... } // 相等比较
if (str1 != str3) { ... } // 不等比较
if (str1 < str4) { ... } // 小于比较 (字典序)
int result = str1.compare(str2); // 返回 0(相等), >0(str1>str2), <0(str1<str2)
int result = str1.compare(0, 3, "Hel"); // 比较 str1 的前 3 个字符和 "Hel"size_t len = str.size(); // 或 str.length(), 当前字符数
bool isEmpty = str.empty(); // 是否为空字符串 (size() == 0)
size_t cap = str.capacity(); // 当前已分配的内存能容纳的字符数 (通常 >= size())
str.reserve(100); // 请求预留至少 100 字符的容量 (优化后续操作,减少重新分配)
str.shrink_to_fit(); // 请求减少 capacity() 到 size() (C++11, 请求非强制)std::cout << str << std::endl; // 输出字符串
std::cin >> str; // 读取一个单词 (遇空格/换行停止)
std::getline(std::cin, str); // 读取一整行 (包括空格,直到换行符)事实上string的接口非常多,穷举都要举办天,所以一般我们会用一些常见的就够用了,对于不常见的碰到了,查文档就可以,文档网址如下: https://legacy.cplusplus.com/
上述对于它的使用是第一层次的,当我们会用之后,可以进入下一层,对它的底层实现加以了解,这会在我们使用它的时候更加得心应手!
事实上,校招找工作的时候会场面临着让你简单实现一个string类的情况。
string的简单模拟实现:
namespace hab
{
class string
{
public:
/*string(char* str)
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
string()
:_str(new char[1])
{
_str[0] = '\0';
}*/
//合并写法
string(const char* str ="\0")
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
string(const string& s)//为什么const string s报错,因为陷入了死循环,一直在调用拷贝构造
:_str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}//这是深拷贝,即重新开辟空间
//但是如果是编译器默认生成的就会造成浅拷贝的问题,浅拷贝就是对同一块空间释放多次导致的错误。
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
}
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
const char* c_str()
{
return _str;
}
size_t size()
{
return strlen(_str);
}
private:
char* _str;
};
}上面是传统的实现方法,对于部分接口有现代的一些写法更简便,如下:
string(const string& s)//拷贝构造的另一种写法
:_str(nullptr)
{
string tmp(s._str);
swap(_str, tmp._str);
}//有点空手套白狼的感觉
//沿用上述思路,可以对赋值进行更改
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// string tmp(s);
// swap(_str, tmp._str);//这里遇到如果由capacity和size时交换存在问题
// }
// return *this;
//}
//也可以用下述的写法
string& operator=(string s)//这里最巧的就是这个传值操作
{
swap(_str, s._str);
return *this;
}我们在模拟实现operator输入的时候,会遇到用in无法读取空格或换行符的情况,最终导致输入无法停止的现象,如下:
istream& operator>>(istream& in, string& s)
{
while (1)
{
char ch;
//in >> ch;//这里会导致无法读取换行符或者空格的问题,
//原因就在于当执行 in >> ch 时,运算符会忽略(跳过)所有前导空白字符(包括空格、换行符 '\n'、制表符等)。
ch = in.get();
if (ch == ' ' || ch == '\n')
break;
else
s += ch;
}
return in;
}解释:
in >> ch 的行为:
in >> ch 时,运算符会忽略(跳过)所有前导空白字符(包括空格、换行符 '\n'、制表符等)。
ch。
'\n'),in >> ch 会持续等待非空白字符输入,导致循环无法终止。
while (1) 是死循环,仅依赖 ch == ' ' || ch == '\n' 来跳出。
>> 跳过了换行符,ch 永远不会等于 '\n',因此循环无法通过换行符终止。
本篇博客我们主要了解学习了string这个容器, string 类是 C++ 标准库中用于处理字符串的重要组件,它提供了丰富的构造函数,如默认构造、用 C 风格字符串构造、拷贝构造等,还支持通过 + 进行字符串连接。在容量管理方面,size 和 length 可获取字符串长度,capacity 返回当前容量,reserve 用于预留空间,resize 可调整字符串大小并填充指定字符。访问和遍历字符串可通过下标运算符 []、迭代器(begin、end、rbegin、rend)以及 C++11 的范围 for 循环实现。修改操作包括 push_back 追加字符、append 追加字符串、operator+= 追加字符串、c_str 返回 C 风格字符串、find 和 rfind 查找字符或子串、substr 提取子串等。非成员函数如 operator+(连接字符串)、operator>>(输入)、operator<<(输出)、getline(读取一行)以及关系运算符(比较字符串)等也极大地方便了字符串的使用。模拟实现 string 类时,需关注深拷贝与浅拷贝问题,确保正确管理内存,避免多次释放同一块内存空间。希望大家有所收获!
写在后面的话: 对于string这个类,上面的接口大家对标注重要的部分加以记忆即可,构造方面:拷贝构造、字符串构造、无参构造这些就足够,修改方面:+=、insert(分为在指定位置插入字符串和字符)、erase、reserve、resize、find、rfind这些是相对比较重要的。另外要格外注意capacity是能存放多少有效字符,而size是现有多少有效字符,这点要格外注意,并且字符串后面一定要时刻提防'\0',不能把它遗忘。 另外,对于string的大部分接口的实现,可以详见本人代码库: https://gitee.com/hanaobo/c-learningcode/tree/master/string_simulate