1 前言
字符及字符串在C语言当中是非常重要的一部分。但是因为C语言本身并没有字符串类型,所以利用C语言处理起字符和字符串比较繁琐。
我们知道,C语言的字符串通常放在常量字符串或者字符数组中。
常量字符串适用于对字符串内容不做修改的字符串函数。
本篇文章将要介绍的字符串函数有:
求字符串长度
—— strlen
长度不受限制的字符串函数
——strcpy
——strcat
——strcmp
长度受限制的字符串函数
——strncpy
——strncat
——strncmp
2 函数介绍
2.1 strlen
2.1.1 strlen的定义
strlen函数是用来求一个字符串长度的,函数定义如下:

字符串是以'\0'作为结束标志的,strlen函数返回的是字符串中从'\0'开始前面出现的字符个数(不包含'\0')
参数指向的字符串必须以'\0'结束,否则计算机会在内存空间中连续查找,直到找到'\0',返回一个随机值
函数的返回类型是size_t(无符号整型)(注意不是int)
第三条规则是一个很重要的易错点,我们来看以下代码:
#include <stdio.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;
}
在vs2022监视中查看

可以发现,以上代码的逻辑与我们惯用逻辑并不一样,长度小的减去长度大的结果大于0,就是因为strlen函数的返回值类型为无符号整型,运算无法得到负数。所以运行结果应该是打印str2>str1。
我们来看运行结果

可是运行结果却跟我们分析的不一样,这是为什么呢?
其实这是编译器优化的结果。vs非常强大,编译的时候就对代码进行了相关的优化,使代码更加贴近人类逻辑。但并非所有平台的编译器都有这样的功能,在不同的环境底下运行结果也可能不一样。
2.1.2 strlen的模拟实现
//模拟实现strlen的三种方式
//1.计数器遍历
int my_strlen1(const char* a)
{
char* end = a;
while (*end != '\0')
{
end++;
}
return end - a;
}
//2.不创建临时变量(递归)
int my_strlen2(const char* str)
{
if(*str=='\0')
return 0;
else
return 1+my_strlen2(str+1);
}
//3.指针-指针
int my_strlen3(const char* s)
{
char* p=s;
while(*p!='\0')
p++;
return p-s
}
在此使用的返回值类型是int类型,就是为了避免2.1.1当中长度相减始终为正值的情况发生。如改用size_t也可行。
2.2 strcpy
2.2.1 strcpy的定义
strcpy是字符串复制函数,功能是将一个字符串的内容(从首元素到‘\0’之前的内容)完全复制给另一个字符串。

函数的两个参数分别为目标字符串的首元素地址和源字符串的首元素地址
源字符串必须以'\0'结束,否则计算机将会在内存空间中连续查找,容易发生越界访问等不好的问题
源字符串的'\0'也会拷入目标空间,作为目标字符串的结束标志
目标空间必须足够大,才能足够存放源字符串(注意,如果目标空间不够大,程序仍然能运行,但是会发生越界访问,这是不好的)
目标空间必须可变,这样才能对目标空间进行修改
2.2.2 strcpy的模拟实现
char* my_strcpy(char* str2, const char* str1)
{
char* ret=str2; //保存目标空间首元素地址
assert(str2 && str1); //防止传入空指针
while(*str2++ = *str1++) //当'\0'出现,结束循环
{
;
}
return ret;
}
2.3 strcat
2.3.1 strcat的定义
strcat是字符串粘贴函数,作用是将源字符串粘贴与目标字符串的后部,从目标字符串的'\0'开始追加。

源字符串和目标字符串都必须以'\0'结束(原理类似上文)
目标空间必须足够大,才能增加存放源字符串的内容
目标空间必须可变
2.3.2 strcat的模拟实现
char* my_strcat(char* dest, const char* sour)
{
char* str = dest;
while (*dest)
{
dest++;
}
while (*dest++ = *sour++)
{
;
}
return str;
}
int main()
{
char a[20] = "Hello ";
char b[20] = "World!";
my_strcat(a, b);
printf("%s\n", a);
return 0;
}
我们来看以下效果

2.3.3 关于strcat的一个易错点
思考一下,如果字符串自己给自己追加,可以使用strcat吗?
假如我们对字符串Hello\0自身使用strcat函数,让我们来分析一下会发生怎样的情况

第一次追加时,目标字符串的'\0'被赋值为'h',但是由于两个指针指向的都是同一个字符串,这一次修改相对于源字符串来说,源字符串的'\0'已经不存在,已经不满足strcat的使用条件。随着程序的不断运行,目标字符串将会无线追加hello,直至程序崩溃。
所以,当字符串需要自己给自己追加时,是不能使用strcat的。
那要如何解决这个问题呢?这就要用到下文将会介绍到的strncat函数。
2.4 strcmp
2.4.1 strcmp的定义
strcmp是字符串比较函数,用来比较两个字符串的大小,定义如下:

C语言标准规定:
第一个字符串大于第二个字符串,返回大于0的数字
第一个字符串小于第二个字符串,返回小于0的数字
第一个字符串等于第二个字符串,返回0
(注:vs对代码进行了优化,大于返回1,小于返回-1)
如何判断两个字符串的大小呢?
比较字符串大小,并不是以字符串的长度来比较,而是以字符串每一位的ASCII码值来比较的
从两个字符串的首字符开始比较,ASCII码值大的字符串就大于另一个字符串
如果相等,则比较下一位字符,直到比出大小
如果比到了'\0'还未分出大小,则相等
2.4.2 strcmp的模拟实现
int my_strcmp(const char* src, const char* dst)
{
int ret = 0;
assert(src && dst);
while (!(ret = *(unsigned char*)src - *(unsigned char*)dst) && *dst)
++src, ++dst;
if (ret < 0)
ret = -1;
else if (ret > 0)
ret = 1;
else
ret = 0;
return ret;
}
2.5 strncpy
就是在strcpy的基础上加了一个参数num,从源字符串拷贝num个字符(字节)到目标空间,大致内容与strcpy相似

注意:
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到拷贝num次。
并不会覆盖目标字符串的内容,只是在相应位置对相对应的元素进行修改
2.6 strncat
就是在strcat的基础上加了一个参数num,从源字符串追加num个字符(字节)到目标空间末尾,大致内容与strcat相似

注意:如果源字符串的长度小于num,即使拷贝完源字符串,也只补一个0
2.7 strncmp
就是在strcmp的基础上加了一个参数num,比较两个字符串的前num个字符,大致内容与strcmp相似

比较到出现两个字符不一样,或者一个字符串,结束或者num个字符全部比较完
(本篇完)