C语言第19讲
目录
字符函数和字符串函数
字符分类函数
字符转换函数
strlen 的使用和模拟实现
strcpy 的使⽤和模拟实现
strcat 的使用和模拟实现
strcmp 的使用和模拟实现
strlen strcpy strcat 和 strnlen strncpy strncat 的区别
strncpy的特性:
strncat的特性:
strncpy的特性:
strstr 函数
strtok 函数
字符函数和字符串函数
字符分类函数
C语⾔中有⼀系列的函数是专⻔做字符分类的,也就是⼀个字符是属于什么类型的字符的。 这些函数的使⽤都需要包含⼀个头⽂件是 ctype.h
isalnum() - 检查字符是否是字母或数字
isalpha() - 检查字符是否是字母
iscntrl() - 检查字符是否是控制字符
isdigit() - 检查字符是否是十进制数字
isgraph() - 检查字符是否是可打印字符(不包括空格)
islower() - 检查字符是否是小写字母
isprint() - 检查字符是否是可打印字符(包括空格)
ispunct() - 检查字符是否是标点符号
isspace() - 检查字符是否是空白字符
isupper() - 检查字符是否是大写字母
isxdigit() - 检查字符是否是十六进制数字
这类函数的用法都类似,所以只举一个例字
例:利用islower写⼀个代码,将字符串中的⼩写字⺟转⼤写,其他字符不变。
代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>int main()
{char ch[] = "Hello World";int i = 0;while (ch[i]){if (islower(ch[i])){ch[i] -= (char)32;}i++;}printf("%s ", ch);return 0;
}
传参的字符为小写 islower 返回为非0,
为大写 islower 返回为0
字符转换函数
int tolower ( int c ); // 将参数传进去的⼤写字⺟转⼩写
int toupper ( int c ); // 将参数传进去的⼩写字⺟转⼤写
现可以将上方代码进行改进
代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>int main()
{char ch[] = "Hello World";int i = 0;while (ch[i]){if (islower(ch[i])){ch[i] = toupper(ch[i]); // 这是传值调用不会将传参的变量// 直接修改掉还需要接受返回值}i++;}printf("%s ", ch);return 0;
}
strlen 的使用和模拟实现
作用:由传参的地址开始计算至\0前的字符个数
使用方式如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main()
{char ch[] = "Hello World";printf("%d", strlen(ch));return 0;
}
模拟及实现:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>size_t my_strlen(const char* p)
{assert(p);const char* p2 = p;while (*p){p++;}return p - p2;
}int main()
{char ch[] = "Hello World";printf("%zd", my_strlen(ch));return 0;
}
strcpy 的使⽤和模拟实现
strcpy的作用:将一个字符数组的复制到另一个字符数组中
声明:
char* strcpy(char * destination, const char * source );
destination 为要粘贴处
source 为复制处
char* 返回粘贴处的首元素地址
ctrcpy的使用:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main()
{char ch1[] = "hello world"; // 复制数组具有\0char ch2[30] = { 0 }; // 要确保空间最够// 避免越界访问char* ret = strcpy(ch2, ch1); printf("%s\n", ret);printf("%s\n", ch2);return 0;
}
strcpy的模拟实现:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>char* my_strcpy(char* p1, const char* p2) // p1为粘贴处 需要修改不用const// p2为复制处 不需要修改 保证安全使用const
{char* ret = p1; // 确保返回值为粘贴处首元素地址assert(p1 && p2); // 确保 p1 和 p2都不为空指针 NULL = (void*)0while (*p1++ = *p2++) // 当*p2都为字符时,赋值给*p1 导致*p1不为\0,判断为真; // 当*p2为\0时,赋值给*p1 导致*p1为\0,判断为假,跳出循环// 可以直接 后置加的原因是,跳出循环后函数结束// 所以p1 p2指向\0后的空间不会被使用return ret;
}int main()
{char ch1[] = "hello world";char ch2[30] = { 0 }; char* ret = my_strcpy(ch2, ch1);printf("%s\n", ret);printf("%s\n", ch2);return 0;
}
strcat 的使用和模拟实现
作用:将一个一串字符串链接到另一字符串上
声明:
char* strcpy(char * destination, const char * source );
和strcpy相同
destination 为修改处
source 为复制处
返回 修改处整改数组的首元素地址
运行逻辑:
先找到 修改处\0结尾处
开始将复制处的字符复制到修改处
直到复制处遇上\0停止
其实仔细琢磨后两步骤会发先和strcpy的操作一致,只是多了一步寻找\0的地址
strcat函数的使用:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main()
{char ch1[30] = "hello ";char* p = "world";char * ret = strcat(ch1, p);printf("%s\n", ret);printf("%s\n", ch1);return 0;
}
strcat的模拟实现:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>char* my_strcat(char* p1, const char* p2)
{assert(p1 && p2);char* ret = p1;while (*p1)p1++; // ++ 不能写在判定表达式中// 因为*p1为\0跳出循环会多一次++,导致替换不完整while (*p1++ = *p2++);return ret;
}int main()
{char ch1[30] = "hello ";char* p = "world";char * ret = my_strcat(ch1, p);printf("%s\n", ret);printf("%s\n", ch1);return 0;
}
问题:如果传参同一字符串呢?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>char* my_strcat(char* p1, const char* p2) // const char* p2// 只是禁止通过p2去修改指针内容// 不是禁止指向内容不能被修改;程序问题不在这
{assert(p1 && p2);char* ret = p1;while (*p1)p1++; while (*p1++ = *p2++);return ret;
}int main()
{char ch1[30] = "hello";char * ret = my_strcat(ch1, ch1);return 0;
}
当p1找到\0后开始替换,\0被替换成 h导致 p2字符串没有\0
由于 p1 和 p2 的偏移速度一致会导致,整个系统循环下去
直到越界访问,程序崩溃
strcmp 的使用和模拟实现
strcmp的作用:比较两字符串大小
声明:
int strcmp (const char * str1, const char * str2)
str1 和 str2 为进行比较的两字符串首元素地址
int 会根据 str1 和 str2的大小返回值
*str1 大 返回 > 0 VS 的实现 1
相等 返回 = 0 0
*str1 小 返回 < 0 -1
strcmp的运行逻辑:
本质是利用字符在ASCII表中的数字大写比较大小(\0 为 0)
按顺序取出字符进行比较,出现不相等时则出现大小
同时出现 \0则为相等
strcmp的使用:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main()
{char ch1[30] = "abd";char* p = "abc";if (strcmp(ch1, p) > 0){printf("ch1大");}else if (strcmp(ch1, p) == 0){printf("相等");}elseprintf("ch1小");return 0;
}
strcmp的模拟实现:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>int my_strcmp(const char* p1, const char* p2)
{assert(p1 && p2);while (*p1 == *p2 ) // 将相同剔除{ if (*p1 == '\0') // 相同的情况为{ // 1.字符相同 2.字符为\0return 0; // P1 \0 或 P2 \0 都为不同归纳出大小比较}p1++;p2++;}return (int)(*p1 - *p2);
}int main()
{char ch1[30] = "abd";char* p = "abc";if (my_strcmp(ch1, p) > 0){printf("ch1大");}else if (my_strcmp(ch1, p) == 0){printf("相等");}elseprintf("ch1小");return 0;
}
strlen strcpy strcat 和 strnlen strncpy strncat 的区别
strcpy strncpy
strcat strncat
strcmp strncmp
后者只是多了个产数去限制函数内部进行的次数
后者声明:
int strncmp ( const char * str1, const char * str2, size_t num );
区别只是多了次数参数,所以后置比较安全
strncpy的特性:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "abcdef";char arr2[20] = "xxxxxxxxxxxxxxx";strncpy(arr2, arr1, 3); // 次数小于arr字符个数 不会在后方加\0printf("%s\n", arr2);char arr3[] = "abcdef";char arr4[20] = "xxxxxxxxxxxxxxx";strncpy(arr4, arr3, 8); // 次数大于arr字符个数 才在后面加\0printf("%s\n", arr4);return 0;
}
strncat的特性:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "abcdef";char arr2[20] = "xx\0xxxxxxxxxxxx"; // 会在后方放\0strncat(arr2, arr1, 3);printf("%s\n", arr2);char arr3[] = "abcdef";char arr4[20] = "xx\0xxxxxxxxxxxx"; // 在arr3第7位时会放\0 第8位不会管了strncat(arr4, arr3, 8);printf("%s\n", arr4);return 0;
}
strncpy的特性:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "abq";char* p = "abc";if (strncmp(arr1, p, 2) > 0){printf("arr1大");}else if (strncmp(arr1, p, 2) == 0){printf("一样大");}else{printf("arr1小");}return 0;
}
只进行2次判断
strstr 函数
作用:在一个字符串中查找另一个字符串
声明:
char * strstr ( const char * str1, const char * str2);
str2 查找的模版字符串
str1 被查找的字符串
char* 根据成功与否返回
失败 返回NULL
成功 返回标记地址
例:在abbc中查找bc 返回为abbc字符串第3个元素的地址
strstr 的使用:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main()
{char ch1[] = "abbcooobcox";char* p = "bc";char* ret =strstr(ch1, p);printf("%s", ret);return 0;
}
strstr 的模拟实现:
情形1:
char* my_strstr(const char* p1, const char* p2)
{const char* s1 = p1; // 后期写函数如果常该传参过来的指针的话很难找回const char* s2 = p2; // 所以创建其余指针进行移动while(*s1){;}s1++;return NULL;
}
情形2:
char* my_strstr(const char* p1, const char* p2)
{const char* s1 = p1; const char* s2 = p2; const char* ret = NULL;while(*s1){ret = s1;while(*s1 && *s2 &&*s1 == *s2) // 只有不等和 出现\0才会跳出循环{ // *s1出现\0;外层循环会跳出,返回NULLs1++; // *s2出现\0;内存有if判断,返回标记指针s2++;}if (*s2 == '/0')return ret; // 现在成功找寻到了,但未有标记最开始==时的指针s1++; } // 还需要多创建个指针用于保存 成功时的返回值return NULL;
}
情形3:
char* my_strstr(const char* p1, const char* p2)
{const char* s1 = p1; const char* s2 = p2; const char* ret = NULL;while(*s1){ret = s1;while(*s1 && *s2 &&(*s1 == *s2)) { s1++; s2++;}if (*s2 == '\0') // *s1 *s2 都不是 \0 时 *s1 == *s2return ret; // 所以s1 要恢复到 ret去// s2 要恢复到 p2去s1 = ret;s2 = p2;s1++;} return NULL;
}
运行逻辑为:
外循环对被查字符串一个字符一个字符检查
一旦发现相同进入内循环,跳出内循环的结果只有:
1.*s1 为 \0 查找失败,没找到
2.*s2 为 \0 查找成功,返回开始ret
因为ret一直处于外循环
3. *s1 != *s2 找到了相同处,
但并不完全相同;需要重置外循环指针;
情形4:
str2传过来一个空字符串时
加上if后并稍作加强安全性,代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>char* my_strstr(const char* p1, const char* p2)
{if (*p2 == '\0')return (char*)p1;assert(p1 && p2);const char* s1 = p1; const char* s2 = p2; const char* ret = NULL;while(*s1){ret = s1;while(*s1 && *s2 &&(*s1 == *s2)) { s1++; s2++;}if (*s2 == '\0') return (char*)ret; s1 = ret;s2 = p2;s1++;} return NULL;
}
int main()
{char ch1[] = "abbcooobcox";char* p = "bc";char* ret =my_strstr(ch1, p);printf("%s", ret);return 0;
}
strtok 函数
声明:
char * strtok ( char * str1, const char * str2);
str1 待被切割的字符串
str2 定义什么字符为分割符
strtok 的使用:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main() {char str[] = "this is an apple"; const char a[] = " "; // 分隔符是空格// 第一次调用char* ret1 = strtok(str, a);printf("%s\n", ret1); // 输出 "this"// 第二次调用char* ret2 = strtok(NULL, a); // 它内部有静态指针来记录上次切割的位置printf("%s\n", ret2); // 输出 "is" // 所以后续调用第一个参数传 NULL// 第三次调用char* ret3 = strtok(NULL, a);printf("%s\n", ret3); // 输出 "an"// 第四次调用char* ret4 = strtok(NULL, a);printf("%s\n", ret4); // 输出 "apple"// 第五次调用char* ret5 = strtok(NULL, a);printf("%s\n", ret5); // 输出 "null" // 没有更多字符串了 返回NULLreturn 0;
}
这有个很巧妙的写法
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>int main() {char str[] = "this is an apple";const char a[] = " ";for (char* ret = strtok(str, a); ret != NULL; ret = strtok(NULL, a)){printf("%s\n", ret);}return 0;
}
strtok(str, a) 只会掉用一次后调用都是strtok(NULL,a)
创建 ret 是为了让一次打印只一段字符串
如果不创建 ret 的话,在判断需要再写一次strtok(NULL,a)
这样 就会出现 一次循环调用两次strtok函数