博主实力有限 ,博文有什么错误,请你斧正,非常感谢! |
---|
编译器:VS2019 |
本文介绍C语言 |
因为C语言只给出了,函数的作用,对函数实现并不关心,因此在VS2019下某些字符串函数的行为可能与其它编译器不同,但是效果是大同小异的。 |
复写strstr时,博主目前掌握了BF算法,对于KMP算法将会在后续独立出一篇博客。 |
size_t strlen(const char string);*
- 返回字符串中的字符数,不包括
终止空字符(‘\0’)
。注意:strlen在计算字符数时,只要碰到‘\0’,就结束计算.这就可以认为当没碰到‘\0’,就不停止计算。因此说长度不受限制
返回值:无符号整形,因此在赋值时,某些编译器可能会报警告
参数是:
常量
字符指针。(不可修改指向的数据)
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
//我的:循环,递归,指针
size_t my_strlen1(const char* string)//循环
{
assert(string);//防止传入NULL指针
size_t cnt = 0;
while (*string++)
{
++cnt;
}
return cnt;
}
size_t my_strlen2(const char* string)//递归
{
assert(string);
return *string == '\0' ? 0 : 1 + my_strlen2(string + 1);
}
size_t my_strlen3(const char* string)//指针
{
assert(string);
const char* bigin = string;
while (*string++)//注意这里当*string=='\0'时,string指向了'\0'后面的位置,因此返回时,要-1
{
}
return (string - bigin-1);
}
int main()
{
char arr[] = "hello";
printf("%d\n", my_strlen1(arr));//循环
printf("%d\n", my_strlen2(arr));//递归
printf("%d\n", my_strlen3(arr));//指针
return 0;
}
char * strcpy(char dest,const char src);
将src指向的内容依次拷贝到 dest指向的内存,直到遇到str中的’\0‘,同时返回dest的首地址。如果没遇到‘\0’,也就是src不存在’\0‘,会访问非法内存,
常量
字符指针,不能更改str指向的内容。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);//防止传入NULL指针
char* ret=dest;
while (*dest++=*src++)//当dest传入'\0',停止拷贝
{
;
}
return ret;
}
int main ()
{
char arr[20]={
0 };
printf("%s\n", my_strcpy(arr, "helloword"));
return 0;
}
char * strcat(char * dest , const char src);**
从dest的字符串结束标志’\0‘开始,将src中的内容追加到dest后面。直到遇到src的’\0‘.如果没遇到,会非法访问内存。因此长度不受限制,不安全
追加字符串时不执行溢出检查。因此dest中 必须由程序执行者,预留足够空间
返回 dest的首地址
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);//防止传入NULL指针
char* ret = dest;
while (*dest)//找dest的'\0'位置,不可while(*dest++),因为找到'\0'时,dest越界了
{
dest++;
}
while (*dest++ = *src++)
{
}
return ret;
}
int main()
{
char arr[20] = "hello";
printf("%s\n", arr);
my_strcat(arr, "word");
printf("%s\n", arr);
return 0;
}
int strcmp(const char str1, const char str2);**
比较字符串看的是ASICC,不是长度
比较 字符串str1与字符串str2是否相等.并返回一个比较值(比较字符串,只能用它。)
比较规则:
- 先比较 str1与str2指向字符的ASSICC值,如果不同
(代表字符串不等)
就返回 2者差值。一旦相等就继续比较,直到str1与str2 同时都是‘\0’,(代表字符串相等)
返回0;
返回值。
<0 字符串1小于字符串2。
0 字符串1与字符串2相同。
>0 字符串1大于字符串2。在复写时,我发现vs2019返回的是三个确定的值。虽然符合返回值要求,但是不能代表全部。这只是 vs内置的结果。因此复写了2种strcmp
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
//正常
int my_strcmp1(const char* string1, const char* string2)
{
assert(string1 && string2);
while (*string1 == *string2)
{
if (*string1 == '\0')//当*s1,*s2同时为'\0'时,才说明字符串相等,返回0
{
return 0;
}
++string1;
++string2;//前置++比后置++快
}
return (*string1 - *string2);
}
// vs2019版
int my_strcmp2(const char* src, const char* dest)
{
int ret = 0;
assert(src);
assert(dest);
while (!(ret = *(unsigned char*)src - *(unsigned char*)dest) && *dest)
//unsigned char* 是因为char是否为有符号,根据编译器,默认不同
//妙!以ret接受差值,通过!非0的数都为真.
//同时&&&的使用,确保了相等的情况。
{
++src;
++dest;
}
if (ret < 0)
{
ret = -1;
}
else if (ret > 0)
{
ret = 1;
}
return (ret);
}
int main ()
{
char arr1[] = "helloword";
char arr2[] = "hello";
if (0 == my_strcmp1(arr1, arr2))
{
printf("相等\n");
}
else
{
printf("不相等\n");
}
if (0 == my_strcmp2(arr1, arr2))
{
printf("相等\n");
}
else
{
printf("不相等\n");
}
return 0;
}
strlen,strcpy…这些字符串函数,只是执行代码,不关心会发生什么。有可能会访问非法内存。不安全。因此后面介绍长度受限制安全的strcnpy,一定程度上安全,但是不绝对
char strncpy(char Dest,const char* Src,size_t count);**
将 src前 count个字符拷贝到dest。但是如果count超过了src的字符个数时,会赋值’\0‘,
dest 的空间仍是程序调用者,自己控制
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
char* my_strncpy(char* dest, const char* src, size_t cnt)
{
assert(dest && src);
char* ret = dest;
if (strlen(dest) < (cnt - 1))//strlen的原因(不计数'\0'),因此cnt-1
{
printf("拷贝数目大于源字符串字节数,此行为是未定义的\n");
exit(1);
}
//开始拷贝
// 下面这种方式虽然可行,但是在vs调试中,我发现strncpy在 多余时,仍会对dest赋值'\0'
//while ((cnt-- > 0)&&(*dest++ = *src++))//'\0'数值为0.因为 &&先左后右的求值顺序特点,
// //当cnt为0时,可能还未到达src的末尾,其会再赋值一次非'\0'
//{
// ;
//}
//开始拷贝
while (cnt > 0)
{
if (*dest = *src)//当*src为'\0'时,src不再++;
{
if (*src != '\0')
{
++dest;
++src;
}
else
{
++dest;
}
}
--cnt;
}
return ret;
}
int main()
{
char arr1[20] = "hello";
printf("%s\n", strncpy(arr1, "word", 8));
char arr2[20] = "hello";
printf("%s\n", my_strncpy(arr2, "word00", 5));
char arr3[] = "hello";
printf("%s\n", my_strncpy(arr3, "word", 4));
return 0;
}
*char strncat(char Dest,const char Src,size_t count );
- 如果count大于src的长度,就将src全部追加到dest,包括‘\0’。反之,追加src前 count的字符。并再追加一个‘\0’
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
char* my_stcncat(char* dest, const char* src, size_t cnt)
{
assert(dest && src);
char* ret = dest;
while (*dest)//定位源字符串的'\0'指针
{
dest++;
}
if (cnt > strlen(src))//当cnt大的时侯,就是完整拷贝。
{
while (*dest++=*src++)
{
;
}
}
else {
while ((cnt-->0)&&(*dest++=*src++))
{
;
}
*dest = '\0';//因为cnt小的原因,根据要求,追加完后,需要再追加一个'\0'
}
return ret;
}
int main()
{
char arr1[20] = "helloxx\0xxxxxxxxxx";
char arr2[20] = "helloxx\0xxxxxxxxxx";
printf("%s\n", my_stcncat(arr1, "word", 3));
printf("%s\n", strncat(arr2, "word", 3));
return 0;
}
int strncmp( const char* str1, const*char str2,size_t cnt )
- 当 cnt 大于 str1与str2 中长度最大的时,效果与 strcmp相同。反之 比较 长度最大的前cnt为。
- 比较规则同strcmp
- 注意复写时,我是以vs2019 为结论编写程序,返回值不是一个定值。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
int my_strncmp(const char* str1, const char* str2, size_t cnt)
{
assert(str1 && str2);
int ret = 0;
while (!(ret=(*str1-*str2))&&(*str1)&&(*str2)&&(cnt>0))//只要当str1,str2其中为'\0',或者cnt为0就停止
{
--cnt;
++str1;
++str2;
}
if (ret == 0)
{
return 0;
}else if(ret>0)
{
return 1;
}
else
{
return -1;
}
}
int main ()
{
char* arr1 = "hello";
char* arr2 = "hello";
printf("%d\n", strncmp(arr1, arr2, 20));
printf("%d\n", my_strncmp(arr1, arr2, 20));
return 0;
}
void * memcpy(void dest,const void src, size_t count )**
**void *memmove(void dest,const void src,size_t count);
这2个函数都是内存拷贝函数,效果一样。区别在于 memcpy只管拷贝(不考虑重叠拷贝),而memmove
在拷贝时会注意重叠拷贝的问题。因此称memcpy是半拷 贝,而memmove是全拷贝。
注意VS2019中的memcpy,memmove都是全拷贝。
因此在复写时,我选择了复写一个memmove
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
void* my_memmove(void* dest, const void* src, size_t count)
{
assert(dest && src);//断言,防止NULL指针
if (dest <= src)//dest在前,从前往后拷贝
{
for (size_t i = 0; i < count; ++i)
{
*((char*)dest+i) =*((char*)src+i);
}
}
else {
for (size_t i = count-1; i>0; --i)//dest在后,从后往前拷贝,注意因为无符号的问题,
//要考虑溢出问题。防止死循环。因此i=0的位置单独赋值
{
*((char*)dest + i) = *((char*)src + i);
}
*(char*)dest = *(char*)src;
}
return dest;
}
int main ()
{
char arr1[20] = "hellowordxxxxx";
char arr2[20] = "hellowordxxxxx";
printf("%s\n", arr1);
my_memmove(arr1, arr1 + 3, 5);//dest在前
printf("%s\n", arr1);
printf("%s\n", arr2);
my_memmove(arr2+3, arr2 , 5);//dest在后
printf("%s\n", arr2);
return 0;
}
**int memcmp(const void buf1,const void buf2,size_t count);
比较内存中的元素,与strcmp道理相同。只是以内存角度比较字节。
memcmp是将内存中的补码看成无符号型,。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
int my_memcmp(const void* buf1, const void* buf2, size_t count)
{
assert(buf1 && buf2);
int ret = 0;
//注意一定是无符号的char*,因为memcmp看的是无符号
while (count&&(!(ret=*(unsigned char*)buf1 -*(unsigned char*)buf2))) //因为&&的原因,count要在前,提前结束,一旦比较元素不相等就停止。
{
//相等就继续比较.因为不同于字符串,直到count为0.
buf1 = ( unsigned char*)buf1 + 1; // 防止比较多了。
buf2 = ( unsigned char*)buf2 + 1;
--count;
}
if (ret > 0)
{
return 1;
}
else if(ret<0)
{
return -1;
}
else
{
return 0;
}
}
int main ()
{
int a = 10001;
int b = 10;
printf("%d\n", my_memcmp(&a, &b, 4));
printf("%d\n", memcmp(&a, &b, 4));
return 0;
}
void memset(void*dest,int c,size_t count);*
- 将缓冲区(内存)设置为指定的字符c。
- 缓冲区的大小由调用者设置,要合适。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
void* my_memset(void* dest, int c, size_t count)
{
assert(dest);
void* ret = dest;
while (count--)
{
*(char*)dest = c;
dest = (char*)dest + 1;
}
return ret;
}
int main ()
{
char arr[20] = "xxxxxword";
printf("%s\n", arr);
my_memset(arr, 'h', 5);
printf("%s\n", arr);
return 0;
}
char strstr(const char*str1,const char * str2);*
在str1中搜索str2.要求str1于str2都是NULL结尾的字符串,否则行为未定义
返回一个指针,指向字符串 中首次出现的str2,如果字符串中没有出现str2,则返回NULL。如果str2指向长度为零的字符串,则函数返回字符串。
使用的是BF(暴力查找法)复写。KMP会在后续单独一篇博客
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
char* my_strstr(const char* string, const char* strCharSet)
{
assert(string && strCharSet);
if (*strCharSet == '\0')//传入空的时候,就返回string
{
return (char*)string;//返回值的类型是 char*,但是string是const char*,因此需要强制为char*
}
while (*string)
{
//进行匹配活动
const char* s1 = string;
const char* s2 = strCharSet;
while ((*s2 != '\0') && (*s1 != '\0') && (*s1 == *s2))//&&的妙用。一旦一个假就停止遍历。
{
s1++;
s2++;
}
if (*s2 == '\0')//我们关注的是:是否找到,必须先判断s2的情况,一旦成立就找到了。其它情况不需要考虑。
{
return (char*)string;
}
if (*s1 == '\0')//当*s1为空时就没有必要再次循环,后面不会出现匹配的情况。
//但是当字符数组中没有一个匹配的字符时,*string为'\0'的特殊情况
{
return NULL;
}
string++;
}
return NULL;//当
}
int main()
{
char arr[] = "abcdef";
if (my_strstr(arr, "def")!=NULL)
{
printf("找到了\n");
}
else
{
printf("找不到了\n");
}
return 0;
}
char strtok(char str,const char sep)*
sep是分隔符的集合
str指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的
标记
,标记是一个非包含sep的字符串。如果传入的参数str不为NULL,那么strtok会返回第一个标记的首地址,并将分隔符置为‘\0’,同时strtok会记住这个分隔符的位置。如果传入的参数为NULL,strtok会从记住的位置开始分割字符串。
也就是说我们想将一个字符彻底切分完,在第二次调用时要传入NULL。
如果字符串中不存在更多的标记,则返回 NULL 指针
下面距离说明:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
int isfind( char* str, const char* sep)//BF查找。二分需要排序。另外esp中字符少
{
for (size_t i = 0; i < strlen(sep); ++i)
{
if (*str == sep[i])
{
return 1;
}
}
return 0;
}
char* my_strtok(char* str, const char* sep)
{
char* ret = NULL;
static char* sp = NULL;
int Flage = 0;//帮助记录标记的起始地址
if (str != NULL)//传入非NULL指针,就找第一个标记
{
for (size_t i = 0; i < strlen(str); ++i)
{
if ((isfind(str + i, sep) == 1)&&(Flage==1))//只有当记录了标记起始地址,才返回值。
{
sp = str + i+1;//+1,是为了方便查找.
*(str + i) = '\0';
return ret;
}
else if((isfind(str + i, sep)==0)&&(Flage==0))
{
ret = str + i;
Flage = 1;//一旦Flage为1后,就不需要担心再次对ret赋值。
}
}
return NULL;//没找到标记就返回NULL
}
else if(str==NULL&&sp!=NULL)
{
char* s = sp;
while (*s!='\0')
{
if (isfind(s, sep) == 1&&Flage==1)
{
*s= '\0';
sp = s + 1;
return ret;
}
else if(isfind(s, sep) == 0 && Flage == 0)
{
ret = s;
Flage = 1;
}
s++;
}
}
sp = NULL;//当遇到'\0'时,需要返回这之前的标记地址。但是需要将记住的静态指针置为NULL.
// 因为最终需要返回一个NULL指针结束main的循环。
return ret;
}
int main ()
{
char arr[] = "- This, a sample string.";
char* p = NULL;
for (p = my_strtok(arr, "- ,"); p != NULL; p = my_strtok(NULL, "- ,"))
{
printf("%s\n", p);
}
return 0;
}
char * strerror(int errnum);
*void*perror(const char string);
- 编译器会将程序中的出现所有错误(如少分号,少{}等)对应一个码,称谓错误码。每个错误码对应一个错误信息字符串。而错误码存储在errno中(< errno,h >)
strerror会返回错误码对应字符串的首地址。不会主动打印错误信息,因此如果需要打印错误信息,需要printf函数
perror不仅·可以自动打印错误信息,还可以人为的添加一些信息,与错误信息组成新的字符串。
- 函数的复写过程,必须考虑到很多情况,提高函数的鲁棒性。可能BF算法笨(我就是。。。。。),但是算法都是一步一步优化的。
- 关于字符串函数就介绍到这了。如果想了解其它字符串函数,可以去网站:https://en.cppreference.com/w/