de风——【从零开始学C++】(七):string类详解

目录

前言

一、标准库中的string类

[1. 头文件](#1. 头文件)

基础概念解释

[2. 标准库中的接口](#2. 标准库中的接口)

基础概念解释

[3. auto与范围for](#3. auto与范围for)

[3.1 auto关键字](#3.1 auto关键字)

基础概念解释

代码例子1:auto基础用法演示

代码例子2:auto的真正威力------简化长类型

[3.2 范围for循环](#3.2 范围for循环)

基础概念解释

代码例子1:范围for遍历数组

代码例子2:范围for遍历string

二、string类的常用接口

[1. string类对象的常见构造](#1. string类对象的常见构造)

基础概念解释

代码例子:四种构造函数的使用

[2. string类对象的容量操作](#2. string类对象的容量操作)

基础概念解释

代码例子:容量操作演示

[3. string类对象的访问及遍历操作](#3. string类对象的访问及遍历操作)

基础概念解释

代码例子:三种遍历方式对比

[4. string类对象的修改操作](#4. string类对象的修改操作)

基础概念解释

代码例子1:尾插操作演示

代码例子2:insert、erase、swap演示

[5. string类非成员函数](#5. string类非成员函数)

基础概念解释

代码例子1:getline的重要性

代码例子2:字符串比较

[6. vs和g++下string结构的说明](#6. vs和g++下string结构的说明)

基础概念解释

代码例子:验证string的大小

三、总结与预告

[📝 本篇总结](#📝 本篇总结)

[🎯 下一篇预告](#🎯 下一篇预告)



前言

哈喽各位小伙伴们大家好呀,我还是你们的老朋友小小风呀!欢迎来到【从零开始学C++】专题的第七篇文章!🎉

在前六篇中,我们已经学习了C++的基础语法、类和对象等核心概念。今天我们要学习一个在实际开发中每天都会用到 的超级实用的类------string类

相信学过C语言的同学都知道,C语言中的字符串操作有多麻烦:

  • 要手动管理'\0'结束符

  • strcpy、strcat等函数容易越界

  • 内存需要自己申请释放

  • 各种坑等着你踩...

而C++的string类就是来拯救我们的!它封装了所有字符串操作,让我们像使用普通变量一样使用字符串,简单、安全又高效!

今天这篇文章我们就从最基础的开始,手把手带大家掌握string类的核心用法。新手小白也能轻松看懂,建议收藏慢慢看~


一、标准库中的string类

1. 头文件

基础概念解释

在C++中使用string类,就像使用其他工具一样,需要先"导入工具箱"。这个"工具箱"就是头文件。

使用string类必须满足两个条件:

  1. 包含头文件#include <string> (注意不是<string.h>哦!那是C语言的)

  2. 使用命名空间 :因为string在std命名空间中,所以需要using namespace std;或者每次写std::string

💡 新手小贴士:很多新手容易写错成 #include <string.h>,这是C语言的字符串头文件,不是C++的string类!一定要注意区分!


2. 标准库中的接口

基础概念解释

string类是C++标准库中非常成熟的类,它提供了上百个接口函数。作为初学者,我们不需要全部记住,只需要掌握最常用的20%就能解决80%的问题。

当你遇到不熟悉的接口时,一定要学会查官方文档!这里给大家推荐最权威的C++参考文档:

👉 官方文档地址cplusplus.com/reference/string/string/

文档使用技巧:

  1. 左边是所有接口的目录,点击即可跳转

  2. 每个接口都有详细说明、参数、返回值、代码示例

  3. 支持中英文切换(右上角)

  4. 遇到不懂的先查文档,这是程序员必备技能!

💡 实用建议:收藏这个链接,写代码时随时查阅,比百度靠谱多了!


3. auto与范围for

在正式学习string之前,先给大家补充两个C++11的超级实用语法,这会让我们后面的代码简洁很多!

3.1 auto关键字
基础概念解释

在C++11之前,auto是用来声明自动变量的,基本没人用。C++11把它改造成了自动类型推导神器!

简单说:auto可以让编译器自动帮你判断变量的类型,你不用自己写了!

使用规则:

  • auto定义的变量必须初始化(不然编译器怎么推导类型?)

  • 用于推导指针时,autoauto*效果一样

  • 用于推导引用时,必须写auto&

  • 同一行定义多个变量时,类型必须一致

适用场景:

  • 类型名特别长的时候(比如迭代器)

  • 你不确定类型是什么的时候

  • 懒得写复杂类型名的时候

💡 新手小贴士:auto不是"动态类型",它是编译时推导的,推导完类型就固定了,和你手动写类型效果完全一样!

代码例子1:auto基础用法演示
cpp 复制代码
#include <iostream>
#include <typeinfo>  // 用于查看类型
using namespace std;

int main()
{
    auto a = 10;        // 自动推导为int
    auto b = 3.14;      // 自动推导为double
    auto c = 'A';       // 自动推导为char
    auto d = "hello";   // 自动推导为const char*
    
    cout << "a的类型:" << typeid(a).name() << endl;
    cout << "b的类型:" << typeid(b).name() << endl;
    cout << "c的类型:" << typeid(c).name() << endl;
    cout << "d的类型:" << typeid(d).name() << endl;
    
    // auto e;  // 错误!auto必须初始化!
    
    return 0;
}

运行结果:

cpp 复制代码
a的类型:int
b的类型:double
c的类型:char
d的类型:char const *
代码例子2:auto的真正威力------简化长类型
cpp 复制代码
#include <iostream>
#include <string>
#include <map>
using namespace std;

int main()
{
    // 定义一个map,key是英文,value是中文
    map<string, string> dict = {
        {"apple", "苹果"},
        {"orange", "橙子"},
        {"pear", "梨"}
    };
    
    // 传统写法:类型名超级长!容易写错!
    map<string, string>::iterator it1 = dict.begin();
    
    // auto写法:太爽了!
    auto it2 = dict.begin();
    
    // 遍历输出
    while (it2 != dict.end())
    {
        cout << it2->first << " -> " << it2->second << endl;
        ++it2;
    }
    
    return 0;
}

运行结果:

cpp 复制代码
apple -> 苹果
orange -> 橙子
pear -> 梨

tops:map我们会在后续学习,这里只用来演示


3.2 范围for循环
基础概念解释

C++11又一个神级语法!专门用来遍历数组、容器等有范围的集合。

传统的for循环需要我们自己控制下标、判断边界,很容易写错。范围for直接帮你搞定一切:

  • 自动迭代

  • 自动取数据

  • 自动判断结束

语法格式:

cpp 复制代码
for (元素类型 变量名 : 要遍历的集合)
{
    // 循环体
}

最佳实践:

  • 只读遍历:用 auto econst auto& e

  • 需要修改元素:用 auto& e(加引用!)

💡 新手小贴士:范围for的底层就是用迭代器实现的,所以支持迭代器的容器都能用范围for!

代码例子1:范围for遍历数组
cpp 复制代码
#include <iostream>
using namespace std;

int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    
    // 传统for循环:要算长度、控制下标
    cout << "传统for:";
    for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    // 范围for:就是这么简洁!
    cout << "范围for:";
    for (auto e : arr)
    {
        cout << e << " ";
    }
    cout << endl;
    
    // 修改元素必须加引用!
    for (auto& e : arr)
    {
        e *= 2;  // 每个元素×2
    }
    
    cout << "修改后:";
    for (auto e : arr)
    {
        cout << e << " ";
    }
    cout << endl;
    
    return 0;
}

运行结果:

cpp 复制代码
传统for:1 2 3 4 5 
范围for:1 2 3 4 5 
修改后:2 4 6 8 10 
代码例子2:范围for遍历string
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
    string str = "hello world";
    
    // 用范围for遍历string的每个字符
    cout << "逐个字符输出:";
    for (auto ch : str)
    {
        cout << ch << "-";
    }
    cout << endl;
    
    // 统计小写字母个数
    int count = 0;
    for (auto ch : str)
    {
        if (ch >= 'a' && ch <= 'z')
            count++;
    }
    cout << "小写字母个数:" << count << endl;
    
    return 0;
}

运行结果:

cpp 复制代码
逐个字符输出:h-e-l-l-o- -w-o-r-l-d-
小写字母个数:10

二、string类的常用接口

1. string类对象的常见构造

基础概念解释

创建string对象的方式有很多,最常用的有4种:

|----------------------------|----------------|
| 构造函数 | 功能说明 |
| string() | 构造空字符串(最常用) |
| string(const char* s) | 用C风格字符串构造(最常用) |
| string(size_t n, char c) | 构造n个字符c组成的字符串 |
| string(const string& s) | 拷贝构造(最常用) |

💡 新手小贴士:前三个和拷贝构造是必须掌握的,其他的用到再查文档就行!

代码例子:四种构造函数的使用
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
    // 1. 构造空字符串
    string s1;
    cout << "s1是空的吗?" << s1.empty() << endl;
    cout << "s1的长度:" << s1.size() << endl;
    
    // 2. 用C风格字符串构造(最常用!)
    string s2("hello string!");
    cout << "s2:" << s2 << endl;
    
    // 3. 构造n个相同字符
    string s3(10, 'x');
    cout << "s3:" << s3 << endl;
    
    // 4. 拷贝构造
    string s4(s2);
    cout << "s4:" << s4 << endl;
    
    // 还有一种更简单的写法(隐式类型转换)
    string s5 = "C++ yyds!";
    cout << "s5:" << s5 << endl;
    
    return 0;
}

运行结果:

cpp 复制代码
s1是空的吗?1
s1的长度:0
s2:hello string!
s3:xxxxxxxxxx
s4:hello string!
s5:C++ yyds!

2. string类对象的容量操作

基础概念解释

string本质上是一个动态字符数组,所以它有容量相关的概念:

  • size/length:有效字符的个数(两个功能完全一样,推荐用size)

  • capacity:总共能存多少字符(不包括'\0')

  • empty:判断是否为空

  • clear:清空内容(不释放空间)

  • resize:改变有效字符个数

  • reserve:预留空间(避免频繁扩容)

重点注意:

  1. size和length完全一样,只是为了和其他容器统一

  2. clear只清数据,不改容量

  3. reserve只改容量,不改有效数据个数

  4. resize既改有效个数,也可能改容量

💡 实用技巧:如果你知道字符串大概多长,提前reserve可以大幅提升性能!

代码例子:容量操作演示
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s("hello");
    cout << "初始状态:" << endl;
    cout << "s = " << s << endl;
    cout << "size = " << s.size() << endl;
    cout << "length = " << s.length() << endl;
    cout << "capacity = " << s.capacity() << endl;
    cout << "empty = " << s.empty() << endl;
    cout << "-------------------" << endl;
    
    // resize演示:扩大到10个字符,用'a'填充
    s.resize(10, 'a');
    cout << "resize(10, 'a')后:" << endl;
    cout << "s = " << s << endl;
    cout << "size = " << s.size() << endl;
    cout << "capacity = " << s.capacity() << endl;
    cout << "-------------------" << endl;
    
    // resize演示:缩小到3个字符
    s.resize(3);
    cout << "resize(3)后:" << endl;
    cout << "s = " << s << endl;
    cout << "size = " << s.size() << endl;
    cout << "capacity = " << s.capacity() << " (注意:容量没变!)" << endl;
    cout << "-------------------" << endl;
    
    // clear演示
    s.clear();
    cout << "clear后:" << endl;
    cout << "s = " << s << endl;
    cout << "size = " << s.size() << endl;
    cout << "capacity = " << s.capacity() << " (注意:容量还是没变!)" << endl;
    
    return 0;
}

运行结果:

cpp 复制代码
初始状态:
s = hello
size = 5
length = 5
capacity = 15
empty = 0
-------------------
resize(10, 'a')后:
s = helloaaaaa
size = 10
capacity = 15
-------------------
resize(3)后:
s = hel
size = 3
capacity = 15 (注意:容量没变!)
-------------------
clear后:
s = 
size = 0
capacity = 15 (注意:容量还是没变!)

3. string类对象的访问及遍历操作

基础概念解释

访问和遍历string有三种方式:

  1. operator[]:像数组一样用下标访问(最常用)

  2. 迭代器:begin()和end()(通用方式,所有容器都支持)

  3. 范围for:C++11新语法(最简洁)

重点注意:

  • operator[]支持读写,越界会崩溃

  • 迭代器是容器通用的遍历方式,学会了所有容器都能用

  • 范围for底层就是迭代器

代码例子:三种遍历方式对比
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s("abcdef");
    
    // 方式1:operator[] 下标访问(最常用)
    cout << "方式1:下标访问" << endl;
    for (size_t i = 0; i < s.size(); i++)
    {
        cout << s[i] << " ";
        s[i] += 1;  // 可以修改
    }
    cout << endl << "修改后:" << s << endl << endl;
    
    // 方式2:迭代器
    cout << "方式2:迭代器" << endl;
    string::iterator it = s.begin();
    while (it != s.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl << endl;
    
    // 方式3:范围for(最简洁)
    cout << "方式3:范围for" << endl;
    for (auto ch : s)
    {
        cout << ch << " ";
    }
    cout << endl;
    
    return 0;
}

运行结果:

cpp 复制代码
方式1:下标访问
a b c d e f 
修改后:bcdefg

方式2:迭代器
b c d e f g 

方式3:范围for
b c d e f g 

4. string类对象的修改操作

基础概念解释

string的修改操作是最常用的,核心有这几个:

  • push_back:尾插一个字符

  • append:尾插字符串

  • operator+=:尾插(最常用!字符、字符串、string都可以)

  • insert:任意位置插入

  • erase:删除字符

  • swap:交换两个string

  • c_str:转回C风格const char*(和C接口交互时用)

💡 重点推荐:90%的场景用operator+=就够了!它是最方便、最常用的!

代码例子1:尾插操作演示
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s;
    
    // push_back:只能插一个字符
    s.push_back('h');
    s.push_back('e');
    cout << "push_back后:" << s << endl;
    
    // append:插字符串
    s.append("llo");
    cout << "append后:" << s << endl;
    
    // operator+=:神器!什么都能插!
    s += ' ';        // 插字符
    s += "world";    // 插字符串
    s += s;          // 插string对象
    cout << "operator+=后:" << s << endl;
    
    return 0;
}

运行结果:

cpp 复制代码
push_back后:he
append后:hello
operator+=后:hello worldhello world
代码例子2:insert、erase、swap演示
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s("hello world");
    
    // insert:在第6个位置插入"C++ "
    s.insert(6, "C++ ");
    cout << "insert后:" << s << endl;
    
    // erase:从第6个位置开始删除4个字符
    s.erase(6, 4);
    cout << "erase后:" << s << endl;
    
    // swap:交换两个string(效率很高,只交换指针)
    string s1("AAA");
    string s2("BBB");
    cout << "交换前:s1=" << s1 << ", s2=" << s2 << endl;
    s1.swap(s2);
    cout << "交换后:s1=" << s1 << ", s2=" << s2 << endl;
    
    // c_str:和C语言接口交互时用
    cout << "c_str():" << s.c_str() << endl;
    
    return 0;
}

运行结果:

cpp 复制代码
insert后:hello C++ world
erase后:hello world
交换前:s1=AAA, s2=BBB
交换后:s1=BBB, s2=AAA
c_str():hello world

5. string类非成员函数

基础概念解释

除了成员函数,string还有一些全局的非成员函数:

  • operator+:字符串拼接(尽量少用,会产生临时对象)

  • operator>> / operator<<:输入输出

  • getline:读取一行(重要!cin遇到空格就停了)

  • 关系运算符:>、<、==、!=等(按字典序比较)

💡 超级重点:getline!读取带空格的字符串必须用它!比如读取"hello world"这种中间有空格的,用cin只能读到"hello"!

代码例子1:getline的重要性
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s;
    
    cout << "请输入一句话(包含空格):" << endl;
    
    // cin的坑:遇到空格、回车、制表符就停止
    // cin >> s;  // 输入"hello world"只能读到"hello"
    
    // getline:读到回车才停止,完美!
    getline(cin, s);
    
    cout << "你输入的是:" << s << endl;
    cout << "长度是:" << s.size() << endl;
    
    return 0;
}

运行示例:

cpp 复制代码
请输入一句话(包含空格):
I love C++!
你输入的是:I love C++!
长度是:10
代码例子2:字符串比较
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s1("apple");
    string s2("banana");
    string s3("apple");
    
    // 直接用比较运算符就行,太方便了!
    cout << "s1 == s3:" << (s1 == s3) << endl;
    cout << "s1 < s2:" << (s1 < s2) << endl;  // 'a' < 'b'
    cout << "s1 != s2:" << (s1 != s2) << endl;
    
    return 0;
}

运行结果:

cpp 复制代码
s1 == s3:1
s1 < s2:1
s1 != s2:1

6. vs和g++下string结构的说明

基础概念解释

这部分属于进阶内容,新手了解即可,面试可能会问到。

不同编译器的string底层实现是不一样的:

VS下的string(32位):

  • 总共占28字节

  • 采用短字符串优化(SSO)

    • 长度<16时:存在内部的16字节数组里(不用开堆,快!)

    • 长度≥16时:开堆空间存储

  • 结构:16字节缓冲区 + 4字节size + 4字节capacity + 4字节其他 = 28字节

g++下的string(32位):

  • 总共占4字节(只有一个指针!)

  • 采用**写时拷贝(COW)**技术

  • 指针指向堆空间,里面存:容量、长度、引用计数、字符串内容

💡 面试考点:短字符串优化(SSO)和写时拷贝(COW)的区别!

代码例子:验证string的大小
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
    cout << "string对象的大小:" << sizeof(string) << "字节" << endl;
    
    // VS下输出28,g++下输出4
    // 大家可以在自己的编译器上试试!
    
    string s1("123456789012345");   // 15个字符
    string s2("1234567890123456");  // 16个字符
    
    cout << "s1(15字符)的地址:" << (void*)s1.c_str() << endl;
    cout << "s2(16字符)的地址:" << (void*)s2.c_str() << endl;
    
    // VS下你会发现:
    // s1的地址和对象地址很近(在栈上)
    // s2的地址离对象很远(在堆上)
    // 这就是短字符串优化!
    
    return 0;
}

三、总结与预告

📝 本篇总结

今天我们系统学习了C++ string类的核心内容,重点掌握:

  1. 基础准备 :头文件<string> + 命名空间std

  2. 实用语法:auto自动类型推导 + 范围for循环

  3. 四大核心操作

    1. 构造:4种常用构造方式

    2. 容量:size、capacity、resize、reserve

    3. 遍历:下标、迭代器、范围for三种方式

    4. 修改:operator+=是神器!

  4. 重点注意:getline读取带空格的字符串

  5. 底层差异:VS的SSO vs g++的COW

string类是C++日常开发中使用频率最高的类没有之一,一定要多写多练,熟练掌握!

🎯 下一篇预告

下一篇文章我们将继续深入string类,讲解:

  • 字符串的查找和截取(find、substr)

  • string类的模拟实现(面试必考!)

如果这篇文章对你有帮助,欢迎点赞👍 + 收藏⭐ + 评论💬 三连支持!

有任何问题都可以在评论区留言,我会一一回复~

关注我,带你从零开始系统学好C++!我们下篇再见!👋

相关推荐
洛水水1 小时前
【力扣100题】48.乘积最大子数组
算法·leetcode·职场和发展
丘比特惩罚陆1 小时前
制作类似aimlab的测试手速反应力的小游戏
开发语言·javascript·visual studio
YL200404261 小时前
042将有序数组转换为二叉搜索树
数据结构·算法·leetcode
江屿风1 小时前
【c++笔记】类和对象流食般投喂(中)
开发语言·c++·笔记
csbysj20201 小时前
C 语言输入与输出(I/O)详解
开发语言
Huangjin007_1 小时前
【C++ STL篇(八)】set容器——零基础入门与核心用法精讲
开发语言·c++·学习
许长安1 小时前
Kafka 架构讲解:从提交日志到分区副本机制
c++·经验分享·笔记·分布式·架构·kafka
邪修king1 小时前
UE5 TA 核心修炼:材质与纹理艺术全解 —— 从 PBR 理论到工业级材质实战
c++·后端·游戏·ue5·材质
c#上位机1 小时前
C#项目中打包文件的三种方式
开发语言·c#