- 1. C语言字符库函数
- 1.1 字符串长度计算函数——strlen
- 介绍
- 模拟实现
- 1.2 字符串拷贝函数——strcpy,strncpy
- 介绍
- 模拟实现
- 1.3 字符串拼接函数——strcat,strncat
- 介绍
- 模拟实现
- 1.4 字符串比较函数——strcmp,strncmp
- 介绍
- 模拟实现
- 1.5 字符串查找函数——strstr
- 介绍
- 模拟实现
- 1.6 切割字符串函数strtok
- 1.7 了解strerror
- 2. C语言内存库函数
- 2.1 内存拷贝函数——memcpy
- 介绍
- 模拟实现
- 2.2 内存移动函数——memmove
- 介绍
- 模拟实现
- 2.3 内存比较函数——memcmp
- 介绍
- 模拟实现
size_t strlen( const char *string );
字符串已 ‘ ’ 作为结束标志,strlen函数返回的是在字符串中 ‘ ’ 前面出现的字符个数(不包含 ‘ ’ )。
注意:
- 传入的字符串一定要包含 ,不然可能会出现结果错误。
- 返回值为size_t,为无符号整型。
针对第2点的一个易错题:
int main()
{
const char* str1 = "wjhwef";
const char* str2 = "abc";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2>str1n");
}
else
{
printf("srt1>str2n");
}
return 0;
}
大家可能会这么想,strlen(str2) = 3 , strlen(str1) = 6,strlen(str2) - strlen(str1) = -3 < 0,程序输出str1 > str2,那我们来看看究竟是不是这样!
运行结果:
str2>str1 C:UsersHPDesktopgiteetestworld7-06-字符串函数x64Debug7-06-字符串函数.exe (进程 20212)已退出,代码为 0。 按任意键关闭此窗口. . .
结果不对,程序输出的结果是str2>str1,这是因为函数strlen返回的类型是无符号整型,两个无符号整型进行加减运算还是无符号整型,而无符号整型的范围是大于0的,所以程序会输出str2>str1。
模拟实现这个函数模拟很简单,根据 为结束标志,计算字符个数即可。
这里有三种实现方式:
- 计数实现
- 递归实现
- 指针 - 指针实现
//计数实现
size_t my_strlen1(const char* string)
{
size_t count = 0;
while (*string++ != ' ')
{
count++;
}
return count;
}
//递归实现
size_t my_strlen2(const char* string)
{
if (*string == ' ')
{
return 0;
}
else
{
return 1 + my_strlen2(string + 1);
}
}
//指针-指针实现
size_t my_strlen3(const char* string)
{
char* tmp = string;
while (*tmp != ' ')
{
tmp++;
}
return tmp - string;
}
int main()
{
char arr[] = "abcdefghijk";
printf("%un", my_strlen1(arr));
printf("%un", my_strlen2(arr));
printf("%un", my_strlen3(arr));
}
//打印结果:11
1.2 字符串拷贝函数——strcpy,strncpy
介绍
char *strcpy( char *strDestination, const char *strSource ); char *strncpy( char *strDest, const char *strSource, size_t count );
这两个函数都能实现字符串的拷贝,只不过strncpy多了一个参数count,用来控制拷贝字符串的个数,count是多少字符串就拷贝多少,当然如果超过了源字符串的个数就会在源字符串拷贝完后直接补0直到数量为count个。
这两个函数都有两个字符指针,前者是是拷贝存放的地方(目标字符串),后者是需要被拷贝的字符串(源字符串)。
注意:
- 源字符串必须含有 。
- 源字符串中的 会被拷贝到目标字符串。
- 目标字符串空间要够大或可变。
int main()
{
char str1[] = "Hello World!";
char str2[100] = { 0 };
char str3[100] = { 0 };
strcpy(str2, str1);
strncpy(str3, str1, 5);
printf("%sn", str2);
printf("%sn", str3);
return 0;
}
Hello World!
Hello
C:UsersHPDesktopgiteetestworld7-3-字符串函数x64Debug7-3-字符串函数.exe (进程 14244)已退出,代码为 0。
按任意键关闭此窗口. . .
模拟实现
strcpy模拟实现思路:
利用结束标志 ,对源字符串遍历,遍历一个就拷贝一个到目标字符串,直到遇到 ,结束遍历,拷贝完成!
strncpy模拟实现思路:
如果count的值不大于源字符串的字符个数(包括 ),则和strcpy拷贝思路是一样的,但是count大于源字符串个数,源字符串中字符拷贝完后,还得在目标字符串补0。
char* my_strcpy(char* des, const char* str)
{
assert(des && str);
char* ret = des;
while (*des++ = *str++)
{
;
}
return ret;
}
char* my_strncpy(char* des, const char* str, size_t n)
{
assert(des && str);
char* ret = des;
while(n--)
{
//源字符串未到结尾
if (*str)
{
*des++ = *str++;
}
else
{
break;
}
}
*des = ' ';
return ret;
}
测试一下:
int main()
{
char str1[] = "Hello World!";
char str2[100] = { 0 };
char str3[100] = { 0 };
my_strcpy(str2, str1);
my_strncpy(str3, str1, 5);
printf("%sn", str2);
printf("%sn", str3);
return 0;
}
Hello World!
Hello
C:UsersHPDesktopgiteetestworld7-3-字符串函数x64Debug7-3-字符串函数.exe (进程 18356)已退出,代码为 0。
按任意键关闭此窗口. . .
1.3 字符串拼接函数——strcat,strncat
介绍
char *strcat( char *strDestination, const char *strSource ); char *strncat( char *strDest, const char *strSource, size_t count );
这两个函数的区别和上面说到的strcpy与strncpy区别是一样的!后者多了个参数count,用来控制拼接字符个数。这里同样有两个字符指针参数,前者是目标字符串,后者源字符串,作用就是将源字符串拼接到目标字符串后面(从目标字符串的 位置开始拼接)。
注意:
- 目标字符串和源字符串都必须含有 。
- 目标空间必须足够大或可变。
- 对于strncat函数,如果参数count大于源字符串个数,只拼接到源字符串的 。
int main()
{
char str1[100] = "Hello ";
char str2[100] = "Hello ";
char str3[] = "World!";
strcat(str1, str3);
strncat(str2, str3, 4);
printf("%sn", str1);
printf("%sn", str2);
return 0;
}
Hello World!
Hello Worl
模拟实现
strcat模拟实现思路:
先遍历目标字符串至 ,然后从 开始将源字符串内容复制到目标字符串!
strncat模拟实现思路:
同样,先需要遍历到目标字符串 处,从此处开始拷贝源字符串中前n个
字符(最多拷贝整个源字符串,包含 )。
char* my_strcat(char* des, const char* str)
{
assert(des && str);
char* ret = des;
//1.找到des最后一个字符
while (*des)
{
des++;
}
//2.将str中字符串拷贝至des末尾
while (*des++ = *str++)
{
;
}
return ret;
}
char* my_strncat(char* des, const char* str, size_t n)
{
assert(des && str);
char* ret = des;
//1.find end
while (*des)
{
des++;
}
//2.add
size_t i = 0;
for (i = 0; i < n && *str; i++)
{
*des++ = *str++;
}
*des = ' ';
return ret;
}
1.4 字符串比较函数——strcmp,strncmp
介绍
int strcmp( const char *string1, const char *string2 ); int strncmp( const char *string1, const char *string2, size_t count );
这两个函数都能比较字符串大小,原理是对应比较每个字符的ASCII码大小,如果相同则比较下一个,不同则比较两个字符的ASCII码,如果string1大于string2返回正值,string1小于string2返回负值,如果两个字符串每个字符都相等则字符串相等。
strncmp相比于strcmp就是控制了比较字符数量多少,count是多少就比较多少个字符,比较字符个数不超过两字符串个数(含 )较少者。
注意:
- 第一个字符串大于第二个字符串,则返回大于0的数字。
- 第一个字符串等于第二个字符串,则返回0。
- 第一个字符串小于第二个字符串,则返回小于0的数字。
- 比较字符串大小不能直接使用==,需要使用该库函数。
strcmp模拟实现思路:
分别从两个字符串第一个字符开始同时遍历两个字符串中的字符,如果字符相等就比较下一个,中间如果存在两字符不相等,结束遍历。最终结果返回两者ASCII码差值(前者减后者)。
strncmp模拟实现思路:
相比于strcmp的模拟实现,该函数多了个比较字符个数n,比较的思路是一样的,但是需要控制字符比较字符的个数,n为几就在第几个字符处返回两者ASCII码差值(前者减后者),如果n比两个字符串长度较小者大(含 )其实和strcmp实现是一样的。
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
//str1 和 str2 都到结尾
if(*str1 == ' ')
{
return 0;
}
str1++;
str2++;
}
return (*str1 - *str2);
}
int my_strncmp(const char* str1, const char* str2, size_t n)
{
assert(*str1 && *str2);
while (--n)
{
if (*str1 != *str2)
break;
if (*str1 == ' ' || *str2 == ' ')
break;
str1++;
str2++;
}
return (*str1 - *str2);
}
1.5 字符串查找函数——strstr
介绍
char *strstr( const char *string, const char *strCharSet );
这个函数能够实现字符串的查找,如果字符串strCharSet在string出现,则返回string中第一次出现该字符串的首地址,否则返回NULL。
模拟实现strstr模拟实现思路:
要在字符串str1中找到str2字符串,首先得在str1中找到与str2首字符相同的字符,找到这个字符之后,就是对str2后面的字符进行逐个比较,如果在后续逐个比较过程中出现了不同的字符,这时候就需要str1返回到之前刚开始对字符比较的地方的下一位置,str2需要返回到首字符,然后重复执行该操作。当然如果在后续逐个比较过程中,str2指向的字符为 这就代表在str1中找到了str2这个字符串,这时候就可以返回str2首字符对应于str1所在的地址。还有一种情况后续遍历过程中str1指向的字符为 ,这就表示在str1中找不到str2这个字符串,直接返回NULL即可。
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = str1;
const char* s2 = str2;
const char* p = s1;
while (*p)
{
s1 = p;
s2 = str2;
while (*s1 != ' ' && *s2 != ' ' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == ' ')
{
return (char*)p;
}
p++;
}
return NULL;
}
1.6 切割字符串函数strtok
char *strtok( char *strToken, const char *strDelimit );
strtok函数可以对字符串进行切分。字符串str 是被切分对象,字符串 strDelimit中包含分隔符号。
- strToken就是我们要去拆分的字符串,注意,我们会对该字符串进行更改,所以一般我们会拷贝一份然后去分割拷贝的那份字符串!
- strDelimit 就是我们定义的切分的符号,假如想要用空格作为分割符,我们就可以定义 char strDelimit [NUM]=" " 请注意里面放了一个空格!
此处NUM可以自己定义大小,这里的delimiters 可以有多种分割方式。 - strtok函数的第一个参数不为 NULL ,函数将找到strToken中第一个标记,strtok函数将会有一个设置保存他修改了的下一个位置的地址,如“wo he ni”,其中若用空格作为分隔符就会将空格处换成 "n",返回w的位置,并且函数内部保存h的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置(即上面所说的位置h)开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
int main()
{
char a[] = "Yang tong xue yao zao qi";
char sep[] = " ";
char cp[100] = { 0 };
strcpy(cp, a);
char* ret = NULL;
for (ret = strtok(cp, sep);
ret != NULL;
ret = strtok(NULL, sep))
{
printf("%sn", ret);
}
return 0;
}
结果:
Yang tong xue yao zao qi C:UsersHPDesktopgiteetestworld7-06-字符串函数x64Debug7-06-字符串函数.exe (进程 18952)已退出,代码为 0。 按任意键关闭此窗口. . .1.7 了解strerror
char *strerror( int errnum );
strerror函数从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。strerror生成的错误字符串取决于开发平台和编译器。
#include#include #include //必须包含的头文件 int main() { FILE* pFile; pFile = fopen("unexist.txt", "r"); if (pFile == NULL) printf("Error opening file unexist.txt: %sn", strerror(errno)); //errno: Last error number return 0; }
运行结果:
Error opening file unexist.txt: No such file or directory2. C语言内存库函数 2.1 内存拷贝函数——memcpy 介绍
void *memcpy( void *dest, const void *src, size_t count );
该函数与字符串拷贝函数strncpy非常的相似,唯一的区别就是strncpy只能拷贝字符串,memcpy函数能够拷贝任何数据类型,它进行的是内存的拷贝,是将源内存src储存的内容拷贝到目标内存dest中。
模拟实现memcpy模拟实现思路:
将传入的两个指针参数强制转换为char*类型进行拷贝操作,操作过程与strncpy是一模一样的。要注意的是,memcpy函数在C标准中并没有要求能够实现含重复地址的拷贝,因为如果出现含重复的地址,拷贝过程中可能会将需要的数据被覆盖的情况,如果需要完成该任务,需要靠memmove函数实现,下面的内容会介绍该函数。
void* my_memcpy(void* des, const void* src, size_t n)
{
assert(des && src);
void* ret = des;
while (n--)
{
*(char*)des = *(char*)src;
des = (char*)des + 1;
src = (char*)src + 1;
}
return ret;
}
#include
#include
int main()
{
int arr1[] = { 1,2 ,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
int arr3[] = { 1,2 ,3,4,5,6,7,8,9 ,10 };
int arr4[10] = { 0 };
int i = 0;
//处理
memcpy(arr2, arr1, 20);
my_memcpy(arr4, arr3, 20);
//打印
printf("memcpy:");
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
printf("n");
printf("my_memcpy:");
for (i = 0; i < 10; i++)
{
printf("%d ", arr4[i]);
}
return 0;
}
运行结果:
memcpy:1 2 3 4 5 0 0 0 0 0 my_memcpy:1 2 3 4 5 0 0 0 0 0 C:UsersHPDesktopgiteetestworld7-06-字符串函数x64Debug7-06-字符串函数.exe (进程 21992)已退出,代码为 0。 按任意键关闭此窗口. . .2.2 内存移动函数——memmove 介绍
void *memmove( void *dest, const void *src, size_t count );
memmove函数相比于memcpy函数能够很好的实现重复地址拷贝,因为该函数考虑了在含有重复地址情况下,可用数据被覆盖的问题,并针对此问题作出了解决方案。
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);//拷贝五个int
int size = sizeof(arr1) / sizeof(arr1[0]);
for (int i = 0; i < size; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
运行结果:
1 2 1 2 3 4 5 8 9 10 C:UsersHPDesktopgiteetestworld7-06-字符串函数x64Debug7-06-字符串函数.exe (进程 16944)已退出,代码为 0。 按任意键关闭此窗口. . .模拟实现
在介绍该函数模拟实现之前我们来看看使用前面模拟的my_memcpy函数来运行上面测试memmove函数:
#define _CRT_SECURE_NO_WARNINGS 1 #include#include int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; my_memcpy(arr1 + 2, arr1, 20); int size = sizeof(arr1) / sizeof(arr1[0]); for (i = 0; i < size; i++) { printf("%d ", arr1[i]); } return 0; }
运行结果:
1 2 1 2 1 2 1 8 9 10 D:gteeC-learning-code-and-projectimitate str and memDebugimitate str and mem.exe (进程 18272)已退出,代码为 0。 按任意键关闭此窗口. . . 我们想要实现的目标: 1 2 1 2 3 4 5 8 9 10
我们发现数据3 4 5 这三个数据被1 2 1 覆盖了,因为该函数的任务并不需要实现重复地址数据的拷贝,当然有部分编译器下memcpy函数能实现该任务,但是并不是所有编译器下的memcpy函数都能完成该任务。
通过这个例子,我们发现要实现memmove函数,就需要在memcpy的基础上解决数据覆盖的情况。
memmove函数模拟实现:
根据目的地与发源地的相对位置,采取不同的拷贝方案!
void* my_memmove(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
//目标起始地址小于源字符串地址
//源字符串后面的内容不会被覆盖
if (dest < src)
{
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
//目标起始地址大于源字符串地址
//从后往前拷贝
else
{
for (int i = count-1; i >= 0; --i)
{
*((char*)dest + i) = *((char*)src + i);
}
}
return ret;
}
同一个测试案例:
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int size = sizeof(arr1) / sizeof(arr1[0]);
memmove(arr1 + 2, arr1, 20);//拷贝五个int
printf("memove: ");
for (int i = 0; i < size; i++)
{
printf("%d ", arr1[i]);
}
printf("n");
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr2 + 2, arr2, 20);//拷贝五个int
printf("my_memove:");
for (int i = 0; i < size; i++)
{
printf("%d ", arr2[i]);
}
}
运行结果:
memove: 1 2 1 2 3 4 5 8 9 10 my_memove:1 2 1 2 3 4 5 8 9 10 C:UsersHPDesktopgiteetestworld7-06-字符串函数x64Debug7-06-字符串函数.exe (进程 16944)已退出,代码为 0。 按任意键关闭此窗口. . .2.3 内存比较函数——memcmp 介绍
int memcmp( const void *buf1, const void *buf2, size_t count );
memcmp函数和strncmp也非常相似,前者能比较所有的数据类型,通过内存中每个字节每个字节地比较,后者只能比较字符串。
int main()
{
int arr1[] = { 1, 3, 4, 6 ,9 };
int arr2[] = { 1, 3, 9, 8 ,0 };
int ret = memcmp(arr1, arr2, 20);
printf("%d ", ret);
return 0;
}
-1
C:UsersHPDesktopgiteetestworld7-06-字符串函数x64Debug7-06-字符串函数.exe (进程 16944)已退出,代码为 0。
按任意键关闭此窗口. . .
程序输出的结果小于0,表示第一个数组小于第二个数组。
模拟实现memcmp函数和strncmp也非常相似,前者能比较所有的数据类型,通过内存中每个字节每个字节地比较,后者只能比较字符串。将该函数传入的两个参数强制转换成char*,然后进行每字节比较,过程与strncmp模拟思路一模一样!
int my_memcmp(const void* src1, const void* src2, size_t n)
{
assert(src1 && src2);
while (--n)
{
if (*(char*)src1 != *(char*)src2)
{
break;
}
(char*)src1 = (char*)src1 + 1;
(char*)src2 = (char*)src2 + 1;
}
//不相同处的字节或者最后一位字节 比较
return *(char*)src1 - *(char*)src2;
}
同一个测试用例:
int main()
{
int arr1[] = { 1, 3, 4, 6 ,9 };
int arr2[] = { 1, 3, 9, 8 ,0 };
int ret = my_memcmp(arr1, arr2, 20);
printf("%d ", ret);
return 0;
}
运行结果:
-5 C:UsersHPDesktopgiteetestworld7-06-字符串函数x64Debug7-06-字符串函数.exe (进程 8328)已退出,代码为 0。 按任意键关闭此窗口. . .
结果同样小于0,表示第一个数组小于第二个数组。



