C语言中有很多库函数,其中我们有必须知道一些函数的使用,其中比较典型是字符串函数,内存函数,和动态内存函数。这篇文章我们就来了解字符串函数。使用字符串函数要引用string.h头文件。
目录
字符串函数
1.strlen函数
这是我们最熟悉的一个求字符串长度的函数。我们先看C语言里面它的标准声明。 C定义size_t是无符号整数(unsigned int),我们会找字符串结束的标志'\0',之后返回无符号整数。我们来模拟实现这个函数。
//size_t == unsigned int
int my_strlen(const char* str)
{//const修饰指针里面的变量,指针指向的内容不能通过解引用改变
int count = 0;
assert(str != NULL);
while (*str != 0)//==while(*str)
{
count++;
str++;
}
return count;
}
int main()
{
//int len = my_strlen("abcdef");
//printf("%d\n", len);
// 3 - 6=-3(无符号数运算结果还是无符号数)
if (strlen("abc") - strlen("abcdef") > 0)
{//strlen返回类型是unsignde int
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
}
这里打印的是“hehe”因为无符号运算无论如何结果都是大于0的,所以打印hehe.
我们还可以通过递归的方法来实现:
int my_strlen(const char* str)
{
if (*str == '\0')
{
return 0;
}
else
{
return 1 + my_strlen(str + 1);
}
}
int main()
{
//递归的方式实现 strlen
char arr[] = "abcd";
size_t len = my_strlen(arr);
printf("len = %zd\n", len);
return 0;
}
当读到最后一个字符‘\0’时返回0。这里不再做过多解释。
2.strcpy函数
拷贝字符串函数,先传入要改变的字符串,再传入要拷贝的内容。返回的值是改变字符串的首元素地址。一定牢记,字符串结束的标志是'\0'。所以只需要将复制的字符串拷贝到'\0'即可,后面即使没有变也不需要读取。
我们先来看标准定义:
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello";
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
目标空间必须确保足够大,能放的下拷贝的字符串。但是有一种情况,就是注意指针可能指向常量字符串,这样空间是不能被修改的。
接下来我们模拟实现strcpy.
char* my_strcpy(char* dest,const char* src)
{//使src是常量字符串
assert(dest != NULL);
assert(src != NULL);
char* ret = dest;
//ret指向dest的首位置
//while (*src)
//{
// *dest++ = *src++;
//}
//*dest = *src;
while (*dest++ = *src++)
{//最后直接把'\0'赋给dest
;
}
//返回目的空间的起始地址
return ret;
}
int main()
{
//错误示范
//char*p="abcdef"是常量字符串内容不可改
//arr1[]="ab"会崩
char arr1[] = "abcdefghi";
char arr2[] = "bit";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
3.strcat函数
追加字符串函数,先传入在前面的字符串,再传入在后面的字符串,返回的是最前面元素的首地址。 一样的,也是要确保目标字符串内存够大。我们先来看看目标空间不足的情况:
int main()
{
char a1[] = "hello";
char a2[] = "world";
strcat(a1, a2);
printf("%s\n", a1);
return 0;
}
所以空间一定要够。
int main()
{
char a1[30] = "hello\0xxxxxxx";
char a2[] = "world";
strcat(a1, a2);
printf("%s\n", a1);
return 0;
}
使用库函数strcat不要给自己追加,否则会崩溃。
那这个情况该如何解决?放心,C语言考虑的很全面,会有办法,我们一会再讲。
我们再来模拟实现一下:
char*my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
//1.找到目的字符串的'\0'
while (*dest != '\0')
{
dest++;
}
//2.追加
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char a1[30] = "hello";
char a2[] = "world";
my_strcat(a1, a2);
printf("%s\n", a1);
return 0;
}
先在原字符串找到‘\0’,之后令它们++相等。
4.strcmp函数
比较字符串函数,从第个开始比较,之后比较下一个,一个一个的比较,直到比较完,如果都相同就返回0;前者>后者返回1(不同编译器返回值不同,可能是两个数的ASCII值的差);前者<后者返回-1(可能是两个值的ASCII值的差)。
int main()
{
//VS 2020
//> 1
//== 0
//< -1
//linux-gcc
//> >0
//== 0
//< <0
char* p1 = "abcdef";
char* p2 = "sqwer";
//strcmp比较的不是不是字符串长度
//一对一对往后比较,不一样的话直接返回
//int ret = strcmp(p1, p2);
if (strcmp(p1, p2) >0)
{
printf("p1>p2\n");
}
else if (strcmp(p1, p2) == 0)
{
printf("p1=p2\n");
}
else if (strcmp(p1, p2) <0)
{
printf("p1<p2\n");
}
//printf("%d\n", ret);
return 0;
}
我们依旧来模拟实现一下:
int my_strcmp(const char* s1, const char* s2)
{
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
}
int main()
{
int ret = my_strcmp("bbq", "abcdef");
if (ret > 0)
{
printf("大于\n");
}
else if (ret == 0)
{
printf("等于\n");
}
else
{
printf("小于\n");
}
return 0;
}
这里是几个重要的字符串函数,我们要学会它们的基本用法。
strcpy、strcat、strcmp长度不受限制的字符串函数。那么刚才strcat无法追加自己,我们总要解决,是不是加上指定追加的长度就可以完成?所以库函数又提供了一些长度受限的字符串函数。
strncpy、strncat、strncmp长度受限制的字符串函数。
5.strncat函数
传入在前的字符串,之后传入在后面的字符串,在之后传入要追加的元素个数,返回的是最前面元素的地址。
int main()
{
char arr1[30] = "hello";
char arr2[] = "world";
strncat(arr1, arr2, 8);
//最后拷贝'\0';即使追加个数大于的原字符串长度
//就把最后的元素追加上去,之后补一个'\0'
printf("%s\n", arr1);
return 0;
}
即使传入的整数>要拷贝的元素个数,最后会用‘\0’来代替。
我们来模拟实现strncat。
void my_strncat(char* dest, const char* str, int sz)
{
assert(str);
char* cur = dest;
while (*cur)
{
cur++;
}
int i = 0;
for (i = 0; i < sz; i++)
{
*cur++ = *dest++;
}
}
int main()
{
char arr1[20] = "abcd";
strncat(arr1, arr1, sizeof(char) * 7);
printf("%s\n", arr1);
return 0;
}
6.strncpy函数
和strcpy函数很像,多传入一个要拷贝字符的数量。返回的类型是整数,和strcpy返回的规律是一样的。
int main()
{
char arr1[20] = "xxxxxxxxx";
char arr2[] = "hello";
strncpy(arr1, arr2, 7);
printf("%s\n", arr1);
return 0;
}
我们来模拟一下:
void my_strncpy(char* dest, const char* str, size_t sz)
{
int i = 0;
char* cur = str;
for (i = 0; i < sz; i++)
{
if (i < strlen(cur))
{
*dest++ = *str++;
}
else
{
*dest++ = '\0';
}
}
}
int main()
{
char arr1[20] = "xxxxxxxxxxx";
char arr2[] = "abcd";
my_strncpy(arr1, arr2, sizeof(char) * 6);
printf("%s\n", arr1);
return 0;
}
7.strncmp函数
和strcmp函数很像。返回的类型是整数,和strcmp返回的规律是一样的。
int main()
{
//strncmp - 字符串比较
const char* p1 = "abcdef";//p1指向内容不能修改
const char* p2 = "abcqwer";
//int ret = strcmp(p1, p2);
int ret = strncmp(p1, p2, 3);//比较前n个字符串
printf("%d\n", ret);
return 0;
}
8.strstr函数
判断字符串(子串)是否为另一个字符串(父串)的子串。返回类型为指针,如果不是返回NULL(空指针)。可以理解为字符串匹配函数。
int main()
{
//strstr查找子字符串函数
//char* strstr(const char* string,const char* strCharSet);
//NULL -- 空指针
//NUL/Null - '\0'
//string
//Null-terminated string to search 从'\0'开始查找原字符串
//strCharSet
//Null-terminated string to search for 要查找的字符串
char* p1 = "abcdefghi";
char* p2 = "def";
char* ret = strstr(p1, p2);
//如果没找到就返回空指针
//如果找到了返回的是源字符串中找到的比较字符串中首元素的地址
if (ret == NULL)
{
printf("子串不存在\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
我们来模拟实现strstr函数,通过暴力求解法BF算法。
//模拟实现strstr
char* my_strstr(const char* p1, const char* p2)
{
assert(p1 && p2);
char* s1 = p1;
char* s2 = p2;
char* cur = p1;
if (*p2 == '\0')
{//如果p2是空字符串
return p1;
}
while (*cur)
{
s1 = cur;//如果碰到两个相同,第三个不同,则下次到第二个相同的开始
//如:abcdddef
// ddef
s2 = p2;//比较的字符串每次从头开始
while ((*s1 != '\0')&&(*s2 != '\0')&&(*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cur;//找到子串
}
cur++;
}
return NULL;//找不到子串
}
int main()
{
char* p1 = "abcdddefg";
char* p2 = "ddef";
char* ret = my_strstr(p1, p2);
//如果没找到就返回空指针
//如果找到了返回的是源字符串中找到的比较字符串中首元素的地址
if (ret == NULL)
{
printf("子串不存在\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
因为指向主串的指针不会回溯,但是指向要匹配的字符串(子串)如果不匹配就要回溯。所以我们多定义两个变量来记录原位置。
这是最暴力的解决方式,当然有一种高效的KMP算法,以后我会发布博客,大家下去也可以探索探索。
9.strtok函数
这个函数很有意思,你可以理解为字符串分割函数。相当于分隔字符串函数,要传入原字符串,之后在创建一个字符串记录原字符串的分隔符,调用一次返回的是原字符串首元素的地址,这个函数会把原字符串中的分隔符改为‘\0’,所以为了不让它修改原字符串,我们先复制一份,在修改。第一次调用后会把分隔符改为‘\0’,下一次要传入一个空指针(NULL),返回的是空指针后面元素的地址。
int main()
{
//192.168.31.121
//192 168 31 121 - strtok
//char* strtok(char* str,char* sep)
//sep参数是个字符串,定义了用作分隔符的字符集合
char arr[] = "zpw@bitedu.tech";
char* p = "@.";
//strtok(arr,p);
char buf[200] = { 0 };
strcpy(buf, arr);
char*ret= strtok(arr, p);
printf("%s\n", ret);
ret= strtok(NULL, p);
printf("%s\n", ret);
ret= strtok(NULL, p);
printf("%s\n", ret);
return 0;
}
strtok函数找到str中的下一个标记,并将其用'\0'结尾,返回一个指向这个标记的指针(就是返回前面的地址)。
上面这个函数写的很多余,很复杂。strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。也就是说,平常我们会先拷贝源字符串,之后对拷贝字符串进行修改。
这里我们没有看其内部的细节,也没有模拟实现(真的懒了),但我们大致可以猜到它里面可能使用了static修饰变量使其生命周期延长。感兴趣的同学可以下去看看源码里面是如何定义的。
int main()
{
char arr[] = "zpw@bitedu.tech";
char* p = "@.";
char tmp[20] = { 0 };
strcpy(tmp, arr);
//zpw\0bitedu\0tech\0
char* ret = NULL;
for (ret = strtok(tmp, p); ret != NULL; ret = strtok(NULL, p))
{
printf("%s\n", ret);
}
return 0;
}
10.strerror函数
这个函数会自动检查你是否使用错了别的函数,如果你使用函数错了,它会自动传一个数值进入这个函数(前提是你要调用它)。要引入errno.h的头文件。
errno是一个全局错误码的变量,如果库函数执行错误,就会把对应错误码赋值到errno中,默认0就是没有错误。
int main()
{
//C语言库函数
// 错误码 错误信息
//传0 - No error
//传1 - Operation not permitted
//传2 - No such file or directory
//...
//errno 是一个全局的错误码的变量
//当C语言的库函数在执行过程中发生了错误,就会把对应的错误码赋值到errno中
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d: %s\n", i, strerror(i));
}
char* str = strerror(errno);//要引入errno.h的头文件
return 0;
}
perror就是printf + strerror函数。
11.其他字符传函数
islower判断字符是否为小写。
isdigit判断字符是否为数字。
tolower大写字符转小写字符,若还是小写字符,则就是小写字符。
toupper小写字符转大写字符若,还是大写字符,则就是大写字符。
int main()
{
char ch = 'w';
//int ret=islower(ch);//判断是不是小写字母
int ret = isdigit(ch);//判断是不是数字
char c= tolower('q');//大写转小写字母,如果本身就是小写字母就不动
char b= toupper('Q');//小写转大写字母
char arr[] = "I Am A Student";
//大写字母转小写字母
int i = 0;
while (arr[i])
{
if (isupper(arr[i]))
{
arr[i]=tolower(arr[i]);
}
i++;
}
printf("%s\n", arr);
return 0;
}
还有很多关于字符串的函数我们不可能一一去详细举例,我们也没有必要全部记住,我们需要的时候查找一下就可以。如果感觉写的还不错,请点点赞吧。