C语言的库函数
你好,这里是新人 Sunfor
这篇是我最近对于C语言库函数的学习心得和错题整理
有任何错误欢迎指正,欢迎交流!
会持续更新,希望对你有所帮助,我们一起学习,一起进步
前言
C语言的库函数可以帮助我们提升写代码的效率、增强我们代码的可读性,同时可以实现跨平台的兼容性,那么接下来我们一起进入库函数的学习吧~
库函数的分类
C语言的库函数可以大致分为
- 字符串处理函数
- 输入输出函数
- 内存管理函数
- 时间处理函数
字符串处理函数与C语言的知识密切相关,我们在之后会详细介绍
输入输出函数在我们平时的学习中经常利用到,就不过多赘述
内存管理函数与我们之后的动态内存管理有很大联系
时间处理函数主要用于获取当时时间、格式化时间、计算时间差、控制程序执行频率以及输出时间字符串
字符库函数
C语言中的字符串是以字符数组形式存储,以空字符(\0)结束
在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语言标准库中提供了一系列的库函数
strlen
size_t strlen(const char* str);
- 字符串以‘\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数,不包含’\0’
- 参数指向的字符串必须以’\0’结束
- 函数的返回值是size_t,为无符号整型
- strlen的使用需要包含头文件
知道了何为strlen函数之后,我们可以试着模拟实现strlen函数
//计数器的方法
int my_strlen(const char* str)
{
int count = 0;
assert(str);
while (*str)
{
count++;
str++;
}
return count;
}
//不创建临时变量计数器(递归)
int my_strlen2(const char* str)
{
assert(str);
if (*str == '\0')//遇到\0则结束
return 0;
else
return 1 + my_strlen(str + 1);//递归
}
//指针-指针的方式
int my_strlen3(char* s)
{
assert(*s);
char(*p) = s;
while (*p != '\0')
{
*p++;
}
return p - s;
}
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (my_strlen3(str2) - my_strlen3(str1) > 0)
{
printf("str2 > str1\n");
}
else
printf("str1 > str2\n");
return 0;
}
以上三种方法就是算法不断优化的过程
尤其是第二种一般考察你的时候
不会直接问你如何用递归的方式实现
而是说如何用不创建临时变量的方法实现
我们要通过积累,锻炼自己的反应能力
strcpy
char* strcpy(char * destination,const char * source);
- 源字符串必须以’\0’结束
- 会将源字符串中的’\0’拷贝到目标空间
- 目标空间必须足够大,以确保能存放源字符串
- 目标空间必须可修改
strcpy的模拟实现
char* my_strcpy(char* dest, const char* src)
{
//先将dest的地址保留
char* ret = dest;
//dest 和 src 的地址都不能为空
assert(dest != NULL);
assert(src != NULL);
//将src的值赋给dest
while (*dest++ = *src++)
{
;
}
//返回ret的值
return ret;
}
int main()
{
char str1[] = "The best day is coming!";
char str2[40];
my_strcpy(str2, str1);
printf("%s\n", str2);
return 0;
}
注意取值不能为空
还有赋值操作不要写成判断相等的操作
strncpy
char * strncpy(char * destination,const char * source,size_t num);
- 拷贝num个字符从源字符串到目标空间
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后面追加 ‘\0’,直到num个
strncpy的模拟实现
//宏定义使代码更灵活
#define NUM 3
char* my_strncpy(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
for (size_t i = 0; i < num; i++)
{
if (*src)//相当 *src != '\0'
{
*dest++ = *src++;
}
else
{
*dest++ = '\0';//拷贝完目标字符串
}
}
return ret;
}
int main()
{
char str1[] = "The best day is coming!";
//最好还是给数组初始化一下,不然容易报烫烫烫
char str2[40] = { 0 };
size_t num = NUM;
my_strncpy(str2, str1,num);
printf("%s\n", str2);
return 0;
}
strncpy与strcpy的区别与联系
- 关联
两者都用于将一个字符串复制到另一个字符串
- 区别
1.参数:
- strcpy 只接受源和目标字符串指针
- strncpy额外接受一个数字参数,指定要复制的字符数
2.处理字符串结束:
- strcpy 在复制时会自动添加’\0’结束符
- strncpy 如果源字符串长度小于指定字符数,仍会用 ‘\0’ 填充直到达到指定长度;如果源字符串长度大于或等于指定长度,则不会添加结束符
3.安全性:
- strcpy 在没有保证目标数组大小时可能导致溢出
- strncpy 可以防止缓冲区溢出,因为可以控制复制的字符数
strcat
char * strcat(char* destination,const char * source);
源字符串必须以’\0’结束
目标字符串中也得有\0,否则没办法知道追加从哪里开始
目标空间必须足够大,能容纳下源字符串的内容
目标空间必须可以修改
模拟实现strcat
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
//dest一直往后走到达数组的末尾
while (*dest)
{
dest++;
}
//这个时候把源字符串的值赋给目标数组
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char str[80] = { 0 };
my_strcat(str, "I Love ");
my_strcat(str, "Advanced Mathematics");
printf("%s\n", str);
return 0;
}
strncat
char * strncat(char * destination,const char * source,size_t num);
- 将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加一个\0字符
- 如果source指向的字符串的长度小于num的时候,只会将字符串中到\0的内容追加到destination指向的字符串的末尾
strncat的模拟实现
#define NUM 8
char* my_strncat(char* dest, const char* src, size_t num)
{
char* ret = dest;
while (*dest)
{
dest++;
}
while (num > 0 && *src != '\0')
{
*dest++ = *src++;
//每追加一个字符,num记得--
num--;
}
return ret;
}
int main()
{
char str1[40] = { "Hello!" };
char str2[] = { "Advanced Mathmatics~" };
size_t num = NUM;
my_strncat(str1, str2, num);
printf("%s\n", str1);
return 0;
}
strncat与strcat的区别与联系
- 关联
两者都是用于将字符串连接到目标字符串的末尾,两者都需要目标字符串和源字符串作为参数
- 区别
1.参数:
- strcat 只接受源和目标字符串
- strncat 额外接受一个数字参数,指定要连接的字符数
2.处理字符串结束:
- strcat 自动在莫表字符串末尾添加’\0’结束符,但不考虑目标字符串的长度
- strncat 在追加源字符串后,会在目标字符串末尾添加 ‘\0’,并且在连接时会考虑目标字符串的剩余空间
3.安全性:
- strcat 在没有保证目标数组大小时可能导致溢出
- strncat 可以防止缓冲区溢出,因为可以限制从源字符串复制的字符数
strcmp
int strcmp(const char* str1,const char* str2);
第一个字符串大于第二个字符串,则返回dayu0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
strcmp的模拟实现
int my_strcmp(const char* str1, const char* str2)
{
assert(*str1 && *str2);
if (*str1 != *str2)
{
return *str1 - *str2;
}
//如果一个字符串到达结束,另一个字符串还没有
else
{
return (unsigned char)*str1 - (unsigned char)*str2;
}
str1++;
str2++;
}
int main()
{
char* str1 = "Apple";
char* str2 = "Android";
char* str3 = "Harmony OS";
int result1 = my_strcmp(str1, str2);
if (result1 < 0)
{
printf("%s is less than %s \n", str1, str2);
}
else if (result1 > 0)
{
printf("%s is better than %s \n", str1, str2);
}
else
printf("%s is equal to %s \n", str1, str2);
return 0;
}
strncmp
int strncmp (const char * str1,const char * str2,size_t num);
- 比较str1 和 str2 的前num个字符,如果相等就继续往后比较,最多比较num个字母,如果提起那发现那不一样就提前结束
strstr
char * strstr(const char* str1,const char * str2);
- 函数返回字符串str2 在字符串str1中第一次出现的位置
- 字符串的比较匹配不包含\0字符,以\0作为结束标志
strstr的模拟实现
char* my_strstr(const char* str1, const char* str2)
{
//cp指向str1 用于遍历整个字符串
char* cp = (char*)str1;
//s1 s2分别用来遍历 str1 和str2
char *s1, *s2;
if (!*str2)
return (char*)str1;//如果str2 为空,返回str1
while (*cp)
{
s1 = cp;
s2 = (char *)str2;
while (*s1 && *s2 && !(*s1 - *s2))
{
s1++;
s2++;
}
//找到匹配,返回起始位置
if (!*s2)
return cp;
cp++;
}
//未找到匹配
return NULL;
}
int main()
{
char str[] = "This is a simple string";
char* pch;
pch = my_strstr(str, "simple");
strncpy(pch, "sample", 6);
printf("%s\n", str);
return 0;
}
内存 库函数
memcpy
void * memcpy(void * destination,const void * source,size_t num);
- memcpy从source的位置开始向后复制num个 字节的数据到destination指向的内存位置
- 这个函数在遇到 ‘\0’ 的时候并不会停下来
- 如果source和destination有任何的重叠,复制的结果都是未定义的
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
输出结果为
memcpy的模拟实现
void* memcpy(void* dest, const void* src, size_t count)
{
void* ret = dest;
assert(dest && src);
while (count--)
{
*(char*)dest = *(char*)src;
//不能直接(char*)dest++ 因为强转限制的区间很小 所以 需要分步操作
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
memmove
void * memmove(void * destination,const void * source,size_t num);
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理
memmove的模拟实现
void my_memmove(void* dest, const void* src, size_t count)
{
void* ret = dest;
if (dest <= src || (char*)dest >= ((char*)src + count))
{
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
//以下为重复出现部分的应对策略
else
{
dest = (char*)dest + count - 1;
src = (char*)src + count - 1;
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest - 1;
src = (char*)src - 1;
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1 + 2, arr1, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
memset
void * memset(void * ptr,int vaule,size_t num);
- memset是用来设置内存的,将内存中的值以字节为单位设置成想要的内容
memcmp
int memcmp(const void * ptr1,const void * ptr2,size_t num);
- 比较从ptr1到ptr2指针指向的位置开始,向后的num个字节
库函数使用的注意事项
1. 了解库函数的文档
- 查阅文档:每个库函数通常都有官方文档,详细说明其功能、参数、返回值和使用示例。了解这些可以避免错误的用法。
- 示例代码:参考文档中的示例代码,确保你理解如何正确调用函数。
2. 参数类型和范围
- 类型检查:确保传入的参数类型符合函数要求,例如整数、字符串等。
- 边界检查:如果函数参数有范围限制,确保传入值在有效范围内,避免出现溢出或异常。
3. 错误处理
- 异常处理:了解库函数可能抛出的异常,并准备相应的错误处理代码,确保程序的鲁棒性。
- 返回值检查:一些库函数可能返回特殊值(如 NULL 或 -1),使用前应检查这些返回值以判断函数是否成功执行。
4. 性能考虑
- 性能评估:在性能敏感的应用中,使用库函数前最好进行性能评估,以确保其符合需求。
- 避免重复调用:如果库函数执行耗时较长,考虑是否可以缓存结果,避免重复调用。
5. 线程安全
- 并发访问:确认库函数在多线程环境下的安全性,特别是修改共享数据时。
- 使用锁:如果库函数不是线程安全的,使用适当的锁机制来保护对共享资源的访问。
6. 版本兼容性
- 版本管理:不同版本的库函数可能会有不同的实现或参数要求,确保使用的库版本与项目需求兼容。
- 弃用警告:留意库函数的弃用通知,及时更新代码以使用推荐的替代函数。
7. 命名冲突
- 命名空间:如果使用多个库,注意可能出现的命名冲突,尤其是在使用全局函数时。
- 使用前缀:在调用函数时,可以使用库的命名空间前缀,避免冲突。
8. 依赖管理
- 包管理工具:使用包管理工具(如 npm、pip、Maven 等)来管理库的依赖,确保版本一致性。
- 定期更新:定期检查库的更新,以获得最新功能和安全补丁。
9. 理解底层实现
- 源码阅读:如果条件允许,阅读库函数的源代码有助于深入理解其实现原理,帮助更好地使用和调试。
- 性能和安全:了解底层实现可以帮助识别潜在的性能瓶颈或安全隐患。
10. 测试
- 单元测试:在使用库函数时,编写单元测试来验证其行为是否符合预期。
- 边界测试:针对边界情况和异常情况进行充分测试,确保函数的健壮性。
后记
关于C语言的库函数的学习我们就暂时在这里告一段落啦~
其实C语言的库函数还有很多,我在这里也只是列出了一些
之后可能还会有库函数的相关内容随机掉落,大家可以持续关注~