#c

C++ String类

现代C++字符串处理利器

#c
@鞠大龙
魅客科创中心

CONTENTS

目录

1. String类基础

1.1 String类概述

  • string类的本质

    • C++标准库中的字符串类
    • 位于std命名空间
    • 自动管理内存,无需担心溢出
    • 提供丰富的字符串操作接口
  • 基本使用

    #include <string>
    using namespace std;
    
    string s1;              // 空字符串
    string s2 = "Hello";    // 用字符串字面量初始化
    string s3(5, 'a');     // "aaaaa"
    string s4 = s2;        // 拷贝构造
    

1.2 String类的特点

优点

  • 自动内存管理
  • 支持动态长度
  • 提供丰富的操作方法
  • 安全性高
  • 与STL完美配合

内存管理

string s = "Hello";
s += " World";     // 自动扩容
s.clear();         // 自动释放

与字符数组对比

// 字符数组
char str1[100];        // 固定长度
strcpy(str1, "Hello"); // 需要手动复制
strcat(str1, " World");// 可能溢出

// string类
string str2 = "Hello"; // 自动管理长度
str2 += " World";      // 安全的拼接

1.3 String类的初始化

// 常见的初始化方式
string s1;                    // 默认构造,空字符串
string s2 = "Hello";         // 字符串字面量
string s3("World");          // 构造函数形式
string s4(5, '*');           // 重复字符:"*****"
string s5 = s2;              // 拷贝构造
string s6 = s2 + " " + s3;   // 表达式初始化

// 从字符数组构造
char arr[] = "Hello";
string s7(arr);              // 从C风格字符串构造
string s8(arr, 3);          // 取前3个字符:"Hel"
string s9(arr + 1, arr + 4); // 取下标范围[1,4):"ell"

2. String类常用操作

2.1 访问与修改

string str = "Hello";

// 访问单个字符
char c1 = str[0];      // 使用[]操作符:'H'
char c2 = str.at(1);   // 使用at()方法:'e'

// 修改单个字符
str[0] = 'h';          // 使用[]修改
str.at(1) = 'E';       // 使用at()修改

// 获取长度
int len1 = str.length();    // 5
int len2 = str.size();      // 5(与length()相同)
bool empty = str.empty();   // false

// 修改整个字符串
str.assign("World");        // 替换整个字符串
str.clear();               // 清空字符串

2.2 拼接与插入

字符串拼接

string s1 = "Hello";
string s2 = "World";

// 使用+运算符
string s3 = s1 + " " + s2;

// 使用append方法
s1.append(" ");        // 追加字符串
s1.append(s2);        // 追加另一个string
s1.append(3, '!');    // 追加3个感叹号

// 使用+=运算符
s1 += " ";
s1 += s2;
s1 += '!';

字符串插入

string str = "Hello";

// 在指定位置插入
str.insert(5, " ");    // "Hello "
str.insert(6, "World");// "Hello World"

// 插入重复字符
str.insert(5, 3, '-'); // "Hello---World"

// 插入其他string对象
string s = "C++";
str.insert(6, s);      // "Hello-C++--World"

2.3 查找与替换

string str = "Hello World Hello";

// 查找子串
size_t pos1 = str.find("Hello");      // 返回0
size_t pos2 = str.find("Hello", 1);   // 返回12(从位置1开始找)
size_t pos3 = str.find("C++");        // 返回string::npos(未找到)

// 替换子串
str.replace(0, 5, "Hi");              // "Hi World Hello"
str.replace(str.find("Hello"), 5, "Bye"); // "Hi World Bye"

// 从右向左查找
size_t pos4 = str.rfind("Hello");     // 返回12

// 查找字符集合
size_t pos5 = str.find_first_of("aeiou"); // 返回第一个元音字母的位置
size_t pos6 = str.find_last_of("aeiou");  // 返回最后一个元音字母的位置

2.4 子串提取与比较

子串提取

string str = "Hello World";

// 使用substr提取子串
string sub1 = str.substr(6);    // "World"
string sub2 = str.substr(0, 5); // "Hello"
string sub3 = str.substr(6, 3); // "Wor"

// 常见应用
string filename = "test.cpp";
string name = filename.substr(0, 
    filename.find('.'));  // "test"
string ext = filename.substr(
    filename.find('.') + 1); // "cpp"

字符串比较

string s1 = "Hello";
string s2 = "World";

// 使用比较运算符
bool b1 = s1 == s2;    // false
bool b2 = s1 < s2;     // true
bool b3 = s1 >= s2;    // false

// 使用compare方法
int cmp1 = s1.compare(s2);    // <0
int cmp2 = s1.compare("Hello");// =0
int cmp3 = s1.compare(0, 2, 
                      "Help", 0, 2); // =0

3. String与字符数组的转换

3.1 转换方法

// 字符数组转string
char arr[] = "Hello World";
string s1(arr);                // 构造函数
string s2 = arr;              // 赋值运算符
string s3(arr, 5);            // 只取前5个字符
string s4(arr + 6, arr + 11); // 取下标范围[6,11)

// string转字符数组
string str = "Hello World";
const char* c1 = str.c_str();  // 返回C风格字符串
const char* c2 = str.data();   // 类似c_str()

// 注意:c_str()和data()返回的是临时指针
// 如果需要持久保存,应该复制到自己的缓冲区
char buffer[100];
strcpy(buffer, str.c_str());

3.2 注意事项

内存管理注意事项

string str = "Hello";
const char* ptr = str.c_str();

str += " World";  // str可能重新分配内存
// 危险!ptr可能已经失效
cout << ptr;      // 可能导致未定义行为

// 正确的做法
const char* new_ptr = str.c_str();
cout << new_ptr;  // 安全

生命周期注意事项

const char* getCharArray() {
    string str = "Hello";
    return str.c_str(); // 错误!str已销毁
}

// 正确的做法
string getString() {
    string str = "Hello";
    return str;  // 返回string对象
}

3.3 性能考虑

// 避免不必要的转换
void processString(const string& s) {
    // 直接使用string,无需转换
    cout << s.length() << endl;
    cout << s.substr(0, 5) << endl;
}

// 必要时的转换
void legacyFunction(const char* s) {
    // 需要配合C风格API时使用转换
    printf("%s\n", s);
}

string str = "Hello";
processString(str);           // 直接使用string
legacyFunction(str.c_str()); // 必要的转换

// 批量处理时的优化
string str = "Hello World";
const char* cstr = str.c_str();
// 多次使用cstr,但要确保str不会被修改
for(int i = 0; cstr[i]; i++) {
    // 处理cstr[i]
}

4. String类的高级功能

4.1 迭代器使用

string str = "Hello World";

// 使用正向迭代器
for(string::iterator it = str.begin(); it != str.end(); ++it) {
    *it = toupper(*it);  // 转换为大写
}

// 使用反向迭代器
for(string::reverse_iterator it = str.rbegin(); 
    it != str.rend(); ++it) {
    cout << *it;  // 反向输出
}

// 使用范围for循环(C++11)
for(char& c : str) {
    c = tolower(c);  // 转换为小写
}

// 常量迭代器
for(string::const_iterator it = str.cbegin(); 
    it != str.cend(); ++it) {
    cout << *it;  // 只读访问
}

4.2 算法库配合

常用算法

#include <algorithm>
string str = "Hello World";

// 排序
sort(str.begin(), str.end());

// 反转
reverse(str.begin(), str.end());

// 计数
int count = count_if(str.begin(), 
    str.end(), ::isalpha);

// 查找
auto it = find_if(str.begin(), 
    str.end(), ::isupper);

自定义操作

// 转换函数
bool isVowel(char c) {
    c = tolower(c);
    return c=='a' || c=='e' || c=='i' 
        || c=='o' || c=='u';
}

// 使用自定义函数
int vowels = count_if(str.begin(), 
    str.end(), isVowel);

// 使用lambda表达式(C++11)
auto digits = count_if(str.begin(), 
    str.end(), 
    [](char c){ return isdigit(c); });

4.3 字符串流

#include <sstream>

// 字符串转数字
string str = "123 45.67";
stringstream ss(str);
int n;
double d;
ss >> n >> d;  // n=123, d=45.67

// 数字转字符串
stringstream ss2;
ss2 << 456 << ' ' << 78.9;
string result = ss2.str();  // "456 78.9"

// 字符串分割
string text = "Hello,World,C++";
stringstream ss3(text);
string token;
while(getline(ss3, token, ',')) {
    cout << token << endl;
}

// 格式化字符串
stringstream ss4;
ss4 << "Score: " << 95 << "%";
string formatted = ss4.str();  // "Score: 95%"

// 清空并重用
ss4.str("");
ss4.clear();
ss4 << "New content";

5. 实战应用

5.1 文本处理

// 单词计数
string text = "Hello World Hello C++";
stringstream ss(text);
string word;
map<string, int> wordCount;

while(ss >> word) {
    wordCount[word]++;
}

// 输出每个单词的出现次数
for(const auto& pair : wordCount) {
    cout << pair.first << ": " << pair.second << endl;
}

// 文本格式化
string formatText(const string& text) {
    string result;
    bool lastWasSpace = true;  // 用于处理开头的空格
    
    for(char c : text) {
        if(isspace(c)) {
            if(!lastWasSpace) {
                result += ' ';
                lastWasSpace = true;
            }
        } else {
            result += c;
            lastWasSpace = false;
        }
    }
    
    // 移除末尾空格
    if(!result.empty() && result.back() == ' ') {
        result.pop_back();
    }
    
    return result;
}

5.2 模式匹配

简单模式匹配

// 查找所有匹配位置
vector<int> findAll(const string& text, 
                    const string& pattern) {
    vector<int> positions;
    size_t pos = 0;
    
    while((pos = text.find(pattern, pos)) 
           != string::npos) {
        positions.push_back(pos);
        pos++;
    }
    
    return positions;
}

通配符匹配

// 支持?和*的简单通配符匹配
bool wildcardMatch(const string& text, 
                  const string& pattern) {
    int i = 0, j = 0;
    int starIdx = -1, iIdx = -1;
    
    while(i < text.length()) {
        if(j < pattern.length() && 
           (pattern[j] == '?' || 
            pattern[j] == text[i])) {
            i++; j++;
        } else if(j < pattern.length() && 
                 pattern[j] == '*') {
            starIdx = j;
            iIdx = i;
            j++;
        } else if(starIdx != -1) {
            j = starIdx + 1;
            i = ++iIdx;
        } else {
            return false;
        }
    }
    
    while(j < pattern.length() && 
          pattern[j] == '*') {
        j++;
    }
    
    return j == pattern.length();
}

5.3 字符串变换

// 大小写转换
string convertCase(const string& str, bool toUpper) {
    string result = str;
    for(char& c : result) {
        if(toUpper) {
            c = toupper(c);
        } else {
            c = tolower(c);
        }
    }
    return result;
}

// 驼峰命名转换
string toCamelCase(const string& str) {
    string result;
    bool nextUpper = false;
    
    for(char c : str) {
        if(c == '_' || c == ' ') {
            nextUpper = true;
        } else {
            if(nextUpper) {
                result += toupper(c);
                nextUpper = false;
            } else {
                result += tolower(c);
            }
        }
    }
    
    return result;
}

// URL编码
string urlEncode(const string& str) {
    string result;
    for(char c : str) {
        if(isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            result += c;
        } else {
            result += '%';
            char hex[3];
            sprintf(hex, "%02X", (unsigned char)c);
            result += hex;
        }
    }
    return result;
}

6. 实例分析

6.1 单词统计问题(HDU 1062)

问题描述
给定一行文本,将每个单词反转后输出,单词之间的空格保持不变。

示例

  • 输入:"Hello World"
  • 输出:"olleH dlroW"
// HDU 1062 Text Reverse
void solveTextReverse() {
    string line;
    while(getline(cin, line)) {
        stringstream ss(line);
        string word;
        bool first = true;
        
        while(ss >> word) {
            if(!first) cout << " ";
            reverse(word.begin(), word.end());
            cout << word;
            first = false;
        }
        cout << endl;
    }
}

6.2 字符串匹配问题(POJ 3080)

问题描述
给定m个长度为60的DNA序列,找出这些序列中最长的公共子串。如果有多个答案,输出字典序最小的那个。

// POJ 3080 Blue Jeans
string findLongestCommonSubstring(vector<string>& dna) {
    if(dna.empty()) return "";
    
    string result;
    const string& first = dna[0];
    
    // 枚举所有可能的子串长度
    for(int len = 60; len >= 3; len--) {
        // 枚举first中所有长度为len的子串
        for(int i = 0; i + len <= first.length(); i++) {
            string sub = first.substr(i, len);
            bool found = true;
            
            // 检查是否在其他序列中都能找到
            for(int j = 1; j < dna.size(); j++) {
                if(dna[j].find(sub) == string::npos) {
                    found = false;
                    break;
                }
            }
            
            if(found) {
                if(result.empty() || sub < result) {
                    result = sub;
                }
            }
        }
        
        if(!result.empty()) break;
    }
    
    return result;
}

6.3 文本转换问题(CSP-J 2019)

问题描述
给定一个加密规则和明文,将明文转换为密文。加密规则是将每个字母替换为另一个字母,保持大小写不变。

// CSP-J 2019 字符串加密
string encrypt(const string& text, const string& rule) {
    string result = text;
    
    // 创建映射表
    map<char, char> upperMap, lowerMap;
    for(int i = 0; i < 26; i++) {
        char original = 'A' + i;
        char encrypted = toupper(rule[i]);
        upperMap[original] = encrypted;
        lowerMap[tolower(original)] = tolower(encrypted);
    }
    
    // 进行转换
    for(char& c : result) {
        if(isupper(c)) {
            c = upperMap[c];
        } else if(islower(c)) {
            c = lowerMap[c];
        }
    }
    
    return result;
}

// 验证规则是否合法
bool isValidRule(const string& rule) {
    if(rule.length() != 26) return false;
    
    set<char> used;
    for(char c : rule) {
        if(!isalpha(c)) return false;
        char upper = toupper(c);
        if(used.count(upper)) return false;
        used.insert(upper);
    }
    
    return true;
}

7. 练习题推荐

7.1 基础题目

1. HDU 1062 - Text Reverse (★☆☆☆☆)

  • 字符串分割和反转
  • 考察基本字符串操作
// 核心思路
string word;
while(ss >> word) {
    reverse(word.begin(), word.end());
    cout << word << " ";
}

2. POJ 1159 - Palindrome (★★☆☆☆)

  • 判断回文串
  • 字符串比较和处理
// 判断回文
bool isPalindrome(const string& s) {
    int i = 0, j = s.length() - 1;
    while(i < j) {
        if(s[i++] != s[j--]) return false;
    }
    return true;
}

7.2 进阶题目

1. POJ 3080 - Blue Jeans (★★★☆☆)

  • 最长公共子串问题
  • 字符串匹配和比较
// 核心思路
for(int len = maxLen; len >= 1; len--) {
    for(int i = 0; i + len <= s.length(); i++) {
        string sub = s.substr(i, len);
        if(isCommonSubstring(sub, strings)) {
            return sub;
        }
    }
}

2. CSP-J 2019 - 字符串加密 (★★★☆☆)

  • 字符映射转换
  • 大小写处理
// 核心思路
map<char, char> cipher;
for(int i = 0; i < 26; i++) {
    cipher['A' + i] = rule[i];
    cipher['a' + i] = tolower(rule[i]);
}

7.3 竞赛真题

  1. USACO 2019 Dec Bronze

    • Word Processor
    • 文本格式化处理
    • 考察字符串基本操作
  2. CSP-J 2022

    • 字符串匹配
    • 模式识别
    • 考察字符串查找和处理
  3. POJ 3461

    • Oulipo
    • 字符串匹配问题
    • 考察高效的字符串搜索算法

8. 总结

8.1 核心知识点

基础操作

  • 字符串初始化和构造
  • 访问和修改
  • 拼接和插入
  • 查找和替换
  • 子串提取

高级功能

  • 迭代器使用
  • 算法库配合
  • 字符串流操作
  • 与字符数组转换

常见错误

  1. 忘记处理string::npos
  2. 未检查下标越界
  3. 不当使用c_str()返回值
  4. 字符串拼接效率低下
  5. 未考虑大小写敏感性

最佳实践

  1. 优先使用string类
  2. 合理使用引用传参
  3. 注意性能优化
  4. 善用STL算法
  5. 处理好边界情况

8.2 进阶建议

  1. 深入学习

    • 了解string的内部实现
    • 学习更多STL算法
    • 掌握正则表达式
    • 研究字符串匹配算法
  2. 实践应用

    • 多做习题
    • 实现常用功能
    • 注重代码质量
    • 关注性能优化
  3. 扩展知识

    • Unicode处理
    • 国际化支持
    • 模式匹配
    • 文本处理库

#c

C++ String类

#c

感谢学习!
如有疑问,请联系:
judal@xmaker.org
魅客科创中心

这个讲义使用了Awesome Marp主题的蓝色风格。 内容针对已经学习过字符数组的学生,介绍C++ string类的使用。