C语言:字符函数与字符串函数(2)
6. strcmp函数的使用和模拟实现
函数原型如下:
int strcmp(const char *s1, const char *s2);
This function starts comparing the first character of each string. If they are equal to eachother, it continues with the following pairs until the characters differ or until a terminatingnull-character is reached.
简单来说就是,strcmp函数会比较两个字符串的大小,比较的规则是:
从两个字符串的首字符(这里以s1与s2代指)开始逐个比较字符对应ASCII码值得大小,如果s1此处字符大于s2此处字符,则字符串s1大于字符串s2返回一个正数;小于,则字符串s1小于s2,返回一个负数;等于则比较下一位,直到比较出大小或是至少比较到其中一个字符串中的 '\0' 为止,若此时仍旧相等,则返回0。
我们可以尝试使用strcmp函数:
#include <string.h>
#include <stdio.h>
int main()
{char s1[] = "Hello ";char s2[] = "Hello";char s3[] = "Hello";printf("%d\n", strcmp(s1,s2));printf("%d\n", strcmp(s2,s1));printf("%d\n", strcmp(s2,s3));return 0;
}
1
-1
0
了解了这些之后,我们尝试模拟实现一下strcmp函数:
首先,我们需要让函数接手两个字符串的地址,用于比较。
接着,我们需要保证传递的指针是有效的指针,需要使用assert函数。
最后,就是核心的比较逻辑的实现。我们利用指针逐个访问字符串中的字符,并进行比较,根据大小结果来返回不同的值。
代码实现形式如下:
int my_cmp(const char* s1,const char* s2)
{assert(s1 && s2);while(*s1 == *s2 && *s1 && *s2){s1++;s2++;}return *s1 - *s2;
}
与上面C语言库中不同的就是,返回值是两个字符的差值,因此,字符串s1大于s2时,返回值不一定是1;小于时,返回值不一定是-1。
我们尝试使用一下该函数:
int main()
{char s1[] = "Hello ";char s2[] = "Hello";char s3[] = "Hello";printf("%d\n", my_cmp(s1,s2));printf("%d\n", my_cmp(s2,s1));printf("%d\n", my_cmp(s2,s3));return 0;
}
32
-32
0
虽然返回值并没有如标准一样返回1或-1,但是也能判断出字符串的大小。如果想要实现这样的效果,可以在函数定义时加上if语句判断。
7. strncpy函数的使用
函数原型如下:
char * strncpy ( char * destination, const char * source, size_t num );
Copies the first num characters of source to destination. If the end of the source C string(which is signaled by a null-character) is found before num characters have been copied,destination is padded with zeros until a total of num characters have been written to it.
简单来说就是:
从源字符串source复制num个字符到目标字符串destination中,复制完num个字符后终止。
如果说,源字符串中字符(不包括'\0')不足num个,那么就会在复制完源字符串后,在目标字符串末尾继续补上'\0',直至复制个数达到num个;相对地,如果源字符串的大小大于num,那么在复制过程中源字符串中的内容有可能覆盖掉原本目标字符串中的'\0',让原本的字符串变为无效字符串。
用代码展示一下使用效果:
#include <string.h>
#include <stdio.h>
int main()
{char s1[] = "11111";char s2[] = "222222";printf("%s\n", strncpy(s1,s2,4));printf("%s\n", strncpy(s1,s2,5));printf("%s\n", strncpy(s1,s2,6));return 0;
}
22221
22222
222222����
所以说,strncpy函数安全性比上strcpy函数要高上一些,但还是有缺陷,例如:依旧无法判断目标的空间大小是否能放下复制的内容,可能导致溢出。适合已知缓冲区大小的情况下使用,依旧要谨慎处理边界情况。
8. strncat函数的使用
函数原型如下:
char * strncat ( char * destination, const char * source, size_t num );
Appends the first num characters of source to destination, plus a terminating null-character.
If the length of the C string in source is less than num,only the content up to the terminatingnull-character is copied.
简单来说就是:
strncat函数会将源字符串source中前num个字符追加到目标字符串destination末尾,然后再追加一个'\0'。当源字符串的大小(不包括'\0')小于num时,函数只会将源字符串'\0'前的内容追加到目标字符串末尾;若大于,则只会将前num个字符追加到目标字符串的末尾,并在末尾强制加上一个'\0'。
我们可以看出,无论如何,strncat函数一定会在目标字符串末尾加上一个'\0',保证目标字符串是有效的字符串。但是,和strcat函数一样,strncat函数也无法判断目标空间是否可以放下追加的内容,可能导致溢出。
#include <string.h>
#include <stdio.h>
int main()
{char s1[20] = "Hello ";char s2[] = "World";char s3[20] = "Hello ";char s4[] = "World";char s5[20] = "Hello ";char s6[] = "World";printf("%s\n", strncat(s1,s2,4));printf("%s\n", strncat(s3,s4,5));printf("%s\n", strncat(s5,s6,6));return 0;
}
Hello Worl
Hello World
Hello World
9. strncmp函数的使用
函数原型如下:
int strncmp ( const char * str1, const char * str2, size_t num );
Compares at most num characters of two possibly null-terminated arrays.
The comparison is done lexicographically.
Characters following the null character are not compared.
简单来说就是比较两个字符串前num个字符,如果相等则一直向后比较,最多比较num个字符,若提前发现不一样,则提前结束并返回对应的值。
除了可以限制比较的最大字符数以外,其余与strcmp函数一致。
#include <string.h>
#include <stdio.h>
int main()
{char s1[] = "asd";char s2[] = "asdf";printf("%d\n", strncmp(s1,s2,3));printf("%d\n", strncmp(s2,s1,4));return 0;
}
0
-1
10. strstr函数的使用和模拟实现
函数原型如下:
char * strstr ( const char * str1, const char * str2);
Returns a pointer to the first occurrence of str2 in str1,
or a null pointer if str2 is not part of str1.
The matching process does not include the terminating null-characters, but it stops there.
简单来说就是:函数strstr会返回字符串str2在str1中第一次出现的地址。
其中,'\0'不作为匹配的内容,只作为字符串结束的标志,因此,如果目标字符串中'\0'提前出现还未寻找成功,那么函数会直接终止,返回NULL。另外,如果str2为空字符串,则会直接返回字符串str1的地址。
尝试使用一下代码:
#include <string.h>
#include <stdio.h>
int main()
{char s1[] = "asdfg";char s2[] = "df";char s3[] = "fg";char s4[] = "gh";printf("%p\n", strstr(s1,s2));printf("%p\n", strstr(s1,s3));printf("%p\n", strstr(s1,s4));return 0;
}
000000CF877FF96C
000000CF877FF96D
0000000000000000
其中,返回的地址0000000000000000是一个无效地址,无法访问。
了解了这些之后,我们来尝试模拟实现strstr函数。
首先,我们需要让函数接收两个字符串的地址。
接着,使用assert函数保证传递的指针是有效的。
接下来,就是核心的查找逻辑。
如果,字符串str2的大小大于字符串s1的大小,那么绝对不可能在字符串s1中找到s2出现的地址,此时直接返回NULL。因为函数strstr在寻找时,如果碰到了str1中的'\0',会直接终止,因此,可以直接使用strlen来计算字符串大小。当字符串str2为空时,直接返回str1地址。否则,开始尝试寻找。
为了实现字符串的比对,我们要创建两个指针变量s1,s2,分别赋值为字符串str1,str2的地址。这样,通过指针s1,s2就可以是实现字符串的。但是,大部分情况下,str2并不是单个字符,因此我们还需要创建另一个指针变量dest,用于存储每一次开始比对字符串时的地址。这样,当一次比对失败后,就让dest挪动一次,并赋值给s1,让s2重新指向str2首字符,进行下一次比对。
最后,如果成功找到符合条件的地址,则返回该地址;否则,返回NULL。
那么,代码形式如下:
#include <string.h>
#include <stdio.h>
char * my_str(const char * str1,const char * str2)
{assert(str1 && str2);size_t len1 = strlen(str1),len2 = strlen(str2);if(len1 < len2)return NULL;const char * dest = str1;if(*str2 == '\0')return dest;while(dest <= str1 + (len1 - len2)){const char * s1 = dest;const char * s2 = str2;while((*s1 == *s2 && *s2))s1++,s2++;if(*s2 == '\0')return dest;dest++;}return NULL;
}
尝试使用一下该函数:
int main()
{char s1[] = "asdfg";char s2[] = "";char s3[] = "asdfgh";char s4[] = "fg";printf("%p\n", my_str(s1,s2));printf("%p\n", my_str(s1,s3));printf("%p\n", my_str(s1,s4));return 0;
}
000000D6C7BFFE0A
0000000000000000
000000D6C7BFFE0D
可见,代码能够正常执行函数strstr的逻辑。
11. strtok函数的使用
函数原型如下:
char * strtok ( char * str, const char * sep);
A sequence of calls to strtok breaks the string pointed to by str into a sequence of tokens, each of which is delimited by a character from the string pointed to by sep.
Each call in the sequence has a search target :
If str s non-null, the call is the first call in the sequence. The search target is null-terminated byte string pointed to by str.
If str is null, the call is one of the subsequent calls in the sequence. The search target is determined by the previous call in the sequence.
简单来说就是:
sep指向一个字符串,定义了用作分隔符的字符的集合。而str则指向了一个包含0个或者多个sep字符串中的一个或多个分隔符的字符串。
strtok函数会在字符串str中找到第一个包含在字符串sep中的分隔符,并在将其替换为'\0'。因此,该函数会直接改变字符串str,我们在使用时一般是传递可修改的临时拷贝内容。函数strtok在第一次调用时不能传递NULL,完成分割操作后会保存修改处的地址。在后续使用时传递NULL则代表示从上一次保存的地址开始查找下一个分隔符。当无法寻找到更多的分隔符时,函数会返回NULL。
我们可以尝试使用一下该函数:
#include <string.h>
#include <stdio.h>
int main()
{char s1[] = "asdfddg";char s2[] = "fg";char * s3;for(s3 = strtok(s1,s2);s3 != NULL;s3 = strtok(NULL,s2))printf("%s\n",s3);printf("s1 = %s",s1);return 0;
}
asd
dd
s1 = asd
12. strreeor函数的使用
函数原型如下:
char* strerror ( int errnum );
Returns a pointer to the textual description of the system error code errnum,identical to the description that would be printed by perror().
简单来说就是:
strerror 函数可以把参数部分错误码对应的错误信息的字符串地址返回来,与函数perror打印的错误信息相同。
在不同的系统和C语言标准库的实现中都规定了一些错误码,一般是放在头文件errno.h中,C语言程序启动的时候就会使用一个全局的变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表示没有错误。当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码,存放在errno中。而一个错误码的数字是整数很难理解是什么意思,所以每一个错误码都是 有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。
我们可以尝试将0~10代表的错误信息打印出来:
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main()
{int i;for (i = 0; i <= 10; i++)printf("%s\n", strerror(i));return 0;
}
这是Windows11+CLion2024打印出的结果:
No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error
No such device or address
Arg list too long
Exec format error
Bad file descriptor
No child processes
我们可以让系统打开一个不存在的文件,来看看函数是否能正确返回错误信息:
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main()
{FILE * pFile;pFile = fopen ("unexist.ent","r");if (pFile == NULL)printf ("Error opening file unexist.ent: %s\n", strerror(errno));return 0;
}
Error opening file unexist.ent: No such file or directory
我们可以可见,函数strerror函数成功指出了错误。
也可以了解一下perror函数,perror函数相当于一次将上述代码中的打印部分完成了,直接将错误信息打印出来。perror函数打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。
int main ()
{FILE * pFile;pFile = fopen ("unexist.ent","r");if (pFile == NULL)perror("Error opening file unexist.ent");return 0;
}
Error opening file unexist.ent: No such file or directory