
函数 | 参数符合下列条件就返回真 |
|---|---|
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 | 任何可打印字符,包括图形字符和空白字符 |
在这些函数中,常用的也就只有那么三四个,判断大小写,判断是否为数字或者字母,其他的都不是很常见,故这里直接使用例子一次带过。 以下给出各个函数的用法例子,参考cplusplus.com中对函数的记载来看,以上所有函数均只需要给他们传字符即可:
int main()
{
// 构造一个“大乱炖”字符串,覆盖控制符、空白、数字、大小写、标点
char test[] = "\n\t !#09Azaz@[]";
// 指针遍历字符串,写法更加简洁
char* p = test;
puts(" char \tiscntrl\tisspace\tisdigit\tisxdig\tislower\tisupper\tisalpha\tisalnum\tispunct\tisgraph\tisprint");
puts("------\t-------\t-------\t-------\t------\t-------\t-------\t-------\t-------\t-------\t-------\t-------");
while (*p)
{
// 避免负数下标陷阱
unsigned char ch = (unsigned char)*p;
// 逐函数测试,真返回 1,假返回 0
printf("%3c\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
(isprint(ch) ? ch : '?'), //不可打印显示 '?'
iscntrl(ch),
isspace(ch),
isdigit(ch),
isxdigit(ch),
islower(ch),
isupper(ch),
isalpha(ch),
isalnum(ch),
ispunct(ch),
isgraph(ch),
isprint(ch));
p++;
}
return 0;
}
unsigned char ch = (unsigned char)*p;这句代码是非常有必要的!!!原因如下:char默认为 signed:范围 −128~127。isxxx()要求下标 0~255;负值 → 数组越界 → UB(未定义行为)。 强制unsigned char范围 0~255,安全。 这个不注意的话会被支配很久。
接下来进行输出可得到:

\t、\n已经被正确识别为控制符。不知道大家有没有这个问题,在刚开始时不是只有
\一个字符吗?为什么它会连带后面的字符一起被识别为控制字符?
\n在 C 源码里看起来是两个字符,但编译器只把它当成一个字符—换行符(ASCII 10)。所以只会被读取 一次,不会拆成 \ + n。这些非零值只是内部实现用的位标志,逻辑上仍然当真用。 调试器显示:原始位掩码 → 可能出现 2/4/8/16/128 等,只要 非 0 就是真。
在这里只对常用的四个函数进行实现,也就是isdigit(ch)、islower(ch)、isupper(ch)、isalpha(ch)其他的不怎么常见常用,这里就忽略了。
在实现这几个函数时,可以通过ASCII码进行实现,也可以直接进行比较,实际上也是使用ASCII码进行对比,那就直接进行对比吧。
// 判断字符是否是数字字符
int is_digit(char ch)
{
return (ch >= '0' && ch <= '9');
}
// 判断字符是否是小写字母
int is_lower(char ch)
{
return (ch >= 'a' && ch <= 'z');
}
// 判断字符是否是大写字母
int is_upper(char ch)
{
return (ch >= 'A' && ch <= 'Z');
}
// 判断字符是否是字母(大写或小写)
int is_alpha(char ch)
{
return (is_lower(ch) || is_upper(ch));
}实现也是比较简单的,只需要直接跟边界字符进行比较,判断字符是否在界内即可。
tolower函数也比较简单,通过名称可以得出,就是将参数传进去的⼤写字⺟转⼩写。
toupper而toupper则与tolower刚好相反,将参数传进去的⼩写字⺟转⼤写。
从ASCII码表可以看出,每一对大小写字母之间的ASCII码差值均为32,故实现时可以直接使用ASCII码加减32得到。
这里
A与a的ASCII码应该记住,大写在前,65,小写在后,97,差值32

// 将字符转换为大写字母
char to_upper(char ch)
{
// 如果字符是小写字母,则将其转换为大写
if (ch >= 'a' && ch <= 'z')
return ch - 32; // 小写字母转换为大写字母
return ch; // 其他字符保持不变
}
// 将字符转换为小写字母
char to_lower(char ch)
{
// 如果字符是大写字母,则将其转换为小写
if (ch >= 'A' && ch <= 'Z')
return ch + 32; // 大写字母转换为小写字母
return ch; // 其他字符保持不变
}strlen的使⽤和模拟实现size_t strlen ( const char * str );'\0'作为结束标志,strlen函数返回的是在字符串中 '\0' 前⾯出现的字符个数(不包含 '\0' )。'\0' 结束。<string.h>模拟实现
// 遍历⽅式
int my_strlen(const char* str)
{
int count = 0;
while (*str)
{
count++;
str++;
}
return count;
}
// 递归方式
int my_strlen(const char* str)
{
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
//指针-指针的⽅式
int my_strlen(char* s)
{
char* p = s;
while (*p != '\0')
p++;
return (p - s);
}\0即可。元素数量,不是字节数,如果你想计算字节数,需要乘上相应指针类型所占的字节数,也就是sizeof(指针类型)。strcpy 和strncpy的使⽤和模拟实现strcpy函数功能:将源指向的 C 字符串复制到目标指向的数组中,包括终止的空字符(并在该位置停止)。
// 模拟 strcpy 函数
char* my_strcpy(char* destination, const char* source)
{
char* dest = destination; // 保存目标指针的起始位置
// 循环遍历源字符串,直到遇到空字符 '\0'
while (*source != '\0')
*destination++ = *source++; // 将源字符复制到目标位置 并同时移动
*destination = '\0'; // 在目标字符串末尾添加 '\0',标志结束
return dest; // 返回目标字符串的起始位置
}这里的复制就相当于直接覆盖,且是全部复制,而
strncpy会有长度这个参数
strncpy函数详解:
char * strncpy ( char * destination, const char * source, size_t num );'\0' 后就结束了,那么目标字符串将用零字符 '\0' 填充,直到目标字符串的长度达到 num。'\0'。'\0' 终止符的情况: '\0',表示源字符串的结束,目标字符串会被填充为零字符,直到目标字符串的长度达到 num。'\0')以及填充的零字符。'\0'),目标数组的剩余部分将用 '\0' 填充,直到达到 num 字符。虽然函数会在源字符串结束后用
'\0'填充目标数组,但是在复制操作中,如果目标数组的大小不足以容纳 num 个字符,且没有正确保证终止符的位置,则可能会导致目标数组溢出。这时候需要手动添加终止符。
void my_strncpy(char* destination, const char* source, size_t num) 、
{
size_t i = 0;
// 复制源字符串中的字符
while (i < num && source[i] != '\0') {
destination[i] = source[i];
i++;
}
// 如果源字符串长度小于 num,填充剩余部分为 '\0'
while (i < num) {
destination[i] = '\0';
i++;
}
}这里只实现了简单版本,需要具体情况具体分析。
strcat和strncat的使⽤和模拟实现strcat功能:把 source 指向的 C 字符串复制到 destination 现有内容的后面。'\0';'\0' 一起拷贝,保证新串也以 '\0' 结束。'\0' 被第一 个 source 字符覆盖,所以新串是destination + source。使用前提
'\0' 结束:否则不知道在哪里停,拷到越界为止,未定义行为(UB)。'\0':否则不知道从哪儿开始追加(找不到“尾巴”),同样 UB。strcat 是前向拷贝,没有像 memmove 那样处理重叠的保障;一旦写入覆盖了还没读的源数据,逻辑就混乱了:可能死循环、无限增长、崩溃,结果不可预期。// 模拟 strcat 函数
char* my_strcat(char* dest, const char* src)
{
char* d = dest;
// 找到dest的结尾 \0
while (*d != '\0')
d++;
// 逐字节拷贝src(含终止符)
while (*src != '\0')
*d++ = *src++;
// 手动添加终止符
*d = '\0';
return dest;
}这段实现假定:
dest与src都是以'\0'结束的有效C串、两者不重叠,且 dest 有充足空间。
strncat:char* strncat(char* destination, const char* source, size_t num);
'\0' 终止符:strncat 会在目标字符串 destination 的末尾追加一个 '\0' 终止符,确保结果是一个有效的 C 字符串。'\0',并把复制的字符放在目标字符串的末尾。'\0'),并不会超出源字符串的结束符。此时,source 字符串的结束符 '\0' 会追加到目标字符串。strncat 使用时的注意事项:
strlen(destination) + num + 1。'\0' 会被覆盖,故调用 strncat 之前,目标字符串必须以 '\0' 结束。'\0',调用 strncat 时会造成未定义行为。// 模拟 strncat 函数
char* my_strncat(char* destination, const char* source, size_t num)
{
char* d = destination;
// 找到destination的末尾(\0)
while (*d != '\0')
d++;
// 复制source的前num个字符到destination末尾
while (num-- > 0 && *source != '\0')
*d++ = *source++;
// 在目标字符串末尾添加\0
*d = '\0';
return destination;
}strcmp和strncmp的使⽤和模拟实现'\0' 为止。'\0'。比较字符的 ASCII 值: '\0'。strtcmp模拟实现:int my_strcmp(const char* str1, const char* str2)
{
while (*str1 == *str2) // 1、比较当前字符是否相等
{
if (*str1 == '\0') // 2、如果正好两边都到 '\0',说明完全相等
return 0;
str1++; // 3、同时推进到下一个字符
str2++;
}
return *str1 - *str2; // 4、第一个不等字符的差值作为结果
}strncmp函数:int strncmp ( const char * str1, const char * str2, size_t num );
'\0' 时停止)。
返回值含义与strcmp返回值一致。
'\0' 结尾:用 strncmp 可以,但确保 至少有 n 个可读字节,避免越界。// 模拟 strncmp 函数
int my_strncmp(const char* s1, const char* s2, size_t n)
{
while (n--) // 只比较前 n 个字符
{
unsigned char c1 = (unsigned char)*s1++;
unsigned char c2 = (unsigned char)*s2++;
if (c1 != c2) // 一旦不相等,直接返回差值
return c1 - c2;
if (c1 == '\0') // 若遇到字符串结尾,提前退出
return 0;
}
return 0; // 前 n 个字符都相同
}strstr的使⽤和模拟实现char* strstr(const char* haystack, const char* needle);NULL。'\0',但遇到 '\0' 即停止。约定:若 needle 是空串(“”),必定返回 haystack(主串开头)。
needle[0] == '\0' → 直接返回 haystack。// 模拟 strstr 函数
char* strstr(const char* str1, const char* str2)
{
char* cp = (char*)str1;
char* s1, * s2;
if (!*str2) // ① 子串为空:按标准直接返回主串开头
return (char*)str1;
while (*cp)
{ // ② 从主串的每个位置 cp 开始尝试匹配
s1 = cp;
s2 = (char*)str2;
while (*s1 && *s2 && !(*s1 - *s2))
s1++, s2++; // ③ 两指针前进:逐字符比较
if (!*s2) // ④ 当 s2 走到 '\0',说明子串已完整匹配
return cp;
cp++; // ⑤ 否则从主串下一位置继续
}
return NULL; // ⑥ 扫描完整个主串也没有匹配
}strtok 函数的使⽤char *strtok(char *str, const char *sep);工作方式:
'\0' 停止;若是分隔符就改成 '\0',返回当前 token。边界与易错点
1. 不能传字符串字面量:strtok("a,b", ",") → 修改常量区,未定义行为;必须用可写数组或堆内存。
2. 多分隔符连在一起:中间会被跳过,不返回空 token。若需要空 token,请用 strsep 或手写解析。
3. 空分隔符集:sep="" → 没有字符可当分隔符,第一次返回整个字符串,后续都返回 NULL。
4. 空字符串:str="" 或第一次调用后就是 '\0' → 直接返回 NULL。
strtok 用于“就地切分、逐个取 token”的简单场景;会修改原串、跳过连续分隔符且非可重入。
// strtok函数示例
static void test2()
{
char arr[] = "192.168.6.111";
char* sep = ".";
char* str = NULL;
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
printf("%s\n", str);
}strerror 函数的使⽤char *strerror(int errnum);// strerror函数使用示例
void test3()
{
int i = 0;
for (i = 0; i <= 10; i++)
printf("%s\n", strerror(i));
}简单理解就是将错误信息打印出来:

perror函数// perror函数
void test4()
{
FILE * pFile;
pFile = fopen("unexist.ent", "r");
if (pFile == NULL)
perror("Error opening file unexist.ent");
}那他们两者的区别也可以简单了解一下:
方式 | 优点 | 缺点 |
|---|---|---|
strerror(errno) + printf | 可自定义格式/重定向 | 需要自己保存errno;非可重入 |
perror("prefix") | 简单、一行搞定;自动读errno | 输出格式固定、总是到 stderr |
这些简单了解了解,需要用的时候再深入学习即可。