目录
[strlen 的使用和模拟实现](#strlen 的使用和模拟实现)
[strcpy 的使⽤和模拟实现](#strcpy 的使⽤和模拟实现)
[strcat 的使用和模拟实现](#strcat 的使用和模拟实现)
[strcmp 的使用和模拟实现](#strcmp 的使用和模拟实现)
[strlen strcpy strcat 和 strnlen strncpy strncat 的区别](#strlen strcpy strcat 和 strnlen strncpy strncat 的区别)
[strstr 函数](#strstr 函数)
[strtok 函数](#strtok 函数)
字符函数和字符串函数
字符分类函数
C语⾔中有⼀系列的函数是专⻔做字符分类的,也就是⼀个字符是属于什么类型的字符的。 这些函数的使⽤都需要包含⼀个头⽂件是 ctype.h
isalnum() - 检查字符是否是字母或数字
isalpha() - 检查字符是否是字母
iscntrl() - 检查字符是否是控制字符
isdigit() - 检查字符是否是十进制数字
isgraph() - 检查字符是否是可打印字符(不包括空格)
islower() - 检查字符是否是小写字母
isprint() - 检查字符是否是可打印字符(包括空格)
ispunct() - 检查字符是否是标点符号
isspace() - 检查字符是否是空白字符
isupper() - 检查字符是否是大写字母
isxdigit() - 检查字符是否是十六进制数字
这类函数的用法都类似,所以只举一个例字
**例:**利用islower写⼀个代码,将字符串中的⼩写字⺟转⼤写,其他字符不变。
代码如下:
cpp
#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 ); // 将参数传进去的⼩写字⺟转⼤写
现可以将上方代码进行改进
代码如下:
cpp
#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前的字符个数
使用方式如下:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char ch[] = "Hello World";
printf("%d", strlen(ch));
return 0;
}
模拟及实现:
cpp
#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的使用:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char ch1[] = "hello world"; // 复制数组具有\0
char ch2[30] = { 0 }; // 要确保空间最够
// 避免越界访问
char* ret = strcpy(ch2, ch1);
printf("%s\n", ret);
printf("%s\n", ch2);
return 0;
}
strcpy的模拟实现:
cpp
#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*)0
while (*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函数的使用:
cpp
#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的模拟实现:
cpp
#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;
}
**问题:**如果传参同一字符串呢?
cpp
#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的使用:
cpp
#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("相等");
}
else
printf("ch1小");
return 0;
}
strcmp的模拟实现:
cpp
#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.字符为\0
return 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("相等");
}
else
printf("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的特性:
cpp
#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字符个数 不会在后方加\0
printf("%s\n", arr2);
char arr3[] = "abcdef";
char arr4[20] = "xxxxxxxxxxxxxxx";
strncpy(arr4, arr3, 8); // 次数大于arr字符个数 才在后面加\0
printf("%s\n", arr4);
return 0;
}


strncat的特性:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[20] = "xx\0xxxxxxxxxxxx"; // 会在后方放\0
strncat(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的特性:
cpp
#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 的使用:
cpp
#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:

cpp
char* my_strstr(const char* p1, const char* p2)
{
const char* s1 = p1; // 后期写函数如果常该传参过来的指针的话很难找回
const char* s2 = p2; // 所以创建其余指针进行移动
while(*s1)
{
;
}
s1++;
return NULL;
}
情形2:


cpp
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;外层循环会跳出,返回NULL
s1++; // *s2出现\0;内存有if判断,返回标记指针
s2++;
}
if (*s2 == '/0')
return ret; // 现在成功找寻到了,但未有标记最开始==时的指针
s1++;
} // 还需要多创建个指针用于保存 成功时的返回值
return NULL;
}
情形3:

cpp
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 == *s2
return ret; // 所以s1 要恢复到 ret去
// s2 要恢复到 p2去
s1 = ret;
s2 = p2;
s1++;
}
return NULL;
}
运行逻辑为:
外循环对被查字符串一个字符一个字符检查
一旦发现相同进入内循环,跳出内循环的结果只有:
1.*s1 为 \0 查找失败,没找到
2.*s2 为 \0 查找成功,返回开始ret
因为ret一直处于外循环
- *s1 != *s2 找到了相同处,
但并不完全相同;需要重置外循环指针;
情形4:
str2传过来一个空字符串时

加上if后并稍作加强安全性,代码如下:
cpp
#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 的使用:
cpp
#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" // 没有更多字符串了 返回NULL
return 0;
}

这有个很巧妙的写法
cpp
#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函数