引言
字符串處理是編程中最常見的任務之一,而在C++中,我們有多種處理字符串的方式。本文將詳細介紹C++中的字符串操作,包括C風格字符串和C++的string類。無論你是C++新手還是想鞏固基礎的老手,這篇文章都能幫你梳理字符串處理的關鍵知識點。
?目錄
1. [C風格字符串](#c風格字符串)
2. [C++ string類基礎](#c-string類基礎)
3. [string類的常用操作](#string類的常用操作)
4. [string類的內存管理](#string類的內存管理)
5. [字符串操作性能優化](#字符串操作性能優化)
6. [實用案例分析](#實用案例分析)
1.C風格字符串
基本概念
C風格字符串本質上是以空字符(`\0`)結尾的字符數組。在C++中,我們仍然可以使用這種方式:
char str[] = "Hello"; ?// 相當于 {'H', 'e', 'l', 'l', 'o', '\0'}char* p = "World"; ? ? // 字符串字面量,p指向常量區
需要注意的是,在新的C++標準中,建議使用`const char*`來表示字符串字面量,因為它們不應被修改:
const char* p = "World"; ?// 更安全的做法
?C風格字符串在內存中的表示
為了便于理解,我們可以看看C風格字符串在內存中的表示:
字符串: "Hello"
內存表示:
其中`\0`是ASCII值為0的空字符,標志字符串的結束。這就是為什么C風格字符串操作函數需要遍歷整個字符串來確定長度 - 它們必須找到這個結束符。
常用函數實際應用
字符串長度計算 (strlen)
#include <cstring>#include <iostream>int main() {char greeting[] = "Hello, C++ programmer!";size_t length = strlen(greeting);std::cout << "字符串: \"" << greeting << "\"" << std::endl;std::cout << "長度: " << length << " 字符" << std::endl;std::cout << "數組大小: " << sizeof(greeting) << " 字節" << std::endl;// 輸出:// 字符串: "Hello, C++ programmer!"// 長度: 22 字符// 數組大小: 23 字節 (包括結尾的'\0')return 0;}
字符串復制 (strcpy vs strncpy)
#include <cstring>#include <iostream>int main() {char source[] = "Source string";char dest1[20]; ?// 足夠大的目標數組char dest2[5]; ? // 故意設置小一些的數組// 安全的復制 (目標足夠大)strcpy(dest1, source);std::cout << "strcpy 結果: " << dest1 << std::endl;// 不安全的復制 (可能導致緩沖區溢出)// strcpy(dest2, source); ?// 危險!會導致未定義行為// 安全的有限復制strncpy(dest2, source, 4);dest2[4] = '\0'; ?// 手動添加字符串結束符std::cout << "strncpy 結果: " << dest2 << std::endl;// 輸出:// strcpy 結果: Source string// strncpy 結果: Sourreturn 0;}
字符串拼接 (strcat)
#include <cstring>#include <iostream>int main() {char result[50] = "Hello"; ?// 初始字符串// 第一次拼接strcat(result, ", ");std::cout << "拼接后: " << result << std::endl;// 第二次拼接strcat(result, "World");std::cout << "拼接后: " << result << std::endl;// 使用strncat限制拼接長度strncat(result, "! This is a very long string", 1);std::cout << "限制拼接后: " << result << std::endl;// 輸出:// 拼接后: Hello,// 拼接后: Hello, World// 限制拼接后: Hello, World!return 0;}
字符串比較 (strcmp)
#include <cstring>#include <iostream>int main() {char str1[] = "apple";char str2[] = "banana";char str3[] = "apple";int result1 = strcmp(str1, str2);int result2 = strcmp(str1, str3);int result3 = strcmp(str2, str1);std::cout << "比較 \"" << str1 << "\" 和 \"" << str2 << "\": ";if (result1 < 0) std::cout << "str1 < str2" << std::endl;else if (result1 > 0) std::cout << "str1 > str2" << std::endl;else std::cout << "str1 == str2" << std::endl;std::cout << "比較 \"" << str1 << "\" 和 \"" << str3 << "\": ";if (result2 == 0) std::cout << "相等" << std::endl;std::cout << "比較 \"" << str2 << "\" 和 \"" << str1 << "\": ";if (result3 > 0) std::cout << "str2 > str1" << std::endl;// 部分比較int partial = strncmp(str1, str2, 1); ?// 只比較第一個字符std::cout << "只比較第一個字符: ";if (partial < 0) std::cout << "'a' < 'b'" << std::endl;// 輸出:// 比較 "apple" 和 "banana": str1 < str2// 比較 "apple" 和 "apple": 相等// 比較 "banana" 和 "apple": str2 > str1// 只比較第一個字符: 'a' < 'b'return 0;}
常用函數
C風格字符串操作主要依賴`<cstring>`頭文件中的函數:
函數 | 功能 | 示例 |
---|---|---|
strlen(s) | 獲取字符串長度(不包括結尾的\0 ) | size_t len = strlen(str); |
strcpy(dest, src) | 復制字符串 | strcpy(dest, src); |
strncpy(dest, src, n) | 復制指定長度的字符串 | strncpy(dest, src, 10); |
strcat(dest, src) | 字符串拼接 | strcat(dest, src); |
strncat(dest, src, n) | 拼接指定長度的字符串 | strncat(dest, src, 5); |
strcmp(s1, s2) | 字符串比較 | if (strcmp(s1, s2) == 0) {...} |
strncmp(s1, s2, n) | 比較指定長度的字符串 | if (strncmp(s1, s2, 3) == 0) {...} |
strchr(s, c) | 查找字符首次出現位置 | char* pos = strchr(str, 'a'); |
strrchr(s, c) | 查找字符最后出現位置 | char* pos = strrchr(str, 'a'); |
strstr(s1, s2) | 查找子串 | char* pos = strstr(str, "sub"); |
注意事項
1. **內存溢出風險**:
C風格字符串操作不會自動檢查邊界,容易導致緩沖區溢出。
?
? ?char small[5];strcpy(small, "This is too long"); ?// 危險!會導致緩沖區溢出
? ?**安全的替代方案**:
?
?char small[5];strncpy(small, "This is too long", 4);small[4] = '\0'; ?// 確保添加結束符
2. **內存管理責任**:
使用C風格字符串時,程序員需要自己管理內存分配和釋放。
?
? ?char* dynamicStr = new char[100];strcpy(dynamicStr, "Hello");// 使用完畢后delete[] dynamicStr; ?// 必須釋放內存
3. **潛在的內存泄漏**:
?
?char* createGreeting(const char* name) {char* greeting = new char[strlen(name) + 10];strcpy(greeting, "Hello, ");strcat(greeting, name);return greeting;}// 使用char* msg = createGreeting("John");std::cout << msg << std::endl;// 如果忘記這一步,會導致內存泄漏delete[] msg;
4. **常見Bug示例**:
?
?// Bug 1: 沒有考慮空終止符char dest[5];char source[] = "Hello";strncpy(dest, source, 5); ?// 復制5個字符,但沒有空間給'\0'// 解決方法:strncpy(dest, source, 4);dest[4] = '\0';// Bug 2: 字符串常量修改char* str = "Hello";str[0] = 'h'; ?// 錯誤!嘗試修改只讀內存// 解決方法:char str[] = "Hello"; ?// 創建可修改的副本str[0] = 'h'; ?// 正確
2.C++ string類基礎
頭文件與命名空間
#include <string>using namespace std; ?// 或使用std::string
創建和初始化?
#include <string>#include <iostream>int main() {// 1. 默認構造函數 - 空字符串std::string s1;std::cout << "s1 (空字符串): [" << s1 << "], 長度: " << s1.length() << std::endl;// 2. 從C風格字符串初始化std::string s2 = "Hello";std::cout << "s2: " << s2 << std::endl;// 3. 構造函數初始化std::string s3("World");std::cout << "s3: " << s3 << std::endl;// 4. 使用多個相同字符初始化std::string s4(5, 'a');std::cout << "s4 (5個'a'): " << s4 << std::endl;// 5. 拷貝初始化std::string s5 = s2;std::cout << "s5 (復制s2): " << s5 << std::endl;// 6. 子串初始化std::string s6(s2, 1, 3); ?// 從s2的位置1開始,3個字符std::cout << "s6 (s2的子串): " << s6 << std::endl;// 7. 移動構造 (C++11)std::string s7 = std::move(s2);std::cout << "s7 (移動自s2): " << s7 << std::endl;std::cout << "s2 (移動后): [" << s2 << "]" << std::endl; ?// s2可能為空或未定義狀態// 8. 初始化列表 (C++11)std::string s8 = {'H', 'e', 'l', 'l', 'o'};std::cout << "s8 (初始化列表): " << s8 << std::endl;// 輸出:// s1 (空字符串): [], 長度: 0// s2: Hello// s3: World// s4 (5個'a'): aaaaa// s5 (復制s2): Hello// s6 (s2的子串): ell// s7 (移動自s2): Hello// s2 (移動后): []// s8 (初始化列表): Helloreturn 0;}
string與char*的轉換?
#include <string>#include <iostream>#include <cstring>int main() {// 1. string轉為C風格字符串std::string cpp_str = "Hello, C++ world!";// 使用c_str()獲取C風格字符串 (const char*)const char* c_str1 = cpp_str.c_str();std::cout << "使用c_str(): " << c_str1 << std::endl;// 使用data()獲取底層數據const char* c_str2 = cpp_str.data();std::cout << "使用data(): " << c_str2 << std::endl;// 注意:c_str()和data()返回的指針在string被修改時可能失效cpp_str += " Modified";std::cout << "修改后的cpp_str: " << cpp_str << std::endl;std::cout << "原c_str1可能已失效!" << std::endl;// 如果需要持久保存,應該復制數據char* persistent = new char[cpp_str.length() + 1];strcpy(persistent, cpp_str.c_str());std::cout << "持久復制: " << persistent << std::endl;// 2. C風格字符串轉為stringconst char* name = "John Doe";std::string cpp_name(name);std::cout << "C風格轉string: " << cpp_name << std::endl;// 部分轉換std::string partial(name, 4); ?// 只取前4個字符std::cout << "部分轉換 (前4個字符): " << partial << std::endl;// 從指定位置std::string lastname(name + 5); ?// 跳過"John "std::cout << "從第5個字符開始: " << lastname << std::endl;// 清理delete[] persistent;// 輸出:// 使用c_str(): Hello, C++ world!// 使用data(): Hello, C++ world!// 修改后的cpp_str: Hello, C++ world! Modified// 原c_str1可能已失效!// 持久復制: Hello, C++ world! Modified// C風格轉string: John Doe// 部分轉換 (前4個字符): John// 從第5個字符開始: Doereturn 0;}
創建和初始化
string s1; ? ? ? ? ? ? ? ?// 空字符串string s2 = "Hello"; ? ? ?// 從C風格字符串初始化string s3("World"); ? ? ? // 構造函數初始化string s4(5, 'a'); ? ? ? ?// 創建含有5個'a'的字符串:"aaaaa"string s5 = s2; ? ? ? ? ? // 拷貝初始化string s6(s2, 1, 3); ? ? ?// 從s2的位置1開始,拷貝3個字符:"ell"
string與char*的轉換
// string轉為C風格字符串string s = "Hello";const char* cstr = s.c_str(); ?// 獲取C風格字符串,不能修改const char* data = s.data(); ? // 類似c_str(),但在C++11之前可能不包含'\0'// C風格字符串轉為stringchar* cstr = "World";string s(cstr);
3.string類的常用操作
訪問與修改?
#include <string>#include <iostream>#include <stdexcept>int main() {std::string s = "Hello";// 訪問單個字符 - 使用下標操作符char first = s[0]; ?// 'H'char last = s[4]; ? // 'o'std::cout << "首字母: " << first << std::endl;std::cout << "末字母: " << last << std::endl;// 使用at()訪問 - 帶邊界檢查try {char safe = s.at(1); ?// 'e'std::cout << "安全訪問位置1: " << safe << std::endl;// 下面這行會拋出std::out_of_range異常char error = s.at(10);}catch (const std::out_of_range& e) {std::cout << "捕獲到邊界檢查異常: " << e.what() << std::endl;}// 修改字符s[0] = 'h'; ? ? ? ? ? ?// 修改第一個字符std::cout << "修改后: " << s << std::endl;s.at(4) = 'O'; ? ? ? ? // 安全地修改最后一個字符std::cout << "再次修改: " << s << std::endl;// 直接訪問首尾字符char front_char = s.front(); ?// C++11, 等價于s[0]char back_char = s.back(); ? ?// C++11, 等價于s[s.length()-1]std::cout << "首字符: " << front_char << std::endl;std::cout << "尾字符: " << back_char << std::endl;// 輸出:// 首字母: H// 末字母: o// 安全訪問位置1: e// 捕獲到邊界檢查異常: invalid string position// 修改后: hello// 再次修改: hellO// 首字符: h// 尾字符: Oreturn 0;}
訪問與修改
string s = "Hello";char first = s[0]; ? ? ? ?// 使用下標訪問:'H'char last = s.at(4); ? ? ?// 使用at()方法(帶邊界檢查):'o's[0] = 'h'; ? ? ? ? ? ? ? // 修改為:"hello"s.at(4) = 'O'; ? ? ? ? ? ?// 修改為:"hellO"
拼接操作?
#include <string>#include <iostream>int main() {// 初始字符串std::string s1 = "Hello";std::string s2 = "World";// 1. 使用+運算符拼接std::string s3 = s1 + " " + s2;std::cout << "s1 + ' ' + s2 = " << s3 << std::endl;// 2. 混合拼接字符串和C風格字符串std::string s4 = s1 + " beautiful " + s2 + "!";std::cout << "混合拼接: " << s4 << std::endl;// 3. 使用+=運算符std::string s5 = "Hi";s5 += ", ";s5 += s2;s5 += "!";std::cout << "使用+=: " << s5 << std::endl;// 4. append方法std::string s6 = "Welcome";s6.append(" to ");s6.append(s2);std::cout << "使用append: " << s6 << std::endl;// 5. append部分字符串std::string s7 = "C++";s7.append(" Programming", 0, 7); ?// 只添加" Progra"std::cout << "部分append: " << s7 << std::endl;// 6. 拼接性能比較 - 推薦用法std::string efficient;efficient.reserve(50); ?// 預留足夠空間efficient += "Efficient ";efficient += "string ";efficient += "concatenation";std::cout << "高效拼接: " << efficient << std::endl;// 輸出:// s1 + ' ' + s2 = Hello World// 混合拼接: Hello beautiful World!// 使用+=: Hi, World!// 使用append: Welcome to World// 部分append: C++ Progra// 高效拼接: Efficient string concatenationreturn 0;}
拼接操作
string s1 = "Hello";string s2 = "World";string s3 = s1 + " " + s2; ?// "Hello World"s1 += " " + s2; ? ? ? ? ? ? // s1變為:"Hello World"
子串與插入
#include <string>#include <iostream>int main() {std::string original = "Hello World! How are you?";// 1. 獲取子串std::string sub1 = original.substr(6, 5); ?// 從位置6開始,長度為5std::cout << "substring(6, 5): " << sub1 << std::endl;// 2. 獲取到末尾的子串std::string sub2 = original.substr(13); ?// 從位置13到結尾std::cout << "substring(13): " << sub2 << std::endl;// 3. 嘗試獲取超出范圍的子串try {std::string sub_error = original.substr(100, 5);}catch (const std::out_of_range& e) {std::cout << "子串越界: " << e.what() << std::endl;}// 4. 基本插入操作std::string s = "Hello World";s.insert(5, " Beautiful");std::cout << "插入字符串: " << s << std::endl;// 5. 在指定位置插入字符s.insert(s.length(), '!');std::cout << "插入字符: " << s << std::endl;// 6. 在指定位置插入多個相同字符s.insert(0, 3, '*');std::cout << "插入3個星號: " << s << std::endl;// 7. 插入C風格字符串的一部分std::string target = "Example";const char* source = "INSERTION";target.insert(2, source, 5); ?// 在位置2插入source的前5個字符std::cout << "插入C字符串的一部分: " << target << std::endl;// 8. 使用迭代器插入std::string iter_example = "135";auto it = iter_example.begin() + 1; ?// 指向'3'前面的位置iter_example.insert(it, '2');it = iter_example.begin() + 3; ?// 現在指向'5'前面的位置iter_example.insert(it, '4');std::cout << "使用迭代器插入: " << iter_example << std::endl;// 輸出:// substring(6, 5): World// substring(13): How are you?// 子串越界: basic_string::substr: __pos (which is 100) > this->size() (which is 24)// 插入字符串: Hello Beautiful World// 插入字符: Hello Beautiful World!// 插入3個星號: ***Hello Beautiful World!// 插入C字符串的一部分: ExINSERample// 使用迭代器插入: 12345return 0;}
子串與插入
string s = "Hello World";string sub = s.substr(6, 5); ?// 從位置6開始,長度為5的子串:"World"s.insert(5, " Beautiful"); ? ?// 在位置5插入:Hello Beautiful World
刪除操作?
#include <string>#include <iostream>int main() {// 基本刪除操作std::string s1 = "Hello World";std::string original = s1;// 1. 刪除指定位置的指定數量字符s1.erase(5, 1); ?// 刪除位置5的1個字符(空格)std::cout << "刪除空格: [" << s1 << "]" << std::endl;// 2. 刪除指定位置到末尾的所有字符s1 = original;s1.erase(5); ?// 刪除位置5及之后的所有字符std::cout << "只保留Hello: [" << s1 << "]" << std::endl;// 3. 使用迭代器刪除單個字符s1 = original;auto it = s1.begin() + 5; ?// 指向空格s1.erase(it);std::cout << "使用迭代器刪除空格: [" << s1 << "]" << std::endl;// 4. 使用迭代器范圍刪除多個字符s1 = original;auto start = s1.begin() + 5; ?// 指向空格auto end = s1.begin() + 11; ? // 指向末尾后一個位置s1.erase(start, end);std::cout << "刪除范圍: [" << s1 << "]" << std::endl;// 5. 清空字符串s1.clear();std::cout << "清空后長度: " << s1.length()<< ", 空字符串: [" << s1 << "]" << std::endl;// 6. 刪除特定字符 (如空白)std::string text = " ?Remove ? extra ?spaces ?";// 去除前導空白size_t start_pos = text.find_first_not_of(" \t\n\r");if (start_pos != std::string::npos) {text.erase(0, start_pos);}std::cout << "去除前導空白: [" << text << "]" << std::endl;// 去除尾部空白size_t end_pos = text.find_last_not_of(" \t\n\r");if (end_pos != std::string::npos) {text.erase(end_pos + 1);}std::cout << "去除尾部空白: [" << text << "]" << std::endl;// 輸出:// 刪除空格: [HelloWorld]// 只保留Hello: [Hello]// 使用迭代器刪除空格: [HelloWorld]// 刪除范圍: [Hello]// 清空后長度: 0, 空字符串: []// 去除前導空白: [Remove ? extra ?spaces ?]// 去除尾部空白: [Remove ? extra ?spaces]return 0;}
刪除操作
string s = "Hello World";s.erase(5, 1); ? ? ? ? ? ? ?// 刪除位置5的空格:HelloWorlds.erase(5); ? ? ? ? ? ? ? ? // 刪除位置5及之后的所有字符:Hello
查找操作
#include <string>#include <iostream>#include <iomanip> ?// 用于格式化輸出// 輔助函數:顯示查找結果void showPosition(const std::string& str, size_t pos) {if (pos == std::string::npos) {std::cout << "未找到" << std::endl;return;}std::cout << "位置: " << pos << std::endl;// 顯示位置示意圖std::cout << str << std::endl;std::cout << std::string(pos, ' ') << "^" << std::endl;}int main() {std::string haystack = "Hello World! Welcome to the C++ programming world!";// 1. 基本查找 - 查找子串std::cout << "在字符串中查找 'World':" << std::endl;size_t pos1 = haystack.find("World");showPosition(haystack, pos1);// 2. 查找單個字符std::cout << "\n查找字符 'o':" << std::endl;size_t pos2 = haystack.find('o');showPosition(haystack, pos2);// 3. 從指定位置開始查找std::cout << "\n從位置8開始查找 'o':" << std::endl;size_t pos3 = haystack.find('o', 8);showPosition(haystack, pos3);// 4. 查找不存在的字符串std::cout << "\n查找不存在的字符串 'Python':" << std::endl;size_t pos4 = haystack.find("Python");if (pos4 == std::string::npos) {std::cout << "未找到 'Python'" << std::endl;}// 5. 從后向前查找 (rfind)std::cout << "\n從后向前查找 'o':" << std::endl;size_t pos5 = haystack.rfind('o');showPosition(haystack, pos5);// 6. 查找任意一個字符首次出現 (find_first_of)std::cout << "\n查找'aeiou'中任意一個字符首次出現:" << std::endl;size_t pos6 = haystack.find_first_of("aeiou");showPosition(haystack, pos6);// 7. 查找不在指定字符集中的字符 (find_first_not_of)std::cout << "\n查找首個不是字母或空格的字符:" << std::endl;size_t pos7 = haystack.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ");showPosition(haystack, pos7);// 8. 查找指定字符集中的字符最后一次出現 (find_last_of)std::cout << "\n查找'aeiou'中任意一個字符最后一次出現:" << std::endl;size_t pos8 = haystack.find_last_of("aeiou");showPosition(haystack, pos8);// 9. 使用find_first_of實現單詞分割std::string sentence = "Breaking,this,into,words";std::cout << "\n分割字符串 '" << sentence << "':" << std::endl;size_t start = 0;size_t end = sentence.find_first_of(",");while (end != std::string::npos) {std::cout << " - " << sentence.substr(start, end - start) << std::endl;start = end + 1;end = sentence.find_first_of(",", start);}std::cout << " - " << sentence.substr(start) << std::endl;// 輸出類似:// 在字符串中查找 'World':// 位置: 6// Hello World! Welcome to the C++ programming world!// ? ? ? ^// ... (其他輸出)return 0;}
查找操作
string s = "Hello World";size_t pos = s.find("World"); ? ? // 查找子串,返回首次出現的位置:6pos = s.find('o'); ? ? ? ? ? ? ? ?// 查找字符:4// 如果未找到,返回string::nposif (s.find("Bye") == string::npos) {cout << "未找到子串" << endl;}// 從指定位置開始查找pos = s.find('o', 5); ? ? ? ? ? ? // 從位置5開始查找'o':7// 反向查找pos = s.rfind('o'); ? ? ? ? ? ? ? // 從后向前查找'o',返回最后一次出現的位置:7
替換操作?
#include <string>#include <iostream>int main() {// 基本替換操作std::string s = "Hello World";std::string original = s;// 1. 替換指定位置和長度s.replace(6, 5, "C++");std::cout << "基本替換: " << s << std::endl;// 2. 替換為重復字符s = original;s.replace(6, 5, 3, '*'); ?// 用3個*替換"World"std::cout << "替換為重復字符: " << s << std::endl;// 3. 使用C風格字符串替換s = original;const char* replacement = "Universe";s.replace(6, 5, replacement);std::cout << "使用C字符串替換: " << s << std::endl;// 4. 使用部分C風格字符串替換s = original;s.replace(6, 5, replacement, 4); ?// 只使用"Univ"std::cout << "部分C字符串替換: " << s << std::endl;// 5. 使用另一個string對象的部分內容替換s = original;std::string other = "Beautiful Planet";s.replace(6, 5, other, 0, 9); ?// 使用"Beautiful"std::cout << "部分string替換: " << s << std::endl;// 6. 使用迭代器范圍替換s = original;std::string iter_repl = "C++ Language";s.replace(s.begin() + 6, s.end(), iter_repl.begin(), iter_repl.begin() + 3);std::cout << "迭代器范圍替換: " << s << std::endl;// 7. 全局替換所有出現的子串std::string text = "The cat sat on the mat with another cat";std::string from = "cat";std::string to = "dog";size_t pos = 0;while ((pos = text.find(from, pos)) != std::string::npos) {text.replace(pos, from.length(), to);pos += to.length(); ?// 跳過剛剛替換的內容}std::cout << "全局替換: " << text << std::endl;// 輸出:// 基本替換: Hello C++// 替換為重復字符: Hello ***// 使用C字符串替換: Hello Universe// 部分C字符串替換: Hello Univ// 部分string替換: Hello Beautiful// 迭代器范圍替換: Hello C++// 全局替換: The dog sat on the mat with another dogreturn 0;}
替換操作
string s = "Hello World";s.replace(6, 5, "C++"); ? ? ?// 替換位置6開始的5個字符:Hello C++
比較操作
#include <string>#include <iostream>#include <iomanip> ?// 用于格式化輸出// 輔助函數:顯示比較結果void showCompareResult(const std::string& s1, const std::string& s2, int result) {std::cout << "比較 \"" << s1 << "\" 和 \"" << s2 << "\": ";if (result < 0)std::cout << "s1 < s2" << std::endl;else if (result > 0)std::cout << "s1 > s2" << std::endl;elsestd::cout << "s1 == s2" << std::endl;}int main() {// 準備測試字符串std::string s1 = "apple";std::string s2 = "banana";std::string s3 = "apple";std::string s4 = "applesauce";std::string s5 = "APPLE";// 1. 使用比較運算符std::cout << "使用比較運算符:" << std::endl;std::cout << std::boolalpha; ?// 使bool值顯示為true/false而非1/0std::cout << "s1 == s3: " << (s1 == s3) << std::endl; ?// truestd::cout << "s1 != s2: " << (s1 != s2) << std::endl; ?// truestd::cout << "s1 < s2: ?" << (s1 < s2) << std::endl; ??// truestd::cout << "s2 > s1: ?" << (s2 > s1) << std::endl; ??// truestd::cout << "s1 <= s3: " << (s1 <= s3) << std::endl; ?// truestd::cout << "s1 >= s3: " << (s1 >= s3) << std::endl; ?// true// 2. 使用compare方法std::cout << "\n使用compare方法:" << std::endl;int result1 = s1.compare(s2);showCompareResult(s1, s2, result1);int result2 = s1.compare(s3);showCompareResult(s1, s3, result2);int result3 = s2.compare(s1);showCompareResult(s2, s1, result3);// 3. 比較部分字符串std::cout << "\n比較部分字符串:" << std::endl;// 比較s1從位置0開始的4個字符與s4從位置0開始的4個字符int result4 = s1.compare(0, 4, s4, 0, 4);std::cout << "比較 \"" << s1.substr(0, 4) << "\" 和 \"" << s4.substr(0, 4) << "\": "<< (result4 == 0 ? "相等" : "不相等") << std::endl;// 4. 大小寫敏感比較std::cout << "\n大小寫敏感比較:" << std::endl;int result5 = s1.compare(s5);showCompareResult(s1, s5, result5);// 5. 自定義比較函數(不區分大小寫)std::cout << "\n不區分大小寫比較:" << std::endl;auto caseInsensitiveCompare = [](const std::string& a, const std::string& b) -> bool {if (a.length() != b.length()) return false;for (size_t i = 0; i < a.length(); ++i) {if (tolower(a[i]) != tolower(b[i])) return false;}return true;};bool equal = caseInsensitiveCompare(s1, s5);std::cout << "不區分大小寫比較 \"" << s1 << "\" 和 \"" << s5 << "\": "<< (equal ? "相等" : "不相等") << std::endl;// 輸出:// 使用比較運算符:// s1 == s3: true// s1 != s2: true// s1 < s2: ?true// s2 > s1: ?true// s1 <= s3: true// s1 >= s3: true//// 使用compare方法:// 比較 "apple" 和 "banana": s1 < s2// 比較 "apple" 和 "apple": s1 == s2// 比較 "banana" 和 "apple": s1 > s2//// 比較部分字符串:// 比較 "appl" 和 "appl": 相等//// 大小寫敏感比較:// 比較 "apple" 和 "APPLE": s1 > s2//// 不區分大小寫比較:// 不區分大小寫比較 "apple" 和 "APPLE": 相等return 0;}
比較操作
string s1 = "abc";string s2 = "abd";if (s1 == s2) { /* 相等 */ }if (s1 != s2) { /* 不相等 */ }if (s1 < s2) ?{ /* s1小于s2 */ } ?// 字典序比較if (s1 > s2) ?{ /* s1大于s2 */ }if (s1 <= s2) { /* s1小于等于s2 */ }if (s1 >= s2) { /* s1大于等于s2 */ }// 也可以使用compare方法int result = s1.compare(s2); ?// 小于0表示s1<s2,等于0表示s1=s2,大于0表示s1>s2
4.string類的內存管理
string類是一個動態內存管理的類,它會根據需要自動調整內存大小。了解其內存管理機制有助于寫出更高效的代碼。
字符串的長度和容量
string s = "Hello";size_t len = s.length(); ? ?// 字符串長度:5(等價于s.size())size_t cap = s.capacity(); ?// 字符串當前分配的容量(通常大于長度)cout << "長度: " << len << ", 容量: " << cap << endl;
容量的自動調整
當字符串內容需要更多空間時,string會自動擴容:
string s = "Hello";cout << "初始容量: " << s.capacity() << endl; ?// 例如15s += " World!";cout << "追加后容量: " << s.capacity() << endl; ?// 可能擴大到31// 多次追加會導致多次重新分配for (int i = 0; i < 10; i++) {s += " Hello";cout << "第" << i << "次追加后容量: " << s.capacity() << endl;}
reserve預留空間
使用`reserve()`可以預先分配足夠的空間,避免頻繁的內存重新分配:
string s;s.reserve(100); ?// 預留100個字符的空間cout << "預留后容量: " << s.capacity() << endl; ?// 至少100// 在容量范圍內添加不會導致重新分配for (int i = 0; i < 10; i++) {s += "Hello";}cout << "多次追加后容量: " << s.capacity() << endl; ?// 仍然是預留的容量
resize改變字符串大小
string s = "Hello";s.resize(10); ? ? ?// 擴展到10個字符,新增的用'\0'填充cout << s << ", 長度: " << s.length() << endl; ?// "Hello", 長度: 10s.resize(10, '*'); // 擴展到10個字符,新增的用'*'填充cout << s << endl; ?// "Hello*****"s.resize(3); ? ? ? // 截斷為3個字符cout << s << endl; ?// "Hel"
shrink_to_fit收縮多余空間
在不需要額外容量時,可以釋放多余空間:
string s = "Hello World";s.reserve(100); ?// 預留大量空間cout << "預留后容量: " << s.capacity() << endl; ?// 至少100s.shrink_to_fit(); ?// 收縮到實際需要的大小cout << "收縮后容量: " << s.capacity() << endl; ?// 接近字符串長度
string::reserve實現原理
當調用`reserve()`函數時,string類會執行以下操作:
1. 檢查請求的容量是否大于當前容量
2. 如果需要擴容,則分配新的更大內存塊
3. 將原有數據復制到新內存
4. 釋放舊內存
5. 更新內部指針和容量計數
這個過程可以示意如下:
// 假設string的簡化實現class SimpleString {private:char* data; ? ? ?// 指向實際字符數據的指針size_t length; ? // 字符串長度size_t capacity; // 當前分配的容量public:void reserve(size_t new_capacity) {if (new_capacity <= capacity)return; ?// 已有足夠容量,不需操作// 分配新內存char* new_data = new char[new_capacity + 1]; ?// +1為了存儲結尾的'\0'// 復制現有數據if (data) {std::memcpy(new_data, data, length + 1);delete[] data; ?// 釋放舊內存}// 更新指針和容量data = new_data;capacity = new_capacity;}// 其他成員函數...};
5.字符串操作性能優化
容量預留
對于需要頻繁追加的字符串,預先分配足夠的空間可以避免多次重新分配內存:
// 低效的方式void badPerformance() {string result;for (int i = 0; i < 10000; i++) {result += "Hello"; ?// 可能導致多次內存重新分配}}// 高效的方式void goodPerformance() {string result;result.reserve(50000); ?// 預留足夠空間for (int i = 0; i < 10000; i++) {result += "Hello"; ?// 不會導致內存重新分配}}
性能對比:
- 未使用reserve:多次重新分配內存,時間復雜度可能達到O(n2)
- 使用reserve:只分配一次內存,時間復雜度降至O(n)
避免不必要的拷貝
使用引用傳遞和移動語義可以提高性能:
// 不好的做法(產生拷貝)string concatenate(string a, string b) {return a + b;}// 更好的做法(避免拷貝)string concatenate(const string& a, const string& b) {return a + b;}// C++11移動語義string getResult() {string result = "Result";return result; ?// 編譯器可能應用返回值優化(RVO)或移動語義}// 移動語義的顯式使用void processStrings() {string heavy = "很長的字符串...";string target;// 移動而非復制target = std::move(heavy); ?// heavy內容被移動到target,heavy變為空}
string_view (C++17)
對于只讀操作,使用`std::string_view`可以避免不必要的內存分配:
#include <string_view>// 傳統方式:會創建string的副本void processOld(const std::string& s) {std::cout << s.substr(0, 5) << std::endl;}// 高效方式:不創建副本void processNew(std::string_view sv) {std::cout << sv.substr(0, 5) << std::endl;}// 使用示例void example() {std::string s = "Hello World";// 字符串字面量直接傳遞processNew("直接傳字面量"); ?// 不會創建string對象// string也可以直接傳遞processNew(s); ?// 不會創建新的string// 創建子視圖也不會分配新內存std::string_view sv = s;std::string_view subview = sv.substr(0, 5); ?// 不會分配新內存}
字符串池化
對于頻繁使用的固定字符串,可以考慮使用字符串池:
#include <unordered_map>#include <string>class StringPool {private:std::unordered_map<std::string, std::string> pool;public:const std::string& intern(const std::string& s) {auto it = pool.find(s);if (it != pool.end()) {return it->second; ?// 返回池中已有的字符串引用}auto result = pool.emplace(s, s);return result.first->second; ?// 返回新添加的字符串引用}};// 使用示例void example() {StringPool pool;// 頻繁使用的字符串for (int i = 0; i < 1000; i++) {const std::string& s1 = pool.intern("Hello");const std::string& s2 = pool.intern("World");// s1和s2分別只存儲了一份實際數據}}
字符串與數值轉換
// 字符串轉數值string numStr = "123";int num = stoi(numStr); ? ? ? // 字符串轉int:123double dNum = stod("3.14"); ? // 字符串轉double:3.14// 帶錯誤處理的轉換try {int x = stoi("not_a_number");} catch (const std::invalid_argument& e) {cout << "無效參數: " << e.what() << endl;} catch (const std::out_of_range& e) {cout << "超出范圍: " << e.what() << endl;}// 數值轉字符串string s1 = to_string(42); ? ?// 整數轉字符串:"42"string s2 = to_string(3.14); ?// 浮點數轉字符串:"3.14"
6.實用案例分析
案例1:字符串分割
vector<string> split(const string& s, char delimiter) {vector<string> tokens;string token;istringstream tokenStream(s);while (getline(tokenStream, token, delimiter)) {if (!token.empty()) {tokens.push_back(token);}}return tokens;}// 使用string text = "apple,banana,orange";vector<string> fruits = split(text, ',');// fruits包含:"apple", "banana", "orange"
案例2:字符串連接
string join(const vector<string>& v, const string& delimiter) {// 計算最終字符串的長度,優化性能size_t totalLength = 0;for (const auto& s : v) {totalLength += s.length();}totalLength += delimiter.length() * (v.size() > 0 ? v.size() - 1 : 0);// 預分配空間string result;result.reserve(totalLength);// 連接字符串for (size_t i = 0; i < v.size(); i++) {if (i > 0) {result += delimiter;}result += v[i];}return result;}// 使用vector<string> words = {"Hello", "World", "C++"};string sentence = join(words, " ");// sentence為:"Hello World C++"
案例3:字符串替換
void replaceAll(string& str, const string& from, const string& to) {// 如果替換的結果會更長,預先計算并預留空間if (to.length() > from.length()) {size_t count = 0;size_t pos = 0;while ((pos = str.find(from, pos)) != string::npos) {++count;pos += from.length();}str.reserve(str.length() + count * (to.length() - from.length()));}// 執行替換size_t pos = 0;while ((pos = str.find(from, pos)) != string::npos) {str.replace(pos, from.length(), to);pos += to.length();}}// 使用string text = "The cat sat on the mat with another cat";replaceAll(text, "cat", "dog");// text變為:"The dog sat on the mat with another dog"
案例4:字符串轉換工具
// 轉換為大寫string toUpper(const string& s) {string result = s;for (char& c : result) {c = toupper(c);}return result;}// 轉換為小寫string toLower(const string& s) {string result = s;for (char& c : result) {c = tolower(c);}return result;}// 修剪字符串首尾空白string trim(const string& s) {const char* whitespace = " \t\n\r\f\v";// 找到第一個非空白字符size_t start = s.find_first_not_of(whitespace);if (start == string::npos) return ""; ?// 全是空白// 找到最后一個非空白字符size_t end = s.find_last_not_of(whitespace);// 提取子字符串return s.substr(start, end - start + 1);}
實際項目應用場景
場景1:配置文件解析
#include <string>#include <fstream>#include <unordered_map>#include <iostream>// 配置文件解析器class ConfigParser {private:std::unordered_map<std::string, std::string> configs;// 修剪空白std::string trim(const std::string& s) {const char* whitespace = " \t\n\r\f\v";size_t start = s.find_first_not_of(whitespace);if (start == std::string::npos) return "";size_t end = s.find_last_not_of(whitespace);return s.substr(start, end - start + 1);}public:bool loadFromFile(const std::string& filename) {std::ifstream file(filename);if (!file.is_open()) {return false;}std::string line;while (std::getline(file, line)) {// 跳過空行和注釋行std::string trimmed = trim(line);if (trimmed.empty() || trimmed[0] == '#') {continue;}// 查找分隔符size_t pos = trimmed.find('=');if (pos != std::string::npos) {std::string key = trim(trimmed.substr(0, pos));std::string value = trim(trimmed.substr(pos + 1));// 存儲配置項configs[key] = value;}}return true;}std::string getValue(const std::string& key, const std::string& defaultValue = "") const {auto it = configs.find(key);if (it != configs.end()) {return it->second;}return defaultValue;}int getIntValue(const std::string& key, int defaultValue = 0) const {auto it = configs.find(key);if (it != configs.end()) {try {return std::stoi(it->second);} catch (...) {return defaultValue;}}return defaultValue;}bool getBoolValue(const std::string& key, bool defaultValue = false) const {auto it = configs.find(key);if (it != configs.end()) {std::string value = trim(it->second);if (value == "true" || value == "yes" || value == "1") return true;if (value == "false" || value == "no" || value == "0") return false;}return defaultValue;}};// 使用示例void configExample() {ConfigParser config;if (config.loadFromFile("settings.conf")) {std::string serverName = config.getValue("server_name", "localhost");int port = config.getIntValue("port", 8080);bool debugMode = config.getBoolValue("debug", false);std::cout << "服務器: " << serverName << ":" << port << std::endl;std::cout << "調試模式: " << (debugMode ? "開啟" : "關閉") << std::endl;} else {std::cout << "無法加載配置文件" << std::endl;}}
場景2:URL解析
#include <string>#include <unordered_map>#include <iostream>// URL解析器class URLParser {private:std::string scheme;std::string host;int port;std::string path;std::string query;std::string fragment;std::unordered_map<std::string, std::string> queryParams;void parseQueryParams() {if (query.empty()) return;size_t startPos = 0;while (startPos < query.length()) {size_t endPos = query.find('&', startPos);if (endPos == std::string::npos) {endPos = query.length();}std::string param = query.substr(startPos, endPos - startPos);size_t equalPos = param.find('=');if (equalPos != std::string::npos) {std::string key = param.substr(0, equalPos);std::string value = param.substr(equalPos + 1);queryParams[key] = value;} else {queryParams[param] = "";}startPos = endPos + 1;}}public:bool parse(const std::string& url) {// 找到冒號和雙斜杠分隔協議size_t protocolEnd = url.find("://");if (protocolEnd != std::string::npos) {scheme = url.substr(0, protocolEnd);protocolEnd += 3; ?// 跳過"://"} else {protocolEnd = 0; ?// 沒有協議}// 找到主機結尾(可能是端口、路徑或查詢)size_t hostEnd = url.find_first_of(":/?\#", protocolEnd);if (hostEnd == std::string::npos) {host = url.substr(protocolEnd);return true;}host = url.substr(protocolEnd, hostEnd - protocolEnd);// 檢查端口if (url[hostEnd] == ':') {size_t portEnd = url.find_first_of("/?\#", hostEnd);std::string portStr = url.substr(hostEnd + 1,(portEnd == std::string::npos ? url.length() : portEnd) - hostEnd - 1);try {port = std::stoi(portStr);} catch (...) {port = 0; ?// 無效端口return false;}hostEnd = portEnd;if (hostEnd == std::string::npos) return true;}// 解析路徑if (url[hostEnd] == '/') {size_t pathEnd = url.find_first_of("?\#", hostEnd);path = url.substr(hostEnd, (pathEnd == std::string::npos ? url.length() : pathEnd) - hostEnd);hostEnd = pathEnd;if (hostEnd == std::string::npos) return true;}// 解析查詢參數if (url[hostEnd] == '?') {size_t queryEnd = url.find('#', hostEnd);query = url.substr(hostEnd + 1,(queryEnd == std::string::npos ? url.length() : queryEnd) - hostEnd - 1);parseQueryParams();hostEnd = queryEnd;if (hostEnd == std::string::npos) return true;}// 解析片段if (url[hostEnd] == '#') {fragment = url.substr(hostEnd + 1);}return true;}std::string getScheme() const { return scheme; }std::string getHost() const { return host; }int getPort() const { return port; }std::string getPath() const { return path; }std::string getQuery() const { return query; }std::string getFragment() const { return fragment; }std::string getQueryParam(const std::string& name, const std::string& defaultValue = "") const {auto it = queryParams.find(name);if (it != queryParams.end()) {return it->second;}return defaultValue;}};// 使用示例void urlExample() {URLParser parser;if (parser.parse("https://www.example.com:8080/path/to/resource?param1=value1¶m2=value2#section1")) {std::cout << "協議: " << parser.getScheme() << std::endl;std::cout << "主機: " << parser.getHost() << std::endl;std::cout << "端口: " << parser.getPort() << std::endl;std::cout << "路徑: " << parser.getPath() << std::endl;std::cout << "參數param1: " << parser.getQueryParam("param1") << std::endl;std::cout << "參數param2: " << parser.getQueryParam("param2") << std::endl;std::cout << "片段: " << parser.getFragment() << std::endl;}}
場景3:日志格式化
#include <string>#include <iostream>#include <chrono>#include <iomanip>#include <sstream>// 簡單日志格式化器class LogFormatter {private:// 獲取當前時間戳std::string getCurrentTimestamp() {auto now = std::chrono::system_clock::now();auto time_t = std::chrono::system_clock::to_time_t(now);auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;std::stringstream ss;ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");ss << '.' << std::setfill('0') << std::setw(3) << ms.count();return ss.str();}// 替換格式化占位符std::string format(const std::string& pattern, const std::unordered_map<std::string, std::string>& values) {std::string result;result.reserve(pattern.length() * 2); ?// 預留足夠空間size_t pos = 0;while (pos < pattern.length()) {size_t placeholderStart = pattern.find('{', pos);if (placeholderStart == std::string::npos) {// 沒有更多占位符result += pattern.substr(pos);break;}// 添加占位符前的內容result += pattern.substr(pos, placeholderStart - pos);// 尋找占位符結束位置size_t placeholderEnd = pattern.find('}', placeholderStart);if (placeholderEnd == std::string::npos) {// 未閉合的占位符,當作普通文本result += pattern.substr(placeholderStart);break;}// 提取占位符名稱std::string placeholder = pattern.substr(placeholderStart + 1,placeholderEnd - placeholderStart - 1);// 替換占位符auto it = values.find(placeholder);if (it != values.end()) {result += it->second;} else {// 未知占位符保留原樣result += "{" + placeholder + "}";}// 更新位置pos = placeholderEnd + 1;}return result;}public:enum LogLevel {DEBUG,INFO,WARNING,ERROR};std::string formatLog(LogLevel level, const std::string& message,const std::string& component = "main") {std::string levelStr;switch (level) {case DEBUG: ? levelStr = "DEBUG"; break;case INFO: ? ?levelStr = "INFO"; break;case WARNING: levelStr = "WARNING"; break;case ERROR: ? levelStr = "ERROR"; break;}std::unordered_map<std::string, std::string> values = {{"timestamp", getCurrentTimestamp()},{"level", levelStr},{"component", component},{"message", message}};return format("[{timestamp}] [{level}] [{component}] {message}", values);}};// 使用示例void logExample() {LogFormatter formatter;std::cout << formatter.formatLog(LogFormatter::INFO, "系統啟動成功", "System") << std::endl;std::cout << formatter.formatLog(LogFormatter::WARNING, "磁盤空間不足", "Storage") << std::endl;std::cout << formatter.formatLog(LogFormatter::ERROR, "數據庫連接失敗: 超時", "Database") << std::endl;// 輸出類似:// [2023-11-30 15:30:45.123] [INFO] [System] 系統啟動成功// [2023-11-30 15:30:45.124] [WARNING] [Storage] 磁盤空間不足// [2023-11-30 15:30:45.124] [ERROR] [Database] 數據庫連接失敗: 超時}
字符串處理的最佳實踐
1. 性能優化
- **預分配內存**:使用`reserve()`避免頻繁重新分配
- **引用傳遞**:優先使用`const string&`作為函數參數
- **移動語義**:利用`std::move()`減少不必要的復制
- **string_view**:對于只讀操作,使用C++17的`string_view`
?2. 安全使用
- **邊界檢查**:優先使用帶邊界檢查的`at()`而非`[]`訪問單個字符
- **容錯處理**:解析和轉換操作要有異常處理
- **輸入驗證**:處理用戶輸入時,先驗證長度和內容
3. 避免常見陷阱
- **內存泄漏**:確保在使用C風格字符串時正確釋放動態分配的內存
- **錯誤的字符串比較**:理解`==`和`compare()`的正確使用方式
- **失效的指針**:避免使用可能失效的`c_str()`或`data()`返回值
- **編碼問題**:注意處理UTF-8等多字節編碼
4. 接口設計
- **一致性**:在項目中保持一致的字符串處理風格
- **顯式轉換**:字符串與其他類型間的轉換應該是明確的,不依賴隱式轉換
- **可擴展性**:設計接受字符串的接口時,考慮未來可能的字符集變化
總結
C++提供了豐富的字符串處理功能,從C風格的底層操作到高級的string類。掌握這些功能可以讓你的代碼更加簡潔、高效和安全。
關鍵要點回顧:
1. **C風格字符串**:簡單但需要手動管理內存和注意緩沖區溢出。
2. **std::string**:現代C++首選的字符串處理方式,自動管理內存。
3. **內存管理**:了解并合理使用`reserve()`和`shrink_to_fit()`可以顯著提高性能。
4. **性能優化**:
? ?- 合理預留空間避免頻繁重新分配
? ?- 使用引用傳遞減少拷貝
? ?- 利用C++17的`string_view`處理只讀操作
? ?- 對于關鍵路徑,考慮自定義字符串池化方案
5. **實用技巧**:掌握字符串分割、連接、替換等常見操作的高效實現。
在實際開發中,推薦優先使用`std::string`而非C風格字符串,以避免內存問題并獲得更好的可讀性。對于性能敏感的場景,可以考慮使用`std::string_view`、容量預留以及移動語義等優化技巧。
希望這篇博客能幫助你更好地理解和使用C++中的字符串操作!
參考資料
- C++ Reference: [std::string](https://en.cppreference.com/w/cpp/string/basic_string)
- C++ Reference: [std::string_view](https://en.cppreference.com/w/cpp/string/basic_string_view) (C++17)
- C++ Reference: [cstring](https://en.cppreference.com/w/cpp/header/cstring)