【C语言】字符+字符串函数精讲

简介: 【C语言】字符+字符串函数精讲

前言

● 从我们第一个C程序——Hello world 的诞生,到字符串的拷贝、比较等各种操作的实现。从中不难发现:我们在处理C语言时对字符字符串的处理很是频繁,因此学习字符及字符串的各种操作函数尤显其必要性。

● C语言本身是没有字符串类型的,字符串通常放在 常量字符串 中或者 字符数组 中。字符串常量适用于那些对它不做修改的字符串函数。

● 补充:本章所讲解的函数均为C语言的库函数,如果想了解更多关于C语言库函数的内容,这里给大家推荐一个官网cplusplus.com大家可以参照官网里的文档学习库函数。

废话少说,上干货。Let’s go!

一、求字符串长度-strlen

🍑1.函数声明

size_t strlen ( const char * str );

注释:

1.字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。最终返回一个无符号的整形,表示字符串的长度。

2.由于strlen 只是计算字符串的长度,不能对原字符串进行更改,所以参数部分采用const修饰。

2.参数指向的字符串必须要以 ‘\0’ 结束,否则返回随机值。

3.注意函数的返回值为size_t 即 unsigned int是无符号的( 易错 )

🍑2.strlen函数使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  if (strlen("abc") - strlen("abcdef") > 0)
    printf("abc>abcdef\n");
  else
    printf("abc<abcdef\n");

  return 0;
}

注释:

我们已知strlen返回的是字符串中‘\0’之前出现的字符个数,即strlen(“abc”)=3,strlen(“abcdef”)=6,但是strlen的返回值为size_t类型,即无符号整数(这里是一个坑),因此无符号整数3-6相当于3的补码加上-6的补码,结果为无符号整数,即一个非常大的数字。因此条件满足,输出abc>abcdef。

🍑3.strlen函数的模拟实现

📝思路一:计数器+遍历查找

#include<assert.h>
size_t mystrlen(const char* str)
{
  assert(str);
  int count = 0;
  while (*str != 0)//找到字符‘\0’的地址
  {
    str++;
    count++;//找到‘\0’之前计数器每次加1
  }
  //遍历结束,返回字符串长度
  return count;
}

📝思路二:递归

#include<assert.h>
size_t mystrlen(const char* str)
{
  assert(str);
  if (*str != 0)//递归条件
    return 1 + mystrlen(str + 1);
  else
    return 0;
}

📝思路三:指针-指针

#include<assert.h>
size_t mystrlen(const char* str)
{
  assert(str);
  const char* start = str;//首字符地址
  const char* end = str;//尾字符地址
  while (*end != 0)//找到字符‘\0’的地址
  {
    end++;
  }
  //即‘\0’的地址-首字符地址,指针相减返回之间元素个数
  return end - start;
}

二、长度不受限制的字符串函数

顾名思义,这一类函数对字符串进行操作的时候总是以’\0’为基准进行的,而不是以字符串的长度为基准。而长度受限制的字符串函数受长度n的限制。

🍑1.字符串拷贝函数-strcpy

🌳(1)函数声明

char* strcpy(char * destination, const char * source );

注释:

1.会将源字符串中的内容拷贝到目标空间中,包括’\0’。最终返回目标字符串的地址。

2.源字符串必须以 ‘\0’ 结束。

3.目标空间必须足够大,以确保能存放源字符串。

4.目标空间必须可变,即不能为常量字符串。

🌳(2)strcpy函数使用

//使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[20] = "abc";
  const char arr2[] = "hello world!";
  strcpy(arr1, arr2);//将arr2的内容拷贝到arr1中
  printf("arr1=%s\n", arr1);
  printf("arr2=%s\n", arr2);
  return 0;
}

🌳(3)strcpy函数的模拟实现

#include<assert.h>
char* my_strcpy(char*dest,const char*src)
{
  assert(dest && src);
  char* ret = dest;//记录目标空间的起始位置
  while (*dest++ = *src++)//拷贝,当*src=0时停止
  {
    ;
  }
  //拷贝完毕,返回目标空间起始位置
  return ret;
}

🍑2.字符串拼接函数-strcat

🌳(1)函数声明

char * strcat ( char * destination, const char * source );

注释:


1.将源字符串的副本附加到目标字符串中。目标字符串中的‘\0’字符将被源字符串的第一个字符覆盖,并且在连接形成的新字符串的末尾包含一个‘\0’字符。最终返回目标字符串的地址。(可以将strcat理解为字符串追加函数,将源字符串追加到目标字符串末尾)

2.源字符串必须以 ‘\0’ 结束。

3.目标空间必须有足够的大,能容纳下源字符串的内容。

4.目标空间必须可修改。

🌳(2)strcat函数的使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[20] = "hello ";
  const char arr2[] = "world!";
  printf("追加前:%s\n", arr1);
  printf("追加前:%s\n", arr2);
  strcat(arr1, arr2);
  
  printf("追加后: % s\n",arr1 );
  return 0;
}

🌳(3)strcat函数模拟实现

char* my_strcat(char* dest, const char* src)
{
 assert(dest&&src);
  char* ret = dest;//记录目标空间首元素地址
  while (* dest!='\0')//找到目标空间dest中‘\0’的地址
  {
    dest++;
  }
  //从目标字符串的‘\0’地址处开始拷贝字符串,包括结尾的'\0'
  //此过程类似于strcpy函数
  while (*dest++ = *src++)
  {
    ;
  }
  return ret;//返回目标空间首元素的地址
}

🌳(4)思考:strcat能否实现字符串给自己追加?

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[20] = "hello";
  strcat(arr1,arr1);
  printf("%s\n", arr1);
  return 0;
}

调试结果:

结论:

strcat在追加时,将末尾的‘\0’字符覆盖,因此在此后追加过程中永远找不到’\0’,即不会正常停止,最终报错:写入冲突。所以strcat函数不能实现自己给自己追加。

🍑3.字符串比较函数-strcmp

🌳(1)函数声明

int strcmp ( const char * str1, const char * str2 );

注释:


1.strcmp函数开始比较两个字符串的第一个字符。如果第一个字符相等,则继续向后比较,直到字符不同或达到终止的‘\0’字符,比较停止。

2.strcmp函数,比较对应位置上的字符大小,而非长度。

3.标准规定:

(1)第一个字符串大于第二个字符串,则返回大于0的数字

(2)第一个字符串等于第二个字符串,则返回0

(3)第一个字符串小于第二个字符串,则返回小于0的数字

🌳(2)strcmp函数使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "abq";
  printf("%d\n",strcmp(arr1, arr2));
  //c>q即arr>arr2。vs下返回 -1

  char arr3[] = "abcd";
  char arr4[] = "abc";
  printf("%d\n",strcmp(arr3, arr4));
  //d>'\0'即arr3>arr4。vs下返回 1

  char arr5[] = "abc";
  char arr6[] = "abc";
  printf("%d\n",strcmp(arr5, arr6));
  //a=a,b=b,c=c,'\0'='\0'即arr5=arr6。vs下返回 0

  return 0;
}

🌳(3)strcmp函数模拟实现

#include<assert.h>
int my_strcmp(const char* s1, const char* s2)
{
  assert(s1 && s2);
  while (*s1 == *s2)//字符串对应字符相同
  {
    if (*s1 == '\0')
    {
      //如果同时为‘\0’,说明两字符串完全相同,返回0
      return 0;
    }
    //否则同时向后偏移,进行下一对字符的比较
    s1++;
    s2++;
    
  }
  //如果不满足对应字符相同则返回二者差值(巧妙地满足标准规定)
  return *s1 - *s2;
}

三、长度受限制的字符串函数介绍

🍑1.strncpy

🌳(1)函数声明

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

注释:


1.表示把source所指向的字符串中以source地址开始的前num个字节复制到destination所指的数组中,并返回被复制后的destination的地址。

2.如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。

3.destination必须具有足够大的空间可以容纳拷贝后的字符串。

🌳(2)strncpy函数的使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[] = "xxxxxxxxx";
  char arr2[] = "123456";
  //当num小于字符串arr2的长度时,拷贝前num个字符到arr1
  strncpy(arr1, arr2, 3);

  char arr3[] = "xxxxxxxxx";
  char arr4[] = "123456";
  //当num大于字符串arr4的长度时,超出的部分自动拷贝0,直到达到num个为止
  strncpy(arr3, arr4, 7);

  char arr5[] = "xxxxxxxxx";
  char arr6[] = "123\0ddd";
  //当arr6中出现\0时,以\0为arr6字符串的结束标志,超出部分会自动补0
  strncpy(arr5, arr6, 4);

  printf("arr1=%s\n",arr1);
  printf("arr3=%s\n",arr3);
  printf("arr5=%s\n",arr5);
  return 0;
}

🍑2.strncat

🌳(1)函数声明

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

注释:


1.把source所指字符串的前num个字符添加到destination所指字符串的结尾处,并覆盖destination所指字符串结尾的’\0’,从而实现字符串的连接。最终返回目标字符串的地址。

2.strncat追加后会自动在最后补上’\0’。

3.如果num大于字符串source的长度,那么仅将source指向的字符串内容追加到destination的尾部。

4.目标字符串必须要有足够的空间容纳追加后的新目标字符串。

🌳(2)strncat函数的使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[20] = "xxxxxxxxx";
  char arr2[] = "123456";
  //当num小于arr2字符串长度时,在arr1后追加前num个字符
  strncat(arr1, arr2, 3);
  printf("arr1 = % s\n", arr1);

  char arr3[20] = "xxxxxxxxx";
  char arr4[] = "123456";
  //当num大于arr4字符串长度时,在arr3后追加arr4字符串
  strncat(arr3, arr4, 7);
  printf("arr3 = % s\n", arr3);
  return 0;
}

补充:与strcat不同,由于strncat追加后会自动在最后补上’\0’,所以可以实现自己给自己追加。

#include<stdio.h>
#include<string.h>
int main()
{
  //注意:arr1需要有足够大的空间可以容纳追加后的字符串
  char arr1[50] = "12345";
  strncat(arr1, arr1,5 );
  printf("arr1 = % s\n", arr1);
  
  return 0;
}

🍑3.strncmp

🌳(1)函数声明

int strncmp ( const char * str1, const char * str2, size_t num );

注释:


1.比较直到出现对应位置上的字符不一样,或者一个字符串结束,或者num个字符全部比较完。

2.strncmp函数,同样比较对应位置上的字符大小,而非长度。

3.标准规定:

(1)第一个字符串大于第二个字符串,则返回大于0的数字

(2)第一个字符串等于第二个字符串,则返回0

(3)第一个字符串小于第二个字符串,则返回小于0的数字

🌳(2)strncmp函数的使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[20] = "abce";
  char arr2[] = "abcdefg";
  //比较前三个字符,a=a,b=b,c=c,即前三个字符相同,返回0
  printf("%d\n",strncmp(arr1, arr2, 3));

  char arr3[20] = "aca";
  char arr4[] = "abcdefg";
  //比较前2个字符,a=a,a<b,即arr3第2个字符小,vs下返回1
  printf("%d\n", strncmp(arr3, arr4, 2));

  char arr5[20] = "a";
  char arr6[] = "abcdefg";
  //由于num>strlen(arr1)+1,比较前2个字符,a=a,'\0'<b,即arr1的第2个字符小,vs下返回-1
  printf("%d\n", strncmp(arr5, arr6, 3));
  
  return 0;
}

四、字符串查找与分割

🍑1.字符串查找函数-strstr

🌳(1)函数声明

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

注释:

1.判断字符串str2是否是str1的子串。如果是,则该函数返回 str1字符串从 str2第一次出现的位置开始到 str1结尾的字符串;否则,返回NULL

2.strstr函数在查找时,大小写会被认为是不同的字符串。

🌳(2)strstr函数的使用

📝用于判断一个字符串中是否含有目标字符子串

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>

int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "bcd";
  char* p = strstr(arr1,arr2);
  if (p == NULL)
  {
    printf("不存在\n");
  }
  else
  {
    printf("%s\n", p);
  }
  return 0;
}

🌳(3)strstr函数模拟实现

查找流程如图所示:

仿照以上思路,代码实现如下:

#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
  assert(str1 && str2);
  const char* s1 = str1;
  const char* s2 = str2;

  const char* p = str1;//用于记录字符串查找开始的位置

  if (*str2 == '\0')//特殊处理
  {
    //如果str2是空串,直接返回str1
    return str1;
  }
  while (*p)//判断str1是否遍历完
  {
    s1 = p;
    s2 = str2;
    while (*s1 != '\0' && *s2 != '\0' && (*s1 == *s2))
    {
      s1++;
      s2++;
    }
    if (*s2 == '\0')
    {
      return (char*)p;//找到了,返回在str1中的开始地址
    }
    p++;//上一次循环没找到,查找位置向后偏移
  }
  //str1中不存在str2,找不到子串
  return NULL;
}

🍑2.字符串分割函数-strtok

🌳(1)函数声明

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

注释:


1.sep参数是一个字符串,定义了用作分隔符的字符集合。

2.第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。

3.strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)

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

5.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。

6.如果字符串中不存在更多的标记,则返回 NULL 指针。

🌳(2)strtok函数的使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  char buf[50] = { 0 };
  char arr[] = "www.The calf wants to turn over@.qq.com";
  strcpy(buf, arr);//将数据拷贝一份,不改变源数据
  char* flag = ".@ ";//分隔符集合
  int count = 0;
  //根据strtok函数特点:
  //第一次第一个参数不为NULL,向后查找分隔符,找到改为'\0',返回分隔符前的子字符串,并保存这一次strtok走到的位置。
  //第二次令第一个参数为NULL,strtok会从上一次保存的位置开始向后查找分隔符,找到改为'\0'并返回第二个子字符串的地址。
  //第三次……
  for (char* str = strtok(buf, flag); str != NULL; str = strtok(NULL, flag))
  {
    count++;
    printf("分割%d=%s\n",count,str);
  }
  return 0;
}

五、错误信息报告

🍑1.错误报告函数-strerror

🌳(1)函数声明

char * strerror ( int errnum );

注释:

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

2.与错误码变量errno搭配使用。(errno 是记录系统的最后一次错误代码)

🌳(2)strerror函数使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
//使用错误码变量需要包含头文件
#include<errno.h>
int main()
{
  FILE* pf = fopen("test.txt", "r");
  //库函数调用失败之后,会把错误码记录到错误码变量中——errno
  if (pf == NULL)
  {
    printf("%s\n",strerror(errno));
    return 1;
  }
  fclose(pf);
  pf = NULL;
  return 0;
}

拓展:与strerror函数类似,使用perror函数也可显示相应错误信息。perror相当于printf+strerror,同时参数可添加提示词。

代码展示

#include<errno.h>
int main()
{
  FILE* pf = fopen("test.txt", "r");
  if (pf == NULL)
  {
    perror("错误信息");
    return 1;
  }
  fclose(pf);
  pf = NULL;
  return 0;
}

六、字符操作函数

🍑1.字符操作函数的分类

字符操作函数 如果它的参数符合下列条件就返回真
iscntrl 任何控制字符
isspace 空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit 十进制数字 0~9
isxdigit 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
islower 小写字母a~z
isupper 大写字母A~Z
isalpha 字母a~z或A~Z
isalnum 字母或者数字,a~z,A~Z,0~9
ispunct 标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph 任何图形字符

🍑2.字符操作函数的简单使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<ctype.h>
int main()
{
  char arr[] = "Are you ok?";
  char* p = arr;
  while (*p)
  {
    if (islower(*p))
    {
      *p = toupper(*p);
    }
    p++;
  }
  printf("%s\n", arr);
  return 0;
}

这些字符操作函数针对单个字符进行操作,它们的参数返回值以及功能都非常简单。这里就不再一一介绍了。

七、内存操作函数

🍑1.内存拷贝函数-memcpy

🌳(1)函数声明

void * memcpy ( void * destination, const void * source, size_t num );

注释:


1.函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。

2.这个函数在遇到 ‘\0’ 的时候并不会停下来。

3.如果source和destination有任何的重叠,复制的结果都是未定义的。(即:memcpy并不支持内存空间有重叠的复制)

🌳(2)memcpy函数的使用

//拷贝arr1中的前8个字节到arr2中
//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  float arr1[] = { 1.0f,2.0f,3.0f,4.0f };
  float arr2[5] = { 0.0f };
  memcpy(arr2, arr1,8);
  return 0;
}

🌳(3)memcpy函数模拟实现

#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
  void* ret = dest;
  assert(dest && src);
  while (num--)//拷贝字节数
  {
    //每次拷贝1个字节,所以将其转换为(char*)指针
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1;
    src = (char*)src + 1;
  }
  return ret;
}

🍑2.内存拷贝函数-memmove(可重叠)

🌳(1)函数声明

void * memmove ( void * destination, const void * source, size_t num );

注释:

1.和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。

2.如果源空间和目标空间出现重叠,就得使用memmove函数处理。

🌳(2)memmove函数的使用

//从arr中拷贝20个字节到arr+2
//注意:使用库函数需要包含头文件
#include<string.h>
int main()
{
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  memmove(arr+2,arr,20);
  return 0;
}

🌳(3)memmove函数的模拟实现

分析:

📝代码实现

#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
  void* ret = dest;
  assert(dest);
  assert(src);
  if (dest < src)//src前——>后拷贝
  {
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1;
    src = (char*)src + 1;
  }
  else//src后——>前
  {
    while(num--)
    {
      *((char*)dest + num) = *((char*)src + num);
    }
  }
  return ret;
}

🍑3.内存比较函数-memcmp

🌳(1)函数声明

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

注释:

1.比较从ptr1和ptr2指针开始的num个字节

2.标准规定:

(1)ptr1对应的字节内容>ptr2对应字节内容,返回大于0的数字

(2)两个内存块内容相同,返回0

(3)ptr1对应的字节内容<ptr2对应字节内容,返回小于0的数字

🌳(2)memcmp函数的使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  int arr1[] = {1,2,3,4,5};
  int arr2[] = {1,2,3,0,0};
  int ret = memcmp(arr1, arr2, 13);
  //分析:前12个字节内容相同,第13个字节内容00000100>00000000
  printf("%d\n", ret);
  return 0;
}

🍑4.内存设置函数-memset

🌳(1)函数声明

void * memset ( void * ptr, int value, size_t num );

注释:

1.将ptr中当前位置后面的num个字节用 value 替换并返回ptr。

2.注意是以字节(byte)为单位。ptr必须具有足够的空间容纳替换后的ptr。

🌳(2)menset函数的使用

//注意:使用库函数需要包含头文件
#include<stdio.h>
#include<string.h>
int main()
{
  char str[] = "xxxxxxxxxx";
  memset(str,'0', 5);//表示将str的前5个字节设置成字符0
  printf("%s\n", str);
  return 0;
}

总结

本章主要对字符串以及字符操作函数进行了一个较为全面的介绍,篇幅较长,但干货满满。建议大家反复观看,慢慢食用😊


相关文章
|
3天前
|
C语言
【C语言基础】:字符串函数(二)
【C语言基础】:字符串函数(二)
|
3天前
|
编译器 C语言 C++
【C语言基础】:字符函数和字符串函数-2
【C语言基础】:字符函数和字符串函数
|
3天前
|
C语言
【C语言基础】:字符函数和字符串函数-1
【C语言基础】:字符函数和字符串函数
|
11天前
|
C语言
C语言学习记录——鹏哥字符分类函数、字符转换函数
C语言学习记录——鹏哥字符分类函数、字符转换函数
12 2
|
11天前
|
安全 编译器 C语言
C语言学习记录——字符串相关函数及部分模拟(strcmp、strncmp、strncat、strncpy、strstr、strtok、strerror)
C语言学习记录——字符串相关函数及部分模拟(strcmp、strncmp、strncat、strncpy、strstr、strtok、strerror)
13 1
|
11天前
|
C语言
C语言学习记录——模拟字符串相关函数(strcpy、strlen、strcat)相关知识-const、typedef
C语言学习记录——模拟字符串相关函数(strcpy、strlen、strcat)相关知识-const、typedef
9 1
|
12天前
|
C语言
【C语言】【字符串函数】【超详解】【下】!!!
【C语言】【字符串函数】【超详解】【下】!!!
|
12天前
|
C语言
【C语言】【字符串函数】【超详解】【上】!!!
【C语言】【字符串函数】【超详解】【上】!!!
10 3
|
19天前
|
存储 C语言 索引
C语言字符数组深入解析与应用实例
C语言字符数组深入解析与应用实例
16 0
|
19天前
|
存储 C语言
C语言中的字符指针技术详解
C语言中的字符指针技术详解
23 0