【2万字解析C语言常用函数】

81 篇文章 2 订阅
65 篇文章 4 订阅

全网最接地气的博客!此处列举一些容易弄混的函数,原因是因为我初学的时候经常混淆,甚至弄不清楚具体的含义,所以想着给一些初学或跟我有同样的痛苦的朋友,把一些基础的函数列举下来,加上一些自己的理解,简要介绍了他们的一些区别。作者实属初学,写博客也是作者学习的一个过程,难免文章中有内容理解不到位或者有不当之处,包括一些函数的自实现或许不是最好理解的,也不是最优化的,还请朋友们不吝指正,希望大家给予支持,赠人玫瑰,手有余香!
说明:本文前4节代码均以博主截图形式呈现,后面的代码以代码块的形式呈现,带来的观感上的差异还请谅解。

一:打印函数printf()

此系列都是针对于标准输出端stdout

1.1:printf与puts

本质上puts与printf在打印字符串时并无多大区别。
puts(str);在结束时自动输出一个\n,所以我们如果需要打印换行也可采用puts("");打印。下面用代码说明:
代码示例1
在这里插入图片描述
代码示例2
在这里插入图片描述

1.2:printf与putchar

int putchar ( int character );
字符写入标准输出(标准输出)。这相当于使用stdout作为第二个参数调用putc。
在这里插入图片描述
代码示例

在这里插入图片描述

  • putchar是输出一个字符。printf("%s\n", str);

二:输入函数scanf()

此系列都是针对于标准输入端stdin

2.1:scanf与gets

在这里插入图片描述

本质上都是从标准输入段输入一个字符串,存入相应的空间中,但是也有一点差别,scanf遇到空格,\n就停止,而gets可以接受空格,下面用代码来检验:
代码示例1
在这里插入图片描述
代码示例2
在这里插入图片描述

2.2:scanf与getchar

  • getchar是从标准输入设备读取一个char; ch = getchar();

  • scanf通过标准输入设备读入一个字符串; scanf("%s", arr);
    在这里插入图片描述

  • getchar的返回值是int型,当getchar输入错误时,返回EOF(#define定义的一个常量:-1).
    在这里插入图片描述
    用control+z可以结束。
    开始执行程序,我们用过键盘输入一个‘a’, 然后又通过printf打印 ‘a’;
    下面我们列举一个getchar的应用
    在这里插入图片描述

三:strlen()函数

3.1:strlen函数计算字符串长度

strlen函数的使用需要引入头文件stdio.h,在使用是我们需要注意一下几点:
①:遇\0停止
②:\0不计入长度
③:strlen返回无符号整型

代码示例
在这里插入图片描述

3.2:strlen函数的自实现

这里介绍一种strlen函数的自实现代码,运用指针相减原理。代码不唯一,朋友们借鉴即可。
在这里插入图片描述
代码示例2:计数器方式:
在这里插入图片描述

3.3:strlen函数的应用

strlen是计算字符串长度的函数,应用十分广泛,我们也简单介绍一个strlen函数的应用,字符串的反转模型(逆置):
在这里插入图片描述
在这里插入图片描述
对于字符串的逆置,只要我么明白了数组的逆置原理,就很好解决这个问题了。

四:字符串处理函数

4.1:字符串拷贝函数strcpy()

4.1.1:strcpy的功能

char * strcpy(char * dest const char * src)
功能:把src所指向的字符串拷贝到dest所指的字符串中,字符串结束标志符‘\0’也会被拷贝。
注意:需要保证源字符串有明确的 \0,且目标空间可改
在这里插入图片描述
这里我们注意到一个有意思的点,就是目标空间原先已有内容时,拷贝是如何进行的呢?
其实拷贝时,目标空间的原内容会被新内容覆盖并丢失,即使原内容比拷贝的内容长,但是由于拷贝的时候将字符串结束符也一起拷贝到dest中,所以即使dest的原内容很长,但是拷贝之后\0后面的内容也会丢失。

4.1.2:strcpy的自实现

代码示例1
在这里插入图片描述
代码示例2
在这里插入图片描述

4.2:字符串拷贝函数strncpy()

4.2.1:strcnpy的功能

char * strcpy(char * dest const char * src size_t n)
这个函数相比strcpy的区别在于对复制的字符个数作出了限定,把src指向的字符串的前n(字节)个字符复制到dest所指的空间中,是否拷贝’\0’还得看指定的长度是否包含‘\0’
在这里插入图片描述
我们注意到第一个打印出来的字符串后面是乱码,而第二个字符串正常输出,原因是我们一开始定义dest时就没有初始化desr[100] = { 0 }; ,所以我们指复制8个字节内容时,并没有复制到’\0’,所以没有字符结束标志符,而dest【8】 = ‘\0’, 就表示我们在复制尾部加了一个结束符,所以就可以正常打印了。

4.2.2:strcnpy的自实现

相比于strcpy函数的自实现,我们此时只需要控制字符字节数n即可。
代码示例1
在这里插入图片描述
代码示例2
在这里插入图片描述

4.3:字符串追加函数strcat()

4.3.1:strcat的功能

char * strcat(char * dest , const char * src), 将src的字符串连接到dest的尾部,’\0’ 也会追加过去
返回值
成功:返回dest字符串的首地址。
失败:NULL;
在这里插入图片描述

4.3.2:strcat函数的自实现

实现这个函数的关键是找到目标字符串的’\0’的位置,然后从此处开始追加源字符串。
代码示例
在这里插入图片描述

从代码中可以看出,图中红圈部分就是在找目标串的\0位置

4.4:字符串追加函数strncat()

4.4.1:strncat的功能

此函数相比strncat()的区别在于对追加的字符个数作出了限定,把src指向的字符串的前n(字节)个字符追加到dest所指的空间中,并且src末尾的’\0’也会追加过去。
char * strcat(char * dest , const char * src, size_t n);
参数
dest:目的字符串首地址;
src:源字符串首地址;
n:指定需要追加字符串个数;
返回值
成功:返回dest字符串首地址;
失败:NULL;
代码示例1
在这里插入图片描述

4.4.2:strncat()函数的自实现

此函数的实现同样是需要找到目标字符串的‘\0’的位置
代码示例
在这里插入图片描述
图中红圈代码是在找目标字符串的’\0’的位置,而绿圈位置的循环,不仅要对源字符串做限制,还需要对n做限制。

4.5:字符串比较函数strcmp()

4.5.1:strcmp()的功能

int strcmp(const char * s1, const char * s2);
功能:比较s1和s2的大小,其实比较的是各个字符的ASCII码的大小
返回值:相等返回0, 大于返回>0(注意,在不同的操作系统可能会有差异), 小于返回<0;
代码示例1
在这里插入图片描述
注意这里比较大小是按每个字符来比较的,与字符串长短无关
代码示例2
在这里插入图片描述

4.5.2:strcmp()函数的自实现

逐个字符的比较对应ASCII值
代码示例
在这里插入图片描述

4.6:字符串比较函数strncmp()

4.6.1:strcnmp()的功能

int strcmp(const char * s1, const char * s2, size_n);
功能:相比于strcmp函数,对比较的字符数做出了限制,比较的是前n个字符
代码示例
在这里插入图片描述

4.6.2:strncmp()函数的自实现

相比于strcmp函数的实现,还多了一处n的限制,这里我们设置一个i的计数器,每当while循环一次,i++,最多循环n次就跳出while循环。
代码示例1
在这里插入图片描述
代码示例2
在这里插入图片描述
注:自实现方式不唯一,且也不是最好的,望读者自行判断。

4.7:字符串格式化打印函数sprintf()

4.7.1:sprintf()函数的功能

int sprintf(char * str, const char * format, …);
**功能:**根据参数format字符串来转换并格式化数据,然后将结果输出到目标字符串空间,直到出现字符串结束符’\0’为止。
**注:**其实相比于printf函数来打印字符串,差别在于sprintf打印到某一个字符串空间中,而我们熟知的printf是打印到屏幕上。
参数:
str:字符串首地址
format:字符串格式,与printf用法一样

返回值:
成功:返回实际格式化字符的个数;
失败:-1;

代码示例
在这里插入图片描述

如图:表示将代码中的红框部分打印到s1所在的内存空间中,共打印17个字符数,返回到 len。

4.8:字符串格式化输入函数sscanf()

4.8.1:sscanf()函数的功能

int sscanf(const char * str, const char * format, …);
功能:从str指定的字符串中读取数据。
参数:
str:字符串首地址
format:字符串格式,与scanf()用法一样

代码示例
在这里插入图片描述
注:此函数就好比说你在某一字符空间存放了字符串,若想从中读取出某些数据,就可以用到此函数。scanf函数是从键盘读取数据,sscanf是从字符串中读取数据。

4.9:字符查找函数strchr()

4.9.1:字符查找函数strchr()的功能

char * strchr(const char * str, char ch)
功能: 在字符串中查找字符ch出现的位置。
参数:
str: 字符串首地址。
ch: 匹配的字符。
返回值:
成功:返回第一次出现目标字符的地址。
失败:返回NULL;
代码示例
在这里插入图片描述
代码中寻找字符‘w’, 返回首次出现的地址并打印字符串。

4.9.2:字符查找函数strchr()自实现

在这里插入图片描述

4.10:字符串查找函数strstr()

4.10.1:字符查找函数strstr()的功能

char * strstr(const char * str, const char * s1);
功能:在字符串str中查找字符串s1的位置,找到返回第一次出现的地址,失败返回NULL;
代码示例
在这里插入图片描述

4.10.2:字符查找函数strstr()的自实现

在这里插入图片描述
注:我们这里会设立几个辅助指针来记录相应的地址。

五:内存操作函数

5.1:memset()

void * memset ( void * ptr, int value, size_t num );
功能:将sp所指的内存区域的前num个字节以value填入
ptr:需要操作的内存空间的首地址;
value:填充的字符,c虽然参数为int,但必须是unsigned char, 范围是0~255;
num:指定需要设置的大小,单位是字节;
在这里插入图片描述
代码示例1:

int main()
{
	char str[] = "almost every programmer should know memset!";
	memset(str, '-', 6);
	puts(str);
	return 0;
}
// 输出
------ every programmer should know memset!

意思是将str所指的内存空间的前6字节重置为 ‘-’。
代码示例2

int main()
{
	int str[10];
	memset(str, 0, sizeof(str));
	memset(str, 97, sizeof(str));
	for (int i = 0; i < 10; i++)
	{
		printf("%c ", str[i]);
	}
	return 0;
}
// 输出
a a a a a a a a a a 请按任意键继续. . .

5.2:memcpy()

void * memcpy ( void * destination, const void * source, size_t num );
功能: 将num字节的值从源指向的位置直接复制到目标指向的内存块。该函数不检查源中是否有任何终止的空字符,它总是精确复制num字节。为避免溢出,目标和源参数指向的数组大小应至少为num字节,且不应重叠(对于重叠的内存块,memmove是一种更安全的方法)。
在这里插入图片描述
返回目标内存块的首地址。

代码示例1

struct 
{
	char name[40];
	int age;
} person, person_copy;

int main()
{
	char myname[] = "Pierre de Fermat";

	/* using memcpy to copy string: */
	memcpy(person.name, myname, strlen(myname) + 1);
	person.age = 46;

	/* using memcpy to copy structure: */
	memcpy(&person_copy, &person, sizeof(person));

	printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);

	return 0;
}
// 输出
person_copy: Pierre de Fermat, 46

代码示例2

int main()
{
	int src[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int dest[10];

	memset(dest, 0, sizeof(dest));
	memcpy(dest, src, sizeof(src));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", dest[i]);
	}
	return 0;
}
//输出
1 2 3 4 5 6 7 8 9 10 请按任意键继续. . .

下面展示一个内存重叠的情况。
代码示例

int main()
{
	int src[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	memcpy(&src[3], src, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", src[i]);
	}
	return 0;
}
// 输出
1 2 3 1 2 3 4 5 9 10 请按任意键继续. . .

这里我们发现好像memcpy也可以处理内存重叠情况,实际上memcpy拷贝的结果是未定义的**(取决于编译平台内部对memcpy的优化处理)**。如果在你编译器上内存重叠时memcpy不会发生问题的话 说明内部已经处理了内存覆盖问题

5.3:memmove()

void * memmove ( void * destination, const void * source, size_t num );
功能:将num字节的值从源指向的位置复制到目标指向的内存块。复制就像使用了中间缓冲区一样进行,相比于memcpy(), 允许目标和源重叠。该函数不检查源中是否有任何终止的空字符,它总是精确复制num字节。为避免溢出,目标和源参数所指向的数组的大小应至少为num字节。
与memcpy的区别:
当dest和src所指的内存空间重叠时,memmove可以保证拷贝正确,不过执行效率比memcpy低一些,原因在于memmove()函数会开辟辅助空间,用完再自动销毁。
代码示例

int main()
{
	char str[] = "memmove can be very useful......";
	memmove(str + 20, str + 15, 11);
	puts(str);
	return 0;
}
// 输出
memmove can be very very useful.

代码示例2

int main()
{
	int src[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	memmove(&src[3], src, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", src[i]);
	}
	return 0;
}
// 输出
1 2 3 1 2 3 4 5 9 10 请按任意键继续. . .

5.4:memcmp()

int memcmp ( const void * ptr1, const void * ptr2, size_t num );
功能:比较两块内存,将ptr1指向的内存块的前num字节与ptr2指向的前num字节进行比较,如果它们都匹配,则返回零;如果不匹配,则返回一个不同于零的值,表示哪个值更大。
注意,与strcmp不同,在找到空字符(字符串结束符)之后,函数不会停止比较。

“C语言的空字符是在字符串结尾系统自动加上的‘\0’,以让系统识别出一个字符串的结尾。 如: 字符串“china”。在系统内是以“china\0”储存的。 C语言中的字符中,除了字符串末尾之外,字符串里不能包含空字符,否则最先读入的空字符就会被误认为是字符串结尾,因此字符串就被无故截断了…”

返回值:
在这里插入图片描述
代码示例1

int main()
{
	char buffer1[] = "DWgaOtP12df0";
	char buffer2[] = "DWGAOTP12DF0";
	int n;
	n = memcmp(buffer1, buffer2, sizeof(buffer1));
	if (n>0)
		printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
	else if (n<0)
		printf("'%s' is less than '%s'.\n", buffer1, buffer2);
	else 
		printf("'%s' is the same as '%s'.\n", buffer1, buffer2);

	return 0;
}

输出:
'DWgaOtP12df0' is greater than 'DWGAOTP12DF0'.

代码示例2

int main()
{
	int s1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int s2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int flag = memcmp(s1, s2, sizeof(s2));
	printf("flag = %d\n", flag);
	return 0;
}

输出:
flag = 0

六:动态内存函数

6.1:为什么存在动态内存分配

我们已经掌握的内存开辟方式有:

int num = 10;//在栈空间上开辟4字节的空间
char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间。

以上两种方式有两种特点:

  1. 空间是静态开辟的,大小固定
  2. 对于不完全初始化数组,必须指定数组的长度,它所需的内存在编译时分配。

但是,我们考虑到我们切实运用时,我们所需的空间大小在程序运行的时候才能知道,那么静态开辟空间就不好满足了,而且也可避免静态开辟空间过大导致内存的浪费。那么这时就要用到动态开辟了。

6.2:malloc()内存开辟

void malloc (size_t size);*
功能在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。
返回值
在这里插入图片描述

  • 如果开辟成功,则返回一个指向开辟好空间的首地址。
  • 如果开辟失败,则返回NULL。
  • 返回值的类型是void* , malloc函数并不知道开辟空间的类型,所以我们需要对返回的指针进行强制类型转换,以便于我们对该指针进行相应操作。
    这里的代码我们和下一个函数一起引出。

6.3:free()内存释放

void free (void ptr);*
功能:有malloc()函数我们引出对应的free()函数,专门用来做动态内存的释放和回收。释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址。对同一内存空间多次释放会出错(因为释放一次该指针就成了野指针,野指针不可释放)。

  • 如果参数ptr所指的内存空间不是动态开辟的,那么free函数的行为是未定义的。
  • 如果参数ptr是NULL指针,那么free不起作用。
    代码示例
int main()
{
	int count, *arr, n;
	printf("请输入要申请数组的个数:\n");
	scanf("%d", &n);
	arr = (int *)malloc(n * sizeof(int));
	if (!arr)
	{
		printf("申请空间失败!\n");
		return -1;
	}
	//将申请到空间清0
	memset(arr, 0, sizeof(int)*n);
	for (count = 0; count < n; count++) /*给数组赋值*/
	{
		arr[count] = count;
	}
	for (count = 0; count < n; count++) /*打印数组元素*/
	{
		printf("%d ", arr[count]);
	}

	free(arr);
	arr = NULL;
	return 0;
}
请输入要申请数组的个数:
10
0 1 2 3 4 5 6 7 8 9 请按任意键继续. . .

注意:free()和malloc()函数是配套使用的,谁开辟谁释放,而且free所释放的指针必须是原malloc所开辟的地址,若地址变了,那么程序出错。
代码示例:

 int main()
{
	char *p = (char *)malloc(10);
	char str[] = "abcdef";
	int n = strlen(str);
	int i = 0;

	for (i = 0; i < n; i++)
	{
		*p = str[i];
		p++; //修改原指针指向
	}
	free(p);
	return 0;
}

可见这里开辟返回的P指针的指向发生了变化,不可直接释放。

6.4:calloc()内存开辟

void calloc (size_t num, size_t size);*
功能:在内存动态存储区中分配num块长度为size字节的连续区域,num代表开辟几个元素的空间,size表示该元素的字节大小。
返回值
在这里插入图片描述
与malloc()函数的区别与联系
联系:为num个大小为size字节的元素开辟一段连续空间。
区别:calloc函数会在返回地址之前先把申请的空间的每个字节初始化为0,不需要在用memset函数。
代码示例

int main()
{
	int * p = (int *)calloc(10, sizeof(int));
	if (p)
	{
		//开辟成功
	}
	free(p);
	p = NULL;//防止p称为野指针
	return 0;
}

6.5:realloc()内存开辟

void realloc (void ptr, size_t size);
功能:如果我们觉得之前申请的内存太小或者太大,那么reallco函数就可以对内存大小做合理的调整,重新分配用malloc或者calloc函数在堆中分配内存空间的大小。ptr是要调整的内存地址,size是调整之后新的内存大小。
:realloc函数调整原内存空间大小的基础上,还会将原内存中的数据移动到新的空间。

返回值
在这里插入图片描述
从返回值我们可以看出存在两种情况

  • 返回原malloc,calloc函数开辟的ptr地址。
  • 返回一个新的内存地址。
    在这里插入图片描述
  • 当情况一发生时,要扩展内存就直接在原内存后追加空间,还是返回ptr指针,原来空间的数据不会发生变化。
  • 当情况二发生时,原空间后没有足够的空间扩展,那么这时就需要在堆空间上另外找一块合适大小的连续空间,那么自然而然也是返回的一个新地址了, 并且把旧内存的值拷贝到新内存,同时释放旧内存。
    代码示例
int main()
{
	int * ptr = (int *)malloc(100);
	memset(ptr, 0, 100);
	if (!ptr)//若开辟失败,则结束。
		return 0;
	int * p = NULL;
	p = realloc(ptr, 1000);
	if (p != NULL)
	{
		//
	}
	free(p);
	p = NULL;
	return 0;
}

OK!!!观众老爷们,这里只是介绍了一些基础函数以及相应的自实现代码,如果朋友们觉得有一点点作用的话,希望朋友们能够给予小菜鸟一点支持!后续继续给朋友们带来更好的博文,还希望朋友们能够继续关注,小菜鸟致力于把自己的学习经验与个人理解更多的分享给大家,如果博文中有错误的地方,还希望朋友们一定指正。谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢

  • 47
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值