目录
- 一、字符函数
-
- [1. 字符分类函数](#1. 字符分类函数)
-
- [1.1 练习](#1.1 练习)
- [2. 字母大小写转换函数](#2. 字母大小写转换函数)
- 二、字符串函数
-
- [1. strlen() 函数的使⽤和模拟实现](#1. strlen() 函数的使⽤和模拟实现)
- [2. strcpy() 函数的使用和模拟实现](#2. strcpy() 函数的使用和模拟实现)
- [3. strcat() 函数的使用和模拟实现](#3. strcat() 函数的使用和模拟实现)
- [4. strcmp() 函数的使用和模拟实现](#4. strcmp() 函数的使用和模拟实现)
- [5. strncpy() 函数的使用和模拟实现](#5. strncpy() 函数的使用和模拟实现)
- [6. strncat() 函数的使用和模拟实现](#6. strncat() 函数的使用和模拟实现)
- [7. strncmp() 函数的使用和模拟实现](#7. strncmp() 函数的使用和模拟实现)
- [8. strstr() 函数的使用和模拟实现](#8. strstr() 函数的使用和模拟实现)
- [9. strtok() 函数的使用和画图模拟](#9. strtok() 函数的使用和画图模拟)
- [10. strerror() 函数的使用](#10. strerror() 函数的使用)
一、字符函数
1. 字符分类函数
在 C 语言标准库中,有一系列函数是专门用来给字符分类的,也就是判断该字符是什么类型的字符,如:数字字符、字母字符等。这些函数都包含在头文件 ctype.h 中。如下图所示:
这些函数的功能都非常相似,只要传入的字符满足其条件,就返回真,否则返回假。下面使用判断小写字符函数 islower() 来进行演示:
其他字符分类函数同样如此。
1.1 练习
题目: 使用字符分类函数实现把字符串中的小写字母,转换为大写字母。
分析: 首先,遍历字符串,判断当前字符是不是小写字符,如果是小写字符则减 32(在 ASCII 码中,小写字母比对应的大写字母大 32)。
代码如下:
c
// 头文件
#include <stdio.h>
#include <ctype.h>
int main()
{
// 把字符串中的小写字母转换为大写字母
char tmp[] = "Hello, world.";
// 遍历字符串
int i = 0;
while (tmp[i])
{
// 如果是小写字母,转换为大写
if (islower(tmp[i]))
tmp[i] -= 32;
// 下一个字符
++i;
}
// 输出验证
printf("%s\n", tmp);
return 0;
}
代码运行结果如下:
2. 字母大小写转换函数
下面两个字母转换函数同样包含在头文件 ctype.h 中:
tolower() 函数的功能是传入大写字母,然后返回对应的小写字母,如果不是大写字母则返回原字符。toupper() 函数与其类似。
有了上述这两个函数,就可以把上一个练习给修改一下:
c
// 头文件
#include <stdio.h>
#include <ctype.h>
int main()
{
// 把字符串中的小写字母转换为大写字母
char tmp[] = "Hello, world.";
// 遍历字符串
int i = 0;
while (tmp[i])
{
// 如果是小写字母,转换为大写
tmp[i] = toupper(tmp[i]);
// 下一个字符
++i;
}
// 输出验证
printf("%s\n", tmp);
return 0;
}
二、字符串函数
1. strlen() 函数的使⽤和模拟实现
函数原型:
c
// strlen() 函数原型
size_t strlen ( const char * str );
函数功能:
给 strlen() 传入一个字符指针,然后从该地址往后统计字符个数,直到遇到空字符,然后返回统计的字符总个数,不包括空字符。
注意事项:
(1)确保传入地址后面有确定的空字符
(2)函数返回类型为 size_t,也就是无符号整型 unsigned int
(3)需要包含头文件 string.h
使用示例:
(1)正确示例,传入字符地址后面有确切的空字符
(2)错误示例,传入地址后面没有确切的空字符
该字符数组末尾没有空字符,但是函数 strlen() 会往该数组后面继续计算字符个数,直到遇到空字符,所以最终打印的结果是随机数。现在有的编译器已经可以对这种错误发出警告了。
模拟实现:
这个函数我们有三种模拟实现的方法:分别是函数递归法、指针相减法和变量计数法。
(1)函数递归法(递归的两个条件:有限制条件,当不满足这个限制条件就跳出递归;每次递归都更加接近这个限制条件)
c
// 1. 函数递归法
size_t my_strlen1(const char* str)
{
// 如果不是空字符,就递归
if (*str)
return 1 + my_strlen1(str + 1);
else
return 0;
}
(2)指针相减法(当两个指针指向同一块空间时,它们相减的结果就是它们之间的元素个数或距离)
c
// 2. 指针相减法
size_t my_strlen2(const char* str)
{
const char* end = str;
// 寻找字符串空字符的地址
while (*end)
{
++end;
}
// 利用指针指向同一块空间想减的结果为两个指针之间相差的元素个数
return end - str;
}
(3)变量计数法
c
// 3. 变量计数法
size_t my_strlen3(const char* str)
{
size_t i = 0;
while (*str)
{
++i;
// 下一个字符
++str;
}
// 返回
return i;
}
2. strcpy() 函数的使用和模拟实现
函数原型:
c
// strcpy() 函数原型
char* strcpy(char * destination, const char * source );
函数功能:
给函数传入两个字符串------目标字符串和源字符串。把源字符串的内容拷贝到目标字符串中,包括结尾的空字符,然后返回目标字符串的首地址。
注意事项:
(1)必须确保目标字符串的大小可以容纳源字符串
(2)需要包含头文件 string.h
使用示例:
(1)正确示例,目标字符串足够容纳源字符串
(2)错误示例,目标字符串不够容纳源字符串
虽然程序运行确实输出了我们想要的结果,但是却显示了错误信息。这是因为目标字符串 s1 的空间不足以存储字符串 s2,所以函数 stcpy() 使用了目标字符串 s1 后面的空间来存放源字符串 s2 剩下的字符,造成了越界访问。
模拟实现:
c
// 模拟实现库函数 strcpy()
char* my_strcpy(char* dest, const char* src)
{
// 存储目标字符串地址
char* ret = dest;
// 拷贝
while (*dest++ = *src++)
continue;
// 返回
return ret;
}
表达式 *dest++ = *src++ 中,后置递增运算符的优先级最高,所以先计算,然后解引用得到两个指针指向的字符,再赋值,然后两个指针都指向下一个字符。如此往复,直到拷贝空字符结束循环。
3. strcat() 函数的使用和模拟实现
函数原型:
c
// strcat() 函数原型
char *strcat(char *dest, const char *src);
函数功能:
给该函数传入两个字符串------目标字符串和源字符串。把源字符串的内容拼接到目标字符串的后面,从目标字符串的空字符处开始拼接。
注意事项:
(1)必须确保目标字符串足够容纳拼接后的字符串
(2)必须包含头文件 string.h
使用示例:
(1)正确示例,目标字符串可以容纳拼接后的字符串
(2)错误示例,目标字符串不能容纳拼接后的字符串
上面这个错误和前面 strcpy() 函数中的错误示例的原因一样,由于目标字符串的空间不足以容纳拼接后的字符串,所以会使用字符串 s1 后面的空间,造成越界访问。
模拟实现:
该函数的需要先定位到目标字符串的空字符的位置,然后按照 strcpy() 的方法拷贝就行。
c
// 模拟实现库函数 strcat
char* my_strcat(char* dest, const char* src)
{
// 保存目标字符串地址
char* ret = dest;
// 定位目标字符串空字符
while (*dest)
++dest;
// 拷贝
while (*dest++ = *src++)
continue;
// 返回
return ret;
}
4. strcmp() 函数的使用和模拟实现
函数原型:
c
// strcmp() 函数原型
int strcmp(const char *s1, const char *s2);
函数功能:
给函数传入两个字符串,然后通过 ASCII 编码或者其他编码对两个字符串逐个字符进行比较,如果前者大于后者,返回一个大于 0 的数;如果前者小于后者返回一个小于 0 的数;如果前者等于后者就继续比较下一对字符,直到两者均为空字符,返回 0。
注意事项:
(1)包含头文件 string.h
使用示例:
模拟实现:
c
// 模拟实现库函数 strcmp()
int my_strcmp(const char* s1, const char* s2)
{
while (*s1 == *s2)
{
// 如果两个字符串同时到达末尾,则相同
if ('\0' == *s1)
return 0;
// 下一对字符
++s1, ++s2;
}
// 当前比较字符不相同,返回其 ASCII 编码差值
return *s1 - *s2;
}
5. strncpy() 函数的使用和模拟实现
函数原型:
c
// strncpy() 函数原型
char* strncpy(char* dest, const char* src, size_t n);
函数功能:
如果源字符串的长度小于 n,那么在复制完源字符串后用空字符补齐 n 的字符。如果源字符串长度大于或等于 n,那么就只拷贝源字符串中的前 n 个字符,不在拷贝的末尾加上空字符。
注意事项:
(1)确保目标字符串能够存储源字符串的前 n 个字符,还需要考虑是否需要手动在末尾添加空字符
(2)需要包含头文件 string.h
使用示例:
(1)正确示例,目标字符串可以容纳源字符串的前 n 个字符
(2)错误示例,目标字符串不能容纳源字符串的前 n 个字符
由于目标字符串 s1 不能容纳源字符串 s2 的前六个字符,所以使用了字符串 s1 后面的空间来存储,并且没有在末尾添加空字符,所以造成了越界访问。
模拟实现:
c
// 模拟实现库函数 strncpy()
char* my_strncpy(char* dest, const char* src, size_t n)
{
// 存储目标字符串地址
char* ret = dest;
// 拷贝
while (n--)
{
if (*src)
*dest++ = *src++;
else
*dest++ = '\0';
}
// 返回
return ret;
}
6. strncat() 函数的使用和模拟实现
函数原型:
c
// strncat() 函数原型
char* strncat(char* dest, const char* src, size_t n);
函数功能:
把源字符串拼接到目标字符串后面,直到拼接 n 个字符后者遇到空字符结束,最后在拼接好的字符串后面添加空字符。
注意事项:
(1)确保目标字符串能够容纳拼接后的字符串
(2)包含头文件 string.h
使用示例:
(1)正确示例,目标字符串足够容纳拼接后的字符串
(2)错误示例,目标字符串不能容纳拼接后的字符串
这里的错误和 strcat() 函数一样,但是 strncat() 函数做的更好的地方在于,它在越界访问之后在末尾添加了空字符。
模拟实现:
c
// 模拟实现库函数 strncat()
char* my_strncat(char* dest, const char* src, size_t n)
{
// 存储目标字符串地址
char* ret = dest;
// 定位目标字符串空字符
while (*dest)
++dest;
// 拼接
while (n--)
{
if (*src)
*dest++ = *src++;
else
break;
}
// 末尾添加空字符
*dest = '\0';
// 返回
return ret;
}
7. strncmp() 函数的使用和模拟实现
函数原型:
c
// strncmp() 函数原型
int strncmp(const char* s1, const char* s2, size_t n);
函数功能:
该函数最多比较两个字符串的前 n 个字符,如果第一个字符串大于第二个字符串,返回正整数,如果第一个字符串小于第二个字符串,返回负整数,否则返回 0。
函数注意事项:
(1)包含头文件 string.h
函数示例:
函数模拟实现:
c
// 模拟实现库函数 strncmp()
int my_strncmp(const char* s1, const char* s2, size_t n)
{
while (n-- && *s1 == *s2)
{
if ('\0' == *s1)
return 0;
// 下一组字符
++s1, ++s2;
}
// 返回
return *s1 - *s2;
}
8. strstr() 函数的使用和模拟实现
函数原型:
c
// strstr() 函数原型
char* strstr(const char* dest, const char* find);
函数功能:
在目标字符串 dest 中,从前往后查找字符串 find,如果找到了返回首地址,没有找到返回空指针。
注意事项:
(1)不能传入空指针
使用示例:
模拟实现:
从目标字符串第一个字符开始,与带查找的字符串进行比较,如果相同就返回目标字符串第一个字符的位置,否则就从目标字符串下一个字符查找。直到找到带查找字符串或者最后一次查找结束。
c
// 模拟实现库函数 strstr()
char* my_strstr(const char* dest, const char* find)
{
// 计算查找次数
int len1 = (int)strlen(dest);
int len2 = (int)strlen(find);
int times = len1 - len2 + 1;
// 查找
while (times-- > 0)
{
if (strncmp(dest, find, len2) == 0)
return (char*)dest;
// 下一次
++dest;
}
// 没找到
return NULL;
}
9. strtok() 函数的使用和画图模拟
函数原型:
c
// strtok() 函数原型
char *strtok(char *str, const char *delim);
函数功能:
一、分割过程
首次调用strtok()时,它以传入的字符串作为输入,并以指定的分隔符集合(由第二个参数指定)来识别子字符串的边界。它会在输入字符串中找到第一个不是分隔符的字符,并将其作为第一个标记的开始位置。然后,它继续扫描字符串,直到找到下一个分隔符或字符串结束标志。此时,它会将找到的分隔符替换为字符串结束符'\0',从而将第一个标记从原始字符串中分离出来。函数返回指向这个标记的指针。
后续调用strtok()时,如果第一个参数为NULL,函数将继续从上次分割的位置开始在原始字符串中查找下一个标记。这个过程会一直持续,直到找不到更多的标记,此时函数返回NULL。
二、分隔符处理
strtok()函数会记住它在原始字符串中的位置,以便在后续调用时继续分割。同时,它会修改原始字符串,将分隔符替换为'\0',这意味着原始字符串被永久性地修改了。
注意事项:
(1)包含头文件 string.h
示例演示:
c
char str[] = "My QQ email is @qq.com1234567890.";
char delimiters[] = "@.";
char* ret = strtok(str, delimiters);
while (NULL != ret)
{
// 打印当前分隔字符串
printf("%s\n", ret);
// 下一次分隔
ret = strtok(NULL, delimiters);
}
画图演示:
10. strerror() 函数的使用
函数原型:
c
// strerror() 函数原型
char *strerror(int errnum);
函数功能:
strerror函数的主要功能是将错误码(通常是整数类型)转换为对应的错误信息字符串。
具体来说:
一、错误码转换
当程序在运行过程中出现错误时,很多系统函数和库函数会返回一个错误码来表示具体的错误情况。例如,文件操作函数可能在无法打开文件时返回特定的错误码。strerror函数接收一个错误码作为参数,然后在内部查找与该错误码对应的错误信息描述,并返回一个指向该描述字符串的指针。
二、错误信息的可理解性
通过strerror函数,程序员可以将晦涩的错误码转换为易于理解的错误信息,从而更方便地诊断和处理程序中的错误。例如,如果错误码表示 "文件不存在",strerror函数可能会返回字符串 "File not found"。
函数讲解:
在程序运行过程中会出现很多错误,而每种错误对应一个错误码,错误码存储在全局变量 errno 中。该函数的作用是,把该错误码转换为对应的错误信息字符串,然后返回该字符串的首地址。
函数注意事项:
(1)包含头文件 errno.h
使用示例:
函数 fopen() 的作用是以某种方式打开一个文件,如果打开成功则返回该文件的指针,如果打开失败则返回空指针。
可以看到显示错误信息,没有该文件或文件夹,因为这个文件名是我乱写的,实际并没有。
可以了解一下 perror() 函数,该函数直接打印错误信息,不需要传递错误码,现在用该函数来实现上述代码:
可以给该函数传递字符串,让其显示出错的函数,这样方便快速定位该函数。