C++ 学习教程,从入门到精通,string类和标准模板库 — 完整知识点详解(16)

string类和标准模板库 --- 完整知识点详解


一、string类

1.1 为什么要学习string类?

在C语言中,我们使用字符数组(char[])来处理字符串,但字符数组有很多"坑":

  • 容易造成缓冲区溢出
  • 需要手动管理内存
  • 字符串拼接、复制等操作繁琐(需要调用strcpy、strcat等函数)

string类 是C++标准库提供的字符串管理类,它把字符串当作一个对象来处理,就像管理一个"智能的字符容器",自动处理内存,提供丰富的操作方法。

通俗比喻: 如果说字符数组是"手工记账",那string类就是"Excel自动计算"------你只需要告诉它你想做什么,它帮你处理底层细节。

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

int main() {
    string s1 = "Hello";        // 方式1:直接赋值
    string s2("World");         // 方式2:用构造函数
    string s3 = s1 + " " + s2;  // 方式3:直接拼接!
    cout << s3 << endl;         // 输出:Hello World
    cout << "长度:" << s3.size() << endl; // 输出:长度:11
    return 0;
}

对比C语言,字符串拼接在C语言中需要:

c 复制代码
char buf[100];
strcpy(buf, s1);
strcat(buf, " ");
strcat(buf, s2);  // 很繁琐,还容易写错

1.2 构造字符串

string类提供了多种构造方式,适用于不同的场景:

构造方式 示例 说明
默认构造 string s; 创建空字符串
C风格字符串构造 string s("hello"); 用字符数组初始化
拷贝构造 string s2(s1); 用另一个string初始化
部分拷贝构造 string s(str, pos, len); 从str的pos位置开始拷贝len个字符
填充构造 string s(5, 'A'); 创建包含5个'A'的字符串
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s1;                    // 空字符串
    string s2 = "C++";           // "C++"
    string s3(s2);               // 拷贝s2,得到"C++"
    string s4("Hello World", 5); // 从"Hello World"取前5个字符 -> "Hello"
    string s5(s2, 1);            // 从s2的第1个位置开始取到末尾 -> "++"
    string s6(4, '#');           // "####"

    cout << "s1: \"" << s1 << "\"" << endl;
    cout << "s2: " << s2 << endl;
    cout << "s3: " << s3 << endl;
    cout << "s4: " << s4 << endl;
    cout << "s5: " << s5 << endl;
    cout << "s6: " << s6 << endl;
    return 0;
}

输出:

复制代码
s1: ""
s2: C++
s3: C++
s4: Hello
s5: ++
s6: ####

1.3 string类输入

string类支持多种输入方式:

方式一:使用cin >>(遇到空白停止)

cpp 复制代码
string word;
cin >> word;  // 输入 "Hello World",只会读取 "Hello"

方式二:使用getline()(读取整行)

cpp 复制代码
string line;
getline(cin, line);  // 输入 "Hello World",读取完整的 "Hello World"

方式三:使用getline()的自定义分隔符版本

cpp 复制代码
string line;
getline(cin, line, ':');  // 以':'作为分隔符
// 输入 "name:Tom",line的值为 "name"(遇到:就停止)

方式四:使用cin.getline()(读入char数组后赋给string)

cpp 复制代码
char buf[100];
cin.getline(buf, 100);
string s(buf);

方式五:使用cin.get()逐字符读取

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

int main() {
    cout << "请输入你的名字:";
    string name;
    getline(cin, name);  // 推荐:可以读取包含空格的名字
    cout << "你好," << name << "!" << endl;

    cout << "请输入一句话(用#结束):";
    string sentence;
    getline(cin, sentence, '#');  // 以'#'结束
    cout << "你说的是:" << sentence << endl;
    return 0;
}

注意: cin >> s 遇到空格、Tab、回车都会停止读取,而 getline(cin, s) 会读取整行(遇到回车才停止)。当你需要读取包含空格的输入时,必须用 getline()。


1.4 使用字符串

1.4.1 基本操作
cpp 复制代码
string s = "Hello";

// 获取长度
s.size();    // 返回 5
s.length();  // 和 size() 一样,返回 5

// 访问单个字符
s[0];    // 'H'
s.at(1); // 'e'(带越界检查,更安全)

// 判断是否为空
s.empty();  // 返回 false(因为s不为空)
1.4.2 字符串拼接
cpp 复制代码
string first = "Hello";
string second = "World";
string full = first + " " + second;  // "Hello World"

first += ", ";    // 追加到first末尾 -> "Hello, "
first += second;  // -> "Hello, World"

first.append("!"); // 也可以用append -> "Hello, World!"
1.4.3 字符串查找
cpp 复制代码
string s = "Hello World, Hello C++";

// find():查找子串第一次出现的位置
size_t pos = s.find("Hello");     // pos = 0
pos = s.find("Hello", 1);        // 从位置1开始找 -> pos = 13

// rfind():从后往前查找
pos = s.rfind("Hello");          // pos = 13

// find_first_of():查找字符集中任意一个字符第一次出现的位置
pos = s.find_first_of("aeiou");  // 找第一个元音字母 -> pos = 1

// find_last_of():查找最后一个
pos = s.find_last_of("aeiou");   // pos = 17

if (pos != string::npos) {
    cout << "找到了,位置是:" << pos << endl;
} else {
    cout << "没找到" << endl;
}

注意: string::npos 是一个特殊常量,表示"没有找到"。

1.4.4 字符串比较
cpp 复制代码
string a = "Apple";
string b = "Banana";

// 直接用比较运算符,按字典序比较
a == b;   // false
a != b;   // true
a < b;    // true('A' < 'B')
a > b;    // false

// compare()方法,返回值类似strcmp
int result = a.compare(b);  // 返回负数(a < b时)
1.4.5 字符串截取与替换
cpp 复制代码
string s = "Hello World";

// substr(pos, len):截取子串
string sub = s.substr(6, 5);  // 从位置6开始取5个字符 -> "World"
string sub2 = s.substr(6);    // 从位置6取到末尾 -> "World"

// replace(pos, len, str):替换
s.replace(6, 5, "C++");       // "Hello C++"

// insert(pos, str):在指定位置插入
s.insert(6, "Beautiful ");    // "Hello Beautiful C++"

// erase(pos, len):删除
s.erase(6, 10);               // 从位置6开始删除10个字符
1.4.6 字符串与其他类型的转换
cpp 复制代码
// 数字转字符串
string num = to_string(42);          // "42"
string pi = to_string(3.14159);     // "3.141590"

// 字符串转数字
int n = stoi("42");                 // 42
double d = stod("3.14159");         // 3.14159
long l = stol("1234567890");        // 1234567890

1.5 string还提供了哪些功能?

功能 方法 说明
反转字符串 reverse(s.begin(), s.end()) 需要<algorithm>
清空 s.clear() 清空字符串
交换两个字符串 s1.swap(s2) 交换s1和s2的内容
获取C风格字符串 s.c_str() 返回const char*,用于与C函数交互
分配空间 s.reserve(100) 预分配100个字符的空间
字符统计 s.find_first_not_of(" ") 找第一个不是空格的字符
cpp 复制代码
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main() {
    string s = "Hello";

    // 反转
    reverse(s.begin(), s.end());
    cout << "反转后:" << s << endl;  // "olleH"

    // 交换
    string a = "AAA", b = "BBB";
    a.swap(b);
    cout << "a=" << a << " b=" << b << endl;  // a=BBB b=AAA

    // 清空
    s.clear();
    cout << "清空后长度:" << s.size() << endl;  // 0

    // c_str()用法:传递给需要char*的C函数
    string filename = "test.txt";
    FILE* fp = fopen(filename.c_str(), "r");  // fopen需要const char*

    return 0;
}

1.6 字符串种类

C++实际上提供了多种字符串类型:

字符串类型 头文件 说明
std::string <string> 基于char,最常用
std::wstring <string> 基于wchar_t,宽字符
std::u16string <string> 基于char16_t(C++11)
std::u32string <string> 基于char32_t(C++11)
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s1 = "Hello";           // 普通字符串
    wstring s2 = L"你好世界";      // 宽字符字符串(中文常用)
    u16string s3 = u"Hello";       // UTF-16字符串
    u32string s4 = U"Hello";       // UTF-32字符串

    cout << "普通字符串:" << s1 << endl;
    // wcout << "宽字符串:" << s2 << endl;  // 需要设置locale
    return 0;
}

为什么要了解多种字符串? 处理中文、日文等多字节字符时,普通string可能不够用,需要使用宽字符版本。但在大多数日常编程中,std::string就足够了。


二、智能指针模板类

2.1 为什么需要智能指针?

先来看一个常见的内存泄漏问题:

cpp 复制代码
void leakyFunction() {
    int* p = new int(42);  // 在堆上分配内存
    // ... 做一些事情 ...
    // 忘记 delete p 了!
    // 函数结束,p被销毁,但堆上的内存没有释放 -> 内存泄漏!
}

智能指针 就像一个"自动管家"------它会在你不再需要某块内存时自动释放它,避免内存泄漏。

通俗比喻: 智能指针就像图书馆的借书系统。你借书(分配内存)后,系统会自动记录。当你忘记还书时,系统会在合适的时机自动帮你处理。而普通指针就像手工记录,容易忘记还。

C++11提供了三种智能指针:

智能指针 特点 类比
unique_ptr 独占所有权,同一时刻只有一个指针拥有对象 一物一主
shared_ptr 共享所有权,可以有多个指针拥有同一对象 共同拥有
weak_ptr 弱引用,不拥有对象,只是"观察" 借来看看

2.2 unique_ptr --- 独占型智能指针

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

int main() {
    // 创建unique_ptr
    unique_ptr<double> p1(new double(3.14));
    cout << *p1 << endl;  // 3.14

    // C++14推荐用make_unique
    unique_ptr<double> p2 = make_unique<double>(2.718);

    // p1可以"接管"p2的所有权
    p1 = move(p2);
    // 此时p2变为null,p1拥有2.718

    if (p2 == nullptr) {
        cout << "p2现在是空的" << endl;
    }

    // 当p1离开作用域时,自动释放内存
    return 0;
}

unique_ptr的重要规则:

  • 不能复制 :unique_ptr<int> p2 = p1; ❌ 编译错误
  • 可以移动 :unique_ptr<int> p2 = move(p1); ✅ 所有权转移
cpp 复制代码
unique_ptr<int> make_int(int n) {
    return make_unique<int>(n);  // 可以从函数返回
}

int main() {
    unique_ptr<int> p = make_int(42);
    cout << *p << endl;  // 42

    // 数组版本
    unique_ptr<int[]> arr = make_unique<int[]>(5);
    arr[0] = 10;
    arr[1] = 20;
    cout << arr[0] << " " << arr[1] << endl;  // 10 20
    return 0;
}

2.3 shared_ptr --- 共享型智能指针

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

class Dog {
public:
    string name;
    Dog(string n) : name(n) {
        cout << name << " 出生了!" << endl;
    }
    ~Dog() {
        cout << name << " 离开了..." << endl;
    }
};

int main() {
    // 共享所有权
    shared_ptr<Dog> p1 = make_shared<Dog>("旺财");
    cout << "p1引用计数:" << p1.use_count() << endl;  // 1

    {
        shared_ptr<Dog> p2 = p1;  // p2也拥有旺财
        cout << "p2引用计数:" << p1.use_count() << endl;  // 2

        shared_ptr<Dog> p3 = p1;  // p3也拥有旺财
        cout << "p3引用计数:" << p1.use_count() << endl;  // 3
    }
    // p2、p3离开作用域,引用计数减1
    cout << "作用域结束后:" << p1.use_count() << endl;  // 1

    return 0;
    // p1离开作用域,引用计数变0,自动释放Dog
}

输出:

复制代码
旺财 出生了!
p1引用计数:1
p2引用计数:2
p3引用计数:3
作用域结束后:1
旺财 离开了...

关键概念:引用计数

  • 每当一个新的shared_ptr指向对象时,计数+1
  • 当一个shared_ptr被销毁时,计数-1
  • 当计数变为0时,对象被自动删除

2.4 weak_ptr --- 弱引用

问题场景: 如果两个对象互相用shared_ptr引用对方,会形成循环引用,导致引用计数永远不为0,内存泄漏。

cpp 复制代码
// 循环引用问题(反面教材)
class B;  // 前置声明
class A {
public:
    shared_ptr<B> b_ptr;  // A拥有B
    ~A() { cout << "A被销毁" << endl; }
};
class B {
public:
    shared_ptr<A> a_ptr;  // B拥有A  -> 循环引用!
    ~B() { cout << "B被销毁" << endl; }
};

// 解决方案:用weak_ptr打破循环
class B_fixed;
class A_fixed {
public:
    shared_ptr<B_fixed> b_ptr;
    ~A_fixed() { cout << "A被销毁" << endl; }
};
class B_fixed {
public:
    weak_ptr<A_fixed> a_ptr;  // 用weak_ptr!不增加引用计数
    ~B_fixed() { cout << "B被销毁" << endl; }
};
cpp 复制代码
#include <iostream>
#include <memory>
using namespace std;

int main() {
    weak_ptr<int> wp;  // weak_ptr不能直接创建对象

    {
        shared_ptr<int> sp = make_shared<int>(42);
        wp = sp;  // weak_ptr从shared_ptr赋值

        cout << "sp引用计数:" << sp.use_count() << endl;  // 1(weak_ptr不增加计数)

        // 使用前需要先"锁定"为shared_ptr
        if (auto temp = wp.lock()) {
            cout << "锁定成功,值为:" << *temp << endl;  // 42
        }
    }
    // sp已销毁

    if (wp.expired()) {
        cout << "对象已经被销毁了" << endl;
    }
    return 0;
}

通俗比喻: shared_ptr是"主人",weak_ptr是"访客"。主人决定对象的生死,访客只是来看看。当所有主人都走了,对象就销毁了,即使还有访客在看。


2.5 使用智能指针的注意事项

注意事项1:不要混用普通指针和智能指针

cpp 复制代码
int* raw = new int(42);
unique_ptr<int> smart(raw);
delete raw;  // ❌ 错误!smart已经拥有了内存,再delete就是双重释放

注意事项2:不要用同一个指针创建两个智能指针

cpp 复制代码
int* raw = new int(42);
unique_ptr<int> p1(raw);
unique_ptr<int> p2(raw);  // ❌ p1和p2都会尝试释放内存

注意事项3:不要用new返回的指针赋值给auto_ptr以外再手动delete

cpp 复制代码
// 错误示范
unique_ptr<int> p(new int(42));
delete p.get();  // ❌ 不要手动删除!

// 正确做法:让智能指针自动管理
unique_ptr<int> p = make_unique<int>(42);  // ✅ 自动管理

注意事项4:使用make_shared/make_unique代替new

cpp 复制代码
// 不推荐
shared_ptr<int> p1(new int(42));

// 推荐(异常安全,且效率更高)
shared_ptr<int> p2 = make_shared<int>(42);
unique_ptr<int> p3 = make_unique<int>(42);

为什么推荐make_shared?

假设执行 shared_ptr<int> p(new int(42), custom_deleter); 时,如果custom_deleter的分配抛出异常,那么new int(42)分配的内存就泄漏了。而make_shared只分配一次内存,更安全。


2.6 unique_ptr为何优于auto_ptr

auto_ptr是C++98引入的早期智能指针,C++11已经将其废弃 (deprecated),用unique_ptr替代。

对比项 auto_ptr unique_ptr
复制语义 会偷偷转移所有权 必须用move()显式转移
与STL容器 不能放入容器 可以放入容器
数组支持 不支持 支持unique_ptr<T[]>
可用性 C++17已移除 C++11起推荐使用
cpp 复制代码
// auto_ptr的坑(不要这样做!)
auto_ptr<string> p1(new string("hello"));
auto_ptr<string> p2 = p1;  // 所有权转移!p1变成null了!
// 之后如果用p1,可能导致程序崩溃

// unique_ptr的安全做法
unique_ptr<string> p3(new string("hello"));
// unique_ptr<string> p4 = p3;  // ❌ 编译错误!
unique_ptr<string> p4 = move(p3);  // ✅ 显式移动,意图清晰

核心区别: auto_ptr的复制操作会"偷偷"转移所有权,你可能不知道原来的指针已经失效了。而unique_ptr要求你必须明确用move()来转移,代码意图更清晰,不容易犯错。


2.7 选择智能指针的决策指南

复制代码
你需要管理动态分配的对象吗?
├── 不需要 → 用普通变量,不要用智能指针
└── 需要 → 谁拥有这个对象?
    ├── 只有当前作用域需要 → unique_ptr
    ├── 多个地方共享 → shared_ptr
    ├── 需要观察但不拥有 → weak_ptr
    ├── 需要在容器中存储 → unique_ptr(容器支持move语义)
    └── 需要共享所有权但有循环引用 → shared_ptr + weak_ptr
cpp 复制代码
// 场景1:函数内部创建对象返回给调用者
unique_ptr<int> createNumber() {
    return make_unique<int>(42);  // 所有权交给调用者
}

// 场景2:多个对象共享同一资源
class ResourceManager {
    shared_ptr<Resource> resource;
public:
    ResourceManager(shared_ptr<Resource> r) : resource(r) {}
};

// 场景3:观察者模式
class Observer {
    weak_ptr<Subject> subject;  // 观察但不拥有
public:
    void update() {
        if (auto s = subject.lock()) {
            // 安全使用
        }
    }
};

三、标准模板库(STL)

3.1 什么是STL?

STL(Standard Template Library)是C++标准库的核心组成部分,它提供了通用的、可复用的数据结构和算法。

通俗比喻: 如果C++是乐高积木的基础砖块,那STL就是预先搭好的各种"组件"------轮子、门、窗户。你不需要自己从头造轮子,直接用现成的就好了。

STL的四大组件:

组件 作用 举例
容器 存储数据 vector, list, map
迭代器 遍历容器 begin(), end()
算法 操作数据 sort(), find()
函数对象 自定义行为 lambda, 函数符

3.2 模板类vector --- 动态数组

3.2.1 为什么用vector?

普通数组大小固定,定义时就必须确定大小:

cpp 复制代码
int arr[100];  // 只能存100个元素,多了就溢出了

vector就像一个可以自动伸缩的数组,你往里面加元素,它会自动扩容。

通俗比喻: 普通数组像固定大小的书架,vector像可以自动生长的书架------书多了,它自己变大。

3.2.2 创建vector
cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main() {
    // 各种创建方式
    vector<int> v1;                    // 空vector
    vector<int> v2(10);               // 10个元素,初始值为0
    vector<int> v3(10, 42);           // 10个元素,初始值为42
    vector<int> v4 = {1, 2, 3, 4, 5}; // C++11初始化列表
    vector<string> v5 = {"hello", "world"};

    // 用另一个vector初始化
    vector<int> v6(v4);               // 拷贝v4
    vector<int> v7(v4.begin(), v4.end()); // 用迭代器范围

    // 查看大小
    cout << "v4的大小:" << v4.size() << endl;  // 5
    cout << "v4的容量:" << v4.capacity() << endl; // 至少5(可能更多)

    return 0;
}

3.3 可对矢量执行的操作

3.3.1 添加和删除元素
cpp 复制代码
vector<int> v = {1, 2, 3};

// 尾部添加
v.push_back(4);      // {1, 2, 3, 4}

// emplace_back(C++11):直接在末尾构造对象,效率更高
v.emplace_back(5);   // {1, 2, 3, 4, 5}

// 插入
v.insert(v.begin() + 1, 10);  // {1, 10, 2, 3, 4, 5}
// 在位置1插入10

// 删除末尾
v.pop_back();         // {1, 10, 2, 3, 4}

// 删除指定位置
v.erase(v.begin() + 1); // {1, 2, 3, 4}(删除位置1的元素)

// 删除范围
v.erase(v.begin(), v.begin() + 2); // {3, 4}(删除前两个)

// 清空所有
v.clear();            // {}
3.3.2 访问元素
cpp 复制代码
vector<int> v = {10, 20, 30, 40, 50};

// 下标访问
v[0];       // 10
v[2];       // 30

// at()访问(带越界检查)
v.at(3);    // 40
// v.at(10); // 抛出 std::out_of_range 异常

// 首尾元素
v.front();  // 10(第一个元素)
v.back();   // 50(最后一个元素)

// 原始指针
int* p = v.data();  // 获取底层数组的指针
3.3.3 容量相关
cpp 复制代码
vector<int> v;
v.reserve(100);    // 预分配100个元素的空间

v.push_back(1);
v.push_back(2);

cout << "大小:" << v.size() << endl;       // 2(实际元素数量)
cout << "容量:" << v.capacity() << endl;   // 100(已分配空间)
cout << "是否为空:" << v.empty() << endl;  // 0(false)

v.resize(5);  // 调整大小为5,多出的元素初始化为0
v.shrink_to_fit();  // 释放多余的内存(C++11)
3.3.4 二维vector
cpp 复制代码
// 创建3行4列的二维数组
vector<vector<int>> matrix(3, vector<int>(4, 0));

matrix[0][0] = 1;
matrix[1][2] = 5;

// 遍历
for (int i = 0; i < matrix.size(); i++) {
    for (int j = 0; j < matrix[i].size(); j++) {
        cout << matrix[i][j] << " ";
    }
    cout << endl;
}

3.4 对矢量可执行的其他操作

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {5, 2, 8, 1, 9, 3};

    // 排序
    sort(v.begin(), v.end());
    // v: {1, 2, 3, 5, 8, 9}

    // 逆序
    reverse(v.begin(), v.end());
    // v: {9, 8, 5, 3, 2, 1}

    // 查找
    auto it = find(v.begin(), v.end(), 5);
    if (it != v.end()) {
        cout << "找到了5,位置:" << (it - v.begin()) << endl;
    }

    // 随机打乱
    random_shuffle(v.begin(), v.end());  // 需要<algorithm>

    // 判断是否包含(C++20有contains,之前用find)
    bool has8 = (find(v.begin(), v.end(), 8) != v.end());
    cout << "有8吗?" << (has8 ? "有" : "没有") << endl;

    // unique:去除相邻重复元素(需要先排序)
    sort(v.begin(), v.end());
    auto last = unique(v.begin(), v.end());
    v.erase(last, v.end());  // 真正删除重复元素

    return 0;
}

3.5 基于范围的for循环(C++11)

传统for循环遍历vector需要管理索引,容易出错:

cpp 复制代码
vector<int> v = {1, 2, 3, 4, 5};

// 传统方式(容易写错索引)
for (int i = 0; i < v.size(); i++) {
    cout << v[i] << " ";
}

// C++11 基于范围的for循环(简洁安全!)
for (int x : v) {
    cout << x << " ";  // 只读访问
}

// 使用引用修改元素
for (int& x : v) {
    x *= 2;  // 每个元素翻倍
}

// 使用auto自动推导类型
vector<string> words = {"Hello", "World", "C++"};
for (const auto& word : words) {  // const引用,高效且不修改
    cout << word << " ";
}

// 遍历初始化列表
for (int x : {1, 2, 3, 4, 5}) {
    cout << x << " ";
}

关键点:

  • for (int x : v) --- 拷贝每个元素,只读
  • for (int& x : v) --- 引用每个元素,可修改
  • for (const int& x : v) --- const引用,只读且不拷贝(推荐!)

3.6 泛型编程

泛型编程(Generic Programming)是编写与数据类型无关的代码。

通俗比喻: 你不会为"红色的苹果"写一个切法,为"绿色的苹果"写另一个切法。你写一个通用的"切水果"方法,不管什么颜色的水果都能用。

cpp 复制代码
// 不使用模板:为每种类型写一遍
void swapInts(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}
void swapDoubles(double& a, double& b) {
    double temp = a;
    a = b;
    b = temp;
}

// 使用模板:一个函数搞定所有类型
template <typename T>
void mySwap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 1, y = 2;
    mySwap(x, y);  // 自动推导T为int

    double a = 1.5, b = 2.5;
    mySwap(a, b);  // 自动推导T为double
}

STL的核心思想就是泛型编程------容器、算法、迭代器都是模板,可以处理任何类型的数据。


3.7 为何使用迭代器

问题:如何用统一的方式遍历不同容器?

vector可以用下标遍历,但list、map等容器没有下标 。迭代器提供了一种统一的遍历方式。

通俗比喻: 迭代器就像一个"遥控器"。不管你用的是电视机(vector)、收音机(list)还是投影仪(map),遥控器的操作方式都差不多------按"下一个"就到下一个,按"上一个"就返回。

cpp 复制代码
#include <iostream>
#include <vector>
#include <list>
#include <map>
using namespace std;

int main() {
    // vector使用迭代器
    vector<int> v = {1, 2, 3, 4, 5};
    vector<int>::iterator it;  // 声明迭代器

    for (it = v.begin(); it != v.end(); ++it) {
        cout << *it << " ";  // 解引用获取元素
    }
    cout << endl;

    // list使用迭代器(和vector一样的写法!)
    list<string> lst = {"apple", "banana", "cherry"};
    for (auto it2 = lst.begin(); it2 != lst.end(); ++it2) {
        cout << *it2 << " ";  // 一样用*it获取元素
    }
    cout << endl;

    // map使用迭代器
    map<string, int> ages = {{"Tom", 25}, {"Jerry", 23}};
    for (auto it3 = ages.begin(); it3 != ages.end(); ++it3) {
        cout << it3->first << ": " << it3->second << endl;
        // map的迭代器指向pair,用->first和->second访问
    }

    return 0;
}

3.8 迭代器类型

迭代器类型 支持操作 对应容器
输入迭代器 读取、单次前进 istream_iterator
输出迭代器 写入、单次前进 ostream_iterator
前向迭代器 读写、多次前进 forward_list, unordered_map
双向迭代器 读写、前进和后退 list, map, set
随机访问迭代器 读写、跳跃、比较 vector, deque, string
cpp 复制代码
vector<int> v = {1, 2, 3, 4, 5};

// 随机访问迭代器(vector的迭代器)
auto it = v.begin();
it += 3;       // 跳到第4个元素
it -= 2;       // 回退2个位置
cout << *it;   // 2

// 双向迭代器(list的迭代器)
list<int> lst = {1, 2, 3, 4, 5};
auto it2 = lst.end();
--it2;         // 可以后退
cout << *it2;  // 5
// ++it2;      // 不能跳多个位置(没有+=操作)

3.9 迭代器层次结构

复制代码
输入迭代器 → 前向迭代器 → 双向迭代器 → 随机访问迭代器
                                              ↓
                                        随机访问迭代器(最强大)

理解关键: 每个高级迭代器都包含低级迭代器的所有功能。随机访问迭代器最强大,可以做所有操作;输入迭代器最基础,只能读取和前进。

cpp 复制代码
// 随机访问迭代器可以做的事
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();

// 比较
it < v.end()    // ✅ 随机访问迭代器支持
it - v.begin()  // ✅ 可以算距离

// 跳跃
it + 3          // ✅ 前进3个位置
it - 2          // ✅ 后退2个位置

// list的双向迭代器不能做的事
list<int> lst = {1, 2, 3};
auto it2 = lst.begin();
// it2 + 2     // ❌ 编译错误!双向迭代器不支持+操作
// it2 < lst.end()  // ❌ 编译错误!双向迭代器不支持<

3.10 概念、改进和模型

概念(Concept): 对迭代器功能需求的描述。例如"随机访问迭代器"就是一个概念,它要求支持+、-、<等操作。

模型(Model): 概念的具体实现。例如vector<int>::iterator就是"随机访问迭代器"概念的一个模型。

改进(Refinement): 一个概念对另一个概念的增强。例如"双向迭代器"是对"前向迭代器"的改进,它增加了--操作。

cpp 复制代码
// 概念和模型的关系
// 概念:随机访问迭代器(要求:支持 +, -, <, [], *= 等)
// 模型:vector<int>::iterator 是随机访问迭代器概念的一个模型
//       string::iterator 也是随机访问迭代器概念的一个模型
//       list<int>::iterator 是双向迭代器概念的模型(不是随机访问的)

// 一个算法可能要求特定的概念
// sort() 要求随机访问迭代器
vector<int> v = {3, 1, 4, 1, 5};
sort(v.begin(), v.end());  // ✅ vector迭代器满足要求

list<int> lst = {3, 1, 4, 1, 5};
// sort(lst.begin(), lst.end());  // ❌ list迭代器只是双向的,不满足要求
lst.sort();  // ✅ list有自己的sort成员函数

为什么这很重要? 使用算法时,你需要确保你的迭代器满足算法要求的概念,否则编译会报错或运行出错。


3.11 容器种类

C++ STL容器分为三大类:

序列容器(按位置顺序存储)
容器 特点 适用场景
vector 连续内存,尾部操作O(1),随机访问O(1) 大多数情况的首选
deque 双端队列,头部和尾部操作O(1) 需要频繁在两端操作
list 双向链表,任意位置插入删除O(1) 频繁在中间插入删除
forward_list 单向链表,C++11 内存受限场景
array 固定大小数组,C++11 需要确定大小的数组
cpp 复制代码
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <array>
#include <forward_list>
using namespace std;

int main() {
    // vector:最常用
    vector<int> v = {1, 2, 3, 4, 5};

    // deque:双端操作
    deque<int> dq = {1, 2, 3};
    dq.push_front(0);  // 前面加元素
    dq.push_back(4);   // 后面加元素

    // list:双向链表
    list<int> lst = {1, 2, 3};
    lst.push_front(0);
    lst.push_back(4);
    lst.insert(lst.begin() + 2, 10);  // ❌ list不支持+操作!
    // 正确做法:
    auto it = lst.begin();
    advance(it, 2);  // 用advance移动迭代器
    lst.insert(it, 10);

    // array:固定大小数组(C++11)
    array<int, 5> arr = {5, 4, 3, 2, 1};

    // forward_list:单向链表
    forward_list<int> fl = {1, 2, 3};
    fl.push_front(0);
    // fl.push_back(4);  // ❌ 不支持push_back!

    return 0;
}
容器选择速查
复制代码
需要随机访问? → vector(首选)或 deque
需要频繁在两端插入? → deque
需要频繁在中间插入删除? → list
需要固定大小? → array
需要单向遍历且内存紧张? → forward_list
需要自动排序? → set 或 map
需要快速查找? → unordered_set 或 unordered_map

3.12 关联容器

关联容器使用键(key)来快速查找元素,底层通常用红黑树实现,查找效率为O(log n)。

容器 说明 键是否唯一 是否有序
set 集合 是 有序
multiset 允许重复的集合 否 有序
map 键值对 是 有序
multimap 允许重复键的映射 否 有序
cpp 复制代码
#include <iostream>
#include <set>
#include <map>
#include <string>
using namespace std;

int main() {
    // ===== set:自动去重和排序 =====
    set<int> s = {5, 3, 1, 4, 2, 3};
    // s: {1, 2, 3, 4, 5}  (自动排序,重复的3只保留一个)

    s.insert(6);      // 添加元素
    s.erase(2);       // 删除元素

    // 查找
    if (s.find(3) != s.end()) {
        cout << "3存在" << endl;
    }
    // 或者用count
    cout << "1的数量:" << s.count(1) << endl;  // 1(存在)

    // 遍历
    for (int x : s) {
        cout << x << " ";  // 1 3 4 5 6(有序!)
    }
    cout << endl;

    // ===== map:键值对 =====
    map<string, int> ages;
    ages["Tom"] = 25;
    ages["Jerry"] = 23;
    ages["Alice"] = 28;

    // 访问
    cout << "Tom的年龄:" << ages["Tom"] << endl;  // 25

    // 查找
    auto it = ages.find("Jerry");
    if (it != ages.end()) {
        cout << it->first << " -> " << it->second << endl;
        // Jerry -> 23
    }

    // 遍历(按键的字典序)
    for (const auto& pair : ages) {
        cout << pair.first << ": " << pair.second << endl;
    }

    return 0;
}

什么时候用关联容器? 当你需要快速查找的时候。例如:学生成绩查询系统(用学号作为键)、电话簿(用名字作为键)、统计词频(用单词作为键)。


3.13 无序关联容器(C++11)

无序关联容器使用哈希表实现,查找效率平均为O(1),比有序关联容器的O(log n)更快。

容器 说明
unordered_set 无序集合
unordered_multiset 允许重复的无序集合
unordered_map 无序映射
unordered_multimap 允许重复键的无序映射
cpp 复制代码
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;

int main() {
    // 统计词频
    unordered_map<string, int> wordCount;
    string words[] = {"apple", "banana", "apple", "cherry", "banana", "apple"};

    for (const string& word : words) {
        wordCount[word]++;  // 不存在则自动创建并初始化为0,然后+1
    }

    for (const auto& pair : wordCount) {
        cout << pair.first << ": " << pair.second << endl;
    }
    // 输出(顺序可能不同,因为是无序的):
    // apple: 3
    // banana: 2
    // cherry: 1

    return 0;
}

有序 vs 无序的选择:

  • 需要按键排序 遍历 → 用有序的(map, set)
  • 只需要快速查找 ,不需要排序 → 用无序的(unordered_map, unordered_set)
  • 无序容器查找平均O(1),但最坏情况可能退化到O(n)
  • 有序容器查找始终O(log n),稳定可预测

3.14 函数对象(函数符)

函数对象(Function Object / Functor)是重载了operator()的类的对象,可以像函数一样被调用。

通俗比喻: 函数对象就像一个"带记忆的函数"。普通函数执行完就忘了之前的计算,而函数对象可以"记住"一些状态。

3.14.1 函数符概念
cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

// 最简单的函数符
class Adder {
    int value;
public:
    Adder(int v) : value(v) {}

    // 重载(),使对象可以像函数一样调用
    int operator()(int x) const {
        return x + value;
    }
};

// 无参函数符
class Printer {
public:
    void operator()() const {
        cout << "Hello from Functor!" << endl;
    }
};

int main() {
    Adder add5(5);
    cout << add5(10) << endl;   // 15(像函数一样调用!)

    Printer print;
    print();                    // "Hello from Functor!"

    // 在STL算法中使用函数符
    vector<int> v = {1, 2, 3, 4, 5};
    Adder add10(10);
    // transform:对每个元素应用函数符
    vector<int> result(v.size());
    transform(v.begin(), v.end(), result.begin(), add10);
    // result: {11, 12, 13, 14, 15}

    for (int x : result) {
        cout << x << " ";
    }
    cout << endl;

    return 0;
}
3.14.2 预定义的函数符

C++在<functional>头文件中提供了很多预定义函数符:

cpp 复制代码
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {5, 3, 8, 1, 9, 2};

    // less<int>():小于比较(默认排序规则)
    sort(v.begin(), v.end(), less<int>());
    // v: {1, 2, 3, 5, 8, 9}

    // greater<int>():大于比较
    sort(v.begin(), v.end(), greater<int>());
    // v: {9, 8, 5, 3, 2, 1}

    // plus<int>():加法
    plus<int> add;
    cout << add(3, 4) << endl;  // 7

    // negate<int>():取负
    negate<int> neg;
    cout << neg(5) << endl;     // -5

    // 多参数函数符
    // bind1st / bind2d 已在C++11中弃用,用bind替代

    return 0;
}

常用预定义函数符:

函数符 功能 示例
less<T> 小于 less<int>()(3, 5) → true
greater<T> 大于 greater<int>()(5, 3) → true
plus<T> 加法 plus<int>()(3, 4) → 7
minus<T> 减法 minus<int>()(5, 3) → 2
multiplies<T> 乘法 multiplies<int>()(3, 4) → 12
negate<T> 取负 negate<int>()(5) → -5
3.14.3 自适应函数符和函数适配器

自适应函数符携带了类型信息(参数类型和返回类型),可以与函数适配器配合使用。

cpp 复制代码
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // bind:绑定参数(C++11推荐替代bind1st/bind2nd)
    // 找到第一个大于5的元素
    auto it = find_if(v.begin(), v.end(),
                      bind(greater<int>(), placeholders::_1, 5));
    // 等价于 lambda:[](int x) { return x > 5; }

    if (it != v.end()) {
        cout << "第一个大于5的元素:" << *it << endl;  // 6
    }

    // 统计大于3的元素个数
    int count = count_if(v.begin(), v.end(),
                         bind(greater<int>(), placeholders::_1, 3));
    cout << "大于3的元素个数:" << count << endl;  // 7

    // 将每个元素乘以2
    vector<int> result(v.size());
    transform(v.begin(), v.end(), result.begin(),
              bind(multiplies<int>(), placeholders::_1, 2));
    // result: {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}

    for (int x : result) {
        cout << x << " ";
    }
    cout << endl;

    return 0;
}

现代C++推荐: 在大多数情况下,使用lambda表达式比bind和函数符更简洁:

cpp 复制代码
// 用lambda替代bind
find_if(v.begin(), v.end(), [](int x) { return x > 5; });
count_if(v.begin(), v.end(), [](int x) { return x > 3; });

四、算法

4.1 算法组

STL算法分为几大类:

非修改序列操作(不改变容器内容)
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // find:查找
    auto it = find(v.begin(), v.end(), 3);
    cout << "3在位置:" << (it - v.begin()) << endl;  // 2

    // count:计数
    vector<int> v2 = {1, 2, 2, 3, 2};
    cout << "2出现的次数:" << count(v2.begin(), v2.end(), 2) << endl;  // 3

    // accumulate:求和(需要<numeric>)
    int sum = accumulate(v.begin(), v.end(), 0);
    cout << "求和:" << sum << endl;  // 15

    // min_element / max_element
    auto minIt = min_element(v.begin(), v.end());
    auto maxIt = max_element(v.begin(), v.end());
    cout << "最小值:" << *minIt << " 最大值:" << *maxIt << endl;

    // all_of / any_of / none_of
    bool allPositive = all_of(v.begin(), v.end(), [](int x) { return x > 0; });
    cout << "全部为正?" << (allPositive ? "是" : "否") << endl;  // 是

    // equal:比较两个范围是否相等
    vector<int> v3 = {1, 2, 3, 4, 5};
    cout << "v和v3相等?" << (equal(v.begin(), v.end(), v3.begin()) ? "是" : "否") << endl;

    return 0;
}
修改序列操作
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // copy:复制
    vector<int> v2(5);
    copy(v.begin(), v.end(), v2.begin());

    // transform:转换
    vector<int> v3(5);
    transform(v.begin(), v.end(), v3.begin(), [](int x) { return x * x; });
    // v3: {1, 4, 9, 16, 25}

    // replace:替换
    replace(v.begin(), v.end(), 3, 100);  // 把所有3替换为100

    // fill:填充
    fill(v.begin(), v.end(), 0);  // 全部设为0

    // generate:生成
    int counter = 0;
    generate(v.begin(), v.end(), [&counter]() { return counter++; });
    // v: {0, 1, 2, 3, 4}

    // remove + erase:删除特定元素(remove只是移动元素到末尾)
    v = {1, 2, 3, 2, 4, 2, 5};
    v.erase(remove(v.begin(), v.end(), 2), v.end());
    // v: {1, 3, 4, 5}

    return 0;
}
排序和相关操作
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    // sort:排序
    vector<int> v = {5, 2, 8, 1, 9, 3};
    sort(v.begin(), v.end());         // 升序
    sort(v.begin(), v.end(), greater<int>());  // 降序

    // stable_sort:稳定排序(保持相等元素的相对顺序)
    // partial_sort:部分排序
    // nth_element:第n个元素就位

    // 二分查找(要求已排序)
    sort(v.begin(), v.end(), greater<int>());  // 降序
    bool found = binary_search(v.begin(), v.end(), 8, greater<int>());
    cout << "找到8:" << (found ? "是" : "否") << endl;

    // lower_bound / upper_bound
    v = {1, 2, 2, 3, 4, 4, 5};
    auto lb = lower_bound(v.begin(), v.end(), 2);  // 第一个>=2的位置
    auto ub = upper_bound(v.begin(), v.end(), 2);  // 第一个>2的位置
    cout << "2的范围:" << (lb - v.begin()) << " 到 " << (ub - v.begin()) << endl;
    // 输出:2的范围:1 到 3

    // next_permutation:下一个排列
    vector<int> p = {1, 2, 3};
    do {
        for (int x : p) cout << x << " ";
        cout << endl;
    } while (next_permutation(p.begin(), p.end()));
    // 输出所有排列:
    // 1 2 3
    // 1 3 2
    // 2 1 3
    // 2 3 1
    // 3 1 2
    // 3 2 1

    return 0;
}

4.2 算法的通用特征

通用特征1:算法通过迭代器操作容器

cpp 复制代码
// 算法不知道底层是什么容器
// 它只通过迭代器来"读写"数据
sort(v.begin(), v.end());  // vector
sort(lst.begin(), lst.end());  // 如果迭代器满足要求

通用特征2:算法可以使用函数对象自定义行为

cpp 复制代码
// 默认升序
sort(v.begin(), v.end());

// 自定义降序
sort(v.begin(), v.end(), greater<int>());

// 自定义比较规则
struct Student {
    string name;
    int score;
};

vector<Student> students = {{"Tom", 85}, {"Jerry", 92}, {"Alice", 78}};

// 按分数降序排列
sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
    return a.score > b.score;
});

通用特征3:部分算法有多个版本

cpp 复制代码
// 默认版本:使用<运算符
sort(v.begin(), v.end());

// 自定义版本:使用比较函数
sort(v.begin(), v.end(), greater<int>());

// 部分算法有_if版本:接受谓词
find_if(v.begin(), v.end(), [](int x) { return x > 5; });

五、STL和string类

5.1 函数和容器方法

STL算法和容器的成员函数有时可以完成同样的任务,但效率可能不同:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {5, 3, 8, 1, 9};

    // 用STL算法查找
    auto it1 = find(v.begin(), v.end(), 8);

    // 用STL算法排序
    sort(v.begin(), v.end());

    // 用STL算法反转
    reverse(v.begin(), v.end());

    // vector自己的方法
    v.push_back(10);    // 只有vector有push_back
    v.size();           // 每个容器都有size
    v.empty();          // 每个容器都有empty
    v.clear();          // 每个容器都有clear

    // 选择原则:
    // 1. 有成员方法就优先用成员方法(可能更高效)
    // 2. 需要通用算法就用STL算法
    // 3. 成员方法和STL算法结合使用

    return 0;
}

对比表:

操作 STL算法 容器方法
查找 find() list::find()
排序 sort() list::sort()
反转 reverse() 无(用算法)
计数 count() 无(用算法)
插入 不适用 insert()
删除 remove() erase()

注意: list的成员方法sort()比STL的sort()更高效,因为sort()需要随机访问迭代器,而list只有双向迭代器,所以list的sort()是成员方法。


六、使用STL

6.1 实战示例:学生成绩管理系统

cpp 复制代码
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <string>
#include <numeric>
using namespace std;

struct Student {
    string name;
    int score;
};

int main() {
    // 1. 用vector存储学生信息
    vector<Student> students = {
        {"Tom", 85},
        {"Jerry", 92},
        {"Alice", 78},
        {"Bob", 95},
        {"Charlie", 88}
    };

    // 2. 按成绩降序排序
    sort(students.begin(), students.end(),
         [](const Student& a, const Student& b) {
             return a.score > b.score;
         });

    cout << "=== 成绩排名 ===" << endl;
    for (int i = 0; i < students.size(); i++) {
        cout << "第" << i + 1 << "名:" << students[i].name
             << " " << students[i].score << "分" << endl;
    }

    // 3. 计算平均分
    int total = accumulate(students.begin(), students.end(), 0,
                           [](int sum, const Student& s) {
                               return sum + s.score;
                           });
    cout << "\n平均分:" << (double)total / students.size() << endl;

    // 4. 找出成绩最高的学生
    auto top = max_element(students.begin(), students.end(),
                           [](const Student& a, const Student& b) {
                               return a.score < b.score;
                           });
    cout << "最高分:" << top->name << " " << top->score << "分" << endl;

    // 5. 统计90分以上的人数
    int count90 = count_if(students.begin(), students.end(),
                           [](const Student& s) { return s.score >= 90; });
    cout << "90分以上的人数:" << count90 << endl;

    // 6. 用map建立名字到成绩的映射
    map<string, int> scoreMap;
    for (const auto& s : students) {
        scoreMap[s.name] = s.score;
    }

    // 7. 查询某个学生的成绩
    string queryName = "Alice";
    if (scoreMap.find(queryName) != scoreMap.end()) {
        cout << queryName << "的成绩:" << scoreMap[queryName] << "分" << endl;
    }

    return 0;
}

七、其他库

7.1 vector、valarray和array

容器 头文件 特点 适用场景
vector <vector> 通用动态数组,支持迭代器和STL算法 通用场景
array <array> 固定大小数组(C++11),栈上分配 大小已知且固定
valarray <valarray> 数学运算优化,支持向量化操作 科学计算/数学运算
cpp 复制代码
#include <iostream>
#include <vector>
#include <array>
#include <valarray>
#include <numeric>
using namespace std;

int main() {
    // vector:通用,最常用
    vector<double> v = {1.0, 2.0, 3.0, 4.0, 5.0};

    // array:固定大小,比vector效率略高
    array<double, 5> arr = {1.0, 2.0, 3.0, 4.0, 5.0};
    cout << "array大小:" << arr.size() << endl;  // 5

    // valarray:数学运算
    valarray<double> va = {1.0, 2.0, 3.0, 4.0, 5.0};

    // valarray支持逐元素数学运算!
    valarray<double> vb = {10.0, 20.0, 30.0, 40.0, 50.0};

    valarray<double> vc = va + vb;     // {11, 22, 33, 44, 55}
    valarray<double> vd = va * vb;     // {10, 40, 90, 160, 250}
    valarray<double> ve = vb - va;     // {9, 18, 27, 36, 45}
    valarray<double> vf = vb / va;     // {10, 10, 10, 10, 10}

    // 求和
    double sum = 0;
    for (int i = 0; i < 5; i++) sum += vc[i];
    cout << "vc求和:" << sum << endl;  // 165

    // 子数组
    valarray<double> sub = va[slice(1, 3, 1)]; // 从位置1取3个,步长1
    // sub: {2, 3, 4}

    return 0;
}

选择指南:

  • 日常编程 → vector(支持STL算法,最灵活)
  • 大小固定的小数组 → array(栈上分配,无内存开销)
  • 数学密集计算 → valarray(编译器可能做向量化优化)

7.2 模板initializer_list(C++11)

initializer_list允许函数接受花括号包围的初始化列表作为参数。

cpp 复制代码
#include <iostream>
#include <vector>
#include <initializer_list>
using namespace std;

// 接受initializer_list的函数
void printList(initializer_list<int> il) {
    for (const auto& x : il) {
        cout << x << " ";
    }
    cout << endl;
}

// 使用initializer_list实现可变参数求和
int sum(initializer_list<int> il) {
    int total = 0;
    for (auto x : il) {
        total += x;
    }
    return total;
}

// 在类中使用initializer_list
class IntArray {
    vector<int> data;
public:
    IntArray(initializer_list<int> il) : data(il) {}

    void print() const {
        for (int x : data) {
            cout << x << " ";
        }
        cout << endl;
    }
};

int main() {
    // 直接用花括号列表调用
    printList({1, 2, 3, 4, 5});
    printList({10, 20, 30});

    cout << "求和:" << sum({1, 2, 3, 4, 5}) << endl;  // 15

    // 用初始化列表构造对象
    IntArray arr = {10, 20, 30, 40, 50};
    arr.print();  // 10 20 30 40 50

    // vector也可以用初始化列表
    vector<int> v = {5, 4, 3, 2, 1};

    return 0;
}

7.3 使用initializer_list的注意事项

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

class Safe {
public:
    Safe(initializer_list<int> il) {
        cout << "创建了包含" << il.size() << "个元素的Safe" << endl;
        // 注意:initializer_list中的元素是const的!
        for (auto& x : il) {
            cout << x << " ";
            // x = 100;  // ❌ 错误!initializer_list元素是const
        }
        cout << endl;
    }
};

int main() {
    Safe s = {1, 2, 3, 4, 5};
    return 0;
}

重要注意事项:

  1. initializer_list中的元素是只读的(const)
  2. initializer_list对象本身只是一个"视图",底层数据在花括号结束后可能失效
  3. 应该立即 把initializer_list的内容拷贝到容器中保存
  4. initializer_list的大小在编译时确定

八、总结

知识体系总览

复制代码
第16章 知识脉络
│
├── string类
│   ├── 构造方式(默认、C字符串、拷贝、填充)
│   ├── 输入方式(cin>>、getline、自定义分隔符)
│   ├── 基本操作(拼接、查找、比较、截取、替换)
│   ├── 类型转换(to_string、stoi、stod)
│   └── 字符串种类(string、wstring、u16string、u32string)
│
├── 智能指针
│   ├── unique_ptr:独占所有权(推荐首选)
│   ├── shared_ptr:共享所有权(引用计数)
│   ├── weak_ptr:弱引用(解决循环引用)
│   ├── auto_ptr:已废弃(不要用)
│   └── 选择策略:独占→unique,共享→shared,观察→weak
│
├── STL
│   ├── 容器
│   │   ├── 序列容器:vector、deque、list、array、forward_list
│   │   ├── 关联容器:set、map、multiset、multimap
│   │   └── 无序容器:unordered_set、unordered_map等
│   ├── 迭代器
│   │   ├── 类型层次:输入→前向→双向→随机访问
│   │   └── 统一遍历不同容器的接口
│   ├── 算法
│   │   ├── 非修改:find、count、accumulate
│   │   ├── 修改:copy、transform、replace
│   │   └── 排序:sort、binary_search、next_permutation
│   └── 函数对象
│       ├── functor:重载operator()的类
│       ├── 预定义函数符:less、greater、plus等
│       └── lambda表达式(现代C++首选)
│
└── 其他
    ├── valarray:数学运算优化
    ├── array:固定大小数组
    └── initializer_list:统一初始化语法

核心使用建议

  1. 字符串处理 :优先使用string,需要与C库交互时用c_str()
  2. 内存管理 :优先使用unique_ptr,需要共享时用shared_ptr,永远不要用auto_ptr
  3. 容器选择 :大多数情况用vector,需要键值查找用map/unordered_map
  4. 遍历容器 :优先使用基于范围的for循环和auto
  5. 自定义行为:优先使用lambda表达式,复杂的用函数对象
  6. 算法使用:优先使用STL算法代替手写循环

本章是C++标准库的核心内容,掌握string、智能指针和STL的使用,将极大提升你的编程效率和代码质量。这些工具让你专注于解决问题本身,而不是重复造轮子。

相关推荐
qq_401700411 小时前
Qt 炫酷曲线,图表,2D/3D开源库
开发语言·qt
无忧.芙桃1 小时前
位图妙用:512MB 实现 40 亿整数的存在性查询
c语言·c++·面试·哈希算法
liangshanbo12151 小时前
面试题:线上 JavaScript 报错如何快速定位到源码?
开发语言·javascript·ecmascript
ShineWinsu1 小时前
对于Redis:Steam、Geospatial、Hyperloglog、Bitmap、Bitfield类型的解析
数据库·c++·redis·分布式·缓存·面试·zset
weixin_440730502 小时前
迭代器Iterator(列表生成器、可迭代的如何生成迭代器)
开发语言·python
朝朝辞暮i2 小时前
C++ 第 31C 课:Subscriber 到底是什么
开发语言·c++·算法
weixin_440730502 小时前
内置函数、json文本、pickle二进制
开发语言·python·json
inferno2 小时前
SVN学习笔记(二)
笔记·学习·svn
殷色玫瑰2 小时前
C++ string类详解:常用接口、字符串操作与模拟实现
java·linux·c语言·开发语言·数据结构·c++