C语言之字符串,内存操作函数详解(一)

简介: C语言的标准库为我们提供了丰富的字符串操作函数与内存操作函数,有我们熟悉的 strlen ,strcpy ,也有我们不熟悉的 strchr , strstr 等.这里我们将一一为大家讲解.初阶的字符串函数我们会讨论自我实现,后面的高阶函数我们只给大家做介绍,希望我的文章能够帮到你.

1. 前言🚩


C语言的标准库为我们提供了丰富的字符串操作函数与内存操作函数,有我们熟悉的 strlen ,strcpy ,也有我们不熟悉的 strchr , strstr 等.这里我们将一一为大家讲解.初阶的字符串函数我们会讨论自我实现,后面的高阶函数我们只给大家做介绍,希望我的文章能够帮到你.


我们这里学习字符串函数和内存操作函数都在cplusplus上查看定义Cplusplus网站


2. strlen函数🚩


先来看定义:

e1172cfe90883e47bb02286499211e5.png


这个函数很明了,就是求字符串长度的函数,并且字符串的结束标志: '\0’不算在内


注意strlen返回一个类型为size_t 的值。这个类型是在头文件stddef.h中定义的,它是一个无符号整数类型。在表达式中使用无符号数可能导致不可预料的结果。例如,下面两个表达式看上去是相等的:


char x[]="abcdef";
char y[]="xyz";
if(strlen(x)>=strlen(y))//式子1
{
  ...
}
if(strlen(x)-strlen(y)>=0)//式子2
{
  ...
}


式子2是不能用于判断字符串x和字符串y谁的长度大的,因为strlen函数的返回值是无符号整型,两个无符号整型的数相减的结果也一定是无符号整型,所以不管是字符串x长度小于字符串y还是大于字符串y,式子2都是恒为真!


如果把strlen的返回值强制类型转换为 int 就可以消除这个问题.


2.1 strlen函数的自我实现🏁

size_t my_strlen(const char* p)//加上const表示我们不应该修改这个字符串
{
  unsigned int count = 0;
  int i = 0;
  while (p[i] != '\0')
  {
  count++;
  i++;
  }
  return count;
}


因为这个函数比较容易理解,所以不做过多解释


3. strcpy函数🚩


它的原型如下:


b9f7f6d50560dfd4ef1351abe8c6555.png


这个函数把参数src字符串复制到dst参数。如果参数src和dst在内存中出现重叠,其结果是未定义的。由于dst参数将进行修改,所以它必须是个字符数组或者是一个指向动态分配内存的数组的指针,不能使用字符串常量。


目标参数的以前内容将被覆盖并丢失。即使新的字符串比dst原先的内存更短,由于新字符串是以NUL字节结尾,所以老字符串最后剩余的几个字符也会被有效地删除。


警告: 程序员必须保证目标字符数组的空间足以容纳需要复制的字符串。如果字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值。strcpy无法解决这个问题,因为它无法判断目标字符数组的长度。


3.1 strcpy函数的自我实现🏁

char* my_strcpy(char *dst,const char *src)
{
  assert(dst != NULL);
  assert(src != NULL);
  char *ret = dst;
  while((* dst++ = * src++) != '\0') 
  ;
  return ret;
}


这里需要注意的是,我们把src里面的内容拷贝到dst中,所以我们是不希望src的内容发生改变的,所以我们加上const,并且strcpy函数的返回值是目标字符串的起始位置的地址,所以这里我们用char*


4. strcat函数🚩


先看函数原型:

4b63cc22f6927003ea392911e4fecdc.png



strcat函数要求dst参数原先已经包含了一个字符串(可以是空字符串)。它找到这个字符串的末尾,并把src字符串的一份拷贝添加到这个位置。如果src和dst的位置发生重叠,其结果是未定义的。


警告: 和前面一样,程序员必须保证目标字符数组剩余的空间足以保存整个源字符串。但这次并不是简单地把源字符串的长度和目标字符数组的长度进行比较,你必须考虑目标数组中原先存在的字符串。


4.1 strcat函数的自我实现🏁

char* my_strcat(char* dest, const char* src)
{
  char* begin = dest;
  //第一步、寻找目标字符串中的'\0'
  while (*dest)
  {
  dest++;
  }
  //第二步、追加源字符串并包含\0
  while (*dest++ = *src++)
  {
  ;
  }
  return begin;
}


4.2 函数的返回值🏁

strcpy和strcat都返回它们第1个参数的一份拷贝,就是一个指向目标字符数组的指针。由于它们返回这种类型的值,所以你可以嵌套地调用这些函数,如下面的例子所示:


char dst = ...;
char a = ...;
char b = ...;
strcat(strcpy(dst,a),b);


5. strcmp函数🚩


先看函数原型:

abfc253650a40959a570a23dbb782fe.png



比较两个字符串涉及对两个字符串对应的字符逐个进行比较,直到发现不匹配为止。那个最先不匹配的字符中较“小”(也就是说,在字符集中的序数较小)的那个字符所在的字符串被认为“小于”另外一个字符串。如果其中一个字符串是另外一个字符串的前面一部分,那么它也被认为“小于”另外一个字符串,因为它的NUL结尾字节出现得更早。这种比较被称为“词典比较”,对于只包含大写字母或只包含小写字母的字符串比较,这种比较过程所给出的结果总是和我们日常所用的字母顺序的比较相同。


int strcmp(char const* s1,char const* s2)

1

如果s1小于s2,strcmp函数返回一个小于零的值。如果s1大于s2,函数返回一个大于零的值。如果两个字符串相等,函数就返回零。


警告:


注意标准并没有规定用于提示不相等的具体值。它只是说如果第1个字符串大于第2个字符串就返回一个大于零的值,如果第1个字符串小于第2个字符串就返回一个小于零的值。一个常见的错误是以为返回值是1和-1,分别代表大于和小于。但这个假设并不总是正确的。

由于strcmp并不修改它的任何一个参数,所以不存在溢出字符数组的危险。但是,和其他不受限制的字符串函数一样,strcmp函数的字符串参数也必须以一个NUL字节结尾。如果并非如此,strcmp就可能对参数后面的字节进行比较,这个比较结果将不会有什么意义.

5.1 strcmp函数的自我实现🏁

int Strcmp(char* str1, char* str2)//strcmp函数具体实现。
{
    while ((*str1 != '\0') && (*str1 == *str2))//判断字符串是否结束。
    {
        str1++;
        str2++;//
    }
    return *str1 - *str2;
}


C语言并没有规定strcmp的返回值是-1或者1,只要是大于0,或者小于0,直接返回即可


6. 长度受限的字符串函数🚩


标准库还包含了一些函数,它们以一种不同的方式处理字符串。这些函数接受一个显式的长度参数,用于限定进行复制或比较的字符数。这些函数提供了一种方便的机制,可以防止难以预料的长字符串从它们的目标数组溢出。这些函数的原型如下:


45535b3087a8447e9269515ca530134b.png

a1bef574fb93401ab5f35d0d047b1fe4.png


a1bef574fb93401ab5f35d0d047b1fe4.png

很明显,在以上函数中加一个n就是限制长度的意思,strcpy加上n就是只能拷贝num个字符串,以此类推

如果源参数和目标参数的地址发生重叠,strncpy和strncat的结果都是未定义的(危险的)


6.1 strncpy函数的注意事项🏁

和strcpy一样,strncpy把源字符串的字符复制到目标数组。然而,它总是正好向dst写入len个字符。如果strlen( src )的值小于len,dst数组就用额外的NUL字节填充到len长度。如果strlen( src )的值大于或等于len,那么只有len个字符被复制到dst中。注意!它的结果将不会以NUL字节结尾。


警告的总结:


strncpy调用的结果可能不是一个字符串,因此字符串必须以NUL字节结尾。如果在一个需要字符串的地方(例如strlen函数的参数)使用了一个不是以NUL字节结尾的字符序列,会发生什么情况呢?strlen函数将无法知道NUL字节是没有的,所以它将继续进行查找,一个字符接一个字符,直到它发现一个NUL字节为止。或许它找了几百个字符才找到,而strlen函数的这个返回值从本质上说是一个随机数。或者,如果函数试图访问系统分配给这个程序以外的内存范围,程序就会崩溃。

这个问题只有当你使用strncpy函数创建字符串,然后或者对它们使用str开头的库函数,或者在printf中使用%s格式码打印它们时才会发生。在使用不受限制的函数之前,你首先必须确定字符串实际上是以NUL字节结尾的。

6.2 strncat函数和strncmp的注意事项🏁

尽管strncat也是一个长度受限的函数,但它和strncpy存在不同之外。它从src中最多复制len个字符到目标数组的后面。但是,strncat总是在结果字符串后面添加一个NUL字节,而且它不会像strncpy那样对目标数组用NUL字节进行填充。注意目标数组中原先的字符串并没有算在strncat的长度中。strncat最多向目标数组复制len个字符(再加一个结尾的NUL字节),它才不管目标参数除去原先存在的字符串之后留下的空间够不够。

最后,strncmp也用于比较两个字符串,但它最多比较len个字节。如果两个字符串在第len个字符之前存在不相等的字符,这个函数就像strcmp一样停止比较,返回结果。如果两个字符串的前len个字符相等,函数就返回零。

7. 字符串查找基础🚩


标准库中存在许多函数,它们用各种不同的方法查找字符串。这些各种各样的工具给了C程序员很大的灵活性!

下面我就给大家介绍几个字符串函数的用法:


7.1 查找一个字符🏁

在一个字符串中查找一个特定字符最容易的方法是使用 strchr和strrchr函数,它们的原型如下所示:

45b2644c37db4cd6bf17853400c2436d.png




cd7743dbe9a9404f9f1931fcac91e5e8.png

注意它们的第2个参数虽然是一个整型值。但是C语言中字符其实就是一个小整型,所以可以用整型来存储字符值。strchr在字符串str中查找字符ch第1次出现的位置,找到后函数返回一个指向该位置的指针。如果该字符并不存在于字符串中,函数就返回一个NULL指针。strrchr的功能和strchr基本一致,只是它所返回的是一个指向字符串中该字符最后一次出现的位置(最右边那个)。


7.2 查找任意几个字符🏁

strpbrk是个更为常见的函数。它并不是查找某个特定字符,而是查找任何一组字符第1次在字符串中出现的位置。它的原型如下:

28a15a8eb68c4961bd2a005a30b13bbb.png



这个函数返回一个指向str中第1个匹配group中任何一个字符的字符位置。如果未找到匹配,函数返回一个NULL指针。如果你不理解它的意思,我们这里写一段代码来理解一下:

#include <stdio.h>
#include <string.h>
int main()
{
    char str[] = "This is a sample string";
    char key[] = "aeiou";
    char* pch;
    printf("Vowels in '%s': ", str);
    printf("\n");
    pch = strpbrk(str, key);
    while (pch != NULL)
    {
        printf("%c ", *pch);
        pch = strpbrk(pch + 1, key);
        printf("\n");
    }
    printf("\n");
    return 0;
}


这里字符串str中出现的字符串key中的第一个字符是 “This” 中的 ‘i’ ,第二个字符是 “is” 里面的 ‘i’ .第三次出现的是字符 ‘a’,以此类推,那么我们设计的代码会将key字符串中在str字符串出现过的字符按顺序打印,注意这里不是先找key字符串中的第一个字符 ‘a’,而是从str字符串中找 key字符串中能匹配的所有字符的第一个那么代码会打印出:



9dd51b9db8484aa6bf4859b85f6ff768.png

7.3 查找一个字串🏁

为了在字符串中查找一个字串,我们可以使用strstr函数,它的原型如下:

9c879f5dae22457ca6724e2f1b7dadfd.png



这个函数在s1中查找整个s2第1次出现的起始位置,并返回一个指向该位置的指针。如果s2并没有完整地出现在s1的任何地方,函数将返回一个NULL指针。如果第2个参数是一个空字符串,函数就返回s1。


标准库中并不存在strrstr或strrpbrk函数。不过,如果你需要它们,它们是很容易实现的。下面的程序显示了一种实现strrstr的方法。这个技巧同样也可以用于实现strrpbrk。


// 在字符串s1中查找字符串s2最右出现的位置,并返回一个指向该位置的指针。
#include <string.h>
char*
my_strrstr( char const *s1, char const *s2 )
{
    register char*last;
    register char*current;    
     //把指针初始化为我们已经找到的前一次匹配位置。    
    last = NULL;
    //只在第2个字符串不为空时才进行查找,如果S2为空,返回NULL。    
    if( *s2 != '\0' ){      
      // 查找s2在s1中第1次出现的位置。
      current = strstr( s1, s2 );
    // 我们每次找到字符串时,让指针指向它的起始位置。然后查找该字符串下一个匹配位置。
          while( current != NULL ){
            last = current;
            current = strstr( last + 1, s2 );
          }
      }
      //返回指向我们找到的最后一次匹配的起始位置的指针。
      return last;
}



8. 总结🚩


字符串操作函数和内存操作函数的内容十分多且冗杂,我们把高阶字符串查找和字符查找,内存操作函数放在下一章来讲解,希望我的内容能帮助到你

7

相关文章
|
20天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
40 10
|
20天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
42 9
|
20天前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
41 6
|
20天前
|
存储 C语言
【C语言】输入/输出函数详解
在C语言中,输入/输出操作是通过标准库函数来实现的。这些函数分为两类:标准输入输出函数和文件输入输出函数。
117 6
|
28天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
224 1
|
17天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
26天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
27天前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
21 3
|
28天前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
45 1
|
1月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。