我们平常经常使用的字符串和字符函数有如下几个
求字符串长度
strlen
长度不受限制的字符串函数
strcpy
strcat
strcmp
长度受限制的字符串函数介绍
strncpy
strncat
strncmp
字符串查找
strstr
strtok
错误信息报告
strerror
字符操作
内存操作函数
memcpy
memmove
memset
memcmp
本文的目标就是力求理解这些函数,并尝试模拟实现它们
size_t strlen ( const char * str );
strlen的返回值是size_t
,这size_t
也就是unsigned int
模拟实现strlen可以用那些方法呢?
或许可以有以下几种方法
♠️计数器的方法
❤️递归的方法
♣️指针-指针
字符串已经\0
作为结束标志,strlen函数返回的是在字符串中\0
前面出现的字符个数(不包含\0
)。
参数指向的字符串必须要以\0
结束。
注意函数的返回值为size_t,是无符号的(易错)
int main()
{
if (strlen("abc") - strlen("abcdef") > 0)
printf(">");
else
printf("<=");
return 0;
}
按理说3-6<0,但是如上代码的输出是>,所以说其实size_t是一个无符号值
int my_strlen(const char* str)
{
assert(str);
int count = 0;
while (*str)
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
int my_strlen(const char *str)
{
assert(str);
if (*str=='\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
int main()
{
char arr[] = "abcd";
int ret = 0;
ret = my_strlen(arr);
printf("ret=%d\n", ret);
return 0;
}
int my_strlen(const char* p)
{
const char* ret = NULL;//指针位置可以改变,但是不可以改变指向的内容
assert(p);
ret = p;
while (*ret != '\0')
{
ret++;
}
return ret - p;
}
int main()
{
char arr[] = "abcdef";
int ret = 0;
ret = my_strlen(arr);
printf("ret=%d\n", ret);
return 0;
}
char* strcpy(char * destination, const char * source );
在认识strcpy之前,首先认识一下长度受限函数和长度不受限函数
对于strcpy来说,由于长度受限,strcpy受限于和被拷贝字符串的\0
作为终止符,假如传入的被拷贝字符串没有\0
的话,就会导致问题发生,比如出现乱码
int main()
{
char arr1[] = {'a', 'b', 'c', 'd', 'e', 'f'};
char arr2[20] = "xxxxxxxxxxxx";
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
所以要使用好该函数,必须了解他的注意事项
Copies the C string pointed by source into the array pointed by destination,including the terminating null character (and stopping at that point).
源字符串必须以’\0’ 结束。
会将源字符串中的’\0’ 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串。
目标空间必须可变。
学会模拟实现。
int main()
{
char arr1[] = {'a', 'b', 'c', 'd', 'e', 'f','\0'};
char arr2[3] = " ";
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
空间不够大就会报错
int main()
{
char arr1[] = {'a', 'b', 'c', 'd', 'e', 'f','\0'};
const char* p = "xxxxxxxxxx";
strcpy(p, arr1);
printf("%s\n",p);
return 0;
}
强行要改是没法改的
参照库函数参数要求模拟实现
注意要实现链式访问效果
char* my_strcpy( char* dest, const char* src)
{
char* ret = dest;
assert(src && dest);
while (*dest++ = *src++)
{//*src =\0 时dest也改变为\0同时跳出
;
}
return ret;
}
实现的效果是字符串追加到目标中
char * strcat ( char * destination, const char * source );
Appends a copy of the source string to the destination string. The terminating null character
in destination is overwritten by the first character of source, and a null-character is included
at the end of the new string formed by the concatenation of both in destination.
源字符串必须以’\0’ 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。
但是字符串自己给自己追加,如何?
这个原因和之前的一样
也和之前一样,被追加字符串空间要足够
空间还是要可以修改才可以追加
但是可以先保存一份在临时变量中,再拼接
char *my_strcat(char* dest, const char* src)
{
assert(dest&&src);
char* ret = dest;
//1. 找 目标空间中的'\0'
while (*dest)
{
dest++;
}
//2. 追加内容到目标空间
while (*dest++ = *src++)
{
;
}
return ret;
}
比较的是对应位置上字符的大小
int strcmp ( const char * str1, const char * str2 );
This function starts comparing the first character of each string. If they are equal to each
other, it continues with the following pairs until the characters differ or until a terminating
null-character is reached.
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
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;
}
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.
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
char * __cdecl strncpy (
char * dest,
const char * source,
size_t count
)
{
char *start = dest;
while (count && (*dest++ = *source++) != '\0') /* copy string */
count--;
if (count) /* pad out with zeroes */
while (--count)
*dest++ = '\0';
return(start);
}
char * strncpy ( 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
terminating null-character is copied.
char * __cdecl strncat (
char * front,
const char * back,
size_t count
)
{
char *start = front;
while (*front++)
;
front--;
while (count--)
if ((*front++ = *back++) == 0)
return(start);
*front = '\0';
return(start);
}
int strncmp ( const char * str1, const char * str2, size_t num );
:比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
int __cdecl strncmp
(
const char *first,
const char *last,
size_t count
)
{
size_t x = 0;
if (!count)
{
return 0;
}
/*
* This explicit guard needed to deal correctly with boundary
* cases: strings shorter than 4 bytes and strings longer than
* UINT_MAX-4 bytes .
*/
if( count >= 4 )
{
/* unroll by four */
for (; x < count-4; x+=4)
{
first+=4;
last +=4;
if (*(first-4) == 0 || *(first-4) != *(last-4))
{
return(*(unsigned char *)(first-4) - *(unsigned char *)(last-4));
}
if (*(first-3) == 0 || *(first-3) != *(last-3))
{
return(*(unsigned char *)(first-3) - *(unsigned char *)(last-3));
}
if (*(first-2) == 0 || *(first-2) != *(last-2))
{
return(*(unsigned char *)(first-2) - *(unsigned char *)(last-2));
}
if (*(first-1) == 0 || *(first-1) != *(last-1))
{
return(*(unsigned char *)(first-1) - *(unsigned char *)(last-1));
}
}
}
/* residual loop */
for (; x < count; x++)
{
if (*first == 0 || *first != *last)
{
return(*(unsigned char *)first - *(unsigned char *)last);
}
first+=1;
last+=1;
}
return 0;
}
char * strstr ( const char *str2, const char * str1);
演示使用strstr
若找到返回值是传找到的地址
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbcd";
char* ret = strstr(arr1, arr2);
if (NULL == ret)
printf("没找到\n");
else
printf("%s\n", ret);
return 0;
}
Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.
先讨论几种情况
找的字符串不可以是空字符串
if (*substr == '\0')
{
return (char*)str;
}
当指针指向的字符是相等的时候substr指针往后走,str也往后走,如果sub走到空说明匹配完了,能找到
但是可能还会有特殊情况
特殊情况
上面的方法会使得bbb阻断bbc的判断
所以应该多两个指针,最好让str和substr不动,新指针向后走,同时也要创一个cur指针,默认指向源字符串的首地址,它的作用是表示判断的起始位置
const char* s1 = str;
const char* s2 = substr;
const char* cur = str;
然后实现循环
什么时候终止循环?
s2到
\0
或s1到\0
都要终止while (*s1 && *s2 && *s1 == *s2)
假如s2能走完那最好直接返回,说明找到
if (*s2 == '\0') return (char*)cur;
char* my_strstr(const char* str, const char* substr)
{
const char* s1 = str;
const char* s2 = substr;
const char* cur = str;
assert(str && substr);
if (*substr == '\0')
{
return (char*)str;
}
while (*cur)
{
s1 = cur;
s2 = substr;
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return (char*)cur;
cur++;
}
return NULL;
}
char * strtok ( char * str, const char * sep );
sep参数是个字符串,定义了用作分隔符的字符集合
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
strtok函数找到str中的下一个标记,并将其用\0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
strtok函数的第一个参数不为NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回NULL 指针。
也就是说,strtok找第一个标记的时候,第一个不是NULL,strtok找不是第一个标记的时候,第一个标记是NULL
看一个
int main()
{
const char* p = "@.#";
char arr[] = "[email protected]#";
char buf[50] = { 0 };
strcpy(buf, arr);
char* str = strtok(buf, p);//Danerious
printf("%s\n", str);
str = strtok(NULL, p);//Targerian
printf("%s\n", str);
str = strtok(NULL, p);//123
printf("%s\n", str);
//strtok - 开始返回NULL
return 0;
}
为了防止我原来的字符串被改变,我想存在一个新的数组中,创建一份拷贝值
防止每次切割的时候都要重复调用一次,利用for循环
int main()
{
const char* p = "@.#";
char arr[] = "[email protected]#jkewfjw.@#";
char buf[50] = { 0 };
strcpy(buf, arr);
char* str = NULL;
for (str = strtok(buf, p); str != NULL; str=strtok(NULL, p))
{
printf("%s\n", str);
}
return 0;
}
根据这样的特性我们猜测,strtok背后是由以一个静态变量来保存的,因此可以实现剪切
char * strerror ( int errnum );
返回错误码,所对应的错误信息。
通过这段代码可以看到strerroer对应不同数字的报错形式,一一对应
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%s\n", strerror(i));
}
return 0;
}
当库函数使用的时候,发生错误会把errno这个全局的错误变量设置为本次执行库函数产生的错误码
errno是C语言提供的一个全局变量,可以直接使用,放在errno.h文件中的
#include
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
//出错误的原因是什么
printf("%s\n", strerror(errno));
return 0;
}
//读文件
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
函数 | 如果他的参数符合下列条件就返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’ |
isdigit | 十进制数字 0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母af,大写字母AF |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 字母az或AZ |
isalnum | 字母或者数字,az,AZ,0~9 |
ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
isspace
#include
int main()
{
char ch = 'w';
if (isspace(ch))
{
//空白字符
}
else
{
}
return 0;
}
isupper
int main()
{
char ch = 0;
ch = getchar();
if (islower(ch))
{
ch = toupper(ch);
}
else
{
ch = tolower(ch);
}
printf("%c\n", ch);
return 0;
}
void * memcpy ( void * destination, const void * source, size_t num );
memcpy可以解决更多拷贝问题,比如说原来是strcpy只能拷贝char[],但是现在memcpy可以拷贝int[]
由于设计者不知道memcpy会传入声明类型的指针,所以干脆采用void*的指针作为类型
int main()
{
int arr1[] = { 1,2,7,4,5 };
int arr2[] = { 1,2,3,4,5 };
int ret = memcpy(arr1, arr2, 9);
printf("%d\n", ret);
return 0;
}
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到’\0’ 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的。
void* my_memcpy(void* dest, const void*src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
但是如果当我们用下面的测试用例来尝试的话,会得到不正确的结果
关键在于,传入的参数部分是重叠的,所以可能产生覆盖
void test1()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
}
int main()
{
test1();
return 0;
}
其实memcpy的任务只要能复制不重复的字符串就可以了,实现重复拷贝可以使用另外一个函数
于是就引出了另一个函数memmove
void * memmove ( void * destination, const void * source, size_t num );
void test1()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr3, arr3+2, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
}
int main()
{
test1();
return 0;
}
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。
分两种情况就可以了
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
if (dest < src){
//前->后
while (num--){
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}else{
//后->前
while (num--){
*((char*)dest+num) = *((char*)src + num);
}
}
return ret;
}
void *memset(void *dest,int c ,size_t count);
memset修改的时候是按照字节来修改的
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memset(arr, 'x', 10);
return 0;
}
int memcmp ( const void * ptr1,
const void * ptr2,
size_t num );
比较从ptr1和ptr2指针开始的num个字节
int main()
{
int arr1[] = { 1,2,7,4,5 };
int arr2[] = { 1,2,3,4,5 };
int ret = memcmp(arr1, arr2, 9);
printf("%d\n", ret);
return 0;
}
小结:
关于字符串函数和字符内存相关函数就学到这里
老铁们有收获的话一定要给个赞,多多评论哦