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

【C语言】字符串与字符函数详解(上)

C语言学习

字符函数和字符串函数上
友情链接:C语言专栏


文章目录

  • C语言学习
  • 前言:
  • 一、const和assert介绍
    • 1.1 const
      • const修饰函数参数
    • 1.2 assert断言
      • assert函数的使用
      • assert的作用
  • 二、字符分类函数
  • 三、字符转化函数
  • 四、strlen的使用和模拟实现
  • 五、strcpy的使用和模拟实现
  • 六、strcat的使用和模拟实现
  • 七、strncpy函数的使用
  • 八、strncat函数的使用
  • 总结
  • 附录
    • 上文链接
    • 下文链接
    • 专栏


前言:

在C语言编程中,字符和字符串的处理是基础且重要的内容。本文将详细介绍C语言标准库中常用的字符分类函数、字符转换函数以及基础字符串操作函数(strlen、strcpy、strcat等),并通过模拟实现帮助读者深入理解这些函数的底层原理。学习这些函数不仅能提升编程效率,更能为后续复杂字符串处理打下坚实基础。


一、const和assert介绍

在讨论咱们下面要说的字符函数和字符串函数和后面博客要说的内存函数的模拟实现时,咱们要对关键字const函数assert有个有个认识,以便于咱们完成某些库函数的模拟实现。ok,咱们先看const吧。

1.1 const

const是一个在 C 和 C++ 中使用的关键字,主要用于定义常量和只读变量。它的主要作用包括:

  1. 限制变量的修改:使用const 可以确保变量的值在初始化后不会被改变,从而提高程序的安全性和可靠性。
  2. 指针的使用:在指针中,const可以用于定义常量指针和指向常量的指针,确保指针指向的地址或值不被修改。
  3. 增强代码可读性:通过使用const,程序员可以清晰地表达某些值是不可变的,从而提高代码的可读性。

修饰变量的示例:

int main()
{const int i = 10;i = 20;//错误,i不可被修改return 0;
}

修饰指针时要注意几个点:
代码1

int main()
{int i = 10; const int* pi = &i;//int const * pi = &i;//两种写法一样,都表示*pi不可改*pi = 20;//错误,不可改pi = NULL;//可改,合法return 0;
}

代码2:

int main()
{int i = 10; int* const pi = &i;*pi = 20;//可改,合法pi = NULL;//错误,不可改return 0;
}

那假如咱们需要将指针pi和*pi都不想让它被修改,想必聪明的你已经想到了,那就加两者const呗。
代码3:

int main()
{int i = 10; const int* const pi = &i;*pi = 20;//错误,不可改pi = NULL;//错误,不可改return 0;
}

const修饰函数参数

咱们再来说一下,const修饰函数参数,比如size_t strlen ( const char * str ):
const 的作用:
仅表示在函数内部不能通过 str 修改它指向的字符串。
两个常见误区:

误区一const char*参数会让字符串变为只读。
事实:const仅限制函数内部的访问方式,不改变原始字符串的存储位置
误区二:函数内部的str一定指向只读区。
事实:取决于主函数传入的是字面量(只读)还是数组(可写)。

对const咱们就了解这么多,后续可以在代码中慢慢体会。
总之,const 是一个重要的关键字,能够帮助开发者编写更安全和可维护的代码。

1.2 assert断言

在C/C++中,assert函数是一个宏,用于在程序运行时进行断言检查。如果条件为假,assert会打印错误信息并终止程序运行,这有助于开发者在开发阶段快速发现并修复错误。

assert函数的使用

assert函数定义在<assert.h>(C++中也可以是)头文件中,其基本用法如下:

#include <assert.h>//……assert(条件表达式);

如果条件表达式的结果为假,assert会显示错误信息,格式通常为:“Assertion failed: 条件表达式, file 文件名, line 行号”,然后程序崩溃并终止运行。如果条件表达式为真,则assert不会有任何操作。
示例:

#include <stdio.h>
#include <assert.h>
int main() 
{int i = 10;//……assert(i > 0);  // 如果 i <= 0,程序终止printf("%d\n", i);return 0;
}

assert的作用

assert的主要作用是作为一个调试工具,帮助开发者在代码中检测和断言预期的条件。它通常用于:

  • 检查函数参数的有效性。
  • 验证代码中的假设条件。
  • 捕捉不应该发生的错误。

在代码发布前,通常会禁用assert,以避免影响程序的性能。这可以通过定义NDEBUG宏来实现,如:

#define NDEBUG
#include <cassert>

assert的缺点是,因为引入了额外的检查,增加了程序的运行时间。
⼀般我们可以在Debug中使用,在Release版本中选择禁用assert就行。在我现在用的vs2022这样的集成开发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在 Release 版本不影响用户使用时程序的效率。

二、字符分类函数

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

函数描述
iscntrl任何控制字符
isspace空白字符:空格 ,换页 \f,换行 \n,回车 \r,制表符 \t 或垂直制表符 \v
isdigit十进制数字 0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母 a~f,大写字母 A~F
islower小写字母 a~z
isupper大写字母 A~Z
isalpha字母 a~zA~Z
isalnum字母或数字,a~zA~Z0~9
ispunct标点符号,任何不属于数字或字母的图形字符(可打印)
isgraph任何图形字符
isprint任何可打印字符,包括图形字符和空白字符

这些函数的使用方法非常类似且简单,我们就来看⼀个函数,其他的非常类似:

int islower ( int c );

说明:

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

咱们可以试着写⼀个代码,将字符串中的小写字母转大写,其他字符不变:

#include<stdio.h>
#include<ctype.h>
int main()
{char str[] = "Hello World!";char* pc = str;while (*pc){if (islower(*pc))//小写则为真*pc -= 32;//大小写的ASCII码值相差32pc++;}printf(str);return 0;
}

测试:
在这里插入图片描述
字符分类函数就讲到这,咱们要多用才会熟悉。

三、字符转化函数

C语⾔提供了2个字符转换函数:

int tolower ( int c ); //将参数传进去的大写字母转小写
int toupper ( int c ); //将参数传进去的小写字母转大写

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

#include<stdio.h>
#include<ctype.h>
int main()
{char str[] = "Hello World!";char* pc = str;while (*pc){if (islower(*pc))//小写则为真//*pc -= 32;*pc = toupper(*pc);pc++;}printf(str);return 0;
}

字符函数就了解到这,接下来咱们来看看常见的字符串函数。

四、strlen的使用和模拟实现

strlen函数是C语言中用于求字符串长度的函数。使用这个函数时,需要包含头文件 <string.h>
既然是求字符串长度的,那么在来再回顾一下字符串吧:字符串以'\0'作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含'\0')。 所以,参数指向的字符串必须要以'\0'结束。
函数原型:

size_t strlen ( const char * str );

解释:

size_t:返回类型为无符号整数(unsigned int),因为函数算的是长度;
const char * str :参数指向待计算长度的字符串的指针,const表示在此函数内部不会修改字符串内容(安全性)。也就是说,函数strlen只负责计算字符串长度,不会改变字符串的内容。

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("srt1>str2\n");}return 0;
}

输出:
在这里插入图片描述
怎么会这样呢?strlen(str2)确实为3,strlen(str1)也是6,那3 -6怎么会大于0呢?这里咱们就要知道,此时strlen返回值3和6为size_t,即 unsigned int,所以3-6之后的结果也是unsigned int型的数据,显然,一定是大于0的。故输出没问题。
那咱们确实需要通过程序知道哪个字符串更长,该怎么办,其实,很简单,咱们把它强制类型转化为int就行。或者用int型的变量来就接受strlen的返回值,然后,再进行比较,也行。
了解了如何让使用,咱们可以自己模拟实现一下strlen函数
strlen的模拟实现:
方法一:

//计数法
#include<assert.h>
size_t my_strlen(const char* pc)
{assert(pc);//断言,防止空指针传入int count = 0;//计数器while (*pc != '\0'){count++;pc++;}return count;
}

方法二:

//指针相减
#include<assert.h>
size_t my_strlen(const char* pc)
{assert(pc);//断言,防止空指针传入char* ch = pc;while (*ch != '\0'){ch++;}return ch-pc;
}

方法三:

//递归实现
#include<assert.h>
size_t my_strlen(const char* pc)
{assert(pc);//断言,防止空指针传入if (*pc == '\0')return 0;elsereturn 1 + my_strlen(pc + 1);
}

法三咱们可能没有想到,就是递归的应用。

五、strcpy的使用和模拟实现

strcpy函数是C语言中用于字符串拷贝函数,用于将一个字符串复制到另一个字符串。使用这个函数时,需要包含头文件 <string.h>
既然要实现字符串拷贝,咱们对于源字符串和目标空间必须满足一定要求,才可以完成函数功能:

  • 源字符串必须以 ‘\0’ 结束。(不然拷贝没有停止条件,且字符串本来就是以’\0’结尾)
  • 函数会将源字符串中的 ‘\0’ 拷贝到目标空间。(字符串以’\0’结尾)
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可修改。(不然不能拷贝)

函数原型:

char* strcpy(char * dest, const char * src );

解释:

dest:目标空间地址;
src:源字符地址;
返回值为目标函数地址,方便链式访问;

strcpy的使用:
咱们先来使用一下strcpy函数:

#include <stdio.h>
#include <string.h> // 必须包含头文件int main() 
{char src[] = "Hello, World!";char dest[20]; // 确保足够大以容纳 src + '\0'strcpy(dest, src); // 复制 src 到 destprintf("%s\n", dest); // 输出: Hello, World!return 0;
}

strcpy函数有了了解,咱们可以试着来模拟实现一下
strcpy模拟实现:

#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{assert(dest && src);char* p = dest;//保留目标空间的起始地址方便返回while (*src!='\0'){*dest++ = *src++;}*dest++ = *src++;//保证'\0'也会拷贝过去return p;
}

很多人可能会这样写,没问题,可以实现strcpy的功能,但咱们也可以看出来,代码有冗余的部分,咱们可以再来改进一下:

#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{assert(dest && src);char* p = dest;//保留目标空间的起始地址方便返回while (*dest++ = *src++){;}return p;
}

这里就解释是一个点:*dest++ = *src++表达式的结果 是 赋值的结果。所以当*str\0赋值给*dest时,循环的判断条件为0(假),退出循环。

六、strcat的使用和模拟实现

strcat函数是C语言中用于字符串连接的函数,其功能是将一个字符串追加到另一个字符串的末尾。使用这个函数时,需要包含头文件 <string.h>
既然是追加,那么我们就要注意一些要点:

  • 源字符串必须以 ‘\0’ 结束。
  • 目标字符串中也得有 ‘\0’ ,否则没办法知道追加从哪里开始。
  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  • 目标空间必须可修改。

函数的原型:

char *strcat(char *dest, const char *src);

解释:

dest :目标空间地址;
src :源字符串的地址;
返回类型:为目标空间地址。

strcat的使用:

#include <stdio.h>
#include <string.h>int main() 
{char arr1[20] = "hello ";char arr2[] = "world";strcat(arr1, arr2);printf("%s\n", arr1);return 0;
}

输出:
在这里插入图片描述
那如果字符串自己给自己追加呢?

#include <stdio.h>
#include <string.h>int main() 
{char arr1[20] = "hello ";strcat(arr1, arr1);printf("%s\n", arr1);return 0;
}

未定义行为,函数会先找到 dest 的末尾(‘\0’),然后从该位置开始复制 src(即自身)。由于复制过程会覆盖原字符串内容,导致无限循环或内存访问越界(具体行为取决于实现)。图示:
数组arr1(未追加前):
在这里插入图片描述
数组arr1(开始追加):
在这里插入图片描述
没有'\0',无停止条件,会无限循环下去。
在这里插入图片描述
会导致非法访问或者其他未定义行为。
ok,有了这些了解。那咱们就模拟实现一下strcat函数吧。
strcat函数模拟实现:

#include<assert.h>
char* my_strcat(char* dest, const char* src)
{assert(dest && src);char* str = dest;while (*++dest);//使得dest指向'\0'while (*dest++ = *src++);//等同于strcpyreturn str;
}

在这个实现中,首先使用while循环找到dest中的'\0'字符,然后使用另一个while循环将src中的所有字符复制到dest中,包括'\0'字符。

七、strncpy函数的使用

strncpy 函数是C语言标准库中的一个函数,用于将一个字符串的前 n 个字符复制到另一个字符串中。
函数原型:

char *strncpy(char *dest, const char *src, size_t n);

解释:

dest:指向目标数组的指针,用于存储复制的内容。
src:指向源字符串的指针。
n:要从源字符串中复制的字符数。

使用:

#include <stdio.h>
#include <string.h>int main() 
{char str1[15];char str2[15];strcpy(str1, "abcdef");strcpy(str2, "ABCDEF");strncpy(str1, str2, 4);printf("%s\n", str1);return 0;
}

输出:
在这里插入图片描述
到这里,或许大家有些疑问,咱们来看看一些注意事项:

  1. **空字节填充:**当源字符串的长度小于 n 时,目标数组的剩余部分将用\0填充。
  2. **不自动添加终止符:**如果源字符串的长度大于或等于 n,目标字符串将不会自动添加终止符\0,需要手动添加。
  3. **目标空间足够大:**确保目标数组的大小足够大,以容纳复制的字符和终止符\0

八、strncat函数的使用

strncat函数是C语言标准库中的一个函数,用于将一个字符串的前n个字符连接到另一个字符串的末尾。这个函数定义在 <string.h> 头文件中。
函数原型:

char *strncat(char *dest, const char *src, size_t n)

strncat使用:

#include <stdio.h>
#include <string.h>int main() 
{char dest[15];char src[15];strcpy(dest, "Hello ");strcpy(src, "world!");strncat(dest, src, 4);printf("%s\n", dest);return 0;
}

输出:
在这里插入图片描述
在这个例子中,strncat 函数将scr字符串的前4个字符追加到dest字符串的末尾。如果src的长度小于n,则会复制整个src字符串。如果 n 大于src的长度,那么只会追加src的全部字符到dest的末尾。无论哪种情况,都会在新字符串的末尾添加空字符。
注意事项:
使用strncat函数时,必须确保目标字符串有足够的空间来容纳追加的字符,以避免溢出。此外,应该注意strncat会覆盖dest字符串最后的空字符,并在追加完成后再次添加一个空字符。


总结

本文系统讲解了C语言中const关键字的用法、assert断言的作用,以及字符分类/转换函数的使用。重点剖析了strlen、strcpy和strcat等字符串函数的实现原理,并通过三种方式模拟实现了strlen函数。掌握这些基础函数的使用和实现原理,是成为合格C程序员的必经之路。下篇我们将继续探讨字符串比较、查找等更高级的字符串处理函数。

附录

上文链接

《深入理解柔性数组:特点、使用与优势分析》

下文链接

《字符串与字符函数详解(下)》

专栏

C语言专栏

http://www.dtcms.com/a/288963.html

相关文章:

  • C++ 详谈继承体系下的构造函数和析构函数
  • k8s:离线添加集群节点的相关组件安装与升级
  • GeoServer 信息泄漏漏洞复现(CVE-2025-27505)
  • 周志华《机器学习导论》第11章 特征选择与稀疏学习
  • 机器学习-数据预处理
  • 闲庭信步使用图像验证平台加速FPGA的开发:第二十六课——正弦波DDS的FPGA实现
  • leetcode75【经典动态规划】之:最长公共子序列
  • nginx源码解读-------整体架构
  • 30天打牢数模基础-LightGBM讲解
  • 网络地址和主机地址之间进行转换的类
  • springboot电影推荐网站—计算机毕业设计源码—30760
  • 在Ubutu22系统上面离线安装Go语言环境【教程】
  • 【开源项目】基于RuoYi-Vue-Plus的开源进销存管理系统
  • Spring之AOP面向切面编程详解
  • 软件工程学概述:从危机到系统化工程的演进之路
  • MySQL详解三
  • Java 字符集(Charset)详解:从编码基础到实战应用,彻底掌握字符处理核心机制
  • 文件编码概念|文件的读取操作|文件读取的课后练习讲解
  • 数据治理,治的是什么?
  • 0719代码调试记录
  • 【星海出品】python安装调试篇
  • 网络安全隔离技术解析:从网闸到光闸的进化之路
  • Spring Boot总结
  • RabbitMQ核心组件浅析:从Producer到Consumer
  • 深入理解设计模式:访问者模式详解
  • 深入理解浏览器解析机制和XSS向量编码
  • Java中List<int[]>()和List<int[]>[]的区别
  • React-Native开发环境配置-安装工具-创建项目教程
  • 数据并表技术全面指南:从基础JOIN到分布式数据融合
  • Pinia 核心知识详解:Vue3 新一代状态管理指南