C语言之库函数的模拟与使用
在我们学习C语言的过程中,难免会遇到这样的一种情况:
我们通常实现一个功能的时候,费尽心血的写出来,却有着满满的错,这时却有人来告诉你说:这个功能可以用相应的库函数来实现。
这时你的心里充满着***。但这并不算坏事,至少加深了你对它的认识与记忆。
所以,今天来漫谈一下 某些库函数的模拟与实现。
而这篇我们主要来介绍一些处理字符和字符串的库函数的使用和注意事项
内容大致如下:
1.求字符串长度 strlen
2.长度不受限制的字符串函数 strcpy strcat strcmp
3.长度受限制的字符串函数介绍 strncpy strncat strncmp
4.字符串查找 strstr strtok
5.错误信息报告 strerror perror
6.字符操作
7.内存操作函数 memcpy memmove memset memcmp
1.求字符串长度–strlen
正如所介绍那样,此函数的功能就是求一个字符串的长度。而我们所需要做的就是传一个字符指针进去。
示例:
#include <stdio.h> #include <string.h> int main () { char szInput[256]; printf ("Enter a sentence: "); gets (szInput); printf ("The sentence entered is %u characters long.\n",(unsigned)strlen(szInput)); return 0; }
结果:
在此函数的使用过程中我们需要注意以下几点:
1.字符串已经 '\0' 作为结束标志
2.strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
3.参数指向的字符串必须要以 '\0' 结束。
4.注意函数的返回值为size_t,是无符号的( 易错 )
我们来看一个对于第四点的例子:
#include <stdio.h> #include<string.h> int main() { const char* str1 = "abcdef"; const char* str2 = "bbb"; if (strlen(str2) - strlen(str1) > 0) { printf("str2>str1\n"); } else { printf("srt1>str2\n"); } return 0; }
这个结果会是什么呢?
相信会有人给出srt1>str2的结果,可编译器给出的结果却是:
为什么呢?
我们原以为 3-6=-3 这个式子没错啊,-3<0 执行 else 的结果
但 因为strlen的返回类型是一个unsigned形,所以相减之后的那个数会变为一个很大很大的数。
我们来模拟实现一下此函数
1.计数器方式
int my_strlen(const char* str) { int count = 0; while (*str) { count++; str++; } return count; }
2.递归实现
int my_strlen(const char* str) { if (*str == '\0') return 0; else return 1 + my_strlen(str + 1); }
3.指针-指针 实现
int my_strlen(char* s) { char* p = s; while (*p != '\0') p++; return p - s; }
2.长度不受限制的字符串函数
1.strcpy
这个函数可谓是见名知意,字符串的拷贝函数,我们来看看介绍:
实例:
#include <stdio.h> #include <string.h> int main() { char str1[] = "Sample string"; char str2[40]; char str3[40]; strcpy(str2, str1); strcpy(str3, "copy successful"); printf("str1: %s\nstr2: %s\nstr3: %s\n", str1, str2, str3); return 0; }
上述代码干了一个什么事呢?
将str1中的内容拷贝到str2中
又将 copy successful 拷贝到str3中
结果:
同样注意以下几点:
Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
源字符串必须以 '\0' 结束。
会将源字符串中的 '\0' 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串。目标空间必须可变。
模拟实现strcpy
#include<assert.h> char* my_strcpy(char* dest, const char* src) { char* ret = dest; assert(dest != NULL); //断言保证dest不为空指针 assert(src != NULL); //断言保证src不为空指针 while ((*dest++ = *src++)) { ; } return ret; }
2.strcat-连接两个字符串
介绍如下:
实例:
#include <stdio.h> #include <string.h> int main() { char str[80]; strcpy(str, "these "); strcat(str, "strings "); strcat(str, "are "); strcat(str, "concatenated."); puts(str); return 0; }
作用:
先将 these 复制到 str 中,在逐次将 strings 、are、concatenated 连接到str上
结果:
注意:
Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination. 1.源字符串必须以 '\0' 结束。 2.目标空间必须有足够的大,能容纳下源字符串的内容。目标空间必须可修改。
还值得注意的一点就是:若自己追加自己将会“永无休止”的循环下去,为什么呢?
模拟实现
#include<assert.h> char* my_strcat(char* dest, const char* src) { char* ret = dest; assert(dest != NULL); assert(src != NULL); while (*dest) //找到\0的位置 { dest++; } while ((*dest++ = *src++))//在此\0位置上覆盖新的内容 { ; } return ret; }
3.strcmp-字符串比较函数
切记:不能用 == 来判断两个字符串是否相等
介绍:
实例:
#include <stdio.h> #include <string.h> int main() { char key[] = "apple"; char buffer[80]; do { printf("Guess my favorite fruit? "); fflush(stdout); scanf("%79s", buffer); } while (strcmp(key, buffer) != 0); puts("Correct answer!"); return 0; }
作用:
定义一个key来表示自己喜欢的水果,
让用户不断来输入,
直到用户输入所指定内容
最后输出 correct answer
结果:
注意:
This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached. 标准规定: 第一个字符串大于第二个字符串,则返回大于0的数字 第一个字符串等于第二个字符串,则返回0 第一个字符串小于第二个字符串,则返回小于0的数字
比较是比的相应字符的ASCII码值,如果相等,则比较下一个以此类推…..
模拟实现:
int my_strcmp(const char* src, const char* dest) { int ret = 0; assert(src != NULL); assert(dest != NULL); while (!(ret = *(unsigned char*)src - *(unsigned char*)dest) && *dest)//dest不为空,且如果相减结果为0,就继续循环 { ++src, ++dest; if (ret < 0) ret = -1; else if (ret > 0) ret = 1; } return(ret); }
#include<assert.h> int my_strcmp(const char*s1, const char*s2) { assert(s1 && s2); while (*s1 == *s2) { if (*s1 == '\0') return 0; s1++; s2++; } return *s1 - *s2; }
3.长度受限制的字符串函数介绍
1.strncpy-从原始串复制自定义个字符到新串中
介绍:
实例:
#include <stdio.h> #include <string.h> int main () { char str1[]= "To be or not to be"; char str2[40]; char str3[40]; /* copy to sized buffer (overflow safe): */ strncpy ( str2, str1, sizeof(str2) ); /* partial copy (only 5 chars): */ strncpy ( str3, str2, 5 ); str3[5] = '\0'; /* null character manually added */ puts (str1); puts (str2); puts (str3); return 0;
}
作用:
将str1中所有的字符复制到str2中
将str1中的前五个字符复制到str3中
结果:
注意:
Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.
拷贝num个字符从源字符串到目标空间。 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
模拟实现:
char* my_strncpy(char* destination, const char* source, size_t num) { assert(destination && source); char* ret = destination; destination[num] = '\0'; while (num--) { if (*source == '\0') { *destination = '\0'; break; } *destination++ = *source++; } return ret; }
2.strncat–从原始串取自定义个字符连接到新串中
介绍:
示例:
#include <stdio.h> #include <string.h> int main() { char str1[20]; char str2[20]; strcpy(str1, "To be "); strcpy(str2, "or not to be"); strncat(str1, str2, 6); puts(str1); return 0; }
作用:
将str2中的前6个字符连接到str1后面
结果:
注意:
Appends the first num characters of source to destination, plus a terminating null-character. If the length of the C string in source is less than num, only the content up to the terminating nullcharacter is copied.
模拟实现:
char* my_strncat(char* destination, const char* source, size_t num) { assert(destination && source); char* ret = destination; while (*destination);//找到\0 destination[num] = '\0';//放置\0 while (num--) { *destination++ = *source++; if (*source == '\0') { *destination = '\0'; break; } } return ret; }
3.strncmp-自定义个字符比较
比较方式与strcmp相同
介绍:
示例:
#include <stdio.h> #include <string.h> int main() { char str[][5] = { "R2D2" , "C3PO" , "R2A6" }; int n; puts("Looking for R2 astromech droids..."); for (n = 0; n < 3; n++) if (strncmp(str[n], "R2xx", 2) == 0) { printf("found %s\n", str[n]); } return 0; }
作用:
比较各字符串的前两个字符是否为R2,如果相同,则输出
结果:
注意:
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
模拟实现:
#include <stdio.h> #include <string.h> int my_strncmp(const char* str1, const char* str2, size_t num) { assert(str1 && str2); while (num--) { if ((*str1 != *str2)) { return *str1 - *str2; } str1++; str2++; } return 0; }
4.字符串查找函数
1.strstr-在一个字符串中查找另一个字符串是否存在并返回相应的地址
介绍:
示例:
#include <stdio.h> #include <string.h> int main() { char str[] = "This is a simple string"; char* pch; pch = strstr(str, "simple"); if (pch != NULL) strncpy(pch, "sample", 6); puts(pch); return 0; }
作用:
在str中查找simple是否存在
结果:
注意:
Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.
模拟实现:
#include <stdio.h> #include <string.h> const char* my_strstr(const char* str1, const char* str2) { assert(str1 && str2); const char* start = str1; while (*start) { const char* e1 = start; const char* e2 = str2; while ((*e1 == *e2) && (*e1) && (*e2)) { e1++; e2++; } if (*e2 == '\0') { return (char*)start; } start++; } return NULL; }
2.strtok-字符串分割函数
介绍:
实例:
#include <stdio.h> #include <string.h> int main() { char str[] = "EG:- This, a sample string."; char* pch; printf("Splitting string \"%s\" into tokens:\n", str); pch = strtok(str, " ,.-"); while (pch != NULL) { printf("%s\n", pch); pch = strtok(NULL, " ,.-"); } return 0; }
作用:
将所给字符串用所给分隔符分割开;
结果:
注意:
char * strtok ( char * str, const char * sep );
sep参数是个字符串,定义了用作分隔符的字符集合
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改 变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL 指针。
5.错误信息报告
1.strerror-返回错误码,所对应的错误信息。
介绍:
示例:
#include <stdio.h> #include <string.h> #include <errno.h>//必须包含的头文件 int main() { FILE* pFile; pFile = fopen("unexist.ent", "r"); if (pFile == NULL) printf("Error opening file unexist.ent: %s\n", strerror(errno)); return 0; }
结果:
注意:
使用时必须包含头文件:errno.h
2.perror-打印对应的错误信息
示例:
#include <stdio.h> int main() { FILE* pFile; pFile = fopen("unexist.ent", "rb"); if (pFile == NULL) perror("The following error occurred"); else fclose(pFile); return 0; }
结果:
6.字符分类函数:
注意:
比较时,是一个一个字符的去比较
示例及结果:
#include <stdio.h> #include <ctype.h> int main () { int i; char str[]="c3po..."; i=0; while (isalnum(str[i])) i++; printf ("The first %d characters are alphanumeric.\n",i); return 0; }
#include <stdio.h> #include <ctype.h> int main() { int i = 0; char str[] = "C++"; while (str[i]) { if (isalpha(str[i])) printf("character %c is alphabetic\n", str[i]); else printf("character %c is not alphabetic\n", str[i]); i++; } return 0; }
7.内存操作函数
内存操作函数不仅可以处理字符串,还可以处理结构体,整形数组等。
1.memcpy–以字节为单位复制
介绍:
示例:
#include <stdio.h> #include <string.h> struct { char name[40]; int age; } person, person_copy; int main() { char myname[] = "Pierre de Fermat"; /* using memcpy to copy string: */ memcpy(person.name, myname, strlen(myname) + 1); person.age = 46; /* using memcpy to copy structure: */ memcpy(&person_copy, &person, sizeof(person)); printf("person_copy: %s, %d \n", person_copy.name, person_copy.age); return 0; }
结果:
注意:
void * memcpy ( void * destination, const void * source, size_t num );
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到 '\0' 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的
模拟实现:
void* my_memcpy(void* destination, const void* source, size_t num) { assert(destination && source); void* ret = destination; while (num--) { *((char*)destination)++ = *((char*)source)++; } return ret; }
2.memmove
介绍:
示例:
#include <stdio.h> #include <string.h> int main () { char str[] = "memmove can be very useful......"; memmove (str+20,str+15,11); puts (str); return 0; }
结果:
注意:
memmove和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。
模拟实现:
void* my_memmove(void* dest, const void*src, size_t count) { void* ret = dest; assert(dest && src); if (dest < src) { //前->后 while (count--) { *(char*)dest = *(char*)src; ++(char*)dest; ++(char*)src; } } else { //后->前 while (count--) { *((char*)dest + count) = *((char*)src + count); } } return ret; }
3.memset
介绍:
示例:
#include <stdio.h> #include <string.h> int main () { char str[] = "almost every programmer should know memset!"; memset (str,'-',6); puts (str); return 0; }
结果:
模拟实现:
void * Memset(void* src, int ch, size_t count) { assert(src != NULL); void* start = src; while (count--) { *(char*)src = (char)ch; src = (char*)src + 1; } return start; }
4.memcmp
介绍:
示例:
#include <stdio.h> #include <string.h> int main () { char buffer1[] = "DWgaOtP12df0"; char buffer2[] = "DWGAOTP12DF0"; int n; n=memcmp ( buffer1, buffer2, sizeof(buffer1) ); if (n>0) printf ("'%s' is greater than '%s'.\n",buffer1,buffer2); else if (n<0) printf ("'%s' is less than '%s'.\n",buffer1,buffer2); else printf ("'%s' is the same as '%s'.\n",buffer1,buffer2); return 0; }
结果:
注意:
比较从ptr1和ptr2指针开始的num个字节
返回值同strcmp
模拟实现:
int my_memcmp(const void* p1, const void* p2, size_t count)//方法1 { assert(p1); assert(p2); char* dest = (char*)p1; char* src = (char*)p2; while (count-- && (*dest++ == *src++)) { ; } if (count == 0) return 0; return *dest - *src; }
若有有错之处,还望大家多多指点!!!
本次讲解就到这了,
如果大家对于函数的模拟实现都能掌握,那么大家肯定对于上述函数有了深刻的理解。