目录
1、初级的函数设定及调用输出
(1)函数的声明、调用与设定
以上图片中,5、4行为函数的声明,意思是“ 我有这些个函数 ”,void后面那些个字母,是我给这些个函数起的名字
7、8、9行为函数的调用,意思是“ 我要用这些个函数啦!”
11、16行则为函数的设定,意思是“ 我为有的这些个函数下个定义”
(2)这里面涉及三目运算,可简化设计,它等同于下面注释的 if 语句
注意我们所设定的函数声明里的参数是形式参数,在调用的时候用的是实际参数,他有一个传参的过程
此处用到三目运算符【x>y?x:y】意思是x如果大于y则输出x的值,若不大于,则输出y的值。
通俗的表达式为k ? m : n,先计算条件k,然后进行判断。如果k的值为真,计算m的值,运算结果为m的值;否则,计算n的值,运算结果为n的值。
一个条件表达式绝不会既计算m,又计算n。条件运算符是右结合的,也就是说,从右向左分组计算。例如,a ? b : c ? d : e将按a ? b : (c ? d : e)执行。
2、返回值类型与函数类型不同会发生什么
float是浮点型数据类型,它是有小数点的,精度要比整数型数据类型(没有小数点)高
所以上题虽然a,b是float型,但在计算后的返回值给了int型的c,因此在输出时精度降低了,木有小数点后面的东东了,你看下面,这不就好了吗
3、函数的调用方式
有自左向右,也有自右向左
以下面这个为例,先不看输出
若自左至右,则函数调用相当于 f (2,3)
若自右向左,则函数调用相当于 f (3,3)
然后就可以理解输出结果了
4、函数声明与调用位置
这两张图上的操作可以看出:
当声明在前面,中间已经调用函数,则需要在后面定义函数
当函数定义已经放到前面,则后面在调用函数之前不加声明也可以
5、递归调用函数(头递归)(尾递归)
头递归的实现中,在进行下一层的调用前,没有进行计算。在下一层返回,才完成了这一层的计算。
尾递归的实现中,在进行下一层的调用前,会先进行计算,而在最终一般条件满足时,会将计算的结果逐层直接返回。
6、判断对错
1、自己定义的函数只可以在main函数中被调用(错)
自己定义的函数可以在其他的数中被调用
2、C语言中定义的函数可以没有返回值(对)
void类型的函数都是没有返回值的
3、尾递归方式调用函数时,往往会传递一个“积累”作用的参数(对)
4、递归方式解决问题效率一定比先得环解决同一问题更高。(错)
递归解法很多时候都不如环解法效率高。
5、在一个递归调用的数中,不可以嵌套调用别的非自身的的数(错)
被递归调用的函数中可以包括其他的函数,比如在递归返回值内使用数学函数
6、c语言的函数定义时可以不写返回值关型(错)
函数是需要有返回值类型的,如果不返回也需要写void类型
7、C语言中定义的函数可以没有参数(对)
7、变参函数——va族
可变参数函数就是输入的参数的个数是可变的
获得a往后的参数列表——va_list类型的变量,表示可变参数列表类型,实际上就是一个char指针
定位a后面第一个参数的位置——va_ start函数,用于获取函数参数列表中可变参数的首指针(获取函数可变参数列表)
获取下一个可变参数列表中的参数——va_arg函数,用于获取当前a所指的可变参数并将a指针移向下一可变参数
结束整个获取可变参数列表的动作——va_end函数,用于结束对可变参数的处理
对可变参数列表的处理过程一般为:
1、用va_list定义一个可变参数列表
2、用va_start获取函数可变参数列表
3、用va_arg循环处理可变参数列表中的各个可变参数
4、用va_end结束对可变参数列表的处理
#include <stdio.h>
#include <inttypes.h> //宏:INT32_MIN所在的头文件
#include <stdarg.h> //va一族所在的头文件
int max_int(int n, ...) //变参列表...中的前n个值中返回一个最大值。省略号(...)表示任意个数的参数
{
int ans = INT32_MIN; //宏:INT32_MIN表示32位整形极小值
va_list arg; //通过va_list定义一个变量arg
va_start(arg, n); //通过va_start给arg赋值,将变量n后面的变参列表的值赋给arg
while (n--)
{
int temp = va_arg(arg, int); //从arg中获取一个int类型的值
if (temp > ans)
{
ans = temp;
}
}
va_end(arg);
return ans;
}
int main()
{
printf("%d\n", max_int(3, 1, 5, 10)); //输出为10
printf("%d\n", max_int(2, 1, 3)); //输出为3
printf("%d\n", max_int(6, 1, 3, 5, 7, 13, 15, 17)); //输出为15,因为第一个参数为6,运行时只获取了第一个参数后面的六个数进行比较运算
return 0;
}
8、函数地址做函数参数
c语言中函数和变量类似,也是有自己的内存地址的。但是,函数不像变量—样可以进行值传递,在将其作为函数参数进行传递时,需要传递它的地址。
对于上面这种用情况,函数g 需要有一个形式参数用于接收函数地址。如下面所示形式的函数,在这个函数定义中,第一个参数需要─个返回值类型为float且有一个int类型参数的函数,第二个参数就是普通的int类型的值。
int g(float (*f)(int), int a)
{
return f(a) ;
}
可以看到,函数g 的第一个形式参数f 接收了一个函数的地址,而在C语言中,直接写f(a)其实就可以通过这个地址使用这个函数,并且给它传入一个参数a。这与用取值符取出函数地址并进行函数调用(*f)(a)的方式是等价的。
不同于传变量地址时需要用取值符的做法,在调用上面这个g 函数,并且将函数地址作为参数时,直接将函数名传入就可以了。如果希望将
float sqrt_minus_one(int x)
{
return sqrt(x)- 1;
}
作为参数,和变量number一起传入给g,那么,调用时则应该写:
g( sqrt_minus_one,number);
将sqrt_minus_one函数和变量number传入到了g后,形式参数float (*f)(int)接收了sqrt_minus_one函数的地址,形式参数int a接收了number的值。接下来,通过f(a)的方式,传入的函数sqrt_minus_one被调用,并将a作为参数传入到了sqrt_minus_one函数中。sqrt_minus_one函数接收到了值后进行了计算并返回,在g函数中,计算结果返回。至此,最初调用的位置得到了计算的最终结果。
举个栗子:牛顿法求方程近似解
#include <stdio.h>
#include <math.h>
#define EPSILON 1e-6
double f(double x)
{
return 2 * pow(x, 3) - 4 * pow(x, 2) + 3 * x - 6;
}
double f_prime(double x)
{
return 6 * pow(x, 2) - 8 * x + 3;
}
double h(double x)
{
return pow(x, 3) - 4 * pow(x, 2) + 3 * x - 6;
}
double h_prime(double x)
{
return 3* pow(x, 2) - 8 * x + 3;
}
double newton(double (*fp)(double),double (*f_prime)(double))
{
double x = 1.5;
while (fabs(fp(x)) > EPSILON)
{
x = x - fp(x) / f_prime(x);
}
return x;
}
int main()
{
printf("%g\n", newton(f,f_prime));
printf("%g\n", newton(h,h_prime));
return 0;
}