【我为同学解难题| 第252期】C++第12期:如何处理字符串数据

文摘   2024-11-20 20:01   江苏  


C++ 中,我们可以通过多种方式处理字符串数据。最常见的两种方式是使用 C 风格字符串 (cstring) C++ 标准字符串类 (std::string)。这两者都用于表示字符串,但它们有着本质的差异。今天,我们将深入探讨 cstring std::string 的差异,并详细介绍一些库中的常见函数的特点和使用方法。



一、C风格字符串(cstring)

     C 风格字符串是以字符数组的形式存储的,是一个以 ‘\0' (空字符)结尾的字符序列。在 C 语言中,字符串就是一个字符数组,而在 C++ 中,虽然 std::string 提供了更强大的字符串操作功能,但 cstring 依然被广泛使用。
     1. cstring 的基本结构
    cstring 本质就是一个 char 类型的数组,表示字符串的内容char str[] = "Hello, World!";者使用指针:const char* str = "Hello, World!";
2. cstring 的常见函数

2.1 strlen() - 获取字符串长度

strlen() 函数用于获取 C 风格字符串的长度(不包括空字符 \0)。

原型:size_t strlen(const char* str);

示例:

char str[] = "Hello, World!";

std::cout << strlen(str);  // 输出: 13

请注意,strlen() 返回的是字符串的字符数,不包括 \0。而且,这个函数并不适用于空指针,如果传入空指针会导致未定义行为。

2.2 strcpy() - 字符串复制

strcpy() 函数可以将源字符串复制到目标字符串中。

原型:char* strcpy(char* dest, const char* src);

示例:

char src[] = "Hello";

char dest[20];

strcpy(dest, src);

std::cout << dest;  // 输出: Hello

在这里需要注意的问题是,目标字符串必须足够大,以容纳源字符串,包括 \0 终止符。同时,strcpy() 可能导致缓冲区溢出,因此需要小心使用,最好使用更安全的版本 strncpy()(指定最大复制长度)。

2.3 strcat() - 字符串拼接

strcat() 函数用于将一个字符串追加到另一个字符串的末尾。

原型:char* strcat(char* dest, const char* src);

示例:

char str1[20] = "Hello";

char str2[] = " World!";

strcat(str1, str2);

std::cout << str1;  // 输出: Hello World!

同样,目标字符串必须有足够的空间来容纳拼接后的字符串,否则会发生缓冲区溢出。strcat() 并不会检查目标字符串的大小,因此需要谨慎使用。

2.4 strcmp() - 字符串比较

strcmp() 函数用于比较两个字符串的大小。

原型:int strcmp(const char* str1, const char* str2);

返回值:

· 如果 str1 等于 str2,返回 0。

· 如果 str1 小于 str2,返回负值。

· 如果 str1 大于 str2,返回正值。

示例:

char str1[] = "apple";

char str2[] = "banana";

int result = strcmp(str1, str2);

if (result < 0) {

    std::cout << "str1 is less than str2" << std::endl;

}

在这里一个特别要注意的问题是,strcmp() 是大小写敏感的,也就是说 "apple" 和 "Apple" 被认为是不同的字符串。它比较的是字符串的字典顺序。

2.5 strncpy() - 安全的字符串复制

strncpy() 函数类似于 strcpy(),但是它允许我们指定复制的最大字符数,从而避免缓冲区溢出。

原型:char* strncpy(char* dest, const char* src, size_t n);

示例:

char src[] = "Hello, World!";

char dest[10];

strncpy(dest, src, sizeof(dest) - 1);

dest[sizeof(dest) - 1] = '\0';  // 确保字符串以 null 结尾

std::cout << dest;  // 输出: Hello
为什么这里要特意设置\0呢,是因为strncpy() 并不会自动添加终止符 \0,所以需要手动确保目标字符串以 \0 结尾。它的优点是不会复制超出指定的字符数,大大降低了缓冲区溢出的风险。

2.6 strncat() - 安全的字符串拼接

strncat() 函数类似于 strcat(),但它允许指定最大拼接长度,从而避免溢出。

原型:char* strncat(char* dest, const char* src, size_t n);

示例:

char str1[20] = "Hello";

char str2[] = " World!";

strncat(str1, str2, 6);  // 只拼接前 6 个字符

std::cout << str1;  // 输出: Hello World

在这里,strncat() 会自动在目标字符串末尾添加 \0,但是仍然需要确保目标字符串有足够的空间

2.7 strchr() - 查找字符

strchr() 函数用于查找某个字符在字符串中首次出现的位置。

原型:char* strchr(const char* str, int ch);

返回值:

如果找到了字符,返回指向该字符的指针;如果没有找到,返回 nullptr。

示例:

const char* str = "Hello, World!";

char* result = strchr(str, 'o');

if (result) {

    std::cout << "Found: " << *result << std::endl;  // 输出: Found: o

}

在这里要区分,strchr() 查找的是字符,而不是字符串。如果查找的字符不存在,返回 nullptr。



二、C++标准字符串类(std:string)

std::string 是 C++ 标准库中的字符串类,定义在 头文件中,提供了非常方便且功能丰富的字符串操作。它封装了 C 风格字符串的基本功能,同时提供了更多的功能和自动内存管理,使得字符串操作更加安全、简洁。它封装了对字符数组的管理,并提供了一些便捷的方法来操作字符串。与 C 风格的字符数组相比,std::string 不仅更安全,还能自动处理内存管理,减少了开发者的负担。(注:如果在使用std::string前使用了using namespace std; 则可直接使用string而无需添加std::前缀
#include<iostream>
#include<string>
int main() {
std::string str = "Hello, World!";
std::cout << str << std::endl;
return 0;
}
1. 创建和初始化 std::string
std::string 可以通过多种方式进行初始化:
std::string s1 = "Hello, World!"; // 使用字符串字面值初始化
std::string s2("Hello"); // 使用构造函数初始化
std::string s3(10, 'A'); // 使用字符重复初始化,创建一个包含 10 个 'A' 的字符串
std::string s4 = s2; // 复制构造,创建 s2 的副本
2. 重要的 std::string 成员函数
2.1 size() 和 .length()
这两个函数用于获取字符串的长度,返回字符串中字符的个数(不包括结束符 \0)。这两个函数是等效的,通常我们使用 .size(),因为它是容器类标准方法。
std::string str = "Hello, World!";
std::cout << "Length of string: " << str.size() << std::endl; // 输出: 13
std::cout << "Length of string: " << str.length() << std::endl; // 输出: 13
std::string 内常使用一个动态分配的字符数组来存储字符数据,因此获取长度是一个常数时间操作 O(1),不需要遍历整个数组。
2.2 empty()
判断字符串是否为空,若字符串长度为 0,返回 true,否则返回 false。
std::string str = "Hello";
if (str.empty()) {
std::cout << "String is empty!" << std::endl;
} else {
std::cout << "String is not empty!" << std::endl; // 输出: String is not empty!
}
2.3 append() 和 operator+=
这两个方法用于向字符串末尾追加内容。append() 可以接受一个字符串或字符数组,并将其附加到当前字符串后面,operator+= 则是另一种简洁的方式来进行拼接。
std::string str = "Hello";
str.append(", World!");
str += " Welcome!";
std::cout << str << std::endl; // 输出: Hello, World! Welcome!
std::string 会将字符数据存储在动态数组中,追加操作会检查当前数组是否有足够空间。如果空间不足,std::string 会重新分配更大的内存并复制原有内容。由于动态数组的复制过程需要 O(n) 时间,因此追加操作在某些情况下可能是比较耗时的。
2.4 substr()
该方法用于提取字符串的子字符串。你可以指定起始位置(索引)和长度。
std::string str = "Hello, World!";
std::string sub = str.substr(7, 5); // 从位置 7 开始,提取 5 个字符
std::cout << sub << std::endl; // 输出: World
.substr() 会创建一个新的字符串对象并返回,原始字符串的数据不会被修改。
2.5 find()
用于查找子字符串或字符在字符串中的位置。返回值是第一个匹配的字符位置,若没有找到,则返回 std::string::npos。
std::string str = "Hello, World!";
size_t pos = str.find("World");
if (pos != std::string::npos) {
std::cout << "Found at position: " << pos << std::endl; // 输出: Found at position: 7
}
.find() 会遍历字符串并比较每个字符,直到找到匹配项或到达字符串末尾,因此时间复杂度是 O(n),其中 n 是字符串的长度。特别地,std::string::npos 的实际值是 std::size_t 的最大值(是一个非常大的无符号整数),而不是 -1。
2.6 erase()
删除字符串中的字符或字符范围,可以指定删除的位置(索引)和长度。
std::string str = "Hello, World!";
str.erase(5, 7); // 从位置 5 开始删除 7 个字符
std::cout << str << std::endl; // 输出: Hello
删除操作会移动字符串中剩余字符的位置,导致时间复杂度为 O(n),其中 n 是剩余字符的数量。
2.7 replace()
替换字符串中的某一部分,可以指定起始位置、替换的长度和替换的新字符串。
std::string str = "Hello, World!";
str.replace(7, 5, "Universe"); // 从位置7开始,替换5个字符
std::cout << str << std::endl; // 输出: Hello, Universe!
.replace() 会通过类似 .erase() 的方式删除旧内容,并插入新内容,因此时间复杂度与删除和插入操作相关。


三、总结

cstring(C 风格字符串)和 string(C++ 标准库字符串类)都是C++ 中处理字符串的两种主要方式,它们在内存管理、功能和安全性等方面有显著差异。
cstring 基于字符数组,以空字符 \0 结尾,需要手动管理内存,操作函数如 strcpy、strlen 等可能导致缓冲区溢出等安全问题;而 std::string 是一个封装了字符串操作的类,自动管理内存,提供了丰富的成员函数,如 append()、find()、substr() 等,使字符串操作更加简洁和安全std::string 能动态调整大小,支持直接赋值和拼接,避免了手动内存管理的复杂性和潜在错误。
总体而言,cstring 适用于与 C 语言兼容或对性能有极高要求的场景,而 std::string 则是现代 C++ 编程中处理字符串的推荐选择,提供了更高的安全性和易用性。

四、习题小练

学习到这里辛苦啦!不过还是想考验一下大家对知识的掌握程度,以下是两道例题。
1.关于 std::string::npos,以下说法错误的是?
A. npos 是 std::string 中用于表示未找到的位置。
B. npos 是一个常量,值为 -1。
C. 使用 std::string::find 返回 npos 表示查找失败。
D. npos 是一个无符号整数类型的值。
正确答案:B
解析:std::string::npos 的实际值是 std::size_t 的最大值(是一个非常大的无符号整数),而不是 -1。
2.以下代码的输出是什么?
char src1[] = "Hello";
char src2[] = "NJUST";
char dest[10];
strcpy(dest, src1);
strncpy(dest, src2, 3);
dest[8] = '\0';
cout << dest << endl;
A. NJUloB. NJUSTC. HelNJUloD. Hello
正确答案:A
解析:strcpy 会把字符串 "Hello" 完整地复制到 dest 中。此时 dest 的内容为:['H', 'e', 'l', 'l', 'o', '\0', ?, ?, ?, ?] (? 表示未定义内容)。
strncpy 会将 "NJUST" 的前 3 个字符复制到 dest 的前 3 个位置。strncpy 并不会自动在字符串的末尾添加空字符,除非拷贝的字符数等于或多于目标数组的空间。因此,在复制时,dest 变成了:['N', 'J', 'U', 'l', 'o', '\0', ?, ?, ?, ?]。strncpy 只复制了 3 个字符,即 "NJU",并没有替换掉整个字符串 dest 的内容。
dest[8] = '\0';这一步手动将 \0 放置在 dest 的第 9 个位置(索引 8)。
然而,这个操作不会影响 cout 输出的内容,因为在字符串输出时,cout 会从第一个遇到的 \0 开始停止输出。所以,dest 的有效内容会到达第一个 \0,即 dest[5] 处的空字符。在经过上述操作后,dest 实际上变成了 "NJUlo"。由于 cout 会在遇到第一个空字符时停止输出,因此最终打印的内容是:"NJUlo"。





策 划 | 本科生学业指导中心

图 文 | 朱霖、朱欣宇

编 辑 | 樊佳鑫

初 审 | 官子涵

复 审 | 陈祚瑜

审 核 | 尚文浩

推荐阅读








先锋计划 | 先锋九期开展户外素质拓展训练活动


传递温情,赋能成长:学校2024年资助宣传大使活动圆满落幕

以梦为马,不负韶华|2024-2025学年校级学导团队介绍

南理工学生事务微平台
南京理工大学学生工作处设立,面向我校全体本科生,推送校园新闻,开设专题报道,建立沟通渠道,提供实用服务,欢迎添加关注!
 最新文章