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

相关文章
|
1天前
|
算法 C语言 人工智能
|
5天前
|
存储 程序员 编译器
C语言:动态内存管理
C语言:动态内存管理
9 1
|
5天前
|
存储 编译器 程序员
C语言:数据在内存中的存储
C语言:数据在内存中的存储
11 2
|
5天前
|
存储 编译器 C语言
C语言:字符函数 & 字符串函数 & 内存函数
C语言:字符函数 & 字符串函数 & 内存函数
12 2
|
8天前
|
编译器
练习使用动态内存相关的4个函数:malloc、calloc、realloc、free
在了解使用动态内存相关的四个函数之前,我们先了解一下,为什么要有动态内存分配?
15 0
|
8天前
|
C语言
C语言中 字符串和数字的相互转换
C语言中 字符串和数字的相互转换
13 1
|
8天前
|
存储 编译器 C++
【C++】内存管理和模板基础(new、delete、类及函数模板)
【C++】内存管理和模板基础(new、delete、类及函数模板)
22 1
|
8天前
|
编译器 C语言 C++
详解内存操作函数
详解内存操作函数
|
13天前
|
缓存 安全 编译器
【C 言专栏】C 语言函数的高效编程技巧
【5月更文挑战第1天】本文探讨了C语言中函数的高效编程技巧,包括函数的定义与作用(如代码复用和提高可读性)、设计原则(单一职责和接口简洁)、参数传递方式(值传递、指针传递和引用传递)、返回值管理、调用约定、嵌套与递归调用,以及函数优化技巧和常见错误避免。掌握这些技巧能提升C语言代码的质量和效率。
【C 言专栏】C 语言函数的高效编程技巧
|
18天前
|
Linux
Linux rsyslog占用内存CPU过高解决办法
该文档描述了`rsyslog`占用内存过高的问题及其解决方案。
41 4