c++从入门到跑路——string类

c++从入门到跑路------string类

1.为什么学习string类?

1.1 C语言中的字符串

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列 的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户 自己管理,稍不留神可能还会越界访问。

1.2 两个面试题(暂不做讲解)

把字符串转换成整数_牛客题霸_牛客网

415. 字符串相加 - 力扣(LeetCode)

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、 快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。讲白了,就是c语言那套面向过程的思想已经跟不上社会发展的变化了,而c++创新地采用了面向对象思想,利用封装,多态,继承,极大的提高了开发效率,以及开发潜力。

2.标准库中的string类

2.1 string类(了解)

cplusplus.com/reference/string/string/?kw=string

这个链接是关于c++里面string类的文档介绍

注意:在使用string类时,必须包含#include头文件以及using namespace std;

2.2 auto和范围for

auto关键字

​ 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个 不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型 指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期 推导而得。

​ 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

​ 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

在这里补充2个C++11的小语法,方便我们后面的学习。

(1)auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

(2) auto不能直接用来声明数组

复制代码
#include<iostream>
using namespace std;
int func1()
{
    return 10;
}
// 不能做参数
void func2(auto a)
{}

// 可以做返回值,但是建议谨慎使用
auto func3()
{
return 3;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = func1();
// 编译报错:rror C3531: "e": 类型包含"auto"的符号必须具有初始值设定项
auto e;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
int x = 10;
auto y = &x;
auto* z = &x;
auto& m = x;
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
cout << typeid(z).name() << endl;

auto aa = 1, bb = 2;
// 编译报错:error C3538: 在声明符列表中,"auto"必须始终推导为同一类型
auto cc = 3, dd = 4.0;

// 编译报错:error C3318: "auto []": 数组不能具有其中包含"auto"的元素类型
auto array[] = { 4, 5, 6 };

return 0;

}

#include<iostream>
#include <string>
#include <map>
using namespace std;
auto aa = 1, bb = 2;
// 编译报错:error C3538: 在声明符列表中,"auto"必须始终推导为同一类型
auto cc = 3, dd = 4.0;
// 编译报错:error C3318: "auto []": 数组不能具有其中包含"auto"的元素类型
auto array[] = { 4, 5, 6 };
int main()
{
std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange", 
"橙子" }, {"pear","梨"} };
// auto的用武之地
//std::map<std::string, std::string>::iterator it = dict.begin();
auto it = dict.begin();
while (it != dict.end())
{
cout << it->first << ":" << it->second << endl;
++it;
}

return 0;
}

范围for

对于一个有范围的集合=而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11中引入了基于范围的for循环。for循环后的括号由冒号" :"分为两部分:第一部分是范围 内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束 范围for可以作用到数组和容器对象上进行遍历 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到

复制代码
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	// C++98的遍历
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		array[i] *= 2;
	}
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		cout << array[i] << endl;
	}
	// C++11的遍历
	for (auto& e : array)
		e *= 2;
	for (auto e : array)
		cout << e << " " << endl;
	string str("hello world");
	for (auto ch : str)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

2.3 string类的常用接口说明(注意下面我只讲解最常用的接口)

1.string类对象的常见构造

构造函数链接:

http://www.cplusplus.com/reference/string/string/string/

一、string () 无参构造函数(重点)
1. 形参类型解释

无输入形参,为无参构造函数。

2. 函数的功能与代码实现

核心功能 :构造一个空的std::string对象,字符串长度为 0,无有效字符,仅初始化底层基础内存结构,无字符数据存储。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    // 基础用法:构造空字符串
    string empty_str;
    cout << "空字符串长度:" << empty_str.size() << endl; // 输出 0
    cout << "是否为空:" << empty_str.empty() << endl;     // 输出 1(true)

    // 典型场景:先构造空串,后续赋值
    string str;
    str = "hello world";
    cout << "赋值后内容:" << str << endl; // 输出 hello world
    return 0;
}
3. 开发中的注意事项
  • 空 string 对象是合法、可安全操作 的对象,不是空指针,调用empty()size()、赋值等成员函数不会触发崩溃;
  • 空串的底层内存会在后续赋值 / 追加内容时自动扩容分配,无需手动管理。
二、string (const char* s) 构造函数(重点)
1. 形参类型解释

形参为const char* s:指向\0结尾的 C 风格字符串的只读指针。

*为什么必须用 const char(一针见血)**:

  1. 兼容字符串字面量 :双引号包裹的"xxx"本质是const char[N],只能绑定到const char*,非 const 的char*无法接收,会直接编译报错;
  2. 保证入参安全:const 承诺函数内部不会修改原字符串内容,避免误改只读内存导致程序崩溃;
  3. 通用性最强 :可同时接收const char*char*、字符数组、字符串字面量,覆盖所有 C 风格字符串场景。
2. 函数的功能与代码实现

核心功能 :读取入参s指向的 C 风格字符串(从起始地址到\0为止),将内容全量拷贝到新构造的std::string对象中,生成与原 C 字符串内容完全一致的 C++ 字符串。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    // 最常用:用字符串字面量直接构造
    string str1("hello C++");
    cout << "str1: " << str1 << endl; // 输出 hello C++

    // 用char数组(C风格字符串)构造
    char c_arr[] = "C style string";
    string str2(c_arr);
    cout << "str2: " << str2 << endl; // 输出 C style string

    // 用const char*指针构造
    const char* c_ptr = "const char string";
    string str3(c_ptr);
    cout << "str3: " << str3 << endl; // 输出 const char string
    return 0;
}
3. 开发中的注意事项
  • 入参s必须以 **\0空字符结尾 **,否则会触发内存越界读取,导致程序崩溃或乱码;
  • 禁止传入nullptr、野指针、未初始化的指针,会触发未定义行为,直接导致程序崩溃;
  • 空串""是合法入参,会构造一个空的 string 对象,与无参构造效果一致。

三、string (size_t n, char c) 构造函数
1. 形参类型解释
  • size_t n:无符号整数类型,指定最终生成字符串的固定长度(字符个数)
  • char c:基础字符类型,用于填充字符串的单个字符。
2. 函数的功能与代码实现

核心功能 :构造一个长度为n的 string 对象,字符串的每一个字符都统一为入参的字符c

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    // 基础用法:生成5个'a'组成的字符串
    string str1(5, 'a');
    cout << "str1: " << str1 << endl; // 输出 aaaaa

    // 实用场景:生成分隔线
    string split_line(20, '-');
    cout << split_line << endl; // 输出 --------------------

    // 给已有字符串赋值
    string str2;
    str2 = string(4, '9');
    cout << "str2: " << str2 << endl; // 输出 9999
    return 0;
}
3. 开发中的注意事项
  • size_t是无符号类型,禁止传入负数,负数会被隐式转换为极大的无符号数,导致内存分配失败、程序崩溃;
  • 字符c必须用单引号' '包裹,双引号包裹的是字符串,会编译报错;
  • n=0时,会构造一个空的 string 对象。
四、string (const string& s) 拷贝构造函数(重点)
1. 形参类型解释

形参为const string& sstd::string类型的只读左值引用

为什么用 const string&

  1. 提升效率:传引用避免拷贝整个字符串的全量数据,仅传递地址,极大降低性能开销;
  2. 保证原对象安全:const 承诺函数不会修改原字符串对象,同时可接收 const / 非 const 的 string 对象、临时 string 对象;
  3. 符合 C++ 语法规范:拷贝构造函数的标准签名要求,是实现值语义的核心,保证拷贝的合法性。
2. 函数的功能与代码实现

核心功能 :用一个已存在的 string 对象s,深拷贝构造出一个内容、长度完全一致的新 string 对象;新对象拥有独立的内存空间,与原对象互不影响,修改其中一个不会改变另一个。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    // 基础用法:拷贝构造
    string origin("origin string");
    string copy_str(origin); // 调用拷贝构造
    cout << "原字符串:" << origin << endl;    // 输出 origin string
    cout << "拷贝字符串:" << copy_str << endl; // 输出 origin string

    // 验证深拷贝:修改拷贝对象,原对象不受影响
    copy_str = "modified string";
    cout << "修改后拷贝字符串:" << copy_str << endl; // 输出 modified string
    cout << "原字符串:" << origin << endl;            // 仍输出 origin string
    return 0;
}
3. 开发中的注意事项
  • 该函数是深拷贝,新对象有独立的内存空间,与原对象生命周期完全解耦;
  • 当 string 对象以值传递的方式传入函数、或以值的方式返回时,编译器会自动调用该拷贝构造函数;
  • 标准库已处理自赋值场景,无需额外判断,但开发中应避免string s(s);这类无意义的自拷贝写法。
2.string类对象的容量操作

链接如下:

cplusplus.com/reference/string/string/?kw=string

注意:

1.size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接 口保持一致,一般情况下基本都是用size()。

2.clear()只是将string中有效字符清空,不改变底层空间大小。

3.resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不 同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数 增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

4.reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参 数小于string的底层空间总大小时,reserver不会改变容量大小

一、size () 函数(重点)
1. 函数应用场景

日常开发中获取字符串有效字符个数的核心函数,用于循环遍历字符串的边界控制、用户输入长度合规校验、字符串截取的边界判断等所有需要获取字符串实际有效长度的场景,是 string 类最高频使用的函数之一。

2. 函数的功能与代码实现

核心功能 :返回 string 对象中有效字符的个数 ,不包含底层自动添加的\0结束符,返回值类型为无符号整数size_t

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello C++";
    // 核心用法:获取有效长度
    size_t valid_len = str.size();
    cout << "有效字符长度:" << valid_len << endl; // 输出9

    // 典型场景:长度合规校验
    if (str.size() > 5) {
        cout << "字符串长度符合要求" << endl;
    }
    return 0;
}
3. 开发中的注意事项
  • 返回值是size_t无符号整数,禁止与负数做比较(无符号数会将负数转为极大值,导致判断逻辑永久失效);
  • 仅统计有效字符,不含底层自动添加的\0结束符;
  • 空字符串调用size()返回 0,是安全操作,不会触发崩溃。

二、length () 函数
1. 函数应用场景

size()功能完全等价,是 C++ string 类的历史兼容设计,仅用于老项目代码维护,新开发代码不推荐优先使用。

2. 函数的功能与代码实现

核心功能 :和size()完全一致,返回 string 对象的有效字符个数,返回值类型为size_t,底层共用同一套实现逻辑。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "test string";
    cout << "length()结果:" << str.length() << endl; // 输出11
    cout << "size()结果:" << str.size() << endl;     // 输出11,结果完全一致
    return 0;
}
3. 开发中的注意事项
  • 功能、性能、返回值与size()完全无差异;
  • 新开发代码优先使用size() ,符合 STL 容器的统一命名规范(vector、list 等容器均用size()获取长度),提升代码可读性;
  • 同样需注意返回值是无符号类型,避免与有符号数错误比较。

三、capacity () 函数
1. 函数应用场景

用于查看 string 底层已分配的内存容量 ,分析字符串自动扩容的性能损耗,配合reserve()做内存预分配优化,高频用于循环拼接字符串、大数据量字符串处理等需要性能调优的场景。

2. 函数的功能与代码实现

核心功能 :返回 string 对象底层已分配的内存空间可容纳的最大字符个数 ,不含\0结束符的占用空间,capacity的值始终大于等于size

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello";
    cout << "有效长度size:" << str.size() << endl;
    cout << "已分配容量capacity:" << str.capacity() << endl;

    // 追加字符,观察自动扩容
    str += "12345678901234567890";
    cout << "追加后size:" << str.size() << endl;
    cout << "追加后capacity:" << str.capacity() << endl; // 容量自动扩容,常见1.5/2倍增长
    return 0;
}
3. 开发中的注意事项
  • capacity已分配的内存上限 ,不是有效字符长度,有效长度始终以size为准;
  • 不同编译器的 string 扩容策略不同,capacity的值不具备跨编译器一致性;
  • capacity不会自动缩小,即使clear()清空字符串,已分配的内存通常也不会释放。

四、empty () 函数(重点)
1. 函数应用场景

高频用于字符串判空 ,比如用户输入合法性校验、接口入参空值判断、循环终止条件判断,是比if(str.size() == 0)更高效、可读性更强的写法。

2. 函数的功能与代码实现

核心功能 :检测 string 是否为空串(有效字符长度为 0),空则返回true,非空返回false,返回值类型为bool

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str1 = "";
    string str2 = "hello";
    // 核心用法:判空
    cout << "str1是否为空:" << str1.empty() << endl; // 输出1(true)
    cout << "str2是否为空:" << str2.empty() << endl; // 输出0(false)

    // 典型场景:输入校验
    string user_input;
    if (user_input.empty()) {
        cout << "输入不能为空,请重新输入" << endl;
    }
    return 0;
}
3. 开发中的注意事项
  • empty()O (1) 时间复杂度 ,部分编译器底层直接判断标志位,比size() == 0性能更优,判空优先使用empty()
  • 仅判断有效字符是否为 0,与capacity无关,即使capacity不为 0,size为 0 时empty()也返回true
  • 空 string 对象调用empty()是安全操作,无崩溃风险。

五、clear () 函数(重点)
1. 函数应用场景

清空字符串的有效内容,重置为空串,用于循环复用 string 对象、处理完字符串后重置状态、清空无效内容,是重置字符串最常用的方法。

2. 函数的功能与代码实现

核心功能 :清空 string 对象的所有有效字符,将size置为 0,不会释放底层已分配的capacity内存,后续复用无需重新分配内存,执行效率更高。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello world";
    cout << "清空前size:" << str.size() << endl;
    cout << "清空前capacity:" << str.capacity() << endl;

    // 核心用法:清空字符串
    str.clear();

    cout << "清空后size:" << str.size() << endl;       // 输出0
    cout << "清空后capacity:" << str.capacity() << endl; // 容量保持不变
    return 0;
}
3. 开发中的注意事项
  • clear()仅清空有效字符,不释放底层内存,适合后续复用该 string 对象的场景;
  • 如需清空同时释放内存,需在clear()后调用shrink_to_fit()
  • 空字符串调用clear()是安全操作,无副作用。

六、reserve () 函数(重点)
1. 函数应用场景

预分配内存,避免字符串频繁自动扩容导致的性能损耗,是高频字符串拼接、大数据量字符串处理的核心优化手段,比如循环拼接大量字符、日志内容拼接、文件内容读取到 string 中,提前知道最终长度时使用。

2. 函数的功能与代码实现

核心功能 :为 string 对象预分配至少能容纳 n 个字符的内存空间 ,仅修改capacity的值,不会改变size(有效字符长度),也不会修改字符串的实际内容。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str;
    cout << "初始capacity:" << str.capacity() << endl;

    // 核心用法:预分配1000个字符的容量
    str.reserve(1000);
    cout << "reserve后capacity:" << str.capacity() << endl; // 容量≥1000
    cout << "reserve后size:" << str.size() << endl;         // 输出0,有效长度不变

    // 优化场景:循环拼接,避免多次扩容
    for (int i = 0; i < 1000; i++) {
        str += 'a';
    }
    cout << "拼接后capacity:" << str.capacity() << endl; // 容量不会再次扩容
    return 0;
}
3. 开发中的注意事项
  • reserve(n)预分配至少 n 的容量 ,编译器可能分配比 n 更大的空间,保证capacity≥n
  • 当 n 小于当前capacity时,reserve()不会缩容,不会修改capacity,属于无操作;
  • 仅修改内存容量,不改变有效字符和size,不会修改字符串内容;
  • 仅当提前知道字符串最终长度时使用,否则无优化意义,过度预分配会造成内存浪费。

七、resize () 函数(重点)
1. 函数应用场景

修改字符串的有效字符长度,用于截断过长的字符串、扩容字符串并填充指定字符、固定长度字符串的初始化,需要同时修改有效长度和字符串内容的场景。

2. 函数的功能与代码实现

核心功能 :修改 string 对象的有效字符个数size为 n ,分两种核心场景,提供两个重载版本:void resize(size_t n);void resize(size_t n, char c);

  1. n < 当前size:截断字符串,保留前 n 个有效字符,size变为 n,capacity不变;

  2. n > 当前size:扩容有效长度到 n,多出的位置用指定字符c填充(不指定则默认用\0填充),若 n 超过capacity会自动扩容。

    #include <iostream>
    #include <string>
    using namespace std;

    int main() {
    string str = "hello";
    cout << "初始内容:" << str << " | size:" << str.size() << endl; // hello | 5

    复制代码
     // 用法1:截断字符串
     str.resize(3);
     cout << "截断后内容:" << str << " | size:" << str.size() << endl; // hel | 3
    
     // 用法2:扩容并指定填充字符
     str.resize(6, 'x');
     cout << "扩容后内容:" << str << " | size:" << str.size() << endl; // helxxx | 6
    
     // 用法3:扩容不指定填充字符,默认用'\0'填充
     str.resize(8);
     cout << "默认扩容后size:" << str.size() << endl; // 8
     return 0;

    }

3. 开发中的注意事项
  • resize()直接修改有效字符长度size,会改变字符串的实际内容 ,这是和reserve()最核心的区别;
  • 当 n 超过当前capacity时,会自动扩容capacity,扩容策略由编译器决定;
  • 截断字符串时,仅修改size,不会释放底层capacity内存;
  • 填充字符仅在扩容时生效,截断时不会修改保留的字符内容。
3.string类对象的访问及遍历操作

链接:

cplusplus.com/reference/string/string/?kw=string

一、operator [] 运算符重载(重点)
1. 函数应用场景

string 类单个字符的随机访问与修改的核心方式,支持数组式的下标操作,O (1) 时间复杂度,是日常开发中最高频的字符访问方式,适用于指定位置字符读写、下标循环遍历、单字符修改等场景。

2. 函数的功能与代码实现

核心功能 :重载 [] 运算符,返回字符串中下标pos位置字符的引用;非 const 对象调用返回可写引用,支持修改字符;const 对象调用返回 const 只读引用,仅支持读操作,不可修改字符。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello";
    // 1. 读操作:获取指定下标字符
    cout << "下标1的字符:" << str[1] << endl; // 输出 e
    // 2. 写操作:修改指定下标字符
    str[0] = 'H';
    cout << "修改后字符串:" << str << endl; // 输出 Hello
    // 3. 下标循环遍历
    for (size_t i = 0; i < str.size(); ++i) {
        cout << str[i] << " ";
    }
    // const对象只读访问
    const string const_str = "const test";
    cout << "\nconst对象下标0:" << const_str[0] << endl; // 合法只读
    // const_str[0] = 'C'; // 非法,编译报错,const引用不可修改
    return 0;
}
3. 开发中的注意事项
  • 下标合法范围为 0 ≤ pos < str.size(),越界访问会触发未定义行为,大概率导致程序崩溃,operator []不做边界检查
  • 如需带边界安全检查的访问,使用at()成员函数,越界会抛出out_of_range异常;
  • 空字符串访问下标 0 行为未定义,访问前必须先判空;
  • const 对象仅能调用 const 版本的 operator [],不可修改字符内容。

二、begin () + end () 正向迭代器对
1. 函数应用场景

string 类STL 兼容的正向遍历、区间操作的标准方式,是所有 STL 容器通用的遍历规范,适配 sort、reverse 等所有 STL 算法,适用于全量 / 区间正向遍历、容器通用代码编写、配合 STL 算法做字符串处理等场景。

2. 函数的功能与代码实现

核心功能

  • begin():返回指向字符串第一个有效字符的正向迭代器;

  • end():返回指向字符串最后一个有效字符的下一个位置的尾后迭代器(不指向任何有效字符,不可解引用);

  • 两者组成左闭右开区间[begin(), end()),完整覆盖所有有效字符;const 对象调用返回 const 迭代器,仅可读不可修改。

    #include <iostream>
    #include <string>
    #include <algorithm>
    using namespace std;

    int main() {
    string str = "abcdef";
    // 1. 正向迭代器遍历
    string::iterator it;
    for (it = str.begin(); it != str.end(); ++it) {
    cout << *it << " "; // 解引用获取字符
    }
    cout << endl;
    // 2. 迭代器修改字符
    for (it = str.begin(); it != str.end(); ++it) {
    *it = toupper(*it); // 转大写
    }
    cout << "转大写后:" << str << endl; // 输出 ABCDEF
    // 3. 配合STL算法:反转字符串
    reverse(str.begin(), str.end());
    cout << "反转后:" << str << endl; // 输出 FEDCBA
    return 0;
    }

3. 开发中的注意事项
  • end()是尾后迭代器,绝对禁止解引用、自增操作,否则触发未定义行为;
  • 字符串发生扩容(如 push_back、append)、删除操作后,原迭代器会失效,不可继续使用;
  • const 对象只能获取 const 迭代器,无法通过 const 迭代器修改字符;
  • 迭代器遍历是 STL 容器通用规范,代码可无缝适配 vector、list 等其他 STL 容器。

三、rbegin () + rend () 反向迭代器对
1. 函数应用场景

string 类反向遍历、逆序区间操作的专用方式,无需手动倒序控制下标,适用于从后往前查找字符、逆序输出字符串、逆序区间处理等场景,代码更简洁安全。

2. 函数的功能与代码实现

核心功能

  • rbegin()(reverse begin):返回指向字符串最后一个有效字符的反向迭代器;

  • rend()(reverse end):返回指向字符串第一个有效字符的前一个位置的反向尾后迭代器,不可解引用;

  • 两者组成左闭右开的反向区间[rbegin(), rend()),完整覆盖所有有效字符;反向迭代器执行++操作时,会向字符串头部移动,实现逆序遍历。

    #include <iostream>
    #include <string>
    using namespace std;

    int main() {
    string str = "hello";
    // 1. 反向遍历
    string::reverse_iterator rit;
    for (rit = str.rbegin(); rit != str.rend(); ++rit) {
    cout << *rit << " "; // 输出 o l l e h
    }
    cout << endl;
    // 2. 反向修改字符
    for (rit = str.rbegin(); rit != str.rend(); ++rit) {
    *rit = toupper(*rit);
    }
    cout << "转大写后:" << str << endl; // 输出 HELLO
    // 3. const对象只读反向遍历
    const string const_str = "reverse test";
    string::const_reverse_iterator crit;
    for (crit = const_str.rbegin(); crit != const_str.rend(); ++crit) {
    cout << *crit << " ";
    }
    return 0;
    }

3. 开发中的注意事项
  • rend()是反向尾后迭代器,禁止解引用、自增操作,否则触发未定义行为;
  • 反向迭代器的++操作是向字符串头部移动,与正向迭代器移动方向相反;
  • 字符串扩容、删除操作后,原反向迭代器会失效,不可继续使用;
  • 如需完整反转整个字符串,优先使用reverse(str.begin(), str.end()),比反向遍历赋值性能更优。

四、范围 for 循环(C++11 及以上)
1. 函数应用场景

C++11 新增的极简全量遍历语法,无需关心下标、迭代器边界,代码可读性极强,适用于无特殊区间要求的字符串全量遍历,是日常开发中无特殊需求时的首选遍历方式。

2. 函数的功能与代码实现

核心功能 :底层基于begin()+end()迭代器实现,自动遍历字符串的全部有效字符,自动处理迭代器的起止与移动,无需手动管理边界;支持只读遍历与可写遍历。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello world";
    // 1. 只读遍历:值拷贝,不修改原字符串
    for (char ch : str) {
        cout << ch << " ";
    }
    cout << endl;
    // 2. 可写遍历:引用传递,直接修改原字符串
    for (char& ch : str) {
        ch = toupper(ch);
    }
    cout << "转大写后:" << str << endl; // 输出 HELLO WORLD
    // 3. 高效只读遍历:const引用避免拷贝
    const string const_str = "const test";
    for (const char& ch : const_str) {
        cout << ch << " ";
    }
    return 0;
}
3. 开发中的注意事项
  • 范围 for 仅支持全量遍历整个字符串,无法指定区间、无法反向遍历,有区间要求的场景需使用普通迭代器;
  • 遍历过程中,若对字符串执行扩容、删除操作,会导致底层迭代器失效,遍历行为未定义,禁止在范围 for 循环内修改字符串容量;
  • 只读遍历推荐使用const char&,避免字符拷贝,提升性能;需要修改原字符串必须使用char&,值拷贝的修改不会影响原字符串;
  • 仅支持 C++11 及以上标准,老版本编译器需开启对应编译标准。
4.string类对象的修改操作

注意: 1. 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c'三种的实现方式差 不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可 以连接字符串。

  1. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
一、push_back 函数
1. 函数应用场景

单字符粒度的字符串尾部追加,是最小单位的字符串拼接操作,适用于循环逐个读取字符、单字符增量拼接的场景,比如从输入流逐字符读取、字符过滤后追加存储。

2. 函数的功能与代码实现

核心功能:在 string 对象的尾部追加 1 个 char 类型字符,自动维护字符串有效长度 size,容量不足时触发自动扩容,无返回值。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello";
    // 核心用法:尾部追加单个字符
    str.push_back(' ');
    str.push_back('w');
    cout << "基础追加后:" << str << endl; // 输出 hello w

    // 典型场景:循环追加字符
    for (char c = 'o'; c <= 'd'; c++) {
        str.push_back(c);
    }
    cout << "循环追加后:" << str << endl; // 输出 hello world
    return 0;
}
3. 开发中的注意事项
  • 仅支持追加单个 char 字符,传入字符串、多个字符会直接编译报错;
  • 频繁调用会触发多次自动扩容,有性能损耗,提前知晓总长度建议先用reserve()预分配内存;
  • 追加触发扩容后,原字符串的迭代器、指针、引用会全部失效,不可继续使用。

二、append 函数
1. 函数应用场景

批量字符 / 字符串的尾部追加,比 push_back 效率更高,支持多类型入参的灵活拼接,适用于整串追加、指定区间片段追加、批量重复字符追加等多字符拼接场景。

2. 函数的功能与代码实现

核心功能:在 string 尾部追加指定的字符序列,提供多版本重载,支持 string 对象、C 风格字符串、指定个数的重复字符、字符串区间片段等入参形式,返回字符串自身的引用,支持链式调用。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello";
    // 1. 追加完整string对象
    str.append(" world");
    cout << "追加整串:" << str << endl; // 输出 hello world

    // 2. 追加n个相同字符
    str.append(3, '!');
    cout << "追加重复字符:" << str << endl; // 输出 hello world!!!

    // 3. 追加字符串的指定区间
    string temp = "abcdef";
    str.append(temp, 1, 3); // 从temp下标1开始,截取3个字符追加
    cout << "追加区间片段:" << str << endl; // 输出 hello world!!!bcd
    return 0;
}
3. 开发中的注意事项
  • 注意重载参数顺序:append(n, c)是追加 n 个 c 字符,不要和append(const char*, n)的参数顺序搞反;
  • 追加 C 风格字符串时,必须保证字符串以\0结尾,否则会触发内存越界读取;
  • 返回自身引用,支持链式调用,如str.append("a").append("b")
  • 大段内容频繁追加,建议先通过reserve()预分配内存,避免多次扩容。

三、operator+= 运算符重载(重点)
1. 函数应用场景

日常开发中首选的字符串追加方式,语法极简、可读性极强,底层封装 append 逻辑,支持追加 string 对象、C 风格字符串、单个字符,适用于绝大多数无特殊区间要求的字符串拼接场景。

2. 函数的功能与代码实现

核心功能:重载 += 运算符,在 string 尾部追加指定的字符 / 字符串,兼容多种入参类型,返回字符串自身的引用,支持链式拼接,是 string 类最常用的语法糖。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello";
    // 1. 追加C风格字符串
    str += " world";
    cout << "追加字符串:" << str << endl; // 输出 hello world

    // 2. 追加单个字符
    str += '!';
    cout << "追加字符:" << str << endl; // 输出 hello world!

    // 3. 追加string对象
    string add_str = " 123456";
    str += add_str;
    cout << "追加string对象:" << str << endl; // 输出 hello world! 123456

    // 4. 链式调用
    string s;
    s += "a" += "b" += "c";
    cout << "链式拼接:" << s << endl; // 输出 abc
    return 0;
}
3. 开发中的注意事项
  • 日常开发优先使用,仅特殊区间 / 批量字符场景用 append,无性能差异,可读性更强;
  • 拼接触发扩容后,原字符串的迭代器、引用会失效,不可继续使用;
  • 多段长字符串拼接,建议先reserve()预分配内存,避免多次扩容带来的性能损耗。

四、c_str () 函数(重点)
1. 函数应用场景

C++ string 与 C 语言接口的兼容桥梁 ,用于将 C++ string 对象转换为 C 风格的const char*字符串,适配所有接收 C 风格字符串的系统函数、第三方库、C 标准库接口,是跨语言 / 接口兼容的核心函数。

2. 函数的功能与代码实现

核心功能 :返回一个指向const char类型的只读指针,指向 string 底层存储的、以\0结尾的 C 风格字符串,指针内容与 string 当前内容完全一致,生命周期与 string 对象强绑定。

复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
using namespace std;

int main() {
    string str = "hello c_str";
    // 1. 适配C标准库printf函数
    printf("C风格输出:%s\n", str.c_str()); // 输出 hello c_str

    // 2. 适配C语言字符串函数
    int len = strlen(str.c_str());
    cout << "strlen获取长度:" << len << endl; // 输出 11

    // 3. 适配系统接口(如文件操作)
    string file_path = "./test.txt";
    FILE* fp = fopen(file_path.c_str(), "r");
    if (fp) {
        cout << "文件打开成功" << endl;
        fclose(fp);
    }
    return 0;
}
3. 开发中的注意事项
  • 返回的是const char*只读指针,绝对禁止通过该指针修改字符串内容,否则触发未定义行为;
  • 指针生命周期与 string 绑定,string 对象销毁、内容修改(追加、赋值、清空)后,原指针会立即失效,不可继续使用;
  • 不可长期保存返回的指针,仅用于临时调用 C 接口,避免指针失效后非法访问;
  • 返回的字符串保证以\0结尾,这是 C++11 前与data()函数的核心区别。

五、find () + npos 常量(重点)
1. 函数应用场景

字符串正向查找匹配的核心函数,用于查找指定字符 / 子串在目标字符串中第一次出现的位置,适用于关键词匹配、子串存在性判断、字符定位、字符串分割等高频业务场景。

2. 函数的功能与代码实现

核心功能

  • find():从指定 pos 位置(默认 0,字符串开头)开始,正向查找 指定的字符 / 字符串,找到则返回第一次匹配的起始下标(size_t无符号类型);找不到则返回string::npos

  • string::npos:string 类内置的静态常量,值为 - 1(转为size_t是无符号最大值),专门用于标记查找失败的场景。

    #include <iostream>
    #include <string>
    using namespace std;

    int main() {
    string str = "hello world, hello C++";
    // 1. 查找子串第一次出现的位置
    size_t pos1 = str.find("hello");
    if (pos1 != string::npos) {
    cout << "hello首次出现下标:" << pos1 << endl; // 输出 0
    }

    复制代码
      // 2. 从指定位置开始查找
      size_t pos2 = str.find("hello", 5);
      if (pos2 != string::npos) {
          cout << "下标5后hello首次出现:" << pos2 << endl; // 输出 13
      }
    
      // 3. 查找单个字符
      size_t pos3 = str.find('w');
      if (pos3 != string::npos) {
          cout << 'w' << "出现下标:" << pos3 << endl; // 输出 6
      }
    
      // 4. 查找失败判断
      size_t pos4 = str.find("java");
      if (pos4 == string::npos) {
          cout << "未找到java子串" << endl;
      }
      return 0;

    }

3. 开发中的注意事项
  • 必须通过== string::npos/!= string::npos判断查找结果,禁止直接和 - 1 比较,避免无符号类型转换导致的逻辑错误;
  • 如需查找所有匹配项,可循环从「上次找到的位置 + 1」开始继续查找;
  • 查找大小写敏感,"Hello""hello"会被判定为不匹配,不区分大小写需自行处理;
  • 查找空串时,直接返回 pos 起始位置(默认 0)。

六、rfind () 函数
1. 函数应用场景

字符串反向查找匹配,从字符串尾部向前查找,用于获取指定字符 / 子串最后一次出现的位置,适用于文件后缀名提取、路径最后一级目录提取、最后一次关键词定位等反向匹配场景。

2. 函数的功能与代码实现

核心功能 :从指定 pos 位置(默认string::npos,字符串最后一个字符)开始,从后往前反向查找 指定的字符 / 字符串,找到则返回最后一次匹配的起始下标;找不到则返回string::npos

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello world, hello C++";
    // 1. 查找子串最后一次出现的位置
    size_t pos1 = str.rfind("hello");
    if (pos1 != string::npos) {
        cout << "hello最后一次出现下标:" << pos1 << endl; // 输出 13
    }

    // 2. 从指定位置向前查找
    size_t pos2 = str.rfind("hello", 10);
    if (pos2 != string::npos) {
        cout << "下标10前hello最后一次出现:" << pos2 << endl; // 输出 0
    }

    // 3. 实用场景:提取文件后缀名
    string file_name = "test.tar.gz";
    size_t dot_pos = file_name.rfind('.');
    if (dot_pos != string::npos) {
        string suffix = file_name.substr(dot_pos + 1);
        cout << "文件后缀:" << suffix << endl; // 输出 gz
    }
    return 0;
}
3. 开发中的注意事项
  • 查找方向为从后往前,返回的是匹配内容的起始下标,不是结束下标,与 find () 返回值规则一致;
  • 同样必须用string::npos判断查找是否失败,禁止直接和 - 1 比较;
  • 当 pos 参数超过字符串长度时,默认从整个字符串的末尾开始查找;
  • 反向查找匹配的是完整子串,不是反向子串,如rfind("ab")查找的是"ab"最后一次出现的位置,不是"ba"

七、substr () 函数
1. 函数应用场景

字符串子串截取,从原字符串中提取指定区间的片段,适用于字符串分割、内容提取、固定长度截取、关键词提取等场景,常和 find ()/rfind () 配合完成复杂字符串处理。

2. 函数的功能与代码实现

核心功能:从原字符串的 pos 下标(默认 0)开始,截取长度为 n 的子串,返回一个全新的 string 对象;若 n 不指定、或 n 超过剩余字符长度,会自动截取到字符串末尾。

函数原型:string substr(size_t pos = 0, size_t n = npos) const;

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello world";
    // 1. 从指定位置截取到末尾
    string sub1 = str.substr(6);
    cout << "下标6截取到末尾:" << sub1 << endl; // 输出 world

    // 2. 截取指定位置、指定长度的子串
    string sub2 = str.substr(0, 5);
    cout << "0开始截取5个字符:" << sub2 << endl; // 输出 hello

    // 3. 配合find提取区间内容
    string info = "name:zhangsan;age:20";
    size_t name_start = info.find(':') + 1;
    size_t name_end = info.find(';');
    string name = info.substr(name_start, name_end - name_start);
    cout << "提取姓名:" << name << endl; // 输出 zhangsan
    return 0;
}
3. 开发中的注意事项
  • 起始 pos 必须在合法范围0 ≤ pos < str.size(),越界会直接抛出out_of_range异常;
  • 截取长度 n 超过剩余字符数时,不会报错,会自动截取到字符串末尾,无越界风险;
  • substr () 返回的是新的 string 对象,原字符串不会被修改;
  • 频繁截取长字符串会产生大量临时对象,有性能损耗,大段内容处理建议用迭代器区间操作。
5. string类非成员函数

上面的几个接口大家了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有 一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。

一、operator+ 加法运算符重载
1. 函数应用场景

仅用于简单、少量字符串的临时拼接,生成全新字符串对象,不修改原字符串,适合一次性短串拼接场景,不推荐高频、长串拼接使用。

2. 函数的功能与代码实现

核心功能 :string 类非成员函数,重载 + 运算符,实现两个可转为 string 的对象(string 对象、C 风格字符串、单个字符)的拼接,返回一个全新的 string 深拷贝对象,原左右操作数均不被修改,支持多类型组合的链式拼接。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str1 = "hello";
    string str2 = " world";
    // 1. 两个string对象拼接
    string res1 = str1 + str2;
    cout << "两个string拼接:" << res1 << endl; // 输出 hello world

    // 2. string与C风格字符串拼接
    string res2 = str1 + " C++";
    cout << "string+字面量:" << res2 << endl; // 输出 hello C++

    // 3. 链式多段拼接
    string res3 = str1 + " " + "world" + "!";
    cout << "链式拼接:" << res3 << endl; // 输出 hello world!
    return 0;
}
3. 开发中的注意事项
  • 核心性能问题:传值返回会触发深拷贝,每次拼接都会生成临时对象,高频 / 长串拼接性能极低,优先使用 operator+=、append 替代;
  • 语法限制:拼接的两个操作数中,至少有一个必须是 string 对象,直接两个 C 风格字符串字面量用 + 拼接会编译报错(如 "hello"+"world" 非法);
  • 不会修改原操作数,所有修改仅体现在返回的新对象中,原字符串内容保持不变。

二、operator>> 输入运算符重载(重点)
1. 函数应用场景

从标准输入流(cin)读取字符串到 string 对象,适用于读取无空格、无换行的单个单词 / 短字符串,是简单无空格输入场景的首选方式。

2. 函数的功能与代码实现

核心功能 :string 类非成员函数,重载 >> 运算符,从输入流中自动跳过开头的空白字符(空格、换行、制表符),直到遇到下一个空白字符停止读取,将读取内容赋值给 string 对象,自动扩容适配内容长度,返回输入流引用,支持链式输入。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string name;
    string age;
    // 1. 基础单值输入
    cout << "请输入你的姓名:";
    cin >> name;
    cout << "你输入的姓名:" << name << endl;

    // 2. 链式多值输入,空格分隔
    cout << "请输入姓名和年龄,空格分隔:";
    cin >> name >> age;
    cout << "姓名:" << name << ",年龄:" << age << endl;
    return 0;
}
3. 开发中的注意事项
  • 核心限制:遇到空格、换行、制表符立即停止读取,无法读取包含空格的完整句子 / 带空格字符串,该场景必须使用 getline;
  • 自动跳过输入开头的所有空白字符,正常场景下不会读取到空串;
  • 输入流异常(如 EOF)时,会停止读取,string 对象保持原有内容不变;
  • 自动扩容适配输入内容,无需手动处理内存,安全性远高于 C 语言字符数组。

三、operator<< 输出运算符重载(重点)
1. 函数应用场景

将 string 对象内容输出到标准输出流(cout),是 string 内容控制台打印、流输出的核心方式,适用于所有需要将字符串内容输出到流的场景。

2. 函数的功能与代码实现

核心功能:string 类非成员函数,重载 << 运算符,将 string 对象的有效字符完整输出到输出流,无额外内容输出,返回输出流引用,支持链式输出,可与其他类型输出组合使用。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello C++ string";
    // 1. 基础字符串输出
    cout << "字符串内容:" << str << endl;

    // 2. 链式多类型组合输出
    string name = "zhangsan";
    int age = 20;
    cout << "姓名:" << name << ",年龄:" << age << endl;

    // 3. 空串输出
    string empty_str;
    cout << "空串输出:[" << empty_str << "]" << endl;
    return 0;
}
3. 开发中的注意事项
  • 仅输出 string 的有效字符(size 长度内的内容),不会输出底层 \0 结束符,也不会自动换行,换行需手动添加 endl 或 '\n';
  • 完全兼容 const string 对象的输出,无使用限制;
  • 支持输出包含空格、特殊字符的完整字符串,无输入运算符的空白限制。

四、getline 函数(重点)
1. 函数应用场景

从输入流中读取一整行包含空格的完整字符串,是读取带空格用户输入、整行文本内容的唯一标准方式,适用于读取完整句子、带空格的地址 / 描述、整行文件内容等场景。

2. 函数的功能与代码实现

核心功能 :string 类非成员函数,从指定输入流中读取字符,直到遇到换行符 '\n'(或自定义分隔符)为止,换行符会被读取并丢弃,不会存入目标 string 对象;读取内容(含中间空格、制表符)完整赋值给 string 对象,自动扩容适配长度,返回输入流引用。

常用原型:istream& getline(istream& is, string& str, char delim = '\n');

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string address;
    string content;
    // 1. 基础用法:读取一整行,直到换行
    cout << "请输入你的完整地址(含空格):";
    cin.ignore(); // 清除之前可能残留的换行符,避免读空
    getline(cin, address);
    cout << "你输入的地址:" << address << endl;

    // 2. 自定义分隔符:遇到','停止读取
    cout << "请输入用逗号分隔的内容:";
    getline(cin, content, ',');
    cout << "逗号前的内容:" << content << endl;
    return 0;
}
3. 开发中的注意事项
  • 核心优势:可读取包含空格的完整行,这是与 cin>> 的核心区别;
  • 高频坑点:若之前用 cin>> 读取过内容,输入流会残留换行符,直接调用 getline 会读取到空串,必须先用cin.ignore()清除残留换行符;
  • 换行符 '\n' 是默认终止符,会被读取并丢弃,不会存入目标 string;
  • 可通过第三个参数自定义终止分隔符,遇到该字符即停止读取;
  • 输入流触发 EOF 时,会停止读取,string 对象保留已读取的内容。

五、relational operators 关系运算符(重点)
1. 函数应用场景

两个字符串的相等性判断、字典序大小比较,适用于字符串内容校验、条件分支判断、字符串排序、字典序匹配等高频业务场景。

2. 函数的功能与代码实现

核心功能 :string 类非成员函数,重载==、!=、<、<=、>、>=全套关系运算符,按照 ASCII 码值逐字符字典序比较,支持 string 与 string、string 与 C 风格字符串的直接比较,返回 bool 类型结果。

比较规则:

  1. 逐字符对比 ASCII 码值,第一个不同字符的大小,决定两个字符串的大小;

  2. 若前缀字符全部相同,长度更长的字符串判定为更大;

  3. 仅当长度和所有字符完全一致时,判定为相等(==)。

    #include <iostream>
    #include <string>
    using namespace std;

    int main() {
    string str1 = "abc";
    string str2 = "abd";
    string str3 = "abc";
    string str4 = "abcd";

    复制代码
     // 1. 相等性判断
     cout << "str1 == str3:" << (str1 == str3) << endl; // 输出 1(true)
     cout << "str1 != str2:" << (str1 != str2) << endl; // 输出 1(true)
    
     // 2. 字典序大小比较
     cout << "str1 < str2:" << (str1 < str2) << endl; // 输出 1,'c' ASCII码 < 'd'
     cout << "str1 < str4:" << (str1 < str4) << endl; // 输出 1,前缀相同,str4更长
     cout << "str2 > str1:" << (str2 > str1) << endl; // 输出 1
    
     // 3. 与C风格字符串直接比较
     cout << "str1 == \"abc\":" << (str1 == "abc") << endl; // 输出 1
     return 0;

    }

3. 开发中的注意事项
  • 比较大小写敏感,大写字母 ASCII 码小于小写字母(如 'A' < 'a'),因此 "ABC" < "abc",不区分大小写比较需先统一转换大小写再对比;
  • 比较的是字符串内容,而非内存地址,两个不同的 string 对象,只要内容完全一致,就会判定为相等,与 C 语言 char * 指针比较完全不同;
  • 空串永远小于任何非空字符串,两个空串判定为相等;
  • 支持 string 对象与 C 风格字符串字面量直接比较,无需手动转换类型。
6.vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

vs下string的结构 :

​ string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string中字符串的存储空间:

​ (1)当字符串长度小于16时,使用内部固定的字符数组来存放

​ (2)当字符串长度大于等于16时,从堆上开辟空间

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建 好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。 其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的 容量

最后:还有一个指针做一些其他事情。 故总共占16+4+4+4=28个字节

g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个 指针,该指针将来指向一块堆空间,内部包含了如下字段:

空间总大小

字符串有效长度

引用计数

指向堆空间的指针,用来存储字符串。

3.与string相关的oj题练习

1.仅仅反转字母

https://leetcode-cn.com/problems/reverse-only-letters/submissions/

复制代码
class Solution {
public:
    bool isLetters(char ch)
        {
            if(ch >= 'a' && ch <='z')
            return true;
            
            else if(ch >= 'A' && ch <='Z')
            return true;
            
            return false;
        }

    string reverseOnlyLetters(string s) {
        
        

        size_t left = 0, right = s.size() - 1;
        while(left < right)
        {
            while(left < right && !isLetters(s[left]))
            {
                left++;
            }

            while(left < right && !isLetters(s[right]))
            {
                right--;
            }
            swap(s[left++],s[right--]);

        }
        return s;
    }
};

2.找字符串中第一个只出现一次的字符

https://leetcode-cn.com/problems/first-unique-character-in-a-string/

复制代码
class Solution {
public:
    int firstUniqChar(string s) {
        int count[250] = {0};
        for(auto ch : s)
        count[ch]++;
        //for(int i=0;i<s.size();i++)
        // {
        //     count[s[i]]++;
        // }
        
        for(int i=0; i < s.size(); i++)
        {
            if(1==count[s[i]])
            return i;
        }

        return -1;
    }
};

3.字符串里面最后一个单词的长度

字符串最后一个单词的长度_牛客题霸_牛客网

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string str;
    
    // 一直读取,直到没有单词可读(遇到 EOF)
    // 每次读取的新单词都会覆盖掉旧的 str
    while (cin >> str) {
        // 循环里面什么都不用做!
    }
    
    // 当循环结束时,str 里保存的就是输入的最后一个单词
    cout << str.length() << endl;
    
    return 0;
}

4.验证一个字符串是否是回文

125. 验证回文串 - 力扣(LeetCode)

复制代码
class Solution {
public:
    bool isPalindrome(string s) {
        string clean_s = ""; // 准备一个新的字符串,只装有用的字符

        // 1. 预处理阶段:清洗并收集字符
        for(int i = 0, n = s.size(); i < n; i++) 
        {
            if(s[i] >= 'A' && s[i] <= 'Z') 
            {
                clean_s += (s[i] + 32); // 大写转小写,并加入新字符串
            }
            else if(s[i] >= 'a' && s[i] <= 'z') 
            {
                clean_s += s[i];        // 小写字母直接加入
            }
            else if(s[i] >= '0' && s[i] <= '9') 
            {
                clean_s += s[i];        // 把遗漏的数字也加上!
            }
            // 注意:如果是标点或空格,这里什么都不做。
            // 这样自然就过滤掉了所有无效字符,新字符串紧凑无比。
        }

        // 2. 双指针阶段:对洗干净的字符串进行对比
        // 注意这里要检查 clean_s 是否为空,虽然 size()-1 处理空串有时会溢出,
        // 但如果 clean_s 为空,下面的 while 根本进不去,直接 return true,逻辑是安全的。
        int left = 0, right = clean_s.size() - 1;
        while(left < right) 
        {
            if(clean_s[left++] != clean_s[right--]) 
            {
                return false;
            }
        }
        
        return true;
    }
};

5.字符串相加

415. 字符串相加 - 力扣(LeetCode)

复制代码
class Solution {
public:
    string addStrings(string num1, string num2) 
{
        // 从后往前相加,相加的结果到字符串可以使用insert头插
        // 或者+=尾插以后再reverse过来
        int end1 = num1.size()-1;
        int end2 = num2.size()-1;
        int value1 = 0, value2 = 0, next = 0;
        string addret;
        while(end1 >= 0 || end2 >= 0)
        {
            if(end1 >= 0)
                value1 = num1[end1--]-'0';
            else
                value1 = 0;
            
            if(end2 >= 0)
                value2 = num2[end2--]-'0';
            else
                value2 = 0;
            
            int valueret = value1 + value2 + next;
            if(valueret > 9)
            {
                next = 1;
                valueret -= 10;
            }
            else
            {
                next = 0;
            }
            
            //addret.insert(addret.begin(), valueret+'0');
            addret += (valueret+'0');
        }
        
        if(next == 1)
        {
            //addret.insert(addret.begin(), '1');
            addret += '1';
        }
        
        reverse(addret.begin(), addret.end());
        return addret;
    }
};

6.反转字符串II

541. 反转字符串 II - 力扣(LeetCode)

复制代码
#include <algorithm>
#include <string>

using namespace std;

class Solution {
public:
    string reverseStr(string s, int k) {
        int n = s.size();
        int left = 0; // left 代表当前这块 2k 字符的起始下标

        // 只要左指针还没越界,就继续处理
        while (left < n) {
            
            // 计算当前还剩下多少个字符没处理
            int remain = n - left;

            // 情况 1:如果剩余字符少于 k 个
            // 题目要求:将剩余字符全部反转
            if (remain < k) {
                reverse(s.begin() + left, s.end());
            } 
            // 情况 2:剩余字符 >= k 个(不管它是不是小于 2k)
            // 题目要求:反转前 k 个字符
            else {
                reverse(s.begin() + left, s.begin() + left + k);
            }

            // 处理完当前这一批,left 指针往后跳 2k 步,进入下一个轮回
            left += 2 * k; 
        }

        return s;
    }
};

7.反转字符串中的单词III

557. 反转字符串中的单词 III - 力扣(LeetCode)

复制代码
class Solution {
public:
    string reverseWords(string s) {
      int sum = 0,left = 0;
      int n = s.size();
      int add = 0;
      while(left <= n)
      {
        if((s[left] == ' ') || left == n)
        {
            
            reverse(s.begin() + add,s.begin() + left );

            add = left + 1;
        }

        left++;
      }  
      return s;
    }
};

8.字符串相乘

43. 字符串相乘 - 力扣(LeetCode)

复制代码
#include <vector>
#include <string>

using namespace std;

class Solution {
public:
    string multiply(string num1, string num2) {
        // 特殊情况:如果其中一个数是 0,直接返回 "0"
        if (num1 == "0" || num2 == "0") {
            return "0";
        }

        int m = num1.size();
        int n = num2.size();
        
        // 准备一个长度为 m + n 的数组,初始化为 0
        vector<int> res(m + n, 0);

        // 从后往前,双层循环模拟竖式乘法
        for (int i = m - 1; i >= 0; i--) {
            for (int j = n - 1; j >= 0; j--) {
                // 将字符转为数字并相乘
                int mul = (num1[i] - '0') * (num2[j] - '0');
                
                // 乘积在结果数组中对应的两个位置
                int p1 = i + j;     // 进位位置 (高位)
                int p2 = i + j + 1; // 当前位位置 (低位)

                // 累加上低位原本就有的数字
                int sum = mul + res[p2];

                // 更新当前位和进位
                res[p2] = sum % 10;       // 取个位留在低位
                res[p1] += sum / 10;      // 十位进上去 (注意这里是 +=,因为 p1 可能已经有值了)
            }
        }

        // 此时 res 数组里存的就是最终结果的每一位数字
        // 我们把它转回 string,要注意去掉开头的多余的 0
        string ans = "";
        for (int i = 0; i < res.size(); i++) {
            // 如果 ans 是空的,且当前数字是 0,说明这是前导 0,跳过
            if (ans.empty() && res[i] == 0) {
                continue;
            }
            // 存入真实数字
            ans += (res[i] + '0');
        }

        return ans;
    }
};

4.string类的模拟实现

4.1经典的string类问题

上面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让 学生自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析 构函数。大家看下以下string类的实现是否有问题?

复制代码
// 为了和标准库区分,此处使用String
class String
{
public:
    /*String()
        :_str(new char[1])
        {*_str = '\0';}
    */
    //String(const char* str = "\0") 错误示范
    //String(const char* str = nullptr) 错误示范
    String(const char* str = "")
    {
        // 构造String类对象时,如果传递nullptr指针,可以认为程序非
        if (nullptr == str)
        {
            assert(false);
            return;
        }
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    ~String()
    {
        if (_str)
        {
            delete[] _str;
            _str = nullptr;
        }
    }
private:
    char* _str;
};
// 测试
void TestString()
{
    String s1("hello bit!!!");
    String s2(s1);
}

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认 的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

4.2浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一 不想分享就你争我夺,玩具损坏。

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父 母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。

4.3深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给 出。一般情况都是按照深拷贝方式提供。

4.3.1传统版写法的String类
复制代码
class String
{
public:
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}
return *this;
}
~String()
{
if (_str)
{

 delete[] _str;
         _str = nullptr;
     }
 }
private:
 char* _str;
};
4.3.2现代版写法的String类
复制代码
class String
{
public:
 String(const char* str = "")
 {
     if (nullptr == str)
     {
         assert(false);
         return;
     }
     _str = new char[strlen(str) + 1];
     strcpy(_str, str);
 }
 String(const String& s)
     : _str(nullptr)
 {
     String strTmp(s._str);
     swap(_str, strTmp._str);
 }
 // 对比下和上面的赋值那个实现比较好?
 String& operator=(String s)
 {
     swap(_str, s._str);
     return *this;
 }
 /*
 String& operator=(const String& s)
 {
     if(this != &s)
     {
        String strTmp(s);
        swap(_str, strTmp._str);
     }
     return *this;
 }
 */
 ~String()
 {
     if (_str)
     {

delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};

4.4 写时拷贝

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。 引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该 资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源, 如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有 其他对象在使用该资源。

写时拷贝:

C++ STL string的Copy-On-Write技术 | 酷 壳 - CoolShell

写时拷贝在读取时的缺陷:

C++的std::string的"读时也拷贝"技术! | 酷 壳 - CoolShell

5.扩展阅读

C++面试中string类的一种正确写法 | 酷 壳 - CoolShell

STL 的string类怎么啦?_string类在stl里面吗-CSDN博客

看完是不是感觉有所收获呢?如果学有所获的话,麻烦给个三连支持一下呗。感谢观看,您的支持,将是我前进路上的重要动力。

相关推荐
无巧不成书02182 小时前
Unicode编码机制全解析:从核心原理到Java 实战
java·开发语言·java字符编码·unicode 15.1码点
楼田莉子2 小时前
设计模式:构造器模式
开发语言·c++·后端·学习·设计模式
lly2024062 小时前
Swift 析构过程
开发语言
mu_guang_2 小时前
计算机体系结构3-cache一致性和内存一致性的区别
java·开发语言·计算机体系结构
邪修king2 小时前
UE5 零基础入门第二弹:让你的几何体 “活” 起来 ——Actor 基础与蓝图交互入门
c++·ue5·交互
lingggggaaaa2 小时前
PHP模型开发篇&MVC层&动态调试未授权&脆弱鉴权&未引用&错误逻辑
开发语言·安全·web安全·网络安全·php·mvc·代码审计
星原望野2 小时前
java:volatile关键字的作用
java·开发语言·volatile
wfbcg2 小时前
每日算法练习:LeetCode 30. 串联所有单词的子串 ✅
算法·leetcode·职场和发展
APIshop2 小时前
Java获取淘宝商品价格、图片与视频:淘宝开放平台API实战指南
开发语言·python