用c语言编程时,我们可以自定义函数,还可以使用c数据库中提供的各式各样的函数,本期介绍c语言中的字符函数和内存函数
字符串函数:
字符串函数和内存函数都包含在 <string.h>头文件当中
#include <string.h>
strlen
我们常常为了完成某项特定任务或解决某项特定问题时,自定义函数来解决问题,此时,函数名就代表了一切,
含义:
strlen前部分是string的缩写后部分是lenth的缩写,所以此函数就是用来求字符串长度的;
用法:
通过查阅cplusplus.https://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen就可以看到关于strlen函数的详解;
我们看到该函数的返回值为size_t,函数参数为const char* ,并且求得的是'\0'之前字符串的长度
知道了函数的参数和返回值,我们就可以调用此函数
char str[] = "abcdef";
ret = strlen(str);
str中存放的是:a b c d e f \0
\0之前有6个字符所以打印出的结果也是6;
strlen模拟实现
计数器方式:
int my_strlen(char* str)
{
int len = 0;
while (*str != '\0')
{
str++;
len++;
}
return len;
}//计数器
计数器的方式非常简单,遍历字符串的每一个字符,直到遇到\0时停止;
递归(不创建临时变量):
int my_strlen2(char* str)
{
if (*str != '\0')
{
return my_strlen2(str + 1) + 1;
}
else
return 0;
}//递归(不创建临时变量)
和计数器的解题思路差不多,就是遍历字符串的内容,不是\0就+1继续遍历下一个字符
指针-指针:
int my_strlen3(char* str)
{
char* p = str;
while (*p != '\0')
{
p++;
}
return p - str;
}//指针-指针
指针±整数 = 指针;那么指针±指针= 整数;
让指针一直++到\0之前,然后再用当前指向\0之前的指针减去指向首元素的指针,就可以求出两指针之间的元素个数;
strcpy
字符串拷贝函数;
用法:
我们了解到strcpy函数的返回参数是char*,函数参数是char* const char*;
返回的是目的地函数的首地址,既然是拷贝,那么只改变目的地所指向的内存空间的内容就可以了,拷贝源头的字符串内容不会改变,
strcpy函数会将源头字符串的所有内容都拷贝到目的地中,包括\0;
注:目标字符串必须拥有足够大的空间来存放拷贝内容
char str1[] = "abcdef";
char str2[20] = { 0 };
printf("%s\n", strcpy(str2, str1));
strcpy模拟实现
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest++ = *src++)
{
;
}
return ret;
}
可能while循环中的内容有点不好理解;就是每次先对指针解引用,赋值,然后++;并且判断条件就是括号内表达式的内容;当遇到\0时,也是会先赋值,再停下并且++,
strcat
字符串拼接
用法:
我们了解到,该函数的返回参数是char*,函数参数是char* , const char*;
函数同样返回目标字符串的首地址,strcat函数会先找出目标函数的\0,再从\0开始把源头的内容拼接在目标字符串后面包括\0,
注意:1.目标字符串必须含有\0,没有\0的话拼接动作不知道从哪里开始;
2. 源字符串中也必须含有\0,不然拼接动作不知道什么时候结束;
3. 目标字符串必须拥有足够大的空间;
char str1[] = "world!";
char str2[20] = "hello ";
printf("%s\n", strcat(str2, str1));
strcat模拟实现:
char* my_strcat(char* dest,const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest != '\0')
{
dest++;
}
while (*dest++ = *src++)
{
;
}
return ret;
}
strcmp
字符串比较函数
用法:
了解到函数的返回参数是int,函数参数是const char*,const char*;
strcpy函数会比较两个字符串相同位置字符的ASCII码值,
大于返回大于0的值;
等于返回0;
小于返回小于0的值;
注:strcmp的比较机制并不是比较字符串的长度,而是比较相同位置字符的ASCII码值;
char str1[] = "abcdef";
char str2[] = "abc";
int ret = strcmp(str1, str2);
strcmp模拟实现:
int my_strcmp(const char* str1,const char* str2)
{
while(*str1 == *str2)
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
return *str1 - *str2;
}
while循环中的判断条件并不取决于指针解引用的值,而是判断两指针解引用所指向的内容相不相等;
strstr
字符串查找函数
用法:
了解到:函数的返回参数是char*,函数参数是const char*,const char*;
strstr函数是在一个字符串中,判断另一个字符串是否是它的子串,并且返回的是它俩相同字符串的首字符地址;若没有找到,就返回NULL;
char str1[] = "abbbcdef";
char str2[] = "bcd";
printf("%s\n", strstr(str1, str2));
strstr模拟实现:
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
char* p1 = NULL;
char* p2 = NULL;
char* cp = (char*)str1;
while (*cp)
{
p1 = cp;
p2 = (char*)str2;
while (*p1 && *p2 && *p1 == *p2)
{
p1++;
p2++;
}
if (*p2 == '\0')
{
return cp;
}
cp++;
}
return NULL;
}
此函数比较有意思,要考虑的细节还是蛮多的;
比如:在遍历长串时,发现比到一半,不相同了,指向长串的指针要移到开始比较的下一个位置;
子串怎么返回起始位置;
在什么条件下算找到子串了;
不妨一开始就创建两个指针p1、p2,分别用来指向两个字符串;然后再利用cp指针代替str1指针,代替它完成移动的操作,
当遍历发现两指针的内容相同时,p1++;p2++;直到一方出现\0或者两指针所指向内容不相同,再判断此时的p2指针是否是\0;若是\0,则证明子串已经遍历完毕,返回cp;不是\0。,此时cp++,再一次循环,重置p1和p2的内容;
strtok
字符串截取(个人理解)
用法:
了解到,函数的返回参数char*,函数参数为char*,const char*
strtok函数比较奇特,
delimiters指针所指向的字符串中定义了用作分隔符的字符串集合;
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:
strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
并且可修改。)
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
中的位置。
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
记。
如果字符串中不存在更多的标记,则返回 NULL 指针。
怎么完美的使用它呢?
比如:
char *p = "123.234.345@567";
const char* sep = ".@";
char arr[30];
char *str = NULL;
strcpy(arr, p);
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
结果为:
将字符串中每一个带有分隔符的小串分开 ;
strerror
strerror是一个将错误码转为错误信息的函数
用法:
查阅strerror - C++ Reference (cplusplus.com)https://legacy.cplusplus.com/reference/cstring/strerror/?kw=strerror了解到;函数的返回参数为char*,函数参数为int;
这个函数就是将数值转为信息;
使用此函数时,必须包含<errno.h>头文件
怎么用呢?
比如;处理文件时
FILE* pFile;
pFile = fopen("test.txt", "r");
if (pFile == NULL)
printf("无法打开此文件: %s\n", strerror(errno));
就会打印出相对应的错误信息:
切记,在使用时,如果想要查看此处的代码有没有错误,要及时的使用此函数进行查看,因为新一段代码运行时都会覆盖上一段代码的错误码,导致不能及时的检查错误;
内存函数:
为什么设计内存函数呢?
因为在处理字符串相关内容时,我们可以使用字符串函数,那别的类型就不能处理了吗?
所以引进了内存函数,处理不同类型,
memcpy
内存拷贝函数
用法:
了解到;函数的返回参数是void*,函数参数是void*,void*,size_t
那么为什么内存函数会接受void*这样一个没有具体类型的指针类型呢?
因为在设计此函数的相关人员,不知道将来要处理哪种类型的数据;就以void*来接受,
我们也看到,函数的第三个参数是要拷贝的字节个数,那么再结合char*指针,一个字节一个字节的拷贝,不就能够达到要求了。
注意:拷贝的目标空间要足够大
int arr1[20] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr1, arr2, 20);
memcpy模拟实现:
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
实现就将(void*)强制类型转换为(char*),一个字节一个字节的拷贝,
而我们知道强制类型转换是临时的,但是将他强转后+1再赋给void*的时候就能达到想要的效果;
memmove
内存挪动函数
用法:![](https://i-blog.csdnimg.cn/blog_migrate/6f4653319775573358fa1f77ab91c590.png)
查阅
了解到,函数的返回类型是void*,函数参数是void*,const void*,size_t;
和memcpy有着同样的返回类型和参数;
int arr1[20] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 , arr2, 20);
模拟实现memmove:
void* my_memmove(void* dest,const void* src,size_t num)
{
void* ret = dest;
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;
}
memmove函数模拟实现时有一些小细节;
当我们对同一块内存空间进行操作时,会覆盖原有的数据,比如
int arr1[20] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr2 + 2, arr2, 20);
在执行这一段操作时,很有可能出现原有数据被覆盖的情况
导致最后结果为:1 2 1 2 1 2 1 8 9 10
所以在实现时,我们分三种情况讨论
一、目标空间出现在源空间的左侧
如图,当目标空间出现在源空间的左侧时,为了保证原有的数据不被覆盖,该怎样去挪动呢
采用 前——>后的方式来进行操作,
二、目标空间出现在源空间的右侧
当目标空间出现在源空间的右侧时,采用 后——>前的方式
三、目标空间和源空间不相干
当目标空间和源空间不相干时,我们发现,不管是 前——>后 还是 后——>前,都是可以的;
那么合并以上三种,寻找最优解,
当目标空间出现在源空间的右侧及以后的位置时,我们采用 后——>前的方式
当目标空间出现在源空间的左侧时,采用 前——>后的方式
在引入了以上两种内存函数时,我们不妨发现,其实memcpy和memmove函数的作用差不多一样,都是挪动或者拷贝内容,
其实memmove的作用是大于memcpy的;
memmove的功能包含了memcpy,因为memcpy不能处理同一个内存空间的拷贝操作;
memset
内存设置函数
用法:
函数的返回类型void*,函数参数void*,int ,size_t
使用memset函数时要注意,此函数是以字节为单位设置内存的,并不是将一个数值存入一个类型中那么简单,
char str[10] = { 0 };
memset(str, '1', 9);
所以,这个函数用来处理字符串还是不错的;
memset模拟实现:
void* my_memset(void* ptr, int value, size_t num)
{
assert(ptr);
void* ret = ptr;
while (num--)
{
*(char*)ptr = value;
ptr = (char*)ptr + 1;
}
return ptr;
}
结尾:
所以,本期就是要向各位说明,在进行编程时要有效的使用我们的工具,查阅查阅各个函数的具体功能、怎么使用;会对效率有所提升的;