这些函数在不久的将来,频繁大量地使用。通过对各类操作函数的模拟实现,不仅对理解类似函数的功能有所帮助,还对往后的刷题环节有帮助,多多练习总结,方可悟道。
1 求字符串长度strlen
strlen函数返回字符串中的字符数,不包括终端NULL。发生错误也不会返回表示错误的值。
size_t是编译器本身用typedef重命名的一个unsigned int类型,也就是该函数的返回类型。该函数的输入类型是字符串的首地址。计算从首地址开始,一直到’\0’之间的字符个数。
下面是strlen的用法,输出的结果为:< 思考一下,如果判断条件是if (strlen(“abc”) - strlen(“abcdef”)>0),结果为什么会是:> ???
#include<string.h>
int main()
{
if (strlen("abc") > strlen("abcdef") )
printf(">");
else
printf("<");
return 0;
}
因为size_t是一个无符号类型的数,两个无符号类型的数相减,结果肯定不会是一个负数,所以不可能会出现小于0 的情况。
模拟实现
程序员在设计这个函数的时候,使用unsigned int类型有两种可能的原因:1是因为字符串的长度不可能出现负数,2是因为无符号类型可以表示的长度更长(仅仅是个人的猜测)。
我在模拟实现的时候,打算使用int类型,因为这样可以使代码更加灵活,可以使上面的两种写法都能运行成功!
有三种方法模拟实现: 法一:计数器
int my_strlen(const char* str)
{
assert(str);
int count = 0;
while (*str)
{
count++;
str++;
}
return count;
}
法二:迭代
int my_strlen(const char * str)
{
if(*str == '\0')
return 0;
else
return 1+my_strlen(str+1);
}
法三:指针 减 指针
int my_strlen(char *s)
{
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}
2 错误信息报告 strerror
当程序运行性出现错误的时候,本次执行库函数产生的错误码会放到全局变量errno中。而strerror函数的功能就是把这个错误代码翻译成人可以看懂的语言。 下面的操作可以看出:不同的errno值,翻译成不同的错误:
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%s\n", strerror(i));
}
return 0;
}
其中errno是C语言提供的一个全局变量,放在errno.h文件中,可以直接使用。 错误报告函数通常出现在文件操作中,如下的代码,以只读的形式打开一个不存在的文件,会报出错误:文件不存在(No such file or directory)
int main()
{
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
printf("%s\n", strerror(errno));
return 0;
}
fclose(pf);
pf = NULL;
return 0;
}
关于更多的文件操作,可以点击这里>>>
3 拷贝 函数
strcpy
实现的功能:拷贝一个字符串。
当被拷贝的字符串中没有\0的时候,打印出来的结果会出现乱码,而带有\0的时候,打印的结果就不会出现乱码。
int main()
{
char arr1[] = { 'a','b','c','d', };
char arr2[20] = "xxxxxxxxxxxxxxxx";
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
模拟实现
形参是字符串的地址,是char* 类型,为了防止有人把原地址和目的地址的位置写反,使用const修饰,使它指向的元素不能被更改。
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest++ = *src++)
{
;
}
return ret;
}
strncpy
拷贝num个字符从源字符串到目标空间。 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加’\0’,直到num个。
例如以下的代码:arr2的长度就不足7个,所以当字符串拷贝到arr1之后,自动在后面补上三个’\0’。
int main()
{
char arr1[] = "XXXXXXXXXXX";
char arr2[] = "abcd";
strncpy(arr1, arr2, 7);
printf("%s\n", arr1);
return 0;
}
模拟实现
char* my_strncpy(char* strDest, const char* strSource, size_t count)
{
assert(strDest && strSource);
char* tmp = strDest;
int i = 0;
for (i = 0; i < count; i++)
{
if (*(strSource+i))
{
*(strDest+i) = *(strSource+i);
}
else
{
*(strDest + i) = '\0';
}
}
return tmp;
}
int main()
{
char arr1[] = "XXXXXXXXXXX";
char arr2[] = "abcd";
printf("%s\n", my_strncpy(arr1, arr2,3));
printf("%s\n", my_strncpy(arr1, arr2,7));
return 0;
}
memcpy
实现功能:在两个数组之间拷贝任意类型的数据。 与strcpy不同的是,strcpy只能拷贝字符串类型的数据,而memcpy可以拷贝任意类型的数据。
void test1()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr3+2, arr3, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
}
int main()
{
test1();
return 0;
}
模拟实现
为了接收任意类型的数据,形参的类型必须是void※类型,(如果是char※类型,就不能接收int※在内的其他类型)。
将void※转化为char※类型,一次对一个字节进行操作。
void* my_memcpy(void* dest, const void*src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr3+2, arr3, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
return 0;
}
memcpy运行的结果是1 2 1 2 3 4 5 8 9 10(结果一),所以我模拟的函数运行的结果也应该是结果一
但是my_memcpy得到的结果是:1 2 1 2 1 2 1 8 9 10(结果二)
这并不是说写的有问题。memcpy能拷贝不重叠的空间就达到要求了。如果发现有重叠拷贝,使用memmove去处理。
vs环境下的memcpy也能实现重叠拷贝,相当于超额完成目标
memmove
功能更加强大的拷贝函数, 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
int main ()
{
char str[] = "memmove can be very useful......";
memmove (str+20,str+15,11);
puts (str);
return 0;
}
模拟实现
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (num--)
{
*((char*)dest+num) = *((char*)src + num);
}
}
return ret;
}
4 字符串追加
strcat
将字符串二,放在字符串一的后面,并返回字符串一的地址。
int main()
{
char arr1[30] = "hello ";
char arr2[20] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
模拟实现
在执行的时候,首先要找到目的字符串的’\0’,从这个位置开始,把字符串二拷贝进来,这个追加到目标空间的过程,可以分为几步: str指针指向的内容给到Dest指向的内容; 然后两个指针指向的内容往后移动一个位置; 最后str指向了’\0’,还是要把’\0’拷贝进去,至此就完成了字符串的追加。
char* my_strcat(char* Dest, const char* str)
{
char* ret = Dest;
assert(Dest && str);
while (*Dest)
{
Dest++;
}
while (*Dest++ = *str++)
{
;
}
return ret;
}
int main()
{
char arr1[30] = "hello ";
char arr2[20] = "world";
my_strcat(arr1, arr2);
printf("%s\n", my_strcat(arr1, arr2));
return 0;
}
strncat
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;
}
5 比较函数
strcmp
int main()
{
char arr1[] = "abc";
char arr2[] = "abq";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
比如这个程序对两个字符串进行比较(比较每一位的ASCII码),a和a一样;b和b一样,c和q明显q的大小更大,所以arr<arr2,返回的结果为一个小于0的数。
模拟实现
先对字符串的每一位进行比较(循环),该循环结束有两种可能: 1 遇到了不一样的字符。 2 字符串每一位比较结束了(’\0’结束)
int my_strcmp(const char* string1, const char* string2)
{
assert(string1 && string2);
while (*string1 == *string2)
{
if (*string1 == '\0')
return 0;
string1++;
string2++;
}
if (*string1 > *string2)
return 1;
else
return -1;
}
int main()
{
char arr1[] = "abc";
char arr2[] = "abq";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
当然标准规定的只是返回大于或小于零的数,并没有要求它的返回一定是+1或者-1! 所以在使用这个函数做判断的时候,一定要注意。
strncmp
比较前n相的字符。
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqqqqqqq";
int ret=strncmp(arr1, arr2, 3);
printf("%d\n", ret);
return 0;
}
模拟实现
int __cdecl strncmp
(
const char *first,
const char *last,
size_t count
)
{
size_t x = 0;
if (!count)
{
return 0;
}
if( count >= 4 )
{
for (; x < count-4; x+=4)
{
first+=4;
last +=4;
if (*(first-4) == 0 || *(first-4) != *(last-4))
{
return(*(unsigned char *)(first-4) - *(unsigned char *)(last-4));
}
if (*(first-3) == 0 || *(first-3) != *(last-3))
{
return(*(unsigned char *)(first-3) - *(unsigned char *)(last-3));
}
if (*(first-2) == 0 || *(first-2) != *(last-2))
{
return(*(unsigned char *)(first-2) - *(unsigned char *)(last-2));
}
if (*(first-1) == 0 || *(first-1) != *(last-1))
{
return(*(unsigned char *)(first-1) - *(unsigned char *)(last-1));
}
}
}
for (; x < count; x++)
{
if (*first == 0 || *first != *last)
{
return(*(unsigned char *)first - *(unsigned char *)last);
}
first+=1;
last+=1;
}
return 0;
}
memcmp
对每一个比特数进行比较,当比较哦前9位的话,得到的结果为1 当比较哦前8位的话,得到的结果为0.
int main()
{
int arr1[] = { 1,2,7,4,5 };
int arr2[] = { 1,2,3,4,5 };
int ret = memcmp(arr1, arr2, 9);
ret = memcmp(arr1, arr2, 8);
printf("%d\n", ret);
return 0;
}
5 字符串查找函数
strstr
在字符串中找到一个子字符串。如果这个要子字符串不存在,返回空指针;如果存在,返回找到位置的地址。
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "bcd";
char* ret = strstr(arr1, arr2);
if (NULL == ret)
{
printf("没找到\n");
}
else
{
printf("%s\n",ret);
}
return 0;
}
模拟实现
需要考虑一下细节: 比如子字符串为空,那么我就不需要找了,直接返回;
最简单的一种情况:substr和str每一位相比,当substr找到‘\0’就说明已经在str中找到了substr。
如果str=abbbcdef sub=bbc,为了能返回原来的位置,最好创建新的指针保存其 当前比较的位置(cur)。
#include<assert.h>
char* my_strstr(const char* str, const char* substr)
{
assert(str, substr);
const char* s1 = str;
const char* s2 = substr;
char* cur = str;
if (*substr == '\0')
{
return (char*)str;
}
while (*cur)
{
s1 = cur;
s2 = substr;
while (*s1!='\0'&& *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return (char*)cur;
cur++;
}
return NULL;
}
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "bcd";
char* ret = my_strstr(arr1, arr2);
if (NULL == ret)
{
printf("没找到\n");
}
else
{
printf("%s\n",ret);
}
return 0;
}
strtok
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记, strtok函数将保存它在字符串中的位置 (不妨使用静态变量保存它) 如果字符串中不存在更多的标记,则返回 NULL 指针。
int main()
{
const char* p = "@.#";
char arr[] = "zpengwei@yeah.net#hehe";
char buf[50] = { 0 };
strcpy(buf, arr);
char* str = NULL;
for (str = strtok(buf, p); str != NULL; str=strtok(NULL, p))
{
printf("%s\n", str);
}
return 0;
}
函数找第一个标记的时候,函数的第一个参数不是NULL 函数找第二个标记的时候,函数的第一个参数是NULL
|