这些函数在不久的将来,频繁大量地使用。通过对各类操作函数的模拟实现,不仅对理解类似函数的功能有所帮助,还对往后的刷题环节有帮助,多多练习总结,方可悟道。
strlen函数返回字符串中的字符数,不包括终端NULL。发生错误也不会返回表示错误的值。
size_t是编译器本身用typedef重命名的一个unsigned int类型,也就是该函数的返回类型。该函数的输入类型是字符串的首地址。计算从首地址开始,一直到’\0’之间的字符个数。
下面是strlen的用法,输出的结果为:<
思考一下,如果判断条件是if (strlen(“abc”) - strlen(“abcdef”)>0),结果为什么会是:> ???
#include
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;
}
当程序运行性出现错误的时候,本次执行库函数产生的错误码会放到全局变量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;
}
关于更多的文件操作,可以点击这里>>>
当被拷贝的字符串中没有\0的时候,打印出来的结果会出现乱码,而带有\0的时候,打印的结果就不会出现乱码。
int main()
{
//char arr1[] = "abcdef";
char arr1[] = { 'a','b','c','d', };//出问题了,要有\0
//char arr1[] = { 'a','b','c','d','\0' };
char arr2[20] = "xxxxxxxxxxxxxxxx";
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
形参是字符串的地址,是char* 类型,为了防止有人把原地址和目的地址的位置写反,使用const修饰,使它指向的元素不能被更改。
#include
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest++ = *src++)
{
;
}
//while (*src)
//{
// *dest=*src;
// dest++;
// src++;
//}
//*dest = *src;//最后还要把\0拷贝进去
return ret;
}
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加’\0’,直到num个。
例如以下的代码:arr2的长度就不足7个,所以当字符串拷贝到arr1之后,自动在后面补上三个’\0’。
int main()
{
char arr1[] = "XXXXXXXXXXX";
char arr2[] = "abcd";
strncpy(arr1, arr2, 7);//一个不多,一个也不少,如果字符串长度不够,后面自动补上\0
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;
}
实现功能:在两个数组之间拷贝任意类型的数据。
与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也能实现重叠拷贝,相当于超额完成目标
功能更加强大的拷贝函数,
和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;
}
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);
//1 找到目标空间中的\0
while (*Dest)
{
Dest++;
}
//2 追加内容到目标空间
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;
}
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;
}
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 先对字符串的每一位进行比较(循环),该循环结束有两种可能: 当然标准规定的只是返回大于或小于零的数,并没有要求它的返回一定是+1或者-1! 对每一个比特数进行比较,当比较哦前9位的话,得到的结果为1 需要考虑一下细节: 最简单的一种情况:substr和str每一位相比,当substr找到‘\0’就说明已经在str中找到了substr。 如果str=abbbcdef 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记, 函数找第一个标记的时候,函数的第一个参数不是NULL模拟实现
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;
//retrun *string1-*string2//标准 的规定是返回负数 正数就行,并不是+1 -1
}
int main()
{
char arr1[] = "abc";
char arr2[] = "abq";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
所以在使用这个函数做判断的时候,一定要注意。strncmp
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 )
{
/* unroll by four */
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));
}
}
}
/* residual loop */
for (; x < count; x++)
{
if (*first == 0 || *first != *last)
{
return(*(unsigned char *)first - *(unsigned char *)last);
}
first+=1;
last+=1;
}
return 0;
}
memcmp
当比较哦前8位的话,得到的结果为0.int main()
{
int arr1[] = { 1,2,7,4,5 };
int arr2[] = { 1,2,3,4,5 };
int ret = memcmp(arr1, arr2, 9);//最后一个是比较的bit数
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;
}
模拟实现
比如子字符串为空,那么我就不需要找了,直接返回;
sub=bbc,为了能返回原来的位置,最好创建新的指针保存其 当前比较的位置(cur)。#include
strtok
记。
strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
并且可修改。)
strtok函数将保存它在字符串中的位置 (不妨使用静态变量保存它)
如果字符串中不存在更多的标记,则返回 NULL 指针。int main()
{
const char* p = "@.#";
char arr[] = "[email protected]#hehe";
char buf[50] = { 0 };// "[email protected]"
strcpy(buf, arr);
//这样子的循环写的好啊,巧妙运用了初始化的不同
char* str = NULL;
for (str = strtok(buf, p); str != NULL; str=strtok(NULL, p))
{
printf("%s\n", str);
}
//char* str = strtok(buf, p);//zpengwei
//printf("%s\n", str);
//str = strtok(NULL, p);//yeah
//printf("%s\n", str);
//str = strtok(NULL, p);//net
//printf("%s\n", str);
//strtok - 开始返回NULL
return 0;
}
函数找第二个标记的时候,函数的第一个参数是NULL