当前位置: 首页 > ops >正文

【C语言】字符函数与字符串函数实战:用法原理 + 模拟实现

在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语言标准库中提供了一系列库函数,接下来我们就学习一下这些函数

一、字符分类函数

C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。这些函数的使用都需要包含一个头文件是ctype.h

这些函数的使用方法非常类似,我们就讲解一个函数的事情,其他的非常类似:

int islower ( int c );

islower 是能够判断参数部分的 c 是否是小写字母的。通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回0。

练习:写⼀个代码,将字符串中的小写字母转大写,其他字符不变。

#include<stdio.h>
#include<ctype.h>
int main()
{char arr[] = "Hello World";//因为字符串放在数组中他是有对应下标的,0也算一个//将字符串中的字符逐个进行处理//遇到小写,转换成大写,再输出//如果不是小写,正常输出int i = 0;while (arr[i] != '\0'){if (islower(arr[i])){arr[i] = arr[i] - 32;}printf("%c", arr[i]);i++;}return 0;
}

二、字符转换函数

C语言提供了俩个字符转换函数

int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写  
int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写 

上面的代码,我们将小写转大写,是-32完成的效果,有了转换函数,就可以直接使用toupper函数。

#include<stdio.h>
#include<ctype.h>
int main()
{char arr[] = "Hello World";int i = 0;while (arr[i] != '\0'){if (islower(arr[i])){arr[i] = toupper(arr[i]);}printf("%c", arr[i]);i++;}return 0;
}

三、strlen函数的使用和模拟实现

函数声明

  • 声明:size_t strlen(const char *str);

功能

  • 统计参数 str 指向的字符串的长度。统计的是字符串中 '\0' 之前的字符的个数。

参数

  • str:指针,指向了要统计长度的字符串。

返回值

  • 返回 str 指向的字符串的长度,返回的长度不会是负数,所以返回类型是 size_t
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{const char* str = "abcdef";printf("%zu\n", strlen(str));return 0;
}

使用注意事项

  • 字符串以 '\0' 作为结束标志,strlen 函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0')。
  • 参数指向的字符串必须要以 '\0' 结束。
  • 注意函数的返回值为 size_t,是无符号的(易错)。
  • strlen 的使用需要包含头文件 <string.h>
2、strlen返回值
#include<stdio.h>
#include<string.h>
int main()
{const char* str1 = "abcdef";const char* str2 = "bbb";if (strlen(str2) - strlen(str1) > 0){printf("str2 > str1\n");}else{printf("str1 > str2\n");}return 0;
}

大家乍一看肯定都会觉得打印的肯定是 str1 > str2 因为这是我们学习前面的知识之后所就会默认觉得的内容,实则打印的结果是:str2 > str1这是因为strlen函数的返回值是size_t类型的(无符号整型),我们用俩个size_t类型的数据相减,其结果也会返回一个size_t类型的数据。为了避免这种问题,应该直接比较两个长度,而不是进行减法运算。

如果我们就是想要返回我们认为的 str1 > str2,我们可以采取强制类型转换,将他转换为int类型。

​
#include<stdio.h>
#include<string.h>
int main()
{const char* str1 = "abcdef";const char* str2 = "bbb";if ((int)strlen(str2) - (int)strlen(str1) > 0){printf("str2 > str1\n");}else{printf("str1 > str2\n");}return 0;
}
3、模拟实现
方法一:计数器方式
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char* str)
{int count = 0;assert(str);while (*str){count++;str++;}return count;
}
int main()
{char arr[] = "Hello World";size_t len = my_strlen(arr);printf("%zu\n", ret);return 0;
}
方法二:递归
#include<stdio.h>
size_t my_strlen(const char* str)
{if (*str != '\0')return 1 + my_strlen(str + 1);elsereturn 0;
}
int main()
{char arr[] = "Hello World";size_t len = my_strlen(arr);printf("%zu\n", len);return 0;
}
方法三:指针 - 指针
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char* str)
{assert(str);char* p = str;while (*p != '\0')p++;return p - str;
}
int main()
{char arr[] = "Hello World";size_t len = my_strlen(arr);printf("%zu\n", len);return 0;
}

四、strcpy函数的使用和模拟实现

函数声明

  • 声明: char* strcpy(char * destination, const char * source );

功能

  • 字符串拷贝,拷贝到源头字符串中的 \0 为止

参数

  • destination:指针,指向目的地空间
  • source:指针,指向源头数据

返回值

  • 返回目标空间的起始地址,返回类型是 char*
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{char arr1[20] = { 0 };char arr2[] = "hello world";//我们可能会想到不用这个函数直接使用循环的方式//arr1 = arr2 (这是不能使用的)//因为数组名是地址,地址是一个编号//地址是一个常量值,不能被修改//地址是指向空间的,但是地址不是空间char* ret = strcpy(arr1, arr2);printf("%s\n", ret);return 0;
}

使用注意事项

  • 源字符串必须以 '\0' 结束。
  • 会将源字符串中的 '\0' 拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可修改。
2、模拟实现
test1(基础版)
#include<stdio.h>
char* my_strcpy(char* dest, const char* src)
//dest不能加const,因为他要去被修改
{char* ret = dest;//拷贝\0前面的字符while (*src != '\0'){*dest = *src;dest++;src++;}*dest = *src;//拷贝\0return ret;
}
int main()
{char arr1[] = "hello world";char arr2[20] = "******************";my_strcpy(arr2, arr1);printf("%s\n", arr2);
}
test2(升级版)
#include <stdio.h>
#include <assert.h>
//1.参数顺序 
//2.函数的功能,停⽌条件 
//3.assert
//4.const修饰指针 
//5.函数返回值 
char* my_strcpy(char* dest, const char* src)
{char* ret = dest;assert(dest != NULL);assert(src != NULL);while ((*dest++ = *src++)){;//先使用再++}return ret;//返回目标空间的起始地址
}
int main()
{char arr1[10] = { 0 };char arr2[] = "hello";my_strcpy(arr1, arr2);printf("%s\n", arr1);return 0;
}

五、strcat函数的使用和模拟实现

函数声明

  • 声明: char * strcat ( char * destination, const char * source );

功能

  • 字符串追加,把 source 指向的源字符串中的所有字符都追加到 destination 指向的空间中。

参数

  • destination:指针,指向目的地空间
  • source:指针,指向源头数据

返回值

  • 返回目标空间的起始地址,返回类型是 char*
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{char arr1[20] = "hello";char arr2[] = " world";strcat(arr1, arr2);printf("%s\n", arr1);return 0;
}

使用注意事项

  • 源字符串必须以 '\0' 结束。
  • 目标字符串中也得有 '\0',否则没办法知道追加从哪里开始。
  • 目标空间必须足够大,能容纳下源字符串的内容。
  • 目标空间必须可修改。
2、模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{char* ret = dest;assert(dest != NULL);assert(src != NULL);//找到目标空间中的第一个\0while (*dest){dest++;}//从\0后面的位置开始追加while ((*dest++ = *src++)){;}return ret;
}
int main()
{char arr1[20] = "hello";char arr2[] = " world";my_strcat(arr1, arr2);printf("%s\n", arr1);return 0;
}

六、strcmp函数的使用和模拟实现

函数声明

  • 声明: int strcmp ( const char * str1, const char * str2 );

功能

  • 用来比较 str1 和 str2 指向的字符串,从两个字符串的第一个字符开始比较,如果两个字符的 ASCII 码值相等,就比较下一个字符。直到遇到不相等的两个字符,或者字符串结束。

参数

  • str1:指针,指向要比较的第一个字符串
  • str2:指针,指向要比较的第二个字符串

返回值

  • 标准规定:
    • 第一个字符串大于第二个字符串,则返回大于 0 的数字
    • 第一个字符串等于第二个字符串,则返回 0
    • 第一个字符串小于第二个字符串,则返回小于 0 的数字
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{char arr1[] = "abcdef";char arr2[] = "abq";int ret = strcmp(arr1, arr2);printf("%d\n", ret);if (ret > 0)printf("arr1 > arr2\n");else if (ret == 0)printf("arr1 == arr2\n");elseprintf("arr1 < arr2\n");return 0;
}
2、模拟实现
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* arr1, const char* arr2)
{int ret = 0;assert(arr1 != NULL);assert(arr2 != NULL);while (*arr1 == *arr2){if (*arr1 == '\0')return 0;arr1++;arr2++;}return *arr1 - *arr2;
}
int main()
{char arr1[] = "abcdef";char arr2[] = "abq";int ret = my_strcmp(arr1, arr2);if (ret > 0)printf("arr1 > arr2\n");else if (ret == 0)printf("arr1 = arr2\n");elseprintf("arr1 < arr2\n");return 0;
}

七、strstr函数的使用和模拟实现

函数声明

  • 声明: char * strstr ( const char * str1, const char * str2);

功能

  • strstr 函数,查找 str2 指向的字符串在 str1 指向的字符串中第一次出现的位置。
  • 简而言之:在一个字符串中查找子字符串。
  • strstr 的使用得包含 <string.h>

参数

  • str1:指针,指向了被查找的字符串
  • str2:指针,指向了要查找的字符串

返回值

  • 如果 str1 指向的字符串中存在 str2 指向的字符串,那么返回第一次出现位置的指针
  • 如果 str1 指向的字符串中不存在 str2 指向的字符串,那么返回 NULL
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{char* str1 = "AABCD";char* str2 = "AB";char* ret = strstr(str1, str2);//会找到AB(即相同部分)之后的地址printf("%s\n", ret);//会输出ABCD//如果没有相同部分的地址就返回NULLreturn 0;
}
2、模拟实现(难点)
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{assert(str1 && str2);char* p = str1;char* s1 = str1;char* s2 = str2;while (*p){if (*str2 == '\0')return str1;s1 = p;s2 = str2;while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2){s1++;s2++;}if (*s2 == '\0')return p;p++;}return NULL;
}
int main()
{char arr1[] = "abbcdef";char arr2[] = "bbc";char* p = my_strstr(arr1, arr2);if (p != NULL)printf("找到了,%s\n", p);elseprintf("找不到\n");return 0;
}

八、strncpy函数的使用

函数声明

  • 声明: char * strncpy ( char * destination, const char * source, size_t num );

功能

  • 字符串拷贝;将 source 指向的字符串拷贝到 destination 指向的空间中,最多拷贝 num 个字符。

参数

  • destination:指针,指向目的地空间
  • source:指针,指向源头数据
  • num:从 source 指向的字符串中最多拷贝的字符个数

返回值

  • strncpy 函数返回的目标空间的起始地址
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{char arr1[20] = { 0 };char arr2[] = "abcdefghi";char* str = strncpy(arr1, arr2, 5);//将arr2中的前五个字符拷贝到arr1中,如果不够会补\0printf("%s\n", arr1);printf("%s\n", str);return 0;
}
2、模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strncpy(char* dest, const char* src, size_t num)
{char* ret =* dest;assert(dest != NULL);assert(src != NULL);while (num){*dest = *src;dest++;src++;num--;}return ret;
}
int main()
{char arr1[] = "hello world";char arr2[20] = "******************";int k = 0;scanf("%d", &k);my_strncpy(arr2, arr1, k);printf("%s", arr2);return 0;
}
3、与strcpy对比 
  1. 关于 strcpy
    • 它的作用是进行字符串拷贝,会一直拷贝直到遇到源字符串的结束符 \0
    • 存在的问题是,如果目标空间不足以容纳源字符串,就很容易出现越界行为,可能会导致程序出现不可预料的错误,比如覆盖其他内存区域的数据等。
  2. 关于 strncpy
    • 它指定了拷贝的字符长度,这是它与 strcpy 一个很重要的区别。
    • 源字符串不一定需要以 \0 结束。
    • 由于在使用时需要考虑目标空间的大小是否够用,所以相对 strcpy 函数来说更加安全,能在一定程度上避免缓冲区溢出等问题。

九、strnact函数的使用

函数声明

  • 声明: char * strncat ( char * destination, const char * source, size_t num );

功能

  • 字符串追加;将 source 指向的字符串的内容,追加到 destination 指向的空间,最多追加 num 个字符。

参数

  • destination:指针,指向了目标空间
  • source:指针,指向了源头数据
  • num:最多追加的字符的个数

返回值

  • 返回的是目标空间的起始地址
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{char arr1[] = "hello";char arr2[] = " world";char* str = strncat(arr1, arr2, 5);//我们追加的时候是从第一个\0开始追加的//追加完成之后会补\0//如果追加到字符数超过了原有字符数//只要追加上原来的即可printf("%s\n", arr1);printf("%s\n", str);return 0;
}
2、模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* dest, const char* src, size_t num)
{char* ret = dest;assert(dest != NULL);assert(src != NULL);while (*dest){dest++;}while (num){*dest = *src;dest++;src++;num--;}return ret;
}
int main()
{int k = 0;scanf("%d", &k);char arr1[20] = "hello ";char arr2[] = "world";my_strncat(arr1, arr2, k);printf("%s", arr1);return 0;
}
3、与strcat对比
  • 参数不同strncat 多了一个参数。
  • 追加内容处理strcat 函数在追加的时候要将源字符串的所有内容,包含 \0 都追加过去,但是 strncat 函数指定了追加的长度。
  • 源字符串结束符要求strncat 函数中源字符串中不一定要有 \0 了。
  • 灵活性和安全性strncat 更加灵活,也更加安全。

十、strncmp函数的使用

函数声明

  • 声明: int strncmp ( const char * str1, const char * str2, size_t num );

功能

  • 字符串比较;比较 str1 和 str2 指向的两个字符串的内容,最多比较 num 字符。

参数

  • str1:指针,指向一个比较的字符串
  • str2:指针,指向另外一个比较的字符串
  • num:最多比较的字符个数

返回值

  • 标准规定:
    • 第一个字符串大于第二个字符串,则返回大于 0 的数字
    • 第一个字符串等于第二个字符串,则返回 0
    • 第一个字符串小于第二个字符串,则返回小于 0 的数字
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{char arr1[] = "abcdef";char arr2[] = "abcqw";int ret1 = strncmp(arr1, arr2, 3);printf("%d\n", ret1);int ret2 = strncmp(arr1, arr2, 4);printf("%d\n", ret2);return 0;
}
2、模拟实现
#include<stdio.h>
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{assert(str1 != NULL);assert(str2 != NULL);int ret = 0;while (num){if (*str1 == *str2){if (*str1 == 0){break;}str1++;str2++;}else{ret = *(unsigned char*)str1 - *(unsigned char*)str2;break;}num--;}return ret;
}
int main()
{char arr1[] = "abcd";char arr2[] = "abd";int k = 0;scanf("%d", &k);int ret = my_strncmp(arr1, arr2, k);if (ret > 0)printf("arr1 > arr2");else if (ret == 0)printf("arr1 = arr2");elseprintf("arr1 < arr2");return 0;}
3、与strcmp对比
  1. 参数不同strncmp 比 strcmp 多了一个用于指定最多比较字符个数的参数。
  2. 比较长度方面strncmp 可以比较任意长度的字符串(通过指定 num 参数来控制比较的字符个数)。
  3. 灵活性和安全性strncmp 更加灵活,也更加安全,因为它可以通过指定比较长度来避免一些不必要的问题,例如在处理不确定长度的字符串时可以更好地控制比较过程。

十一、strtok函数的使用

函数声明

  • 声明: char *strtok(char *str, const char *delim);

功能

  • 分割字符串:根据 delim 参数中指定的分隔符,将输入字符串 str 拆分成多个子字符串。
  • 修改原始字符串strtok 会直接在原始字符串中插入 '\0' 终止符,替换分隔符的位置,因此原始字符串会被修改。

参数

  1. str:首次调用时传入待分割的字符串;后续调用传入 NULL,表示继续分割同一个字符串。
  2. delim:包含所有可能分隔符的字符串(每个字符均视为独立的分隔符)。

返回值

  • 成功时返回指向当前子字符串的指针。
  • 没有更多子字符串时返回 NULL

使用步骤

  1. 首次调用:传入待分割字符串和分隔符。
  2. 后续调用:传入 NULL 和相同的分隔符,继续分割。
  3. 结束条件:当返回 NULL 时,表示分割完成。
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{char arr[] = "zpengwei@yeah.net";char sep[] = "@.";char buf[20] = { 0 };strcpy(buf, sep);//我们将arr拷贝一份到buf中,修改buf不去动arr//第一次调用//第一次调用会将zpengwei@yeah.net中的@改为\0//即zpengwei\0yeah.net并且返回z的地址char* p1 = strtok(buf, sep);printf("%s\n", p1);//zpengwei//后续调用都传入空指针//第二次调用会将zpengwei\0yeah.net中的.改为\0//即zpengwei\0yeah\0net并且返回y的地址char* p2 = strtok(NULL, sep);printf("%s\n", p2);//yeahchar* p3 = strtok(NULL, sep);printf("%s\n", p3);//net//如果还有p4则会返回NULLchar* p4 = strtok(NULL, sep);printf("%s\n", p4);//(null)return 0;
}

这段代码也可以采取循环的方式去优化

#include<stdio.h>
#include<string.h>
int main()
{char arr[] = "zpengwei@yeah.net";char sep[] = "@.";char buf[200] = { 0 };strcpy(buf, arr);char* p = NULL;for (p = strtok(buf, sep); p != NULL; p = strtok(NULL, sep)){printf("%s\n", p);}return 0;
}
2、注意事项

破坏性操作

  • strtok 会直接修改原始字符串,将其中的分隔符替换为 '\0'。如果需要保留原字符串,应先拷贝一份。

连续分隔符

  • 多个连续的分隔符会被视为单个分隔符,不会返回空字符串。

空指针处理

  • 如果输入的 str 为 NULL 且没有前序调用,行为未定义。

十二、strerror函数的使用

函数声明

char* strerror ( int errnum );

功能

  1. strerror 函数可以通过参数部分的 errnum 表示错误码,得到对应的错误信息,并且返回这个错误信息字符串首字符的地址。
  2. strerror 函数只针对标准库中的函数发生错误后设置的错误码的转换。
  3. strerror 的使用需要包含 <string.h>

在不同的系统和 C 语言标准库的实现中都规定了一些错误码,一般是放在 <errno.h> 这个头文件中说明的。C 语言程序启动的时候就会使用一个全局的变量 errno 来记录程序的当前错误码,只不过程序启动的时候 errno 是 0,表示没有错误。当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码存放在 errno 中。而一个错误码的数字是整数,很难理解是什么意思,所以每一个错误码都是有对应的错误信息的。strerror 函数就可以将错误码对应的错误信息字符串的地址返回。

参数

  • errnum:表示错误码。

这个错误码一般传递的是 errno 这个变量的值,在 C 语言中有一个全局的变量叫 errno,当库函数的调用发生错误的时候,就会将本次错误的错误码存放在 errno 这个变量中。使用这个全局变量需要包含头文件 <errno.h>

返回值

函数返回通过错误码得到的错误信息字符串的首字符的地址。

1、代码演示
#include <errno.h>
#include <string.h>
#include <stdio.h>
//我们打印⼀下0~10这些错误码对应的信息 
int main()
{int i = 0;for (i = 0; i <= 10; i++) {printf("%d: %s\n", i, strerror(i));}return 0;
}

#include <stdio.h>#include <string.h>
#include <errno.h>
int main ()
{FILE * pFile = NULL;//fopen函数以读的形式打开⽂件,如果⽂件不存在,则打开失败。 pFile = fopen ("unexist.ent", "r");if (pFile == NULL){printf ("错误信息是:%s\n", strerror(errno));return 1;//错误返回 }return 0;
}

2、perror

也可以了解一下 perror 函数, perror 函数相当于一次将上述代码中的第11行完成了,直接将错误 信息打印出来。 perror 函数打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{FILE * pFile = NULL;pFile = fopen ("unexist.ent", "r");if (pFile == NULL){perror("错误信息是");return 1;}return 0;
}

http://www.xdnf.cn/news/19142.html

相关文章:

  • 零、2025 年软件设计师考试大纲
  • Citrix 零日漏洞自五月起遭积极利用
  • Redis-基数统计、位图、位域、流
  • LangChain.js 实战与原理:用 LCEL 构建可维护的 RAG / Agent 系统(含 4 套 30+ 行代码)
  • 大语言模型生成的“超龄劳动者权益保障制度系统化完善建议(修订版)”
  • Day17(前端:JavaScript基础阶段)
  • Elasticsearch:Semantic text 字段类型
  • PostgreSQL令牌机制解析
  • Linux从入门到进阶--第四章--Linux使用操作
  • TuringComplete游戏攻略(2.1算数运算)
  • Xshell 自动化脚本大赛技术文章大纲
  • BGP路由协议(三):路径属性
  • Git 的核心工作流程(三区域模型)
  • 第四章:大模型(LLM)】08.Agent 教程-(11)构建历史与数据分析协作系统
  • Kafka 主题级配置从创建到优化
  • 第二十六天-ADC基本原理
  • 一个wordpress的网站需要什么样的服务器配置
  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(七)
  • 本地运行的检索PDF文件中出现关键字的python程序
  • Coze源码分析-API授权-编辑令牌-后端源码
  • K8s服务日志收集方案文档
  • 【90页PPT】新能源汽车数字化转型SAP解决方案(附下载方式)
  • (纯新手教学)计算机视觉(opencv)实战十——轮廓特征(轮廓面积、 轮廓周长、外接圆与外接矩形)
  • Redis 缓存热身(Cache Warm-up):原理、方案与实践
  • docker,mysql安装
  • 35.Ansible的yaml语法与playbook的写法
  • 嵌入式Linux I2C驱动开发
  • 从零到一:使用Flask构建“我的笔记”网站
  • [光学原理与应用-337]:ZEMAX - 自带的用于学习的样例设计
  • LeetCode100-240搜索二维矩阵Ⅱ