💖💖⚡️⚡️专栏:C高手编程-面试宝典/技术手册/高手进阶⚡️⚡️💖💖
「C高手编程」专栏融合了作者十多年的C语言开发经验,汇集了从基础到进阶的关键知识点,是不可多得的知识宝典。如果你是即将毕业的学生,面临C语言的求职面试,本专栏将帮助你扎实地掌握核心概念,轻松应对笔试与面试;如果你已有两三年的工作经验,专栏中的内容将补充你在实践中可能忽略的新技术和技巧;而对于资深的C语言程序员,这里也将是一本实用的技术备查手册,提供全面的知识回顾与更新。无论处在哪个阶段,「C高手编程」都能助你一臂之力,成为C语言领域的行家里手。
概述
本章深入探讨C语言中的字符串处理技术,包括字符串的表示、长度测量、字符串函数的使用、字符串危险操作、字符串格式化以及字符串化操作符。这些概念对于理解和编写高效安全的C语言程序至关重要。通过本章的学习,读者将能够理解这些概念的工作原理,并能在实际编程中正确地运用它们。
1. 字符串基础
1.1 定义
-
定义:字符串是由字符组成的序列,通常以空字符
\0
结尾。 -
示例代码:
char str[] = "Hello, World!";
详细说明:
str
是一个包含13个字符的数组,最后一个字符是空字符\0
。
1.2 字符串字面量
-
定义:字符串字面量是直接在源代码中定义的字符串。
-
示例代码:
char *greeting = "Hello, World!";
详细说明:
greeting
指向一个只读的字符串字面量。- 字符串字面量在程序运行期间不能被修改。
1.3 字符串数组
-
定义:字符串也可以存储在字符数组中。
-
示例代码:
char greeting[14]; strcpy(greeting, "Hello, World!");
详细说明:
greeting
是一个包含14个字符的数组,其中13个字符用于存储字符串,第14个字符用于存储结束标志\0
。- 使用
strcpy
函数将字符串复制到greeting
数组中。 - 字符数组可以被修改。
1.4 字符串长度
-
定义:字符串长度是指从第一个字符到最后一个非空字符的字符数量。
-
示例代码:
char str[] = "Hello, World!"; size_t len = strlen(str); // len will be 13
详细说明:
strlen
函数返回从第一个字符到最后一个非空字符的字符数量,不包括结束标志\0
。strlen
函数遍历整个字符串直到遇到结束标志\0
。
1.5 字符串比较
-
定义:使用
strcmp
函数比较两个字符串。 -
示例代码:
char str1[] = "Hello"; char str2[] = "hello"; int result = strcmp(str1, str2); // result will be non-zero
详细说明:
strcmp
函数按字典序比较两个字符串,如果两个字符串相等则返回0,如果不相等则返回非零值。strcmp
函数逐字符比较两个字符串,直到遇到结束标志\0
或发现不同字符为止。
2. 字符串长度与sizeof
2.1 sizeof运算符
-
定义:
sizeof
运算符返回对象或类型的字节大小。 -
示例代码:
char str[] = "Hello, World!"; size_t size = sizeof(str); // size will be 14
详细说明:
sizeof(str)
返回整个数组的大小,包括字符串本身和结束标志\0
。sizeof
运算符在编译时计算对象或类型的大小。
2.2 sizeof与strlen的区别
-
定义:
sizeof
返回的是数组或对象的大小,而strlen
返回的是字符串的实际长度。 -
示例代码:
char str[] = "Hello, World!"; size_t size = sizeof(str); // size will be 14 size_t len = strlen(str); // len will be 13
详细说明:
sizeof(str)
返回的是整个数组的大小,即14个字节。strlen(str)
返回的是字符串的实际长度,即13个字符。strlen
函数遍历整个字符串直到遇到结束标志\0
,而sizeof
运算符在编译时计算数组的大小。
2.3 常见陷阱与注意事项
-
定义:确保正确理解和使用
sizeof
和strlen
。 -
解决方案:正确区分
sizeof
和strlen
的用途。详细说明:
sizeof
返回的是数组或对象的大小,包括结束标志\0
。strlen
返回的是字符串的实际长度,不包括结束标志\0
。- 使用
sizeof
时需要注意它返回的是整个数组的大小,即使数组中只有一部分被使用。 - 在处理动态分配的字符串时,使用
strlen
而不是sizeof
来获取字符串的实际长度。
3. 字符串处理函数
3.1 基础函数
3.1.1 strcpy
-
定义:
strcpy
函数用于复制一个字符串到另一个字符串。 -
示例代码:
char str1[] = "Hello"; char str2[6]; strcpy(str2, str1);
详细说明:
strcpy
函数将str1
复制到str2
中。- 确保目的数组足够大,以避免缓冲区溢出。
strcpy
函数复制源字符串直到遇到结束标志\0
。
3.1.2 strcat
-
定义:
strcat
函数用于连接两个字符串。 -
示例代码:
char str1[] = "Hello, "; char str2[] = "World!"; strcat(str1, str2);
详细说明:
strcat
函数将str2
追加到str1
的末尾。- 确保目的字符串有足够的空间来存储附加的字符串。
strcat
函数从目的字符串的当前位置开始追加源字符串,直到遇到结束标志\0
。
3.1.3 strchr
-
定义:
strchr
函数用于在字符串中查找指定字符的位置。 -
示例代码:
char str[] = "Hello, World!"; char *pos = strchr(str, ',');
详细说明:
strchr
函数返回第一次出现指定字符的位置。- 如果没有找到指定字符,则返回
NULL
。 strchr
函数遍历整个字符串直到遇到结束标志\0
。
3.2 高级函数
3.2.1 strstr
-
定义:
strstr
函数用于在字符串中查找子字符串的位置。 -
示例代码:
char str[] = "Hello, World!"; char *pos = strstr(str, "World");
详细说明:
strstr
函数返回子字符串“World”在主字符串中的位置。- 如果没有找到子字符串,则返回
NULL
。 strstr
函数遍历整个字符串直到遇到结束标志\0
。
3.2.2 strtok
-
定义:
strtok
函数用于将字符串分割成令牌。 -
示例代码:
char str[] = "one,two,three,four,five"; char *token = strtok(str, ","); while (token != NULL) { printf("%s\n", token); token = strtok(NULL, ","); }
详细说明:
strtok
函数根据分隔符,
将字符串分割成令牌。- 第一次调用时传入原始字符串和分隔符,之后的调用只需传入
NULL
即可继续分割剩余的部分。 strtok
函数遍历整个字符串直到遇到结束标志\0
。
3.3 示例代码
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Hello, ";
char str2[] = "World!";
char str3[12];
strcpy(str3, str1);
strcat(str3, str2);
printf("str3: %s\n", str3);
char *pos = strchr(str3, ',');
if (pos != NULL) {
printf("Found comma at position: %ld\n", pos - str3);
}
char *sub = strstr(str3, "World");
if (sub != NULL) {
printf("Found 'World' at position: %ld\n", sub - str3);
}
return 0;
}
详细说明:
- 输出结果为
str3: Hello, World!
。 - 输出结果还包括逗号和子字符串“World”的位置。
strchr
函数返回逗号的位置。strstr
函数返回子字符串“World”的位置。
4. 字符串危险操作
4.1 缓冲区溢出
-
定义:缓冲区溢出是指向缓冲区写入超出其边界的数据。
-
示例代码:
char str1[10]; strcpy(str1, "This is a very long string"); // Potential buffer overflow
详细说明:
- 如果目标缓冲区的大小不足以容纳源字符串,则可能发生缓冲区溢出。
- 缓冲区溢出可能导致程序崩溃或安全漏洞。
4.2 解决方案
-
定义:使用安全的字符串处理函数。
-
解决方案:使用
strncpy
代替strcpy
,并检查是否达到目的缓冲区的末尾。详细说明:
strncpy
函数允许指定复制的最大字符数。- 使用
strncpy
时,需要检查是否达到目的缓冲区的末尾,并手动添加结束标志\0
。 strncpy
函数复制源字符串直到达到指定的最大字符数或遇到结束标志\0
。
4.3 示例代码
#include <stdio.h>
#include <string.h>
int main() {
char str1[10];
const char *str2 = "This is a very long string";
strncpy(str1, str2, sizeof(str1) - 1);
str1[sizeof(str1) - 1] = '\0'; // Ensure null termination
printf("str1: %s\n", str1);
return 0;
}
详细说明:
strncpy
函数将最多9个字符从str2
复制到str1
中。- 手动添加结束标志
\0
确保字符串正确终止。 strncpy
函数复制源字符串直到达到指定的最大字符数或遇到结束标志\0
。
4.4 常见陷阱与注意事项
-
定义:确保正确理解和使用字符串处理函数。
-
解决方案:使用安全的字符串处理函数,并检查边界条件。
详细说明:
- 使用
strncpy
而不是strcpy
来避免缓冲区溢出。 - 使用
strncat
而不是strcat
来避免缓冲区溢出。 - 总是确保目的缓冲区有足够的空间来存储源字符串和结束标志
\0
。 - 避免使用未初始化的指针或数组。
- 使用
strlen
来检查字符串的实际长度,以确保不会发生缓冲区溢出。
- 使用
5. 字符串格式化
5.1 基本概念
-
定义:字符串格式化用于生成格式化的字符串。
-
示例代码:
int num = 10; char str[20]; sprintf(str, "Number: %d", num);
详细说明:
sprintf
函数将格式化的字符串写入str
数组中。- 确保目的数组足够大,以避免缓冲区溢出。
sprintf
函数将格式化的字符串写入目的数组,直到遇到结束标志\0
。
5.2 格式化指令
-
定义:格式化指令用于指定输出的格式。
-
示例代码:
double pi = 3.14159265; char str[50]; snprintf(str, sizeof(str), "Pi: %.2f", pi);
详细说明:
snprintf
函数将格式化的字符串写入str
数组中,最多写入sizeof(str)
个字符。- 格式化指令
%.2f
指定输出的浮点数保留两位小数。 snprintf
函数提供了一个安全的版本,可以避免缓冲区溢出。snprintf
函数将格式化的字符串写入目的数组,直到达到指定的最大字符数或遇到结束标志\0
。
5.3 示例代码
#include <stdio.h>
int main() {
int num = 10;
double pi = 3.14159265;
char str[50];
snprintf(str, sizeof(str), "Number: %d, Pi: %.2f", num, pi);
printf("%s\n", str);
return 0;
}
详细说明:
- 输出结果为
Number: 10, Pi: 3.14
。 snprintf
函数将格式化的字符串写入目的数组,直到达到指定的最大字符数或遇到结束标志\0
。
5.4 常见陷阱与注意事项
-
定义:确保正确理解和使用字符串格式化。
-
解决方案:使用安全的格式化函数,并检查边界条件。
详细说明:
- 使用
snprintf
而不是sprintf
来避免缓冲区溢出。 - 总是确保目的缓冲区有足够的空间来存储格式化的字符串。
- 避免使用无效的格式化指令。
- 使用
snprintf
时,确保指定的目的数组大小足够大。
- 使用
6. 字符串化操作符
6.1 定义
-
定义:字符串化操作符
#
用于将宏名转换为其字符串形式。 -
示例代码:
#define NAME "John Doe" char *str = NAME; char *str2 = #NAME;
详细说明:
str
指向宏NAME
定义的字符串。str2
包含宏名NAME
的字符串形式。- 字符串化操作符仅在预处理阶段有效。
6.2 示例代码
#include <stdio.h>
#define NAME "John Doe"
int main() {
char *str = NAME;
char *str2 = #NAME;
printf("String value: %s\n", str);
printf("String name: %s\n", str2);
return 0;
}
详细说明:
- 输出结果为
String value: John Doe
和String name: NAME
。 - 字符串化操作符将宏名转换为字符串。
6.3 常见陷阱与注意事项
-
定义:确保正确理解和使用字符串化操作符。
-
解决方案:正确使用字符串化操作符。
详细说明:
- 字符串化操作符仅在宏展开时生效。
- 不要在运行时使用字符串化操作符。
- 使用字符串化操作符时,确保宏名正确无误。
- 字符串化操作符仅在预处理阶段有效。
7. 综合使用
在实际编程中,字符串处理函数、字符串格式化以及字符串化操作符常常结合使用,以达到特定的效果。
7.1 复合表达式
-
定义:通过结合使用上述概念,可以构建复杂的字符串处理逻辑。
-
示例代码:
char str[] = "Hello, World!"; char *pos = strchr(str, ','); if (pos != NULL) { *pos = '\0'; // Remove the comma and everything after it } printf("Modified string: %s\n", str);
详细说明:
- 如果字符串中含有逗号,则去除逗号及其后面的所有字符。
- 使用
strchr
函数查找逗号的位置。 - 使用指针操作去除逗号及其后面的所有字符。
7.2 示例代码
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, World!";
char *pos = strchr(str, ',');
if (pos != NULL) {
*pos = '\0'; // Remove the comma and everything after it
}
printf("Modified string: %s\n", str);
return 0;
}
详细说明:
- 输出结果为
Modified string: Hello
。 - 使用
strchr
函数查找逗号的位置。 - 使用指针操作去除逗号及其后面的所有字符。
7.3 常见陷阱与注意事项
-
定义:确保正确理解和使用这些概念的综合应用。
-
解决方案:仔细检查表达式的顺序和预期行为。
详细说明:
- 当使用字符串处理函数、字符串格式化以及字符串化操作符时,要特别注意表达式的顺序和预期行为。
- 使用这些概念时,需要确保代码的逻辑正确无误。
- 在复杂的表达式中,可能需要使用圆括号来明确优先级。
- 在使用指针操作时,确保指针指向有效的内存区域。
7.4 实际应用
-
定义:综合使用这些概念可以构建更复杂的字符串处理逻辑。
-
示例代码:
char str[] = "Hello, World!"; char *pos = strchr(str, ','); if (pos != NULL) { *pos = '\0'; // Remove the comma and everything after it } printf("Modified string: %s\n", str);
详细说明:
- 如果字符串中含有逗号,则去除逗号及其后面的所有字符。
- 使用
strchr
函数查找逗号的位置。 - 使用指针操作去除逗号及其后面的所有字符。
7.5 性能考量与优化技巧
-
定义:综合使用这些概念可以构建更复杂的字符串处理逻辑。
-
理由:使用这些概念可以帮助解决字符串处理问题,并可以构建复杂的字符串处理逻辑。
详细说明:
- 当使用字符串处理函数、字符串格式化以及字符串化操作符时,要特别注意表达式的顺序和预期行为。
- 使用这些概念时,需要确保代码的逻辑正确无误。
- 在复杂的表达式中,可能需要使用圆括号来明确优先级。
- 优化技巧包括避免不必要的字符串复制和确保使用最合适的字符串处理函数。
- 使用
strncpy
和strncat
代替strcpy
和strcat
以避免潜在的缓冲区溢出问题。
8. 总结
通过本章的学习,我们深入了解了C语言中的字符串处理技术,包括字符串的表示、长度测量、字符串函数的使用、字符串危险操作、字符串格式化以及字符串化操作符。我们探讨了这些概念的基本概念、使用方法以及注意事项,并提供了详细的示例代码。此外,我们还讨论了如何避免常见的陷阱和危险操作,确保代码的安全性和效率。
- 字符串基础:字符串的表示和基本操作。
- 字符串长度与sizeof:
strlen
和sizeof
的区别。 - 字符串处理函数:常用的字符串处理函数。
- 字符串危险操作:缓冲区溢出和其他常见陷阱。
- 字符串格式化:格式化字符串的方法。
- 字符串化操作符:宏名的字符串化。