c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第三式】字符函数和字符串函数
【心法】
【第零章】c语言概述
【第一章】分支与循环语句
【第二章】函数
【第三章】数组
【第四章】操作符
【第五章】指针
【第六章】结构体
【第七章】const与c语言中一些错误代码
【禁忌秘术】
【第一式】数据的存储
【第二式】指针
【第三式】字符函数和字符串函数
【第四式】自定义类型详解(结构体、枚举、联合)
【第五式】动态内存管理
【第六式】文件操作
【第七式】程序的编译
前言
c语言中对字符和字符串的处理非常频繁,但c语言本身是没有字符串类型的,字符串通常存储在
常量字符串
或字符数组
中。
常量字符串
适用于不需要对其进行修改的字符串函数。
一、函数介绍
1. strlen
- strlen函数的作用是获得一个字符串的长度;
- 接收一个const修饰的字符指针变量,这个指针指向的字符串必须有结束符
\0
; - 返回一个无符号(易错点)的int类型的数值,表示这个字符串的长度(不包括结束符
\0
);如果没有返回值,则表示出错;
// 使用示例
#include <stdio.h>
#include <string.h>
int main()
{
char *a = "abcdefg";
printf("a的长度 = %d\n", strlen(a));
return 0;
}
易错点
#include <stdio.h>
#include <string.h>
int main()
{
char* a = "abcdefg";
char* b = "bbb";
if ((strlen(b) - strlen(a)) >= 0)
{
printf("b >= a\n");
}
else
{
printf("b < a\n");
}
return 0;
}
上面这段代码按照设计思路,此时应该输出b < a
。我们来看看,它的执行结果是否是这样。
运行结果:
可以看到输出仍是b >= a
。这是因为strlen函数的返回值类型为size_t
也就是unsigned int
,是一个无符号数,是恒大于等于0的,所以上面代码只会输出b >= a
。
2. strcpy
- strcpy函数的作用是拷贝一个字符串;
- 接收两个参数,一个为字符指针类型,一个是由const修饰的字符指针类型,前者是拷贝的目的串,这片空间必须可变,后者是用于复制的串,这个字符串必须是有结束符
\0
的字符串; - 返回目的字符串的地址;如果没有返回值,就代表出错;
- 注意,这个函数没有溢出检验,如果你目的位置的长度不足,无法接收你要复制字符串,它不会管你这那的,一定会将其拷贝过去,这就会产生错误;除此之外,该函数并没有定义源串和目的串在内存中产生重合时的情况,所以这时也无法完成拷贝的任务;
// 使用示例
#include <stdio.h>
#include <string.h>
void init(char* d)
{
int i = 0;
for (i = 0; i < 20; i++)
{
d[i] = 1;
}
}
int main()
{
const char *s = "test of strcpy"; // 源字符串必须有结束字符 \0
char d[20]; // d的长度一定要大于 s的长度,以确保能放下源字符串
init(d);
// strcpy函数会将源字符串中的 \0一起拷贝过去
strcpy(d, s);
printf("d = %s\n", d);
return 0;
}
从上图中可以看到,目的字符串中确实有了结束符 \0;
出现越界
有两种情况会出现越界访问的问题:
- 目的串长度不够:
#include <stdio.h>
#include <string.h>
int main()
{
const char *s = "test of strcpy";
char d[3] = { 0 };
strcpy(d, s);
printf("d = %s\n", d);
return 0;
}
使用strcpy函数拷贝字符串后,使用printf函数格式化输出字符串d时,将整个字符串都输出了,所以的确将整个字符串复制了过去,但因为目的位置d没有足够的空间而产生了越界,所以产生了错误;
- 源字符串没有结束符:
出现重合
#include <stdio.h>
#include <string.h>
int main()
{
char s[20] = "test of strcpy";
strcpy(&s[3], s);
printf("d = %s\n", &s[3]);
return 0;
}
可以看到此处的结果,和我们的预期并不相符;这里的输出不一定是这个,因为,c语言中没有定义这种行为,这里的结果由编译器决定。
3. strcat
- strcat是将一个字符串附加到另一个字符串的后面;
- 返回目的串(它的首字符地址),没有返回值则表示出错;
- strcat有两个参数,两个都是指向有结束符\0的字符串的字符指针;
- 该函数在为目的地址追加字符串后,会在新字符串后面加上结束符\0;
- 初始的源字符串会将目的串中的结束符覆盖掉;
- 没有溢出检查,同样的,c语言中并未定义,添加的源串和目的串有重合的情况;
上图中展示了strcat的工作过程。
#include <stdio.h>
#include <string.h>
int main()
{
char a[20] = "hello ";
const char *b = "world";
strcat(a, b);
printf("%s\n", a);
return 0;
}
运往结果:
错误使用:
- 没有结束字符\0
- 出现重合
4. strcmp
- strcmp函数的功能是比较两个字符串的大小;比较规则,同一位置的字母进行比较,越靠前越小,如果相同则继续往后比较,直到分出结果;
- 该函数有两个参数,分别接收一个const修饰的字符指针变量,这两个指针指向有结束符的字符串;
- 标准规定,返回值
<0
,表示str1比str2小;返回值=0
,表示str1和str2相等;返回值>0
,表示str1比str2大;
#include <stdio.h>
#include <string.h>
int main()
{
const char *a = "abcde";
const char *b = "abc";
printf("%d\n", strcmp(a, b));
return 0;
}
此时字符串a和字符串b的前三个字符相同,a比b更长,所以a>b,所以strcmp的返回值>0;这里返回1是因为VS编译器设计成这样。其它的编译器就不一定。
当比较的字符串没有结束符时:
5. strncpy
- 这个函数的作用与strcpy大致相同,也是将一个字符串复制到另一个字符串中,但是这个函数能够指定复制多少个字符到目的字符串中;
- 与strcpy不同的是,strncpy有三个参数。前两个参数与strcpy相同,都是两个字符串,但两个字符串不要求有结束字符\0了,第三个参数为无符号类型的整型数值,表示复制的字符个数;
- 返回值为目的字符串,没有返回值表示出错;
- strncpy是复制src字符串最开始的
count
个字符到dest字符串,并且返回dest串; count
应该小于或等于src的长度,但它不会自动添加结束字符\0;- 如果
count
比src的长度更长,它会在后面添加\0到足够长度; - 同样的,c语言中并未定义两个字符串出现重合的情况;
正常使用情况:
#include <stdio.h>
#include <string.h>
int main()
{
char a[20] = "abcdef";
const char *b = "zyxw";
strncpy(a, b, 2); // 将字符串 b的前两个字符复制到字符串 a
printf("%s\n", a); // 预期结果为输出 zycdef
return 0;
}
运行结果:
从这个例子中,我们也能看出,strncpy这个函数并不会自动在复制完字符串内容后添加\0。
count比src的长度更长
出现重合
6. strncat
- strncat的功能是在一个字符串后添加字符;
- 有3个参数,前两个表示目的字符串和源字符串,第三个是要从源字符串添加的字符个数;
- 返回值为指向目的字符串的指针。没有返回值则表示出错;
- 目的字符串和源字符串必须是有结束字符\0的字符串;
- count是一个无符号的整型数;
- 该函数只会将源字符串前面的count个字符追加到目的串后;
- src串的第一个字符会覆写dest串的\0;
- 当src的长度比count更小时(换句话说,在复制的过程中,还没复制到第count个字符就遇到了\0),遇到这种情况,strncat只会将整个src字符串追加过去就结束(复制到\0就结束);
- 同样的,该函数并未对两个字符串出现重合的情况进行定义;
正常使用示例:
#include <stdio.h>
#include <string.h>
int main()
{
char a[20] = { "abcdef" };
const char *b = "qwert";
strncat(a, b, 3);
printf("%s\n", a);
return 0;
}
运行结果:
可以strncat是会自动在添加的字符串后面添加结束字符\0;
count比src更长
出现重合
7. strncmp
- 与strcmp一样,strncmp的功能也是比较两个字符串的大小(字典序比较);
- 有三个参数,前两个字符指针指向要比较的两个字符串,第三个参数count表示比较这两个字符串的前count个字符;
- 返回值和strmp相同,str1大于str2就返回大于0的数,str1小于str2就返回小于0的数,str1等于str2就返回0;
使用示例:
#include <stdio.h>
#include <string.h>
int main()
{
char *a = "abcdef";
char *b = "abcde";
printf("%d\n", strncmp(a, b, 5));
return 0;
}
运行结果:
可以看到这里的结果是两个字符串相等,这是因为strcmp中的第三个参数为5,只比较这两个字符串的前5个字符,所以得到的结果是0,如果用strcmp比较,这两个字符串肯定不相等。
对上面6个函数的小总结:
strcpy
、strcat
、strcmp
这三个函数是长度不受限的函数。长度不受限指的是,它们会在函数没有达到结束条件时一直执行下去,直到遇到\0;
strncpy
、strncat
、strncmp
这三个函数是长度受限的函数。长度受限指的是,它们只会处理指定数量的字符;
其中strcpy
、strcat
、strncpy
、strncat
都没有定义两个字符串出现重合的行为,当出现这种情况时,输出结果取决于使用的编译器;
strcpy
没有溢出检测,所以需要src串有结束标志\0,且src串的长度不能大于dest串的长度;
strncpy
也没有溢出检测,但两个字符串都不一定要有\0,因为该函数复制的字符数由使用者自己决定,需要使用者在使用时考虑清楚,标准规定当count超过src的长度时,后面补\0;
同时,这两个函数,在对重合字符串进行复制的时候,当src串的位置在dest的后面,则可以正常使用,反之就可能会出错;
这两个函数都不会自动在结束时添加\0。strcpy是将src的\0也一起复制过来,所以才会结束;
strcat
中两个字符串都要有结束标志\0,这是因为,cat是在dest串的后面追加,需要找到dest的尾部,追加的是整个src字符串,所以需要知道src什么时候结束;
strncat
中两个字符串也都要有结束标志\0,dest的原因和上面一样,而src也要有的原因是,标准规定当count超过src的长度时,复制到src的\0就结束追加,所以这里也需要知道src什么时候结束;
strcat
根据规定,当出现重合时,肯定会出错,因为它的结束条件是遇到\0,但无论是src在dest的前面还是后面,都会将后面的\0覆写成有效字符,所以永远都没有\0,肯定会导致越界访问,也不可能得到预期结果;
strncat
则是会出现结果与预期相符的情况;与前几个函数不同,strncat
会自动在结束位置放上\0;
8. strstr
- strstr的功能是在一个字符串中找子串;
- 它有两个参数,前者是字符串,有结束标志\0;后者是要找的子串,有结束标志\0;
- 它返回找到的第一个子串的字符指针,不包括结束标志\0,只是返回它的起始位置;没找到则返回NULL指针,如果子串的长度的0(空串),返回字符串本身;
#include <stdio.h>
#include <string.h>
int main()
{
char *a = "abcdefgqwertbcdee";
char *b = "bcde";
printf("%s\n", strstr(a, b));
return 0;
}
运行结果:
可以看到strstr返回的确实是一个指向找到的第一个与子串相同的字符的位置,并没有在这个子串后面添加\0;
9. strtok
- strtok的功能是找到字符串中下一个词;
- 它有两个参数,前者是待分割字符串,后者是分割字符集合;
- 返回值为在字符串中找到的下一个词的首字符的指针。如果返回值为NULL,则表示字符串中所有的词全部已找到,找到这个字符串的结束标志\0。每次找到一个词时,该函数都会修改这个字符串(将这个分隔字符替换成\0);
- 第二个参数
strDelimit
明确指出当前调用中字符串中可能出现的分隔字符; - 在首次调用strtok时,这个函数会跳过所有的先导分隔符(找到字符串中第一个非分隔符的字符,从它开始找第一个词),并返回这个字符串中的第一个词,并将它后面的分隔符修改成\0;
- 可以通过调用一系列的strtok将剩余的字符串中其他的词全部分出来;
- 每次调用strtok都会修改目标字符串,因为它会将找到的分隔符替换成\0;(所以调用该函数时,通常都会复制一个字符串,并使用这个复制出来的字符串来进行分割)。
- 要找一个字符串中的下一词,要再次调用strtok,但是,第一个参数应设为NULL;当第一个参数为NULL时,strtok会在被修改过的字符串中去找下一个词;(使用static修饰的局部变量保存这个地址);
- 每次调用时的分隔符集合都可以不相同,也就是有多种分隔符集;
- 如果多重或同时调用这个函数,这就会导致有高概率出现数据损坏和得到不正确的结果;因此需要注意不要同时调用这个函数来处理不同的字符串;同时也应该意识到当我们在一个循环中调用这个函数时,该循环中是否还有其他语句会调用这个函数。
- 但是多个线程同时调用这个函数就不会产生不良影响;
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "- This is, a sample string.";
char *pch = NULL;
printf("Splitting string \"%s\" into tokens:\n", str);
pch = strtok(str, "-,. ");
while (pch != NULL)
{
printf("%s\n", pch);
pch = strtok(NULL, "-,. ");
}
return 0;
}
运行结果:
从上面的输出可以看出,字符串最开始出现的分隔符被跳过了,直接从第一个有效字符开始找第一个token。
然后我们再来看看str这个数组的内容:
10. strerror
strerror
和_strerror
前者用来获取一个系统的错误信息,后者是用来打印用户提供的错误信息;strerror
只有一个参数,表示错误信息的错误码;_strerror
也只有一个参数,这是用户提供的信息;- 这两个函数都是返回一个指向错误信息字符串的指针;下一次调用这两个函数会覆写这个字符串;
- 注意:
strerror
和_strerror
这两个函数都不会将信息打印出来,需要使用其他输出函数才行; - 如果_strerror函数的参数为NULL,它就会返回上一个调用库函数产生的错误的信息;
_strerror
的错误信息的结束标志是换行符\n,如果,_strerror
的参数不是NULL,则它返回一个指向错误信息字符串的指针。这个字符串的结构组成按顺序为,你提供的信息、一个冒号、一个空格、最后一次调用库函数产生的错误、一个换行符\n,它会自动在字符串后面加一个\n;- 你提供的信息长度不能超过94个字节;
_strerror
函数使用的实际错误码是存放在变量errno
中;- 系统的错误信息是从变量
_sys_errlist
中获取到的,这是一个根据错误码的顺序排列的错误信息数组; _strerror
通过使用error
的值作为索引来访问_sys_errlist
中的对应错误信息;- 变量
_sys_nerr
的值被定义为数组_sys_errlist
中的最大元素个数; - 为了获取到准确的结果,应该在一个库例程出错后立刻调用
_strerror
,否则对strerror
或_strerror
的后续调用会改变errno的值; - 需注意
_strerror
是VS对strerror
的扩展,其他编译器并不支持;并且可以使用perror
函数来替代它;
#include <stdio.h>
#include <string.h>
#include <errno.h> // 包含errno这个全局变量
int main()
{
FILE* pf = fopen("test", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
printf("%s", _strerror("test"));
perror("test");
}
// 使用文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
当前目录中并没有test这个文件,所以FILE类型的指针pf
指向的文件为NULL,此时文件打开失败,开始输出错误信息;
可以看到,_strerror
这个函数比strerror
多输出了一个test:
,这里由用户自己提供的信息;可以看到用printf函数输出_strerror
得到的信息得到的结果与perror
函数输出的结果相同,且perror
函数不需要调用printf之类的输出函数,就能直接将信息输出到屏幕;
并且perror
函数是ANSI标准中的库函数,所以为了可移植性,推荐使用perror
;
11.字符分类函数
函数 | 如果它的参数符合下列条件则返回真 |
---|---|
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 | 任何可打印字符,包括图形字符和空白字符 |
常用的是isdigit
,islower
,isupper
,isalpha
12. memcpy
memcpy
函数的作用是在内存之间复制字符;- 它有3个参数,两个void *类型的指针变量,前者指向目的空间,后者指向源空间,第三个参数num表示要复制的字节数;
- 返回指向目的空间的指针;
- dest和src指向空间的底层类型并不影响该函数的功能,它是对二进制的数据进行操作;
- 这个函数不会检查src中的任何空字符\0,它总是会复制num个字节;
- 为了避免溢出,dest和src的占据的字节数最少也要等于num,且这两片内存不应该出现重合;
可能大家看完上面的一系列函数已经有点迷糊了,会出现前面不是有了strcpy和strncpy这两个复制函数吗,还要这个memcpy干嘛?
来想一个场景,有两个int类型的数组arr1、arr2,它们的长度都是10(能保存10个int类型数据),现在需要将arr1前5个元素复制到arr2,该怎么办?
能使用strcpy或strncpy吗?
不能,因为这两个函数的参数类型为字符指针,返回值也为字符指针,与int数组类型不符;
使用循环?可以。但还有其它方法吗?
使用memcpy,因为这个函数是以字节为单位进行处理,什么类型都能处理,也就是说,前面使用strcpy或strncpy的地方也能用memcpy实现;
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
int arr2[10] = {0};
memcpy(arr2, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
运行结果:
验证\0不会影响复制
检验不会检查溢出
出现重合
memcpy出现重合时与strncpy出现重合时的情况类似,有的符合预期,有的不行。
13. memmove
- memmove的功能和memcpy相同,都是将一片内存空间的内容复制到另一个地方;不过,memmove可以处理两片内存空间出现重合的情况;
- 两个指针的类型仍为void *,原因相同;
- 不检查\0,总是会复制num个字节的数据;
- 为了不出现溢出,num应该小于dest和src长度;
14. memcmp
- 与strcmp和strncmp的功能相似,memcmp是比较两片内存空间中的内容是否相同(以字节为单位);
- 比较ptr1指向空间的前num个字节和ptr2指向空间的前num个字节是否相同,相同返回0,大于返回值>0,小于返回值<0;
- 与strcmp和strncmp不同的是,memcmp遇到\0也不会停止比较,而一定会比较完num个字符或比较出结果;
#include <stdio.h>
#include <string.h>
int main()
{
char *str1 = "abcde\0fg";
char *str2 = "abcde";
printf("strcmp:\n");
if (strcmp(str1, str2) > 0)
{
printf("str1 > str2\n");
}
else if (strcmp(str1, str2) < 0)
{
printf("str1 < str2\n");
}
else
{
printf("str1 = str2\n");
}
printf("strncmp:\n");
if (strncmp(str1, str2, 7) > 0)
{
printf("str1 > str2\n");
}
else if (strncmp(str1, str2, 7) < 0)
{
printf("str1 < str2\n");
}
else
{
printf("str1 = str2\n");
}
printf("memcmp:\n");
if (memcmp(str1, str2, 7) > 0)
{
printf("str1 > str2\n");
}
else if (memcmp(str1, str2, 7) < 0)
{
printf("str1 < str2\n");
}
else
{
printf("str1 = str2\n");
}
return 0;
}
运行结果:
从上图可以验证,memcmp确实不会因为\0而停止比较,而是会在没比较出结果或没比较到第num个字节时,一直比较下去。
15. memset
- memset的功能是设置一片内存空间的值,用指定值设置ptr指向空间的前num个字节的内容,指定值value是这个填充字符的ASCII码值;
#include <stdio.h>
#include <string.h>
int main()
{
char str[20];
// 对str进行赋值
memset(str, '*', 15);
int arr[10] = {0};
// 使用memset将arr的元素的值设为 1,2,3,4,5,6,7,8,9,10
int i = 0;
for (i = 0; i < 10; i++)
{
// 小端字节序
memset((char *)arr + (i * sizeof(int)), i + 1, 1);
}
return 0;
}
int main()
{
int arr[10] = {0};
// 使用memset将arr的元素的值设为 1,2,3,4,5,6,7,8,9,10
int i = 0;
for (i = 0; i < 10; i++)
{
memset(arr + i, i + 1, 1);
}
return 0;
}
这段代码与前面的代码功能相同,但一个是前者是将其转换为char *类型,一个字节一个字节的处理,后者是一个4个字节4个字节的处理,void *类型的参数接收到的指针的底层类型为int *,移动步长为4个字节;
二、库函数模拟实现
1.my_strlen
#include <assert.h>
typedef unsigned int uint;
// 使用计数器
uint my_strlen(const char* str)
{
assert(str);
int ret = 0;
while (*str++)
{
ret++;
}
return ret; // \0不算在字符串长度中
}
// 不使用临时变量,递归
uint my_strlen(char* str)
{
assert(str);
if (*str == '\0')
{
return 0; // \0不算在字符串长度中
}
return 1 + my_strlen(str + 1);
}
// 使用指针相减
uint my_strlen(const char* str)
{
assert(str);
char *eos = str;
while (*eos++)
{ }
return eos - str - 1; // \0不算在字符串长度中
}
2.my_strcpy
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* start = dest;
while (*dest++ = *src++)
{}
return start;
}
// 递归
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* start = dest;
if ((*dest++ = *src++) == '\0'){}
else my_strcpy(dest, src);
return start;
}
3.my_strcat
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* start = dest;
// 找dest串的尾部
while(*dest++)
{ }
dest--;
// 追加字符串src
while (*dest++ = *src++)
{ }
return start;
}
4.my_strcmp
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2 && *str1 != '\0')
{
str1++;
str2++;
}
return (*str1 - *str2);
}
5.my_strncpy
#include <assert.h>
typedef unsigned int uint;
char* my_strncpy(char* dest, const char* src, uint num)
{
assert(dest && src);
char* start = dest;
// 先判断num是否为0,如为0,则直接结束,不需判断当前src指向的字符是否\0
while (num && (*dest++ = *src++)) // dest,src永远指向下一个字符复制的位置
{
num--;
}
if (num) // num不为0,此时只能是*(src-1)指向了\0
{
while (--num)// dest复制了一个src中的\0,但还没有减少num的值,所以需要先减一次
{
*dest++ = '\0'; // 当复制字符数量不足num个时,填充\0
}
}
return start;
}
6.my_strncat
#include <assert.h>
typedef unsigned int uint;
char* my_strncat(char* dest, const char* src, uint num)
{
assert(dest && src);
char* start = dest;
while(*dest++) // 找到dest的尾部
{ }
dest--; // 回到dest的\0
while(num && (*dest++ = *src++))
{
num--;
}
if (num == 0) // 此时追加的字符串没有\0
{
*dest = '\0'; // 补上\0
}
return start;
}
// 另一种写法
char* my_strncat(char* dest, const char* src, uint num)
{
assert(dest && src);
char* start = dest;
while (*dest++) // 找到dest的尾部
{
}
dest--; // 回到dest的\0
while (num--)
{
if ((*dest++ = *src++) == 0)
{
return start;
}
}
// 此时是因为num==0,所以追加的字符串没有\0
*dest = '\0'; // 补上\0
return start;
}
7.my_strncmp
#include <assert.h>
typedef unsigned int uint;
int my_strncmp(const char* str1, const char* str2, uint num)
{
assert(str1 && str2);
while ((*str1 == *str2) && (*str1 != 0))
{
num--;
if (num == 0)
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
8.my_strstr
#include <string.h>
#include <assert.h>
// 暴力匹配
char* my_strstr(const char* str, const char* substr)
{
assert(str && substr);
const char* a = str;
const char* b = substr;
const char* c = str;
if (strlen(substr) == 0)
{
return a;
}
while (*a)
{
while (*b != 0 && *c == *b)
{
c++;
b++;
}
if (*b == 0)
{
return a;
}
a++;
c = a;
b = substr;
}
return NULL;
}
// 除了暴力匹配法外,还可以使用KMP算法,此处就不再详细展示
9.my_strtok
#include <stdio.h>
#include <string.h>
#include <assert.h>
int isDelimit(const char *a, const char* strDelimit)
{
int length = strlen(strDelimit);
int i = 0;
for (i = 0; i < length; i++)
{
if (*a == *(strDelimit + i))
{
return 1;
}
}
return 0;
}
char* my_strtok(char* str, const char* strDelimit)
{
assert(strDelimit);
static char* cp = NULL; // 记录位置的指针
char* start = NULL;
// 继续上一次的处理
if (str == NULL)
{
// 跳过先导分隔符
while (isDelimit(cp, strDelimit))
{
cp++;
}
start = cp;
while (*cp && !isDelimit(cp, strDelimit))
{
cp++;
}
if (*cp != 0)
{
*cp++ = 0; // 将分隔符改成\0
}
}
else
{
// 跳过先导分隔符
while (isDelimit(str, strDelimit))
{
str++;
}
start = str;
// 开始分隔
// 有两种情况会停下来
// 1.遇到\0
// 2.遇到分隔符
while (*str && !isDelimit(str, strDelimit))
{
str++;
}
if (*str == 0)
{
cp = str;
}
else
{
cp = str + 1;
*str = 0; // 将分隔符改成\0
}
}
if (*start == 0)
{
return NULL;
}
else
{
return start;
}
}
10.my_memcpy
#include <assert.h>
typedef unsigned int uint;
void* my_memcpy(void* dest, const void* src, uint num)
{
assert(dest && src);
void* start = dest;
while (num--)
{
*(((char*)dest)++) = *(((char*)src)++);
}
return start;
}
11.my_memmove
memmove函数能够处理两片空间出现重合的情况,所以在这里我们就具体分析一下,它是如何实现的。
当重合时出现结果与预期不符是因为,后续要复制的数据被前面的复制修改了。也就是当src在dest的前面时,src后面要复制给dest的值在此之前就被前面的复制操作修改了。
所以当src在dest的后面时,从前往后复制是没有问题的。那么当src在dest的前面应该如何复制,来保证前面的复制不会影响到之后的复制呢?
从后往前。
但是注意src在dest的后面时,使用从后往前的方式又会导致错误,所以memmove应该分两种情况来处理,
- src在dest前面:从后往前复制;
- src在dest后面:从前往后复制;
#include <assert.h>
typedef unsigned int uint;
void* my_memmove(void* dest, const void* src, uint num)
{
assert(dest && src);
void *start = dest;
// src在前
if (src < dest)
{
// 从后往前复制
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
else
{
while (num--)
{
*(((char*)dest)++) = *(((char*)src)++);
}
}
return start;
}
12.my_memcmp
#include <assert.h>
typedef unsigned int uint;
int my_memcmp(const void* ptr1, const void* ptr2, uint num)
{
assert(ptr1 && ptr2);
while (num && *(char*)ptr1 == *(char*)ptr2)
{
num--;
((char*)ptr1)++;
((char*)ptr2)++;
}
if (num == 0)
{
return 0;
}
return (*(char*)ptr1 - *(char*)ptr2);
}
13.my_memset
#include <assert.h>
typedef unsigned int uint;
void* my_memset(void* ptr, int value, uint num)
{
assert(ptr);
void* start = ptr;
int i = 0;
for (i = 0; i < num; i++)
{
*((char*)ptr + i) = value;
}
return start;
}
总结
本文对c语言中会使用到的字符串函数进行了详细介绍说明,并给出了模拟实现的代码,希望对你们的c语言学习有帮助。