Skip to content
cpp
# 1. 字符串类型

在C语言中,没有字符串类型的。C语言中使用字符数组/字符指针来表示一串字符。

```cpp
char str[128] = "hello"; // 栈区,可以修改
str[0] = 'H'; // 正确
char *p_str = "hello"; // 常量区
// p_str[0] = 'H'; // 错误 常量区的内容不能修改

在C++标准库中,提供了一个字符串类型,实际上是一个字符串类。类名叫做string,专门用于描述一个字符串对象,并且提供了操作字符串的一系列函数接口。

1.1 使用方法

指定作用域,因为是标准库中的,所以要使用std的命名空间。

cpp
std::string str = "蔡徐坤";

1.2 字符串对象的实例化方式

  • 无参构造函数方式
cpp
std::string str; // 空字符串
  • 含参构造方式
cpp
std::string str("蔡徐坤");
// 或
std::string str = std::string("范丞丞");
  • 使用字符和长度作为参数的构造函数
cpp
std::string str2("abcdefg", 3); // "abc"
  • 拷贝构造
cpp
std::string str1 = std::string("范丞丞");
std::string str2(str1);
// 或
std::string str2 = str1;

// 使用字符数组
char c[] = "范丞丞";
std::string str2(c);
  • 拷贝赋值
cpp
std::string str1 = std::string("范丞丞");
std::string str2("蔡徐坤");
str2 = str1;

// 使用字符数组
char c[] = "范丞丞";
std::string str2("蔡徐坤");
str2 = c;

1.3 常用的成员函数(访问元素)

  • at(int size)
    访问指定字符,有边界检查,返回指定位置的字符的引用
cpp
std::string str("abcdefg");
char c = str.at(3); // 'd' 
std::cout << c << std::endl; // 如果越界会崩溃
  • operator[]
    下标访问
cpp
std::string str("abcdefg");
char c = str[3]; // 'd'
std::cout << c << std::endl;
  • front()
    访问首字符
cpp
std::string str("abcdefg");
char c = str.front(); // 'a'
std::cout << c << std::endl;
  • back()
    访问最后有效字符
cpp
std::string str("abcdefg");
char c = str.back(); // 'g'
std::cout << c << std::endl;
  • data()
    返回指向字符串首字符的指针
cpp
std::string str("abcdefg");
const char *p = str.data();
std::cout << p << std::endl; // "abcdefg"
std::cout << std::hex << (void *)p << std::endl; // 地址
  • c_str()
    返回指向字符串首字符的指针,同 data()
cpp
std::string str("abcdefg");
const char *p = str.c_str();
std::cout << p << std::endl;

关键差异在于:c_str() 会在数据末尾自动添加一个终止符 \0,而 data() 不会!

1.4 容量相关函数

  • empty()
    检查字符串是否为空,返回布尔值
cpp
string str = "abcdefg";
cout << str.empty() << endl; // 0 (false)
  • size()/length()
    返回有效字符长度
cpp
string str = "abcdefg";
cout << str.size() << endl; // 7
cout << str.length() << endl; // 7
cout << sizeof(str) << endl; // 40 (平台相关)
  • max_size()
    返回std::string类型对象所能容纳的最大字符数
cpp
string str = "abcdefg";
cout << str.max_size() << endl; // 9223372036854775807 (理论最大值)
  • reserve(size_t size)
    预留字符串对象的内存空间
cpp
string str = "abcdefg";
str.reserve(100); // 预留至少100字符内存
  • capacity()
    返回当前已分配内存可容纳的最大字符数
cpp
string str = "abcdefg";
std::cout << "length: " << str.length() << std::endl; 
std::cout << "capacity: " << str.capacity() << std::endl;

1.5 字符串操作

  • clear()
    清除内容
cpp
std::string str = "Hello";
str.clear();
cout << "str = " << str << endl; // 空字符串
  • insert(size_t pos, 字符串)
    插入字符串
cpp
std::string str = "Hello";
str.insert(1, "abc"); // 在位置1插入
cout << "str = " << str << endl; // "Habcello"
  • find(字符串)
    查找子串,返回第一次出现的索引
cpp
std::string str = "abcdefg";
size_t pos = str.find("def");
if (pos != std::string::npos) {
    cout << "Found at position: " << pos << endl; // 3
}

2. 迭代器模式

2.1 设计模式概述

设计模式是一种在软件设计中常用的解决问题的方法或模板。它们描述了在特定情境下的问题和解决方案,并提供了一种被广泛接受的方法来设计和构建软件系统。

设计模式可以帮助开发人员解决常见的设计问题,并提供一种可重用的、经过验证的方法来构建高质量的软件系统。它们被视为一种优秀的实践,可以提高代码的可维护性、可扩展性和可重用性。

2.2 迭代器模式

在软件开发的过程中,集合内部的结构经常发生改变。对于这些集合对象,用户希望在可以不了解内部结构的同时,可以让外界透明的访问其中的元素,不管集合内部的数据结构如何变化,反正对于集合外部的访问接口都保持不变。

这种"让外界透明的访问"为同一种接口(begin/end)可以在多种集合对象上进行同一种操作。

使用面向对象的技术,将这种遍历机制抽象为"迭代器对象",可以描述、表示一个元素的位置为遍历"变化中的集合对象",提供一种不变的访问接口。

2.2.1 迭代器的定义

  • 基本类型(和指针的定义方法一致)
cpp
int * iter;
  • 容器类型(以string为例)
cpp
string::iterator iter;

2.2.2 迭代器常用函数

  • begin()
    返回值:容器(集合)的首元素的地址

    基本类型:

cpp
int arr[5] = { 1, 2, 3, 4, 5 };
auto iter = std::begin(arr);

容器类型:

cpp
std::string str("abcdefg");
auto iter = str.begin();
  • end()
    返回值:容器(集合)的最后一个元素的下一个地址

    基本类型:

cpp
int arr[5] = { 1, 2, 3, 4, 5 };
auto end_iter = std::end(arr);

容器类型:

cpp
std::string str("abcdefg");
auto end_iter = str.end();

2.2.3 使用迭代器遍历基本类型的数组

cpp
#include<iostream>
using namespace std;

int main()
{
    int arr[5] = { 1, 2, 3, 4, 5 };
    
    // 使用auto自动推断迭代器类型
    for (auto iter = std::begin(arr); iter != std::end(arr); iter++)
    {
        cout << *iter << endl;
    }
    
    // 传统指针遍历方式
    for (int* p = arr; p < arr + 5; p++)
    {
        cout << *p << endl;
    }
    
    return 0;
}

为什么使用迭代器而不使用指针?

  1. 抽象性和通用性:迭代器提供了一种通用的遍历方式,适用于各种容器
  2. 安全性和封装性:迭代器隐藏了具体容器的实现细节
  3. 可迭代性的概念:语法更清晰易读

为什么循环终止条件是 iter != std::end(arr)

因为end()函数返回最后一个有效数字地址的下一个地址("尾后"位置),使用半开区间(左闭右开)表示范围:

  • 包括起始位置
  • 不包括结束位置

2.2.4 使用迭代器遍历容器(字符串)

cpp
#include<iostream>
using namespace std;

int main()
{
    string str("abcdefg");
    
    // 普通迭代器(可修改元素)
    for (string::iterator iter = str.begin(); iter != str.end(); iter++)
    {
        cout << *iter << endl;
        // *iter = 'x'; // 可以修改元素
    }
    
    return 0;
}

常量迭代器

防止修改字符串内容:

cpp
#include<iostream>
using namespace std;

int main()
{
    string str("abcdefg");
    
    // 常量迭代器(不可修改元素)
    for (string::const_iterator iter = str.cbegin(); iter != str.cend(); iter++)
    {
        // *iter = 'q'; // 错误:不能修改
        cout << *iter << endl;
    }
    
    return 0;
}

反向迭代器

cpp
#include<iostream>
using namespace std;

int main()
{
    string str("abcdefg");
    
    // 反向迭代器(从后往前遍历)
    for (string::reverse_iterator iter = str.rbegin(); iter != str.rend(); iter++)
    {
        cout << *iter << endl; // 输出: g,f,e,d,c,b,a
    }
    
    // 常量反向迭代器
    for (string::const_reverse_iterator iter = str.crbegin(); iter != str.crend(); iter++)
    {
        cout << *iter << endl;
    }
    
    return 0;
}

2.3 使用迭代器操作字符串

  • erase() 删除字符串
    • 整数参数:删除该位置后面所有字符串
    • 迭代器参数:删除当前迭代器位置的字符
cpp
string str = "abc defg";
str.erase(5); // 删除位置5后的所有字符
cout << "str = " << str << endl; // "abc d"

// 使用迭代器删除空格
for (auto it = str.begin(); it != str.end();) {
    if (*it == ' ') {
        it = str.erase(it); // 删除空格,返回下一个位置的迭代器
    }
    else {
        ++it;
    }
}
cout << "str = " << str << endl; // "abcdefg"

知识如风,常伴吾身