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

相关文章
|
7天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
23 6
|
9天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
30 6
|
21天前
|
C语言
【c语言】动态内存管理
本文介绍了C语言中的动态内存管理,包括其必要性及相关的四个函数:`malloc`、``calloc``、`realloc`和`free`。`malloc`用于申请内存,`calloc`申请并初始化内存,`realloc`调整内存大小,`free`释放内存。文章还列举了常见的动态内存管理错误,如空指针解引用、越界访问、错误释放等,并提供了示例代码帮助理解。
33 3
|
20天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
23天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
19 0
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
370 0
|
22天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
46 1
|
27天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
1月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
1月前
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
41 4