【C语言】深剖字符串函数和内存函数2

简介: 【C语言】深剖字符串函数和内存函数

1.1.4 strcmp

int strcmp( const char *string1, const char *string2 );


字符串比较


函数细节


  • 比较的是字符串的内容,不是字符串的长度
  • 比较时内容相同则比较下一对,直到不同或都遇到\0
  • 第一个字符串大于第二个字符串,则返回大于0的数字
  • 第一个字符串等于第二个字符串,则返回0
  • 第一个字符串小于第二个字符串,则返回小于0的数字


使用方法

int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "abcdea";
  int ret = strcmp(arr1, arr2);
  if (ret > 0)
  {
    printf(">");
  }
  else if (ret < 0)
  {
    printf("<");
  }
  else
  {
    printf("=");
  }
  return 0;
}

运行结果:

eed2dff7aaf805ac05151c106d89fea6.png


模拟实现

int my_strcmp(const char* e1, const char* e2)
{
  assert(e1 && e2);
  while (*e1 == *e2)
  {
    if (*e1 == '\0')
      return 0;//相等
    e1++;
    e2++;
    //注意这里不可以调换判断\0的位置,因为这样就在比较第一个元素之前就进行了调整,可能第一个元素就不想相等了,按照这样就错了
  }
  //不相等
  /*if (*e1 > *e2)
    return 1;
  else 
    return 0;*/
  return *e1 - *e2;//更加简洁
}
int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "abcdea";
  int ret = my_strcmp(arr1, arr2);
  if (ret > 0)
  {
    printf(">");
  }
  else if (ret < 0)
  {
    printf("<");
  }
  else
  {
    printf("=");
  }
  return 0;
}

运行结果:

a2d05795a7118be4b2aa5036bfe9d180.png


1.1.5 strstr

char * strstr ( const char *str1, const char * str2);


查找子串


函数细节


  • 查找子串,返回子串第一次出现位置的首地址,找不到返回空指针

使用方法


int main()
{
  char arr1[] = "abcdeqabcdef0";
  char arr2[] = "cdef";
  char* ret = strstr(arr1, arr2);
  if (NULL == ret)
  {
    printf("找不到子串\n");
  }
  else
  {
    printf("%s\n", ret);
  }
  return 0;
}

运行结果:

1e1aae72e7cb599889033de120a129ee.png

模拟实现

char* my_strstr(const char* str1, const char* str2)
{
  assert(str1 && str2);
  const char* s1 = str1;//拷贝地址
  const char* s2 = str2;
  const char* cur = str1;//拷贝str1地址,记录当前位置
  //特殊情况
  if(*str2 == '\0‘)//!*str2 ok
  {
    return str1;//如果字串为空串,直接返回\0
  }
  while (*cur)
  {
    s1 = cur;//若一次查找不想等,则重新赋值,cur前的位置一定不可能为字串位置
    s2 = str2;//s2始终为str2首地址,用于判断完整子串
    while (*s1 && *s2 && (*s1 == *s2))//s1,s2不能为\0,且s1/s2所对应元素相等
    //while(*s1 && *s2 && !(*s1 - *s2)
    //当它们两个都为0,即括号中表达式结果为假时进入循环
    {
      s1++;
      s2++;
    }
    if (*s2 == '\0')//s2查找到了
    {
      return (char*)cur;//直接返回,但是cur类型为const char*,需要强转
    }
    cur++;//cur自增,跳过不可能为字串出现位置的元素
  }
  return NULL;//找不到返回空指针
}
int main()
{
  char arr1[] = "abcdeqabcdef0";
  char arr2[] = "cdef";
  char* ret = strstr(arr1, arr2);
  if (NULL == ret)
  {
    printf("找不到子串\n");
  }
  else
  {
    printf("%s\n", ret);
  }
  return 0;
}


运行结果:

f49c866a4f1b1fa886cf1143e390eac2.png


1.1.6 strtok

char * strtok ( char * str, const char * sep );


   以分隔字符为分隔线,分隔字符串


函数细节


   sep参数是个字符串,定义了用作分隔符的字符集合,第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。

   strtok函数找到str中的下一个标记,并将其用\0 结尾,返回一个指向这个标记的指针。

   strtok函数的第一个参数不为NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。


   strtok函数的第一个参数为NULL ,函数将在同一个字符串中被保存的位置(\0)开始查找下一个标记。如果字符串中不存在更多的标记,则返回NULL 指针。

   strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。


简单来说就是strtok会把分隔符置为\0,并且返回分隔符隔开字段的第一个字符的地址。

使用方法



int main()
{
  char arr[] = "a1249967801@163.com@haha nihao";//需要切割的字符串
  char buf[50] = { 0 };
  strcpy(buf, arr);//拷贝一份字符串,strtok会分割
  const char* sep = "@. ";//分隔符的集合,sep指向的字符串
  char* str = NULL;
  for (str = strtok(buf, sep); str != NULL;str = strtok(NULL, sep))//循环遍历
  {
    printf("%s\n", str);
  }
    //三个分隔符,直接穷举
  //printf("%s\n", strtok(arr, sep));//只找第一个标记
  //printf("%s\n", strtok(NULL, sep));//从保存好\0的位置继续往后找
  //printf("%s\n", strtok(NULL, sep)); 
  //如果字符串中不存在标记了,则返回空指针
  return 0;
}



运行结果:

2a3b28c4873b77d8f0388ce0ed3c7dfc.png

重复切割问题

若一个字符串被重复切割,且两次切割第一个参数均为待切割字符串,它切割的过程是什么样的?能否详细解释?

案例:

int main()
{
  char arr[] = "hello@anduin@hello";
  char buf[200] = { 0 };
  strcpy(buf, arr);
  const char* p = "@";
  //char* str = NULL;
  char* str = strtok(buf, p);
  for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p))
  {
    printf("%s\n", str);
  }
  return 0;
}


分析:


这是使用方法部分的错误写法,是我犯的一个错误,为了搞清这个错误,我花了许多时间,接下来就让我阐述一下这段代码干了什么:


这里我们可以看到str在进入循环之前进行了两次切割,这就为字符串重复切割,根本问题就出现在这里。


第一次切割:str首先被strtok切割,此时把分隔符@替换为\0,此时buf为hello\0anduin@hello。


第二次切割:从起始处开始处理,但是字符串处理的是以\0结尾的字符串,也就是说这里处理的是hello\0,而不是hello\0anduin@hello,而buf本身不是NULL,所以当当地一个字符串找不到分隔符时,它返回的就是起始地址。第二次进入循环时将NULL传给strtok函数,由于是从\0处开始的就认为没什么可处理的,于是返回NULL,循环结束。


所以最后打印的结果就为hello。


运行结果


9d92f2638fcf6e4142cebcdc0ac09153.png

1.1.7 strerror

char * strerror ( int errnum );


   返回错误码,所对应的错误信息。


函数细节


   全局变量:errno(错误码),为全局整形变量,当库函数调用失败会把相应错误码放到errno中,把错误码传给strerror函数,会返回char* 类型地址,为错误信息的首元素地址,通过printf可以打印出相应错误信息。


   函数对应头文件是include <errno.h>


使用方法

int main()
{
  printf("%s\n", strerror(0));
  printf("%s\n", strerror(1));
  printf("%s\n", strerror(2));
  printf("%s\n", strerror(3));
  int* p = (int*)malloc(INT_MAX);
  if (p == NULL)
  {
    printf("%s\n", strerror(errno));//Not enough space  
  }
  return 0;
}



分析:

不同的数字对应不同的错误码,当库函数调用失败时就会将对应的错误码记录到errno中。如果将数字或errno传给strerror函数,就会得到错误信息的首元素地址,并且可以通过printf打印错误信息。

malloc向堆区申请内存,将起始地址返回,类型是void,所以需要进行强制类型转换,malloc开辟空间失败返回空指针,malloc是库函数,调用失败会把错误码放到errno中~

且错误码是全局的,会被实时更新!错误码不能一次全部返回!!!

运行结果:


9ef419fab0f61c6912019cddda91ced4.png


strerror和perror的抉择

C语言中能得到错误信息的函数不止strerror一个,还有一个函数叫做perror。


void perror( const char *string );

   打印错误信息


在某种程度上,perror的使用方式比strerror更加方便,如果给出自定义提示信息,它会自动补上:和空格并且直接打印出错误信息;若不给提示信息,则会直接打印错误信息。perror在得到库函数调用失败的错误码后,会根据错误码打印对应的错误信息。

int main()
{
  int* p = (int*)malloc(INT_MAX);
  if (p == NULL)
  {
    perror("Malloc");//Malloc: Not enough space,通过提示信息加上冒号和空格,打印出错误信息
        perror("");//不加提示信息
        //但是不能什么都不加,函数的参数有规定,起码也得是个空字符串
    return 1;//打印结束直接返回,1表示异常返回
  }
  return 0;
}


运行结果:

5089d1a109f5f225d88f0d6954da6fe1.png



   perror是否一定比strerror好?相比于perror,strerror有什么优点?


它俩各有长处,perror一定会打印错误信息(冒号前的为自定义信息),如果只是想打印错误信息,那么perror比较方便;但是如果不想打印,只想拿到错误信息,strerror会根据错误码转化为错误信息,不打印。在这时我们最好的选择就是strerror。



1.1.8 Tip

以上是长度不受限制的字符串函数!在使用时可能会有风险,因为不能规定操作的字符个数,而接下来,我们将要介绍长度受限制的字符串函数~



1.2 长度受限制的字符串函数

长度受限制的字符串函数相比于长度不受限制的字符串函数更加安全,有了这个限制,就在一定范围内约束了越界访问,非法访问内存等错误。



1.2.1 strncpy

char * strncpy ( char * destination, const char * source, size_t num );



拷贝指定个数的字符到目标字符串


函数细节


  • 拷贝num个字符从源字符串到目标空间。
  • 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
  • \0不算做字符串内容,num为\0之前出现的字符个数。



使用方法


int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "and";
  strncpy(arr1, arr2, 3);//拷贝三个字符
  printf("%s\n", arr1);
  strncpy(arr1, arr2, 6);//源字符串长度<操作长度,strncpy确实会操作操作长度大小的字符个数,不够的会用\0来填充
  printf("%s\n", arr1);
  return 0;
}


图:

46c456a828b2bbf22eb860859a281118.png


运行结果:

4ec3146414dc73602fb59afa7a6434b7.png


模拟实现

char* my_strncpy(char* dest, const char* src, size_t count)
{
  assert(dest && src);
  char* start = dest;
  while (count && (*dest++ = *src++) != '\0')//仍然会用\0覆盖
    count--;
  if (count)//处理操作长度大小>源字符串长度的情况
  {
    while (--count)//覆盖次数为count - 1次,因为上方\0已经被覆盖过一次
    {
      *dest++ = '\0';
    }
  }
  return (start);
}
int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "anduin";
  size_t count = 0;
  scanf("%u", &count);
  char* ret = my_strncpy(arr1, arr2, count);
  printf("%s\n", ret);
  return 0;
}


运行结果:


1e5448af5797db66bc2f6b1af6a26756.png


1.2.2 strncat

char * strncat ( char * destination, const char * source, size_t num );


追加指定个数的字符


函数细节


  • 追加num个字符从源字符串到目标空间。
  • 如果源字符串的长度小于num,只会追加字符串,其余不会操作,并不会自动追加\0。
  • 从目标字符串的\0处,开始追加指定字符。


使用方法

int main()
{
  char arr1[20] = "abcdef\0XXXXXXX";
  char arr2[] = "and";
    strncat(arr1, arr2, 6);
  //如果操作长度大于字符串长度
  //则只会追加字符串,其余不会操作,既不会自动填充\0
    //在这种情况下,在追加的末尾加上\0
  printf("%s\n", arr1);
}

图:

a8f5273f5be4a469572be72a5bda82e0.png


运行结果:ad40c701da331922b075b8bdcaeaa39f.png

模拟实现

char* my_strncat(char* dest, char* src, size_t count)
{
  char* start = dest;
  while (*dest)//找目标字符串的\0
  {
    dest++;
  }
  while (count--)//count为真
  {
    if ((*dest++ = *src++) == 0)//操作长度大于源字符串,不进行补\0,直接返回
      return(start);
  }
  *dest = '\0';//追加完毕没有返回,则手动加上\0,此时\0经过++指向追加元素最后元素的后一个位置
  return (start);
}
int main()
{
  char arr1[20] = "hello ";
  char arr2[] = "worldld";
  char* ret = my_strncat(arr1, arr2, 5);
  printf("%s\n", ret);
  return 0;
}


运行结果:


61470166696670bc0f0dd38976e1d229.png


1.2.3 strncmp

int strncmp( const char *string1, const char *string2, size_t count );


比较指定长度的字符串的大小


函数细节


  • 比较指定个数的字符,count为字符个数
  • 第一个字符串大于第二个字符串,则返回大于0的数字
  • 第一个字符串等于第二个字符串,则返回0
  • 第一个字符串小于第二个字符串,则返回小于0的数字


使用方法

int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "abcdeq";
  int ret = strncmp(arr1, arr2, 4);
  printf("%d\n", ret);
  ret = strncmp(arr1, arr2, 6);
  printf("%d\n", ret);
  return 0;
}

运行结果:


42700814af2118d47ae8eabb55585fe2.png

模拟实现

int my_strncmp(char* str1, char* str2, size_t count)
{
  assert(str1 && str2);
  while (!((*str1 - *str2)) && *str1 && *str2 && --count)
  //如果两者相等且不为'\0',并且只操作count个数,
  //这里需要前置--,如果使用后置--,那么还会多进入一次循环,导致count多执行一次
  //前置--,无需进行特殊处理,当前情况直接返回即可
  {
    str1++;
    str2++;
  }
  return *str1 - *str2;//无论是\0还是其他情况都能处理
}
int main()
{
  char arr1[] = "abcd";
  char arr2[] = "abcq";
  int ret = my_strncmp(arr1, arr2, 3);
  if (ret > 0)
  {
    printf("arr1 > arr2");
  }
  else if (ret < 0)
  {
    printf("arr1 < arr2");
  }
  else
  {
    printf("arr1 = arr2");
  }
  return 0;
}


运行结果:

14447b82d08edea514b7834372fa4a98.png

相关文章
|
19天前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
31 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
19天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
39 10
|
19天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
42 9
|
19天前
|
存储 Unix Serverless
【C语言】常用函数汇总表
本文总结了C语言中常用的函数,涵盖输入/输出、字符串操作、内存管理、数学运算、时间处理、文件操作及布尔类型等多个方面。每类函数均以表格形式列出其功能和使用示例,便于快速查阅和学习。通过综合示例代码,展示了这些函数的实际应用,帮助读者更好地理解和掌握C语言的基本功能和标准库函数的使用方法。感谢阅读,希望对你有所帮助!
31 8
|
19天前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
40 6
|
19天前
|
存储 C语言
【C语言】输入/输出函数详解
在C语言中,输入/输出操作是通过标准库函数来实现的。这些函数分为两类:标准输入输出函数和文件输入输出函数。
108 6
|
19天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
48 6
|
19天前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
27 5
|
23天前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
40 6
|
24天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。