字符串函数剖析(1)

简介: 详解strlen的细节首先了解strlen 函数的参数size_t 是什么东西呢?

详解strlen的细节

首先了解strlen 函数的参数

size_t strlen ( const char * str );

size_t 是什么东西呢?

看下面的操作:

ce047079f2e04072917e715221bbd99d.gif

这里介绍一个快捷键,输入size_t后,按住ctrl + 鼠标左键,即可转到定义,或者右击鼠标,点击转到定义即可

所以 size_t == unsigned int ,strlen 的返回类型是无符号类型


注意这里,这是无符号整型!下面的介绍大有用处


下面介绍strlen 的用法:

strlen是一个库函数,它会顺着你 传过来的地址,一直往下找,直到找到\0为止,返回的是无符号整型

a1ab3b31bd904944a4349ee8cd02c00c.png

输出结果为6

再看下面的代码:


adf27f0e91854077998ac618c55412e5.png

结果还是6吗,不再是6了,而是一个随机值。字符串这样放在数组里,没有\0,所以strlen会顺着arr不断往下找,什么时候找到\0,我们不得而知。

这证实strlen是顺着地址往下找的。

模拟实现strlen

int my_strlen(const char* str)
{     //const修饰的内容无法更改
  int count = 0;
  assert(str != NULL);
  while (*str) 
  {
    count++;
    str++;
  }
  return count;
}
int main()
{
  int len = my_strlen("abcdef");
  printf("%d\n", len);
  return 0;
}

打印出来的结果仍然是6,这里要讲的重点不是如何实现strlen函数

细心的你会发现:

strlen 函数的返回值不应该是size_t 吗,为什么上面写的是 my_strlen 是 int 类型呢?

是因为: 其实这两种写法都可以,各有利弊:

先看下面的一段代码:

int main()
{
  if (strlen("abc") - strlen("abcdef") > 0)
    注意这里是库函数的strlen
  {
    printf("hehe\n");
  }
  else
  {
    printf("haha\n");
  }
  return 0;
}

请说出上面代码的输出结果:

没注意到细节,一定会说出会打印haha,运行出真知:

ccfef11b3350448698dd57e97225ef01.png

为什么呢?

回到上面的strlen的返回类型,size_t,是 unsigned int ,无符号整型,abc的长度是3,abcdef长度是6,那么3-6 == -3 ,-3作为 unsigned int 类型,是一个大于0的数,

打印结果如下图:


ce738ee7e0924bf69a75a2028bc22931.png

是一个大于0的数,所以会打印hehe,

假如是用自己的my_strlen 函数,结果如下:

d8857f15bd9e47639ecdf4299a45e12d.png

这就很符合我们的认知, 3 - 6 == -3 <0,走else 语句。

不过,这两种写法,各有各的好处,假如你想跟着标准走的话,写size_t是绝对没有问题的,因为计算长度是不可能有负数的。

但是写 int 类型,也没有什么问题,单独计算一个字符串的长度时,返回值也是一个大于0的数,当3 - 6 == -3时,的确是会走if 语句,更符合我们的理解,不容易产生歧义。

所以两种写法各有利弊。

注意事项:

 db8efb50a9fd492396a83bf425b0e1e6.png

.strcpy函数的巧妙讲解

先看库函数的声明:

 char * strcpy ( char * destination, const char * source );
            目的地         源头

需要两个地址,一个是目的地地址,一个是源头地址

举个例子:

int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "hello";
  strcpy(arr1, arr2);
       将arr2字符串拷贝到arr1
  printf("%s\n", arr1);
}

运行结果如下:


9dfc82b5aa7e4d598e20e4384f9306f8.png

下面来模拟实现:

模拟实现strcpy函数:

char* my_strcpy(char* dest,  char* src)
{
  assert(dest && src);
  char* ret = dest;
  while (*src!='\0')
  {
    *dest = *src;
     dest++;
     src++;
  }
  *dest = *src;
  //循环结束后,src指向了\0的位置,所以将\0也赋值给dest;
  return ret;
}
int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "hello";
  char *ret = my_strcpy(arr1, arr2);
  printf("%s\n", ret);
}

特殊位置已注释讲解 ,但是你会发现,模拟代码还可以精简化

char* my_strcpy(char* dest, const char* src)
        目标空间必须可变,源头空间不可变
{
  assert(dest && src); 判断指针的有效性
  char* ret = dest;
  while (*dest++ = *src++);
  return ret ;
}

每次src对应的字符赋值给dest后,再++,dest也++,然后进行判断,如果为\0 , \0的ascii码值为0,while为0,退出循环

这样的代码才是,满分代码strcpy注意事项:

5ebaaada0f9a466eb08428ed5cc183b5.png

  char arr1[] = { 'a','b','c','d','e','f' };
  错误代码:字符串无\0
  char arr1 = "abcdef";
  错误代码:常量字符串无法更改

d69d81665b3249a98bd0782af005b9d9.png

请注意第三点:目标空间必须足够大。

这是因为strcpy这个函数,它只管拷贝,只管找到\0才停止,它才不管你的空间够不够,这就是strcpy函数的脾气,所以必须充分了解。

strcmp函数详解

首先了解函数的声明:

int strcmp ( const char * str1, const char * str2 );

strcmp的参数是两个不可更改的char*的指针

返回值:当第一个字符串大于第二个时,返回一个大于0的数,当第一个字符串小于第二个时,返回小于0的数,等于时,返回0

702430e39a4f46eaba0515c7e98b33f9.png

测试一下结果:

5f722c05f0b747ba9b55033f999da674.png

但是这里,在visual studio环境下,返回值只是-1,0,1,是不是说这个strcmp函数有问题呢?其实并不是,当我们模拟实现的时候就能够发现。

下面来模拟实现strcmp函数:

模拟实现strcmp

int my_strcmp(const char* str1, const char* str2)
{
  assert(str1 && str2);
  while (*str1 == *str2)
  {
    if (*str1 == '\0')
    {
      return 0;//相等的情况
    }
    str1++;
    str2++;
  }
  return (*str1 - *str2);//不相等的情况
}
int main()
{
  char* p1 = "abcdef";
  char* p2 = "sqwer";
  int ret = my_strcmp(p1,p2);
  printf("%d", ret);
  return 0;
}

可以看到,a和s相比,相差了18,a的ascii码值是97,s的ascii码值是115,两者相减,结果就是-18,所以p1 小于 p2

45ea797be5db4e838db58b9508025b5c.png

strcat函数剖析

这是一个追加函数,意思是在原字符串的末尾,继续追加其他字符串。

char * strcat ( char * destination, const char * source );

该函数的声明如上:

参数是两个指针,一个是目的地指针,一个是源头指针。

返回类型是目的地起始地址。

int main()
{
  char arr1[30] = "hello";
  char arr2[] = "world";
  strcat(arr1, arr2);
  printf("%s\n", arr1);

来看一下strcat函数的实现过程,

2ec0cdf6e1ce44f4ad01fa15ce9e250f.png

在arr1之后追加了arr2.

但是会不会连world末尾的\0也追加上去呢?

答案是会的:

6e3a7d8ba4c94ff0b0a388587506923b.png

可知,world后面连\0也追加上去了;

那么能不能自己追加自己?

可以看到,程序崩溃了

694f36e20c474bf8bd7959ad87546d7b.png

原因究竟是什么?

6e2b3fd07aca43b78393a78a3d48ca36.png

调试之后我们发现,arr1没有\0了!,hello未被追加时,后面还有一个\0,但是追加之后,arr2是从\0开始追加的,所以当我们追加之后,\0已经被覆盖了。所以它会无限追加下去。所以这个程序就崩溃了。

模拟实现strcat函数:

char* my_strcat(char* dest, const char* src)
{
  assert(dest && src);
  char* ret = dest;
  //1.找到目的字符串的\0位置
  while (*dest!='\0')
  {
    dest++;
  }
  //2.追加的过程其实就是拷贝的过程
  while (*dest++ = *src++)
  {
    ;
  }
  return ret;
}
int main()
{
  char arr1[30] = "hello";
  char arr2[] = "world";
  my_strcat(arr1, arr2);
  printf("%s\n", arr1);
}

重点部分已经作了注释:

不过有几点要注意一下:

8f8acebd880c418aac6ef1221c5bcf64.png

与strlen相似,如果源字符串后无\0,这就像自己追加自己,导致程序崩溃

如下图:


d1ab1e16282440bbbbff7c3f6a86223e.png

arr1的只有6个大小的空间,如果强制追加arr2上去,就会导致程序崩溃。

当源头字符串后面没有\0时,会出现:

70cb4d74a0ed4059a14bbc4a5184eec5.png

追加过去之后,但是后面没有\0,就没有\0追加,无结束标志,程序会崩溃。

如果对你有帮助的话,就关注一下吧!

相关文章
|
3月前
|
安全 C语言
需要知道的字符串函数
需要知道的字符串函数
|
3月前
字符串函数
字符串函数
|
存储 C语言
字符串函数介绍&应用(一)
字符串函数介绍&应用
|
存储 C语言
字符串函数介绍&应用(二)
字符串函数介绍&应用
|
3月前
|
C语言
字符函数和字符串函数
字符函数和字符串函数
59 0
|
9月前
|
编译器
C详细的字符串函数
C详细的字符串函数
53 0
|
8月前
|
C语言
详解字符函数和字符串函数-2
详解字符函数和字符串函数
37 0
|
C语言
一文带你玩转C库中的一系列字符串函数
一文带你玩转C库中的一系列字符串函数
60 2
|
12月前
|
C语言
字符函数和字符串函数详解(一)
字符函数和字符串函数详解
36 0
|
编译器 C++
【字符串函数原来还能这样用?】
【字符串函数原来还能这样用?】
49 0