1. 字符分类函数
C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。这些函数的使用都需要包含一个头文件是<ctype.h>
<ctype.h>
头文件中的字符分类函数提供了一组用于检查单个字符特性的函数。这些函数接收一个字符(通常为int
类型的char
值),返回布尔值(非零为真,零为假)。常用于处理用户输入或文本解析。
1.1 函数原理
字符分类函数通过ASCII码表定义字符范围。例如,isalpha
判断是否是字母的逻辑是基于字符ASCII码是否位于大写字母(65-90)或小写字母(97-122)的范围内。
1.2 标准库实现的特点
- 实现高效,直接使用位掩码或跳转表。
- 通常硬件层面优化,比如ARM架构中会用单指令完成掩码操作。
1.3 用法
函数 | 如果参数符号符合下列条件就返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格 ' ',换页 '\f',换行 '\n',回车 '\r',制表符 '\t' 或者垂直制表符 '\v' |
isdigit | 十进制数字 '0' ~ '9' 字符 |
isxdigit | 十六进制数字,包括所有十进制数字字符,小写字母 a~f,大写字母 A~F |
islower | 小写字母 a~z |
isupper | 大写字母 A~Z |
isalpha | 字母 a~z 或 A~Z |
isalnum | 字母或者数字 a~z, A~Z, 0~9 |
ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
这些函数的使用方法非常类似,我们就讲解一个函数的事情,其他的非常类似:
int islower ( int c );
islower
是能够判断参数部分的c
是否是小写字母的。
通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回0。
1.4 使用示例
写一个代码,将字符串中的小写字母转大写,其他字符不变。
#include <stdio.h>
#include <ctype.h>
// 函数实现小写字母转大写
void toUppercase(char *str) {
int i = 0;
while (str[i] != '\0') { // 遍历字符串
if (islower(str[i])) { // 判断是否是小写字母
str[i] -= 32; // 转换为大写字母
}
i++;
}
}
int main() {
char str[100];
// 输入字符串
printf("请输入一个字符串:");
fgets(str, sizeof(str), stdin); // 使用fgets以支持空格输入
// 转换小写字母为大写
toUppercase(str);
// 输出转换后的字符串
printf("转换后的字符串:%s", str);
return 0;
}
输入:
Hello, World!
输出:
制换后的字符串:HELLO, WORLD!
2. 字符转换函数
2.1 toupper
函数
功能:将小写字母转换为对应的大写字母。如果字符不是小写字母,则返回原字符。
函数原型:
int toupper(int c);
参数:
c
是一个字符(通常以整数形式传递,如char
类型值)。
返回值:
- 如果
c
是小写字母(‘a’ 到 ‘z’),返回其对应的大写字母; - 如果
c
不是小写字母,直接返回c
。
示例:
#include <stdio.h>
#include <ctype.h>
int main() {
char ch = 'a';
printf("toupper('%c') = '%c'\n", ch, toupper(ch));
ch = 'A';
printf("toupper('%c') = '%c'\n", ch, toupper(ch));
ch = '1';
printf("toupper('%c') = '%c'\n", ch, toupper(ch));
return 0;
}
2.2 tolower
函数
功能:将大写字母转换为对应的小写字母。如果字符不是大写字母,则返回原字符。
函数原型:
int tolower(int c);
参数和返回值:同 toupper
,但作用于大写字母。
示例:
#include <stdio.h>
#include <ctype.h>
int main() {
char ch = 'A';
printf("tolower('%c') = '%c'\n", ch, tolower(ch)); // 输出:tolower('A') = 'a'
ch = 'a';
printf("tolower('%c') = '%c'\n", ch, tolower(ch)); // 输出:tolower('a') = 'a'
ch = '!';
printf("tolower('%c') = '%c'\n", ch, tolower(ch)); // 输出:tolower('!') = '!'
return 0;
}
3. strlen 的使用和模拟实现
size_t strlen ( const char * str );
3.1 函数原理
strlen
通过指针遍历,寻找终止符\0
,并计算字符个数。标准实现可能对齐内存块,通过字节操作实现性能优化。
3.2 标准库实现的特点
- 在大多数平台上,
strlen
通过内存对齐和SIMD指令完成高效计算。 - 一次性读取多个字节,通过掩码操作检测是否包含
\0
。
3.3 注意事项
- 字符串以
'\0'
作为结束标志,strlen
函数返回的是在字符串中'\0'
前面出现的字符个数(不包含'\0'
)。若字符串未正确终止,可能导致越界读取。 - 参数指向的字符串必须要以
'\0'
结束。 - 注意函数的返回值为
size_t
,是无符号的(易错) - 在循环中多次调用
strlen
会带来性能问题,应将长度预存。 strlen
的使用需要包含头文件<string.h>
3.4 模拟实现
3.4.1 方式1
//通过指针运算
#include <stdio.h>
size_t my_strlen(const char *str) {
const char *ptr = str;
while (*ptr != '\0') {
ptr++;
}
return ptr - str;
}
int main() {
char str[] = "I like KUST";
printf("字符串长度: %zu\n", my_strlen(str));
return 0;
}
3.4.2 方式2
#include <stdio.h>
//计数器方式
int my_strlen(const char * str)
{
int count = 0;
while(*str)
{
count++;
str++;
}
return count;
}
int main() {
char str[] = "I like KUST";
printf("字符串长度: %zu\n", my_strlen(str));
return 0;
}
3.4.3 方式3
#include <stdio.h>
//不能创建临时变量计数器,递归方式
int my_strlen(const char * str)
{
if(*str == '\0')
return 0;
else
return 1 + my_strlen(str+1);
}
int main() {
char str[] = "I like KUST";
printf("字符串长度: %zu\n", my_strlen(str));
return 0;
}
4. strcpy的使用和模拟实现
char* strcpy(char * destination, constchar * source );
4.1 函数原理
strcpy
逐字节将源字符串的内容复制到目标地址,最后加上终止符\0
。标准实现可能使用内存块操作(如memcpy
)加速。
4.2 标准库实现的特点
- 不检查目标缓冲区大小,容易导致缓冲区溢出。
- 通常通过CPU指令(如MOVS指令)实现批量拷贝。
4.3 注意事项
- Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
- 源字符串必须以
'\0'
结束。 - 会将源字符串中的
'\0'
拷贝到目标空间。 strcpy
不会检查dest
的大小,应确保目标空间必须足够大,以确保能存放源字符串。- 目标空间必须可修改。
4.4 模拟实现
#include <stdio.h>
char *my_strcpy(char *dest, const char *src) {
char *ptr = dest;
while ((*ptr++ = *src++) != '\0');
return dest;
}
int main() {
char src[] = "I like KUST";
char dest[50];
my_strcpy(dest, src);
printf("复制后的字符串: %s\n", dest);
return 0;
}
5.strcat 的使用和模拟实现
5.1 函数功能
strcat
函数用于将一个字符串连接到另一个字符串的末尾。它会覆盖目标字符串的末尾的 \0
,然后追加源字符串内容,最后加上新的终止符 \0
。
5.2 函数声明
#include <string.h>
char *strcat(char *dest, const char *src);
dest
:目标字符串,存放拼接结果。src
:源字符串,追加到目标字符串后。- 返回值:目标字符串
dest
的指针。
5.3 注意事项
- 源字符串必须以
'\0'
结束。 - 目标字符串中也得有
\0
,否则没办法知道追加从哪里开始。 - 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
- 如果
src
是空字符串,则目标字符串不会发生任何变化。
5.3 使用示例
#include <stdio.h>
#include <string.h>
int main() {
char str1[50] = "I like "; // 确保str1有足够的空间存储拼接结果
char str2[] = "KUST!";
strcat(str1, str2); // 将str2拼接到str1末尾
printf("拼接结果: %s\n", str1); // 输出拼接后的字符串
return 0;
}
输出结果:
拼接结果: I like KUST!
5.4 模拟实现
可以通过指针操作手动实现 strcat
,逐步找到目标字符串的末尾,并从末尾开始追加源字符串。
#include <stdio.h>
char *my_strcat(char *dest, const char*src)
{
char *ret = dest;
while(*dest)
{
dest++;
}
while((*dest++ = *src++));
return ret;
}
int main() {
char str1[100] = "I like ";
char str2[] = "Kunming University of Science and Technology";
my_strcat(str1, str2);
printf("拼接结果: %s\n", str1);
return 0;
}
输出结果:
拼接结果: I like Kunming University of Science and Technology
6. strcmp
的使用和模拟实现
strcmp
是 C 语言 <string.h>
库中最常用的字符串比较函数之一,用于比较两个字符串的字典序大小。以下从函数功能、实现原理、标准用法、模拟实现、常见问题、注意事项和扩展应用等方面进行详细说明。
6.1 函数功能
strcmp
用于逐字符比较两个字符串的大小,其比较规则如下:
- 如果字符串
str1
等于str2
,返回值为0
。 - 如果字符串
str1
小于str2
(基于ASCII码值),返回负值。 - 如果字符串
str1
大于str2
(基于ASCII码值),返回正值。
6.2 函数声明
#include <string.h>
int strcmp(const char *str1, const char *str2);
- 参数:
str1
:第一个字符串的指针。str2
:第二个字符串的指针。
- 返回值:
0
:两个字符串相等。- 正值:
str1
的第一个不匹配字符大于str2
对应字符。 - 负值:
str1
的第一个不匹配字符小于str2
对应字符。
6.3 注意事项
-
This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached.
-
strcmp
是区分大小写的。例如,"Apple"
与"apple"
是不同的。 -
空字符串的比较结果永远小于任何非空字符串。
-
确保字符串以
\0
结尾,否则可能导致内存越界。
6.4 函数使用示例
以下示例比较两个字符串并根据返回值输出结果。
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "apple";
char str2[] = "banana";
int result = strcmp(str1, str2);
if (result == 0) {
printf("两个字符串相等\n");
} else if (result < 0) {
printf("\"%s\" 小于 \"%s\"\n", str1, str2);
} else {
printf("\"%s\" 大于 \"%s\"\n", str1, str2);
}
return 0;
}
输出结果:
"apple" 小于 "banana"
6.5 实现原理
strcmp
的核心逻辑是:
- 逐字符比较两个字符串对应位置的字符。
- 如果找到不相同的字符,则返回它们的 ASCII 值差。
- 如果全部字符相等,返回
0
。
6.6 模拟实现
以下代码实现了 strcmp
的核心逻辑。
#include <stdio.h>
int my_strcmp (const char * str1, const char * str2)
{
int ret = 0 ;
while(*str1 == *str2)
{
if(*str1 == '\0')
return 0;
str1++;
str2++;
}
return *str1-*str2;
}
int main() {
char str1[] = "hello";
char str2[] = "world";
int result = my_strcmp(str1, str2);
if (result == 0) {
printf("两个字符串相等\n");
} else if (result < 0) {
printf("\"%s\" 小于 \"%s\"\n", str1, str2);
} else {
printf("\"%s\" 大于 \"%s\"\n", str1, str2);
}
return 0;
}
输出结果:
"hello" 小于 "world"
7. strncpy
函数的使用
7.1 函数功能
strncpy
用于将一个字符串的指定长度字符复制到目标字符串。如果目标长度大于源字符串长度,会填充额外的空字符 \0
。如果指定的复制长度小于源字符串的长度,则目标字符串可能不是一个以 '\0'
结尾的有效 C字符串,可能引发未定义行为。
7.2 函数声明
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
dest
:目标字符串。src
:源字符串。n
:要复制的最大字符数。- 返回值:返回目标字符串
dest
。
7.3 使用示例
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "I like KUST which is the best school in my heart";
char dest[20];
strncpy(dest, src, 11);
dest[11] = '\0'; //手动添加终止符
printf("目标字符串: %s\n", dest);
return 0;
}
输出结果:
目标字符串: I like KUST
8. strncat
函数的使用
8.1 函数功能
- Appends the first num characters of source to destination, plus a terminating null-character.(将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加一个
\0
字符)。 - If the length of the C string in source is less than num, only the content up to the terminating null-character is copied.(如果source 指向的字符串的长度小于num的时候,只会将字符串中到
\0
的内容追加到destination指向的字符串末尾)。
8.2 函数声明
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
dest
:目标字符串,需有足够空间存储结果。src
:源字符串。n
:追加的最大字符数。- 返回值:目标字符串
dest
。
8.3 使用示例
#include <stdio.h>
#include <string.h>
int main() {
char str1[20] = "I like ";
char str2[] = "KUST is the best school in my heart";
strncat(str1, str2, 4); // 只追加3个字符
printf("拼接结果: %s\n", str1);
return 0;
}
输出结果:
拼接结果: I like KUST
9. strncmp
函数的使用
9.1 函数功能
strncmp
用于比较两个字符串的前 n
个字符,并返回比较结果。
9.2 函数声明
#include <string.h>
int strncmp(const char *str1, const char *str2, size_t n);
str1
和str2
:要比较的两个字符串。n
:比较的字符数。- 返回值:
0
:相等。- 正值:
str1
大于str2
。 - 负值:
str1
小于str2
。
9.3 使用示例
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "abcdef";
char str2[] = "abcxyz";
int result = strncmp(str1, str2, 3);
if (result == 0) {
printf("前3个字符相等\n");
} else {
printf("前3个字符不相等\n");
}
return 0;
}
输出结果:
前3个字符相等
10. strstr
的使用和模拟实现
strstr
是 C 标准库中一个非常有用的字符串操作函数,用于在一个字符串中查找子字符串的第一次出现。下面将详细解析 strstr
的功能、用法、原理、模拟实现、注意事项和扩展应用。
10.1 函数功能
-
strstr
用于在字符串haystack
中查找子字符串needle
的第一次出现,并返回指向该子字符串起始位置的指针。如果未找到,返回NULL
。 -
Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.(函数返回字符串str2在字符串str1中第一次出现的位置)。
-
The matching process does not include the terminating null-characters, but it stops there.(字符串的比较匹配不包含
\0
字符,以\0
作为结束标志)。
10.2 函数声明
#include <string.h>
char *strstr(const char *haystack, const char *needle);
- 参数:
haystack
:要搜索的目标字符串。needle
:要查找的子字符串。
- 返回值:
- 如果找到子字符串,返回其在
haystack
中的起始地址。 - 如果未找到子字符串,返回
NULL
。 - 如果
needle
为空字符串,则返回haystack
的起始地址。
- 如果找到子字符串,返回其在
10.3 使用示例
#include <stdio.h>
#include <string.h>
int main() {
char haystack[] = "Kunming University of Science and Technology Faculty of Information Engineering and Automation";
char needle[] = "Technology";
char *result = strstr(haystack, needle);
if (result != NULL)
{
printf("子字符串 \"%s\" 找到了,起始位置: %s\n", needle, result);
}
else
{
printf("子字符串 \"%s\" 未找到。\n", needle);
}
return 0;
}
输出结果:
子字符串 "Technology" 找到了,起始位置: Technology Faculty of Information Engineering and Automation
10.4 模拟实现
以下是 strstr
的核心逻辑模拟实现:
#include <stdio.h>
char * my_strstr (const char * str1, const char * str2)
{
char *cp = (char *) str1;
char *s1, *s2;
if ( !*str2 )
return((char *)str1);
while (*cp)
{
s1 = cp;
s2 = (char *) str2;
while ( *s1 && *s2 && !(*s1-*s2) )
s1++, s2++;
if (!*s2)
return(cp);
cp++;
}
return(NULL);
}
int main() {
char haystack[] = "Faculty of Information Engineering and Automation";
char needle[] = "Information";
char *result = my_strstr(haystack, needle);
if (result != NULL) {
printf("子字符串 \"%s\" 找到了,起始位置: %s\n", needle, result);
} else {
printf("子字符串 \"%s\" 未找到。\n", needle);
}
return 0;
}
输出:
子字符串 "Information" 找到了,起始位置: Information Engineering and Automation
10.5 注意事项
-
如果
needle
是空字符串(""
),则strstr
返回haystack
的起始位置。 -
strstr
是大小写敏感的。如果需要忽略大小写,可以结合tolower
和toupper
函数对字符进行预处理。
11. strtok
函数的使用
strtok
是 C 标准库中的字符串处理函数,用于将字符串分割为多个子字符串(token),以指定的分隔符为分割依据。
11.1 函数功能
strtok
用于将字符串分割为一个个子字符串,分隔符由用户指定。每次调用 strtok
会返回字符串中的下一个子字符串,并用 NULL
标记字符串的结束。
11.2 函数声明
#include <string.h>
char *strtok(char *str, const char *delim);
- 参数:
str
:待分割的字符串(首次调用时提供整个字符串,之后传入NULL
表示接着上次的位置继续分割)。delim
:分隔符字符串(可包含多个字符)。
- 返回值:
- 如果找到子字符串,则返回指向该子字符串的指针。
- 如果字符串分割完成或找不到分隔符,则返回
NULL
。
11.3 注意事项
- delim参数指向一个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由delim字符串中一个或者多个分隔符分割的标记。
strtok
函数找到str中的下一个标记,并将其用\0
结尾,返回一个指向这个标记的指针。(注:strtok
函数会改变被操作的字符串,所以被strtok
函数切分的字符串一般都是临时拷贝的内容并且可修改。)strtok
函数的第一个参数不为NULL
,函数将找到str中第一个标记,strtok
函数将保存它在字符串中的位置。strtok
函数的第一个参数为NULL
,函数将在同一个字符串中被保存的位置开始,查找下一个标记。- 如果字符串中不存在更多的标记,则返回
NULL
指针。
11.4 使用示例
以下代码使用 strtok
按空格分割字符串:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "Kunming University of Science and Technology Faculty of Information Engineering and Automation";
char* sep = " ";
char* str = NULL;
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("子字符串:%s\n", str);
}
return 0;
}
输出结果:
子字符串:Kunming
子字符串:University
子字符串:of
子字符串:Science
子字符串:and
子字符串:Technology
子字符串:Faculty
子字符串:of
子字符串:Information
子字符串:Engineering
子字符串:and
子字符串:Automation
11.5 实现原理
- 首次调用:
strtok
接收完整字符串str
和分隔符delim
。- 找到第一个分隔符位置,将其替换为
\0
,以标记第一个子字符串的结束。 - 返回指向第一个子字符串的指针。
- 后续调用:
- 如果
str
参数为NULL
,strtok
会继续从上次的位置向后查找下一个子字符串。 - 找到分隔符后继续分割,直到字符串末尾。
- 如果
12. strerror
函数的使用
12.1 函数功能
strerror
是 C 标准库中的一个错误处理函数,用于将错误码(通常是 errno
)转换为人类可读的错误信息字符串。这在调试程序和输出错误信息时非常有用。
12.2 函数声明
#include <string.h>
char *strerror(int errnum);
- 参数:
errnum
:一个整数类型的错误代码(通常是全局变量errno
的值)。
- 返回值:
- 一个指向描述错误信息的字符串的指针。
- 如果
errnum
不对应任何已定义的错误代码,则返回的字符串是实现相关的。
12.3 使用示例
我们首先来认识一下错误代码对应的字符串
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
for (int i = 0; i < 42; i++)
{
printf("%d: %s\n", i, strerror(i));
}
return 0;
}
输出:
0: No error
1: Operation not permitted
2: No such file or directory
3: No such process
4: Interrupted function call
5: Input/output error
6: No such device or address
7: Arg list too long
8: Exec format error
9: Bad file descriptor
10: No child processes
11: Resource temporarily unavailable
12: Not enough space
13: Permission denied
14: Bad address
15: Unknown error
16: Resource device
17: File exists
18: Improper link
19: No such device
20: Not a directory
21: Is a directory
22: Invalid argument
23: Too many open files in system
24: Too many open files
25: Inappropriate I/O control operation
26: Unknown error
27: File too large
28: No space left on device
29: Invalid seek
30: Read-only file system
31: Too many links
32: Broken pipe
33: Domain error
34: Result too large
35: Unknown error
36: Resource deadlock avoided
37: Unknown error
38: Filename too long
39: No locks available
40: Function not implemented
41: Directory not empty
以下代码展示了 strerror
的基本用法:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main() {
FILE *file;
// 尝试打开一个不存在的文件
file = fopen("nonexistent.txt", "r");
if (file == NULL) {
// 打印errno的值和对应的错误信息
printf("错误代码: %d\n", errno);
printf("错误信息: %s\n", strerror(errno));
}
return 0;
}
输出结果(示例):
错误代码: 2
错误信息: No such file or directory
12.4 strerror
的常见用途
- 错误处理和调试:
- 在程序出错时,输出错误码对应的可读信息,便于排查问题。
- 通常配合标准库的
errno
使用。
- 日志记录:
- 将错误信息写入日志文件,便于开发者在后续分析错误原因。
- 用户提示:
- 当程序出错时,向用户展示错误原因,而不仅仅是错误代码。
12.5 实现原理
错误代码 (errno
):
errno
是一个全局变量,定义在 <errno.h>
中,用于存储最近一次函数调用的错误代码。标准库函数在出错时会设置 errno
,以便开发者通过 strerror
查询错误信息。
strerror
的工作机制:
strerror
通过errnum
查找内部的错误信息表,并返回对应的描述信息。- 如果
errnum
超出范围,则返回实现相关的默认错误信息。
12.6 扩展
可以了解一下 perror
函数,perror
函数相当于一次将上述代码中的第9行完成了,直接将错误信息打印出来。perror
函数打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{
FILE * pFile;
pFile = fopen ("unexist.ent","r");
if (pFile == NULL)
perror("Error opening file unexist.ent");
return 0;
}
输出:
Error opening file unexist.ent: No such file or directory
—完—