库函数的使用和实现
写在前面
库函数的实现在C语言的复习中占据着举足轻重的地位,因为本身实现就考验个人对C语言指针等知识点的理解与应用。当中,出现考察频率比较高的也是比较难的点的是:strcpy, strlen, strstr, memcpy, memove, strcat, atoi, itoa. 我们着手于这些比较常出现的库函数的实现。
同时,我将从函数介绍(如何使用及使用的注意事项),模拟实现两方面来展开。
库函数
字符串函数
strlen
size_t strlen ( const char * str );
注意:
- 字符串以’\0’ 为结束标志,strlen函数返回的是在字符串中‘\0’ 之前的字符个数,不含’\0’。
- 必须以’\0’ 为结束,否则是随机值。
- 注意函数返回值,size_t (一种无符号整型)
模拟实现:
//计数器方式
int myStrlen(const char* str)
{
int count = 0;
while(*str)
{
count++;
str++;
}
return count;
}
//不可以创建临时变量(不能用计数器),递归。
int myStrlen(const char* str)
{
if(*str == '\0')
{
return 0;
}
return 1+myStrlen(str+1);
}
//指针-指针,也可以得到字符串长度
int myStrlen(const char* s)
{
char* p = s;
while(*p != '\0')
{
p++;
}
return p - s;
}
strcpy
char* strcpy(char * destination, const char * source );
字符串拷贝:
- 源字符串必须以’\0’结尾。
- 源字符串中的’\0’ 也会被拷贝至目标空间。
- 目标空间确保要足够大。
- 目标空间必须可变,不能不让操作。
- 返回的是destination字符串
- 要求destination可变,所以一般用数组。不可以用个const char*类型的字符串,因为其本身不可变。
模拟实现:
char* myStrcpy(char* dest, const char* source)
{
char* ret = dest;
assert(dest != NULL);
assert(source != NULL);
while(*s != '\0')
{
*dest = *source;
dest++;
source++;
}
dest = '\0';
return ret;
}
strcat
char * strcat ( char * destination, const char * source );
- 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.
- 追加字符串。
- 源字符串必须’\0’结尾。
- 目标空间必须足够大。
- 目标空间必须可变。
- 返回destination
模拟实现:
char* myStrcat(char* dest, const char* source){
char* ret = dest;
assert(dest);
assert(source);
while(*dest != '\0')
{
dest++;
}
while((*dest++ = *source++ ));
return ret;
}
strcmp
int strcmp ( const char * str1, const char * str2 );
- 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的数字。
模拟实现:
int myStrcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
// while(*str1++ == *str2++ && *str1 != '\0');
while(*str1 == *str2 && *str1 != '\0'){
str1++;
str2++;
}
if(*str1 == *str2 && *str1 == '\0'){
return 0;
}
return (int)(*str1 - *str2);
}
strncpy
char * strncpy ( char * destination, const char * source, size_t num );
- 与strcpy的区别在于,多了一个参数num。
- 表示从源字符串拷贝num个字符到目标空间。
- 如果源字符串的长度小于num,追加0,致总长度为num。
不需要掌握带n的字符串函数的模拟实现,只需要掌握原始的,并且清楚他们的区别。
strncat
char * strncat ( char * destination, const char * source, size_t num );
strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
- 比较到出现字符不相同,或者一个字符串结束,或者num个字符比较完成。其余与strcmp完全相同。
strstr
char* strstr(const char* str1, const char* str2);
- 寻找str1中的子串str2,如果找到了返回str2开始的位置,如果没找到返回空指针。
- 一般用来寻找子串开始的位置然后进行修改。
模拟实现:
char* myStrstr(const char* str1, const char* str2){
char* cp = (char*)str1;
char* s1, *s2;
if(*str2 == NULL)
return ((char*) str1);
while(*cp)
{
s1 = cp;
s2 = ((char*) str2);
while(*s1 && *s2 && !(*s1 - *s2) )
{
s1++, s2++;
}
if(*s2 == '\0')
return cp;
cp++;
}
return NULL;
}
strtok
char * strtok ( char * str, const char * sep );
- sep参数是字符串,定义分隔符的合集。
- 第一个参数str制定一个字符串,包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数会找到str中下个标记,并将其用’\0’结尾,返回一个指向这个标记的指针。
- strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容而且可以修改。
- strtok函数的第一个参数若为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- strtok函数的第一个参数若不为NULL,函数将找到str中的第一个标记,并保存它在字符串中的位置。(等待调用第一个参数为NULL时再使用它)。
使用:
int main()
{
char str[] = "Please! Give age to civilization, not civilization to age."
char* p;
printf("%s\n", str);
p = strtok(str, ",.!");
while(p != NULL)
{
printf("%s\n",p);
p = strtok(NULL,",.!");
}
return 0;
}
//输出:
//Please
// Give age to civilization
// not civilization to age
- 不要求掌握模拟实现
strerror
char * strerror ( int errnum );
- 作用是返回错误码所对应的错误信息。
- 必须包含头文件 <errno.h>
- 如果运行程序失败,返回了错误信息,会保存在errno中,使用打印strerror(errno) 的方法可以打印出错误信息。
字符分类函数
函数 | 参数符合下列条件返回true |
---|---|
iscntrl | 控制字符 |
isspace | 空白字符, 包括space : ’ ‘, 换页’\f’, 换行’\n’, 回车’\r’, 制表符’\t’或者垂直制表符’\v’ |
isdigit | 十进制数组0-9 |
isxdigit | 十六进制数字,包括十进制数字和大小写字母a-f,A-F |
islower / isupper | 大写小写字母 |
isgraph | 图形字符 |
isalpha | 大小写字母 |
isalnum | 字母或者数字 |
ispunct | 标点符号 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
字符转换函数
int tolower(int c);
int toupper(int c);
- 转换成大写或者小写字母。
memcpy
void* memcpy(void* destination, const void* source, size_t num);
- 函数memcpy从source的位置开始向后复制num个字节的数据,到destination的内存位置。
- 遇到’\0’不会停下来。
- 如果source和destination有任何重叠,复制结果都是未定义的。不能用来处理任何内存重叠的空间!
模拟实现:
void* myMemcpy(void* destination, const void* source, size_t num)
{
void* ret = dest;
assert(destination && source);
while(num --){
*(char*) destination = *(char*) source;
destination = (char*)destination + 1;
source = (char*)source + 1;
}
return ret;
}
//一定要强转成char*类型再去赋值和++;
memmove
void * memmove ( void * destination, const void * source, size_t num );
- 和memcpy相近,区别是,memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
模拟实现:
void* myMemmove( void* destination, const void* source, size_t num)
{
void* ret = destination;
if(destination <= source || (char*destination >= ((char*)source + num))){
//Non-overlapping
//just like the implement of memcpy
while(num--){
*(char*)destination = *(char*)source;
destination = (char*)destination + 1;
source = (char*)source + 1;
}
else{
//overlapping
//copy from higher add to lower add.
// in case of wrong override.
destination = (char*)destination + num - 1;
source = (char*)source + num - 1;
while(num --){
*(char*)destination = *(char*)source;
destination = (char*)destination - 1;
source = (char*)source - 1;
}
}
}
return ret;
}
memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
- 比较从ptr1 和ptr2 指针开始的num 个字节
- 返回值:如果内存块中内容相同返回0,总之,返回(*ptr1 - *ptr2)的int值。
- 模拟实现与strcmp类似,不再重复实现。
小结
本小节的内容主要是库函数的使用及其实现。需要注意的是使用上述的函数,都需要包含头文件,如下所示。
#include<string.h>//上述所有的str和mem函数
#include<errno.h>//使用strerror(errno);
其中比较重要的模拟实现已经列在上面,多复习多总结,尤其是拷贝内存的memmove函数,比较关键。加油!