【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++ 中使用的关键字,主要用于定义常量和只读变量。它的主要作用包括:
- 限制变量的修改:使用
const
可以确保变量的值在初始化后不会被改变,从而提高程序的安全性和可靠性。- 指针的使用:在指针中,
const
可以用于定义常量指针和指向常量的指针,确保指针指向的地址或值不被修改。- 增强代码可读性:通过使用
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~z 或 A~Z |
isalnum | 字母或数字,a~z ,A~Z ,0~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;
}
输出:
到这里,或许大家有些疑问,咱们来看看一些注意事项:
- **空字节填充:**当源字符串的长度小于 n 时,目标数组的剩余部分将用
\0
填充。 - **不自动添加终止符:**如果源字符串的长度大于或等于 n,目标字符串将不会自动添加终止符
\0
,需要手动添加。 - **目标空间足够大:**确保目标数组的大小足够大,以容纳复制的字符和终止符
\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语言专栏