【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

相关文章
|
14天前
|
程序员 C语言
C语言库函数 — 内存函数(含模拟实现内存函数)
C语言库函数 — 内存函数(含模拟实现内存函数)
24 0
|
16天前
|
存储 编译器 C语言
深入探索C语言动态内存分配:释放你的程序潜力
深入探索C语言动态内存分配:释放你的程序潜力
28 0
|
2天前
|
编译器 C语言
字符串与内存函数
字符串与内存函数
18 0
|
1天前
|
存储 C语言
C语言函数的返回值
C语言函数的返回值
6 0
|
1天前
|
C语言 Windows
C语言中的fopen与fclose函数详解
C语言中的fopen与fclose函数详解
10 1
|
1天前
|
C语言
深入理解C语言中的printf函数及数据输出
深入理解C语言中的printf函数及数据输出
11 0
|
14天前
|
程序员 C语言 开发者
C语言库函数 — 字符串函数(含模拟实现字符串函数)
C语言库函数 — 字符串函数(含模拟实现字符串函数)
35 0
|
1月前
|
存储 JSON 监控
Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
【2月更文挑战第30天】Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
14 1
|
1月前
|
存储 C语言
C语言--------数据在内存中的存储
C语言--------数据在内存中的存储
26 0
|
5天前
|
存储 NoSQL Oracle
Oracle 12c的内存列存储:数据的“闪电侠”
【4月更文挑战第19天】Oracle 12c的内存列存储以超高速度革新数据处理,结合列存储与内存技术,实现快速查询与压缩。它支持向量化查询和并行处理,提升效率,但需合理配置以平衡系统资源。作为数据管理员,应善用此功能,适应业务需求和技术发展。