【C语言库函数】(超详细理解+例题、错题)

你好,这里是新人 Sunfor

这篇是我最近对于C语言库函数的学习心得和错题整理
有任何错误欢迎指正,欢迎交流!
会持续更新,希望对你有所帮助,我们一起学习,一起进步

前言

在这里插入图片描述

C语言的库函数可以帮助我们提升写代码的效率、增强我们代码的可读性,同时可以实现跨平台的兼容性,那么接下来我们一起进入库函数的学习吧~

库函数的分类

C语言的库函数可以大致分为

  • 字符串处理函数
  • 输入输出函数
  • 内存管理函数
  • 时间处理函数

字符串处理函数与C语言的知识密切相关,我们在之后会详细介绍
输入输出函数在我们平时的学习中经常利用到,就不过多赘述
内存管理函数与我们之后的动态内存管理有很大联系
时间处理函数主要用于获取当时时间、格式化时间、计算时间差、控制程序执行频率以及输出时间字符串

字符库函数

C语言中的字符串是以字符数组形式存储,以空字符(\0)结束
在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语言标准库中提供了一系列的库函数

strlen

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

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

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

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

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

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

strncmp库函数介绍

int strncmp (const char * str1,const char * str2,size_t num);
  • 比较str1 和 str2 的前num个字符,如果相等就继续往后比较,最多比较num个字母,如果提起那发现那不一样就提前结束

strstr

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

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

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

memset库函数介绍

void * memset(void * ptr,int vaule,size_t num);
  • memset是用来设置内存的,将内存中的值以字节为单位设置成想要的内容

memcmp

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语言的库函数还有很多,我在这里也只是列出了一些
之后可能还会有库函数的相关内容随机掉落,大家可以持续关注~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值