【C++ 基础】字符与字符串

📅 2026/7/18 11:48:49 👁️ 阅读次数 📝 编程学习
【C++ 基础】字符与字符串

ASCLL码表

1. 引言:从字符到字符串

在 C++ 编程中,字符(char)和字符串(string)是最基础、最常用的数据类型之一。无论是处理用户输入、文件读写、网络通信,还是实现各种文本处理算法,都离不开对字符和字符串的操作。然而,C++ 中的字符与字符串处理有着独特的历史背景和多种实现方式,从传统的 C 风格字符串到现代的 C++std::string,再到宽字符和多字节字符的支持,形成了一个丰富而复杂的体系。

本文将深入探讨 C++ 中字符与字符串的各个方面,包括:

  • 字符类型(char,wchar_t,char8_t,char16_t,char32_t
  • C 风格字符串(字符数组)的原理与操作
  • C++std::string类的全面使用
  • 字符串字面量的各种形式
  • 字符编码与国际化支持
  • 性能优化与最佳实践

2. 字符类型:不只是 char

C++ 提供了多种字符类型,以适应不同的编码需求和国际化场景。

2.1 基本字符类型 char

char是最基本的字符类型,通常占用 1 字节(8 位),可以表示 ASCII 字符集中的 128 个字符。在大多数系统中,char默认是有符号的(signed char),但具体实现可能有所不同。

// char 类型的基本使用 char ch1 = 'A'; // 字符字面量 char ch2 = 65; // ASCII 码值 char ch3 = '\n'; // 转义字符:换行 char ch4 = '\x41'; // 十六进制表示:'A' char ch5 = '\101'; // 八进制表示:'A' // 输出验证 std::cout << "ch1: " << ch1 << std::endl; // 输出: A std::cout << "ch2: " << ch2 << std::endl; // 输出: A std::cout << "ASCII of ch1: " << static_cast<int>(ch1) << std::endl; // 输出: 65

竞赛考点提示:在 GESP 和 CSP-J/S 中,经常考察字符与 ASCII 码的转换、转义字符的使用。记住常见 ASCII 码:'A'=65,'a'=97,'0'=48。

2.2 宽字符类型 wchar_t

wchar_t用于表示宽字符,通常用于支持 Unicode 字符。其大小取决于编译器实现(Windows 上通常为 2 字节,Linux 上通常为 4 字节)。

#include <bits/stdc++.h> using namespace std; int main() { // wchar_t 的使用 wchar_t wch1 = L'A'; // 宽字符字面量 wchar_t wch2 = L'中'; // 中文字符 wchar_t wstr[] = L"Hello, 世界!"; // 宽字符串 // 宽字符输出(需要设置本地化) #include <locale> wcout.imbue(locale("")); wcout << L"宽字符: " << wch2 << endl; wcout << L"宽字符串: " << wstr << endl; return 0; }

竞赛考点提示:NOIP 高级组可能涉及宽字符的基本概念,但实际编程题中较少使用。了解其存在即可。

2.3 固定宽度字符类型(C++11 起)

C++11 引入了固定宽度的字符类型,明确指定了字符的编码方式:

  • char8_t:用于 UTF-8 编码(C++20 起)
  • char16_t:用于 UTF-16 编码
  • char32_t:用于 UTF-32 编码
#include <bits/stdc++.h> using namespace std; int main() { // 固定宽度字符类型(C++11/20) char8_t utf8_char = u8'A'; // UTF-8 字符 char16_t utf16_char = u'中'; // UTF-16 字符 char32_t utf32_char = U'😀'; // UTF-32 字符(表情符号) // 对应的字符串字面量 const char8_t* utf8_str = u8"UTF-8 字符串"; const char16_t* utf16_str = u"UTF-16 字符串"; const char32_t* utf32_str = U"UTF-32 字符串"; return 0; }

竞赛考点提示:GESP 和 CSP-J/S 主要考察基本char类型,这些新类型了解即可。

2.4 有符号与无符号 char

charsigned charunsigned char是三种不同的类型:

#include <bits/stdc++.h> using namespace std; int main() { char c1 = 'A'; // 可能是 signed 或 unsigned,取决于编译器 signed char c2 = -128; // 明确有符号,范围:-128 到 127 unsigned char c3 = 255; // 明确无符号,范围:0 到 255 // 类型转换注意事项 int ascii1 = c1; // 可能产生符号扩展问题 int ascii2 = static_cast<unsigned char>(c1); // 安全转换 return 0; }

竞赛考点提示:注意字符类型转换时的符号扩展问题,这在 CSP-J/S 的编程题中可能成为陷阱。

3. C 风格字符串:字符数组

C 风格字符串本质上是字符数组,以空字符('\0')作为结束标志。

3.1 声明与初始化

#include <bits/stdc++.h> using namespace std; int main() { // 多种初始化方式 char str1[] = "Hello"; // 自动计算大小(6 字节,包含 '\0') char str2[10] = "World"; // 指定大小,剩余部分填充 '\0' char str3[] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 字符数组初始化 char str4[10]; // 未初始化,需要后续赋值 // 错误示例 // char str5[5] = "Hello"; // 错误:数组太小,无法容纳 '\0' // char str6[]; // 错误:没有初始值,无法确定大小 return 0; }

竞赛考点提示:GESP 和 CSP-J 经常考察字符数组的初始化、大小计算和结束符'\0'的重要性。

3.2 常用操作函数

C 标准库提供了一系列字符串操作函数,需要包含头文件<cstring>

#include <bits/stdc++.h> using namespace std; int main() { char str1[20] = "Hello"; char str2[] = "World"; char str3[20]; // 字符串长度(不包含 '\0') cout << "str1 length: " << strlen(str1) << endl; // 5 // 字符串复制 strcpy(str3, str1); // str3 = "Hello" strncpy(str3, str2, 3); // str3 = "Worlo"(注意:不会自动添加 '\0') str3[3] = '\0'; // 手动添加结束符 // 字符串连接 strcat(str1, " "); // str1 = "Hello " strcat(str1, str2); // str1 = "Hello World" // 字符串比较 int cmp1 = strcmp("apple", "banana"); // 负数("apple" < "banana") int cmp2 = strcmp("hello", "hello"); // 0(相等) int cmp3 = strcmp("zoo", "apple"); // 正数("zoo" > "apple") // 字符串查找 char text[] = "The quick brown fox jumps over the lazy dog"; char* found = strstr(text, "fox"); // 查找子串 if (found) { cout << "Found at position: " << (found - text) << endl; } // 字符查找 char* ch_pos = strchr(text, 'q'); // 查找字符 if (ch_pos) { cout << "'q' found at: " << (ch_pos - text) << endl; } return 0; }

竞赛考点提示:必须熟练掌握strlenstrcpystrcatstrcmpstrstrstrchr等函数的使用,这是 CSP-J/S 和 NOIP 的必考内容。

3.3 安全版本函数(C11)

为了防范缓冲区溢出,C11 标准引入了带边界检查的安全版本函数:

#include <bits/stdc++.h> using namespace std; int main() { // 安全版本函数(需要定义 __STDC_LIB_EXT1__) #define __STDC_WANT_LIB_EXT1__ 1 #include <cstring> char dest[10]; const char* src = "This is a very long string that might overflow"; // 传统函数(危险) // strcpy(dest, src); // 可能缓冲区溢出 // 安全版本 errno_t err = strcpy_s(dest, sizeof(dest), src); if (err != 0) { cout << "Buffer overflow prevented!" << endl; } return 0; }

竞赛考点提示:竞赛中通常使用传统函数,但要知道缓冲区溢出的风险和安全函数的存在。

3.4 优缺点分析

优点:

  • 内存布局简单,与 C 语言兼容
  • 在某些嵌入式或性能敏感场景中更高效
  • 可以直接操作内存,灵活性高

缺点:

  • 容易发生缓冲区溢出
  • 需要手动管理内存和结束符
  • 长度计算需要遍历整个字符串(O(n) 复杂度)
  • 缺乏现代面向对象的封装和安全性

4. C++ std::string:现代字符串处理

std::string是 C++ 标准库提供的字符串类,封装了字符数组的管理,提供了丰富的成员函数。

4.1 基本使用

#include <iostream> #include <string> int main() { // 多种构造方式 std::string s1; // 空字符串 std::string s2 = "Hello"; // 从 C 风格字符串构造 std::string s3("World"); // 直接构造 std::string s4(5, 'A'); // 重复字符构造:"AAAAA" std::string s5(s2); // 拷贝构造 std::string s6(s2, 1, 3); // 子串构造:"ell"(从位置1开始,长度3) std::string s7 = std::string("Hello") + " " + "World"; // 临时对象 // 赋值操作 s1 = "C++ String"; // 赋值 s1.assign("Assigned", 4); // 赋值前4个字符:"Assi" // 获取 C 风格字符串 const char* c_str = s2.c_str(); // 返回 const char* const char* data_ptr = s2.data(); // C++17 起,与 c_str() 相同 return 0; }

竞赛考点提示:CSP-S 和 NOIP 中,std::string的使用频率远高于 C 风格字符串,必须熟练掌握各种构造方式。

4.2 字符串操作

#include <iostream> #include <string> int main() { std::string str = "Hello, C++ World!"; // 长度和容量 std::cout << "Length: " << str.length() << std::endl; // 17 std::cout << "Size: " << str.size() << std::endl; // 17(与 length 相同) std::cout << "Capacity: " << str.capacity() << std::endl; // 当前分配的容量 std::cout << "Empty: " << (str.empty() ? "Yes" : "No") << std::endl; // 访问字符 char first = str[0]; // 'H'(不检查边界) char second = str.at(1); // 'e'(检查边界,越界抛出异常) char last = str.back(); // '!'(C++11) char front = str.front(); // 'H'(C++11) // 修改内容 str.append(" Welcome!"); // 追加字符串 str.push_back('!'); // 追加单个字符 str.insert(7, "Modern "); // 在位置7插入 str.erase(5, 2); // 从位置5删除2个字符 str.replace(7, 3, "Standard"); // 从位置7开始替换3个字符 // 查找 size_t pos1 = str.find("World"); // 查找子串 size_t pos2 = str.find('W'); // 查找字符 size_t pos3 = str.find("Java"); // 未找到,返回 std::string::npos // 提取子串 std::string sub1 = str.substr(7); // 从位置7到结尾 std::string sub2 = str.substr(0, 5); // 前5个字符 // 比较 int cmp = str.compare("Hello, C++ World!"); // 返回0表示相等 // 清空和释放内存 str.clear(); // 清空内容 str.shrink_to_fit(); // 释放多余内存(C++11) return 0; }

竞赛考点提示:findsubstreraseinsert等函数是字符串处理题目的核心,必须熟练掌握。

4.3 字符串与数值转换

#include <iostream> #include <string> #include <sstream> #include <cstdlib> // 用于 C 风格转换 int main() { // 字符串转数值(C++11 起) std::string num_str = "123.45"; int int_val = std::stoi(num_str); // 转整数 long long_val = std::stol(num_str); // 转长整数 float float_val = std::stof(num_str); // 转单精度浮点数 double double_val = std::stod(num_str); // 转双精度浮