字符函数和字符串函数介绍及解析
常见字符串的库函数的使用和注意事项
求字符串长度
strlen函数
我们都知道C语言没有字符串类型,举个例子:char arr[ ]=" abcdef " ; arr作为数组名会被传递到strlen()函数中来计算字符串长度。了解一下strlen函数的基本信息:
strlen函数的函数声明
strlen函数的参数和返回类型
但这只是一个普通的字符串,abcdef后面会有‘\0’,但是如果没有‘\0’呢?
#include<stdio.h>
//strlen函数的实现
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
int ret = strlen(arr);
printf("%d\n", ret);
return 0;
}
如上代码运行后发现,程序最终会打印一个不是我们意料中的数,这是因为strlen函数以 \0 作为结束标志,strlen函数返回的是 \0 前面字符出现的个数(不包含 \0 )这里注意两个点:
第二点是使用者尤其会忽略的一个地方,这里再次提醒。
模拟实现strlen函数:
//模拟实现strlen
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char* str)
{
int count = 0;
assert(str != NULL);
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
return 0;
}
字符串拷贝函数strcpy
#include<stdio.h>
int main()
{
char arr1[20]={0};
char arr2[]="abcdef";
strcpy(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
参考如上代码可以知道strcpy函数的基本实现,就是把arr2的内容拷贝到arr1中。但是被拷贝的内容中要有 ‘\0’ , strcpy会把arr2中‘ \0 ’之前的所有内容全部拷贝到arr1中,同时将 ‘ \0 ’也进行拷贝。 如果arr2中没有‘ \0 ’,就可能会导致程序崩溃。strcpy只会寻找‘ \0 ’,如果没有找到,就会一直向后寻找,最终导致访问越界。但是看下面这个例子:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[10] = { 'a','b','c' };
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
运行一下会发现没有任何问题,这是因为arr2数组被初始化了,虽然只存放了三个元素,但是剩余的空间被初始化成了‘ \0 ’。因此strcpy函数还是可以正常使用的。
目标空间必须足够大,以便确保字符串能存放到目标空间中。
此时就引出了strcpy函数的一个缺陷:strcpy函数不会考虑拷贝会不会导致程序崩溃,只会一股脑进行拷贝,因此此时程序员不能依靠函数本身来进行纠错,此时进行代码编写的时候要格外注意。
目标空间必须是可变的
这样的代码时错误的,在运行的时候会报错。
strcpy的模拟实现
//strcpy函数
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* str1, const char* str2)
{
assert(str1 && str2);
char a = 0;
char* ret = str1;
while (*str1++ = *str2++)
{
;
}
*str1 = *str2;//拷贝'\0'
return ret;
}
int main()
{
char arr1[20] = {0};
char arr2[20] = "hello,bit";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
字符串追加函数strcat
基本了解一下strcat的作用:
int main()
{
char arr1[20] = "hello";
char arr2[] = " bit";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
strcat函数在使用的时候,原字符串必须要有‘ \0 ’,要以‘ \0 ’作为结束标志,目标字符串中也要有‘ \0 ’,从‘ \0 ’向后追加。
目标空间必须足够大,能容纳得下源字符串的内容。
目标空间必须可以修改
模拟实现strcat
//strcat函数
#include<stdio.h>
#include<assert.h>
my_strcat(char* str1, const char* str2)
{
assert(str1 && str2);
char* ret = str1;
while (*str1 != '\0')
{
str1++;
}
/*while (*str2 != '\0')
{
*str1 = *str2;
str1++;
str2++;
}
*str1 = '\0';*/
while (*str1++ = *str2++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello";
char arr2[] = " bit";
//strcat(arr1, arr2);
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
字符串比较函数strcmp
该函数返回int类型
第一个字符串大于第二个字符串,返回大于0的数字
第一个字符串等于第二个字符串,返回0
第一个字符串小于第二字符串,返回小于0的数字
注意,strcmp比较的不是字符串的长度,比较的方式是对每个元素的ASCII码值进行比较,比如上面的例子中,arr1中的‘ a ’与arr2中的 ‘ a ’相同,一直到arr1中的‘c’与‘q’进行比较,后者的ASCII码值比前者的ASCII码值大,因此输出-1.strcmp 的比较会一直进行到不同或者都遇到‘ \0 ’。
模拟实现strcmp
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 != '\0' && *str2 != '\0'&&*str1==*str2)
{
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
以上所有的函数都是长度不受限制的字符串函数
长度受限制的字符串函数
strncpy函数
程序的运行基本演示了函数的使用方法和作用:
size_t num表示拷贝字符串的个数,这么做程序就看起来安全了,但这只是相对安全,毕竟你阻止不了一个想写bug的程序员。
看一下下面这个特殊情况:
#include<stdio.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "qwd";
strncpy(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}
如果代码是这样,arr2只有四个字符,但是却要求操作五个字符,当超出操作字符的数量时,会用‘ \0 ’补齐,直到num个。
strncat函数
代码实现如下:
strncat会从‘ \0 ’处进行追加,但是不同于上面的情况,当num越界时:
#include<stdio.h>
int main()
{
char arr1[20] = "abcde\0xxxxxx";
char arr2[] = "qwe";
strncat(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}
经过调试发现,并没有多余的‘ \0 ’。
strncmp函数
函数参数中的size_t num表示的是比较几个元素。
//strncmp函数
#include<stdio.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcettyu";
int ret=strncmp(arr1, arr2,4);
printf("%d\n", ret);
return 0;
}
strstr函数
函数的作用是看一个字符串是不是另一个字符串的子串,也就是str2是不是在str1中,如果在返回字符串第一次出现的位置(地址)没有出现,就会返回空指针。 下面来看代码:
#include<stdio.h>
int main()
{
char arr1[] = "abcdeafgcdefxxxx";
char arr2[] = "cdef";
char*p=strstr(arr1, arr2);
if (p == NULL)
{
printf("找不到子串\n");
}
else
{
printf("%s\n", p);
}
return 0;
}
模拟实现strstr
//strstr函数
#include<stdio.h>
char* my_strstr(char* str1, char* str2)
{
char* s1 = str1;
char* s2 = str2;
char* cur = str1;
while (*cur)
{
s1 = cur;
s2 = str2;
while (*s1 == *s2&&*s1&&*s2)
{
s1++;
s2++;
}
if (*s2 == '\0');
{
printf("找到了\n");
return cur;
}
cur++;
}
return NULL;
}
int main()
{
char arr1[] = "abcdeafgcdefxxxx";
char arr2[] = "cdef";
char*p=my_strstr(arr1, arr2);
if (p == NULL)
{
printf("找不到子串\n");
}
else
{
printf("%s\n", p);
}
return 0;
}
strtok函数
sep参数是个字符串,定义了用作分隔符的字符集合。
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。也就是说,str指向的字符串是sep中的字符分开的。
strtok函数要找到str中的下一个标记,并将其用‘ \0 ’结尾,返回一个指向这个标记的指针。strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般是临时拷贝的内容并且可修改
strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为UNLL,函数将在同一个字符串中被保存的位置开始,查护下一个标记。
如果字符串中不存在更多的标记,则返回NULL指针。
好像有些不好懂,我们来看一下代码:
//strtok函数
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "adffvgb@year.net";
char buf[30] = { 0 };
strcpy(buf,arr);
//strtok函数
//"@."定义为一个字符串
const char* str = "@.";
char* pp = NULL;
//printf("%s\n",strtok(buf, str));//只找第一个标记
//printf("%s\n", strtok(NULL, str));//从保存好的位置开始继续往后查找
//printf("%s\n", strtok(NULL, str));
for (pp = strtok(buf, str); pp != NULL; pp=strtok(NULL, str))
{
printf("%s\n",pp);
}
return 0;
}
这里注意,只要含有str内容中的字符,就会进行分割。
strerror函数
全局变量errno(错误码)–>一旦某一个库函数调用失败,就会返回一个错误码,将错误码放进errno中
返回值为错误码对应错误信息的指针。
strerror函数和errno的使用(malloc函数是在堆区开辟空间)
字符转换函数
int tolower ( int c ) ; 函数实现大写转换小写
int toupper ( int c ) ; 函数实现小写转换大写
内存操作函数
memcpy函数
参数类型之说以是void*是因为作者在实现memcpy的时候,并不知道使用者会传递一个什么样的参数进去(与qsort函数类似)
代码基本包含了函数的作用和使用方法,其中count的意思是拷贝的字节数,是一个无符号整型数据。
模拟memcpy函数
void* my_memcpy(void* str1, const void* str2,size_t count)
{
assert(str1 && str2);
while (count--)
{
*((char*)str1) = *((char*)str2);
str1 = (char*)str1 + 1;
str2 = (char*)str2 + 1;
}
}
但是还是会有一些意外:
这个程序,我们预期的结果是1 2 1 2 3 4 5 8 9 10,但实际上结果是1 2 1 2 1 2 1 8 9 10,这是因为程序改变了源数组中的内容,数组内部的拷贝发生了重叠,这时就引出了另外一种函数:memmove函数
memmove函数
memmove函数可以实现重叠内存的拷贝
但是如果模拟这个函数,还有很多细节要注意:
以上的这两种拷贝字符串如果使用同一种拷贝顺序,必然会导致拷贝出现问题,综上,memmove函数的模拟实现要分情况。
src是源数组首元素的地址,dest是目标数组的首元素的地址
如果src的地址比dest大,就从前往后进行拷贝。
如果src的地址dest小,但是二者仍然重叠,就从后往前进行拷贝。
dest继续向后二者不在重叠的时候,可以从后向前拷贝,也可以从前向后拷贝。
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* str1, const void* str2,size_t count)
{
assert(str1 && str2);
while (count--)
{
*((char*)str1) = *((char*)str2);
str1 = (char*)str1 + 1;
str2 = (char*)str2 + 1;
}
}
void*my_memmove(void* str1, const void* str2,size_t count)
{
if (str1 < str2&&str1<(char*)str2+count)
{
//前->后
assert(str1 && str2);
while (count--)
{
*(char*)str1 = *(char*)str2;
str1 = (char*)str1 + 1;
str2 = (char*)str2 + 1;
}
}
else if(str1>str2)
{
//后—>前
assert(str1 && str2);
while (count--)
{
*((char*)str1 + count) = *((char*)str2 + count);
}
}
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[5] = { 0 };
//my_memcpy(arr2, arr1, 20);
memmove(arr1 + 2, arr1, 20);//实现重叠内存的拷贝
return 0;
}
memcmp函数
对于函数的返回值
函数的比较是一个字节一个字节进行比较的。
memset函数—内存设置
dest是数组地址,c是改变的内容,count是改变的字节数,修改的是每个字节,而不是某个字节
函数的意思就是把内存中的每个字节都改成6。
内容有点多,大家慢慢学习,希望大家收获知识。心之所向,素履以往!