用C语言实现常用的字符串函数
注:凡是要使用库里面的字符串函数,都需要引用头文件<stdio.h>
一、常用字符串函数介绍
1.strlen
size_t strlen(const char* str);
-
功能是计算字符串的长度。字符串以’\0’作为结束标志,strlen的返回值是在’\0’前(不包括\0)的字符数 char arr[] = "abc";
printf("%d\n", strlen(arr));
-
需要保证参数中的字符串有’\0’,不然返回的值是不确定的 char arr[] = {'a', 'b', 'c'};
printf("%d\n", strlen(arr));
-
strlen函数返回值是size_t类型的无符号整数。因此以strlen(a)-strlen(b)为判断条件时,如果字符串a的长度小于b,计算结果不会是负值,而是一个极大的正值,需要注意
2.strcat
char* strcat(char* dest, const char* src);
-
功能是追加字符串。找到目标空间的’\0’,然后从’\0’处开始,逐字符替换为源字符串的内容,拷贝过程直到遇到源字符串的’\0’结束,返回目标字符串的首地址 char arr1[20] = "hello ";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
-
src代表源字符串首字符地址,dest代表目标字符串首字符地址(后面也一样) -
目标空间必须足够大,能容纳源字符串的内容 -
目标空间必须可修改 char* p = "abcdef";
char p[] = "abcdef";
-
不能自己给自己追加字符串 char* arr[] = "abc";
printf("%s\n", strcat(arr, arr));
原因在strcat的实现原理,如图:
拷贝过程中,arr的首字符a会先被拷贝到原来的\0处,这导致源字符串的内容发生改变。
当需要将\0拷贝到后面的内存块中时,\0的内容已被覆盖,而源字符串的拷贝工作要遇到\0才会停止,结果可想而知,字符串会无限地以"abcabc"的形式拷贝下去,陷入死循环。这也是为什么源字符串前面要用关键字const修饰,因为源字符串的内容发生改动会带来很大的风险
长度有限制的同类字符串函数:strncat
char* strncat(char* dest, const char* src, size_t n);
- 功能是在目标字符串末尾追加源字符串的前n个字符,并在后面补上空字符’\0’,返回目标字符串的首地址
- 如果n的值大于源字符串长度,也只拷贝到源字符串的第n个字符为止,不会追加多余内容
char arr1[20] = "hello ";
char arr2[] = "world";
strncat(arr1, arr2, 3);
printf("%s\n", arr1);
3.strcpy
char* strcpy(char* dest, const char* src);
-
功能是复制字符串。将源字符串的全部内容复制到目标字符串空间中,包括空字符’\0’ char arr1[20] = "aaa";
char arr2[] = "bbb";
strcpy(arr1, arr2);
printf("%s\n", arr1);
-
目标空间必须足够大,能容纳源字符串的内容 -
目标空间必须可修改
长度有限制的同类字符串函数:strncpy
char* strncpy(char* dest, const char* src, size_t n);
-
功能是从源字符串复制前n个字符到目标字符串空间中 char arr1[20] = "aaa";
char arr2[] = "bbb";
strncpy(arr1, arr2, 2);
printf("%s\n", arr1);
-
如果n的值大于源字符串长度,则在拷贝完源字符串所有内容后,在目标的后面追加0,直到n个
例:
char arr1[20] = "aaaaaaaaaaaaa";
char arr2[] = "bbb";
strncpy(arr1, arr2, 8);
printf("%s\n", arr1);
对于以上代码,arr1拷贝前的存储信息:
拷贝后:
4.strcmp
int strcmp(const char* str1, const char* str2);
计算机内的数据是以AscⅡ码的形式存储的,因此字符串比较实际是对一个一个字符的AscⅡ码进行比较,比如字符’b’的AscⅡ码98大于字符’a’的AscⅡ码97,因此’b’>‘a’
-
功能是比较两个字符串。从两个字符串首地址处逐字符比较,当该次比较的两个字符相同时继续比较两个字符串的下一个字符,当出现相异字符或其中一个字符串结束时比较结束 例:字符串str1"abcdef"和字符串str2"abqa"的比较 比较结果相同,指针p1和p2都往后移一个字符,继续比较下一个字符,直到遇到不同的字符或’\0’(字符串结束标志) c<q,因此str1<str2,比较结束 -
返回值:如果比较结果是str1(第一个参数)>str2(第二个参数),则strcmp函数会返回一个大于0的整型值;如果比较结果是str1<str2,则会返回一个小于0的整型值;如果str1=str2,即两个字符串完全相同,会返回0
(图片来源于MSDN)
char arr1[] = "abcdef";
char arr2[] = "abqa";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);
除了比较字符串,strcmp函数最常用的应用还是将返回值作为判断条件来判断两个字符串是否相等
长度有限制的同类字符串函数:strncmp
int strncmp(const char* str1, const char* str2, size_t n);
-
功能是比较两个字符串的前n个字符。和strcmp相似,比较到出现相异字符或其中一个字符串结束或n个字符全部比较完时结束 char arr1[] = "abcdef";
char arr2[] = "abqa";
int ret = strncmp(arr1, arr2, 2);
printf("%d\n", ret);
5.strstr
char* strstr(const char* str1, const char* str2)
char arr1[] = "abbbcd";
char arr2[] = "bbc";
char* ret = strstr(arr1, arr2);
printf("%s\n", ret);
6.strerror
char* strerror(int errnum);
这里要说到一个用于存放C语言全局错误码的变量——errno。当程序运出错时,会自动将对应的错误码存放在这个全局变量内,当程序运行出错我们又不知道原因时,就可以尝试将这个变量作为参数传递给strerror,让其打印我们所需要的错误信息
使用errno需要引用头文件<errno.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(INT_MAX);
if(p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
free(p);
p = NULL;
return 0;
}
7.strtok
char* strtok(char* str, char* sep);
-
功能是切割字符串。 -
str是需要切割的目标字符串的首字符地址。sep是一个字符串,包含了用作分隔符(字符串切割依据)的字符集合 -
目标字符串中包含了一个或多个sep中的分隔符标记。strtok函数会将目标字符串逐字符与sep中的存在的分隔符标记进行对比,每在目标字符串中找到一个分隔符标记就将其替换为’\0’。显然这个操作会改变目标字符串内容,因此在切割的目标字符串往往都是临时拷贝的一个与目标字符串相同且可修改的字符串 -
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置;strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记;如果字符串中不存在更多的标记,则返回 NULL 指针 人话是只有第一次切割时传的参数是目标字符串首元素地址和分隔符字符串,第二次及以后切割都只传NULL和分隔符字符串就行了,因为strtok函数会保存上一次切割时的分隔断点 可以用循环来巧妙限制切割次数: char arr[100] = "805975991@dabuding.com";
char sep[] = "@.";
char tmp[100] = { 0 };
strcpy(tmp, arr);
char* p = NULL;
for (p = strtok(tmp, sep); p != NULL; p = strtok(NULL, sep))
{
printf("%s\n", p);
}
切割结果
二、常用字符串函数的实现
提示:设计函数时善用const关键字和assert函数可以有效增强代码的健壮性
有写错的或更好的实现方法欢迎指出!|?・ω・` )Thanks?
1.strlen
size_t my_strlen1(const char* str)
{
assert(str);
int n = 0;
while (*str++)
{
n++;
}
return n;
}
size_t my_strlen2(const char* str)
{
assert(str);
if (*str == '\0')
return 0;
else
return (my_strlen2(++str) + 1);
}
2.strcat
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* p = dest;
while (*dest != '\0')
{
dest++;
}
while (*dest++ = *src++)
{
;
}
return p;
}
3.strncat
char* my_strncat(char* dest, const char* src, size_t n)
{
assert(dest && src);
char* p = dest;
while (*dest)
{
dest++;
}
while (n--)
{
if ((*dest++ = *src++) == '\0')
{
return p;
}
}
*dest = '\0';
return p;
}
4.strcpy
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* p = dest;
while (*src != '\0')
{
*dest++ = *src++;
}
*dest++ = '\0';
return p;
}
5.strncpy
char* my_strncpy(char* dest, const char* src, size_t n)
{
assert(dest && src);
char* p = dest;
while (n && (*dest++ = *src++) != '\0')
{
n--;
}
while (n--)
{
*dest++ = '\0';
}
return p;
}
6.strcmp
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
7.strncmp
int my_strncmp(const char* str1, const char* str2, size_t n)
{
assert(str1 && str2);
while (n-- && (*str1 != '\0' && *str2 != '\0'))
{
if ((*str1 - *str2) != 0)
return (*str1 - *str2);
else
{
str1++;
str2++;
}
}
return 0;
}
8.strstr
char* my_strstr(const char* dest, const char* src)
{
assert(dest && src);
const char* s1 = dest;
const char* s2 = src;
char* p = dest;
while (*p)
{
s1 = p;
s2 = src;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return p;
p++;
}
return NULL;
}
|