Bootstrap

C之自定义函数

哈喽小伙伴们,帅气的我又来了哦~

今天我们来看看C中的函数是怎么个事~

①  函数的定义格式及其解释

话说,在C中所有的代码都是用函数来实现的,比如程序的入口: main函数 (这个在之前提到过喔~,不知道各位小伙伴是否记得呢~)

如果不记得的话,咱们一起来看看吧!

#include <stdio.h>

int main()
{
	printf("hehe\n");
	return 0;
}

如图所示,上面从第二行开始往下,就是main 函数的定义啦!

一般,函数的定义格式如下:

通过上面的批注,相信你已经大概了解了函数的定义格式,那么请各位小伙伴仿照这个批注说出main函数的各个部分是怎样的~  main函数的返回类型是整形(int),函数名为main ,没有形式参数(注:这里说的没有是指在C中一般不在main后面的括号里写参数,而不是说这个函数本身就没有参数),下面的大括号里面的代码统称为函数体(这里可以理解为函数的身体,也就是函数可实现的功能),这里可以看到,main函数的函数体也就是实现的功能是打印一个hehe 并换行,  至于后面的return 0  则与前面的返回类型int 相呼应.也就是说,在这个函数用完之后,会返回一个整数0  (当然,在这里return 0  是在main函数中固定的格式,此时不需要深究这个返回0的操作有什么用,只需要记住它是这么写的就OK了,后面在写其他函数时,会用到相对应的返回值,到时候各位小伙伴就知道返回值的作用,而对于这个main函数,则不用深究返回值的用途)  

② 返回值的用途

接下来就给大家看看函数返回值到底是干什么的,咱们废话不多说,来看下面一串代码:

通过上面的批注,你明白了嘛? 这就是把is_leap_year 的返回值用于if语句中,这种方式也是使用返回值较为常见的一种方式~ 想必各位小伙伴已经理解了返回值的用途了吧~

下面来看看运行结果:

③ 形参和实参的区别

接下来我们来介绍形参和实参:

首先来看下面一串代码:

通过上面的注解,你明白了什么是形参,什么是实参了嘛?

如果还是不能理解,那么小编就用数学中的函数来类比吧! 希望可以用我有限的语言表达能力来尽量表达清楚这两者的意思.

在数学中,一个单元函数,eg: f(x)=x^2+2^x+1  在这个式子中,x是整个函数唯一的参数,而在C中却要说成整个函数唯一的形参,因为在这里,x 是一个泛指,可以代表所有的实数,所以它被称为形参 

而我现在要计算f(1),如果在C中是不是要把这个1传给 x 呀,那这里的1就是实参.

也就是说,实参是形参的实例化.

不知我这样说,各位小伙伴能不能理解~

如果能够理解的话,那咱们现在来看看它们之间的区别~

这个区别用一句话来概括就是:形参是实参的一份临时拷贝.

啥意思呢?  我们还是根据上面这个add 函数的代码来看:调试结果如下:(关于调试内容后期细讲~)

源代码:

int add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
    
    int c = add(a, b);
	printf("%d\n", c);
	return 0;
	
}

从上面可以看出,x和y只是拷贝了a 和 b 的值,但是 x ,y, a, b  的地址各不相同,也就是说,a 和 b 只是把值传给了x 和y 在这之后,x 和 y 变成了两个不依赖于a ,b的两个独立的变量,所以我在自定义函数的内部一切都只是对x ,y进行的操作,而不影响 a 和 b 这两个变量.

如果有小伙伴不能理解我说的啥意思,那么来看下面一个错误代码的示范 :

这是一个交换算法的错误示范,其目的是通过调用swap 函数来实现a 和b 中的值的交换, 在这里可以看到,调用之前和调用之后,a 和b 中的值并没有交换,这是因为 在调用函数时,把 10 和 20 作为值的x 和y 变成了两个独立的变量,这时在swap 函数的内部完成的是对x 和y 值 的交换,与a 和 b 无关 .所以调用之后a 和b的值没有进行交换.不知各位小伙伴能否理解~(至于怎样解决这个问题,后期涉及指针的时候再细讲~,各位小伙伴稍安勿躁哈~)

④ 函数的嵌套调用和链式访问

了解了实参和形参的区别,我们接下来看看什么是函数的嵌套调用和链式访问~

⑴ 嵌套调用

啥叫函数的嵌套调用? 我们知道,C 程序是由一个个的函数组成的,而想要完成比较复杂的功能,这些函数必然不是孤立存在的,必然是你中有我,我中有你,一起协同来完成某个庞大而复杂的工程,咱们废话不多讲,一起来看下面一串代码:

int main()
{
	printf("hehe\n");
	return 0;
}

看到这里,有的小伙伴就比较晕了~ 这不是一个简单的打印操作嘛,怎么算得上嵌套调用呢? 兄弟,好好看看哦~ 这里的main 是不是主函数? 而printf是不是定义在C的标准库中的函数? 这不就是在main函数里面调用了一个printf函数嘛~ 这不就是函数的嵌套调用嘛~

所以,其实小伙伴们发现,我们每天都在写嵌套调用对不对? 这也印证了一个道理,我们每天在做的事情或许在当下看起来只有很小的作用,但在未来的某一天里,你一定会突然发现这一件件小事都能起到意想不到的作用. 所以加油吧,屏幕前的你!

⑵ 链式访问

知道了函数的嵌套调用,再让我们来看看什么叫链式访问吧

首先,所谓链式访问,顾名思义,就是把一个个函数像链条一样拼在一起然后进行访问. 咱们同样废话不多说,直接来看这样一串代码:

瞧瞧,这就是所谓的链式访问,在这里的printf 和strlen 是不是像链条一样接在一起呀~

接下来给出链式访问精确的定义:所谓函数的链式访问,就是把一个函数的返回值作为另一个函数的参数. 各位小伙伴看一看,这里是不是把strlen 的返回值作为printf 的参数呀~

⑶ 举例

接下来看看这两个板块的两个典型例子~

练习: 计算某年某月有多少天

针对这个练习,咱们直接来看下面一串代码:

//练习:计算某年某月有多少天

int is_leap_year(int year)
{
	if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
	{
		return 1;
	}

	return 0;
}


int get_day(int year, int month)
{
	int daysofordinary_year[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = daysofordinary_year[month];
	
	if (is_leap_year(year) && month == 2)
	{
		day++;
	}
	
	return day;
}

int main()
{
	int year = 0;
	int month = 0;
	scanf("%d %d", &year, &month);
	int day=get_day(year, month);
	printf("%d\n", day);

	return 0;
}

要完成这个练习,请各位小伙伴想一想,其实除了2月以外,其他月份的天数是不是不随着年份的改变而变化呀~

所以咱们先不管那么多,先把平年的设计出来再说~

怎样设计呢?  其实小伙伴们可以看看我上面 get_day 函数的第一行代码,是不是把平年所有月份的天数放在了一个名叫daysofordinary_year 的数组里面呀~

然后这里有一个巧妙的设计就是,我为了让每一个天数元素的下标与它的月份相等,在这个数组的最前面添加了一个0,这样操作之后大家想,1月的天数所对应的下标就是1,这样做以后,我是不是就可以用month作为数组的下标,并且每个month正好对应每个月的天数呀~

以上是平年计算的思路,那闰年呢?

我们知道,平年和闰年唯一的区别是2月,闰年的2月比平年的2月多1天,所以在后面还要判断这个下标所对应的是不是闰年的2月,如果是,则天数在原先的基础上自增1. 不知各位小伙伴能否理解~

如果能理解的话,那么各位小伙伴可以从全局上看看:我在main函数里调用了get_day 函数,在get_day 函数里调用了is_leap_year 函数,这是不是函数的嵌套调用呀~

明白了这个例子,那咱们来看看下一个吧~

//练习:判断代码输出结果

int main()
{
	printf("%d", printf("%d", printf("%d", 43)));
	return 0;
}

要想完成这个练习,首先,大家想应该怎么入手呢?

如果有小伙伴想不出来,那我来类比一下数学吧~  在数学中的复合函数,eg: f [g(x)]   一般像这种复合函数的题是怎么处理的呀?  是不是从里向外的原则,先看g(x) 所对应的值,再把它的值带入f(x)的表达式中呀?

那么大家看,这个代码的逻辑是不是跟这种复合函数差不多呀~ 就是printf 复合多个printf 呗~ 

明白了这个道理,我们再来看一个比较容易忽略的点,: %d  打印的是后面参数的返回值,而对于每一个printf 函数来说,它首先得打印后面的内容,然后自身还要返回一个值(这个返回值实际上是它所打印的字符的个数),这时,在它外层的printf 打印的是不是这个printf的返回值呀~

经过这一系列分析,我想你已经明白了:最内层的printf 会打印43 并返回一个整数2,所以倒数第二层的printf 打印的是一个整数2 并返回一个整数1  然后最外层的printf打印一个1 返回值为1(只不过最外层的返回值没有用到而已)  而由于打印之间没有换行,所以打印结果为:4321

下面来验证一下:

你看,结果是不是跟我们预想的一模一样呀~  你听懂了嘛~ 

好啦,咱们这一篇先到这里,我们下一篇再见~

另外,这是这一篇所有的源码~

#include <stdio.h>

int main()
{
	printf("hehe\n");
	return 0;
}


int is_leap_year(int year)
{
	if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
	{
		return 1;
	}
	
	return 0;
}

int main()
{
	int year = 2024;
	if (is_leap_year(year))
	{
		printf("%d年是闰年\n", year);
	}
	else
	{
		printf("%d年不是闰年\n",year);
	}
	return 0;
}


//写一个名为add的加法函数.

int add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);

	int c = add(a, b);
	printf("%d\n", c);
	return 0;
}


//错误的交换算法

void swap(int x, int y)
{
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}


int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("%d %d\n", a, b);
	swap(a, b);
	printf("%d %d\n", a, b);

	return 0;
}

//函数的嵌套调用:在main函数里面嵌套调用其他函数

int main()
{
	printf("hehe\n");
	return 0;
}

//函数的链式访问

#include <string.h>

int main()
{
	char arr[] = "abc";
	printf("%d\n",strlen(arr));
	return 0;
}

//练习:计算某年某月有多少天

int is_leap_year(int year)
{
	if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
	{
		return 1;
	}

	return 0;
}


int get_day(int year, int month)
{
	int daysofordinary_year[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = daysofordinary_year[month];
	
	if (is_leap_year(year) && month == 2)
	{
		day++;
	}
	
	return day;
}

int main()
{
	int year = 0;
	int month = 0;
	scanf("%d %d", &year, &month);
	int day=get_day(year, month);
	printf("%d\n", day);

	return 0;
}

//练习:判断代码输出结果

int main()
{
	printf("%d", printf("%d", printf("%d", 43)));
	return 0;
}

;