1、库函数
2、自定义函数
1、实际参数
2、形式参数
1、传值调用
2、传址调用
在维基百科中定义为:子程序
在计算机科学中,子程序是一个大型程序中的某部分代码,有一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
1、库函数
1、库函数是把函数方静库里,供其他人使用的一个方式。方法就是把一些常用的函数编完放进一个文件里,供不一样的人完成调用。调用时把它所在的文件名用#include<>加到里面就可以了。
2、通常就是指编译器供应的可在c源程序中调用的函数。可分为两类,一类是C语言标准规定的库函数,一类是编辑器特定的库函数。
2、自定义函数
由程序员根据自己的需求编写,自定义函数不仅要在程序中定义函数本身,必须还要在主函数中调用该函数。自定义函数和库函数一样,有函数名,返回值类型和函数参数。
函数介绍:
ret_type fun_name(para1, ,)
{
starement;
}
ret_tdype 是返回类型
fun_name 是函数名
para1 是函数参数
函数参数可以有一个,多个,也可以没有。
举例:
1、写一个函数可以找出两个整数中的较大值。这里用的方法是(传值调用)
#include
int get_max(int x,int y)//形参
{
if(x > y)
return x;
else
return y;
}
int main()
{
int a=0;
int b=0;
scanf("%d %d",&a,&b);//输入
//函数使用场景
int m = get_max(a,b);//实参 传值调用
printf("%d\n",m);
return 0;
}
2、 写一个函数可以交换两个整型变量的内容,这里使用的是(传址调用)
//当函数调用的时候
//实参传给形参时,形参将是实参的一份临时拷贝
//所以对形参的修改是不影响实参的
#include
void Swap(int* px, int* py)
{
int t = 0;
t = *px;
*px = *py;
*py = t;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);//输入
printf("交换前:a=%d b=%d\n", a, b);
//传值调用
//传地址,传址调用
Swap(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
1、实际参数(参数):
真实传给函数的参数,叫实参。
实参可以是:常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行函数调用时,他们都必须有确定的值,以便把这些值传送给形参。
2、形式参数(形参):
形式参数是指函数名后括号中的变量,因为形式参数只有在被函数调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数中在函数中有效。
#include
int get_max(int x,int y)//函数get_max()函数括号中的 int x,int y 是形参
{
return(x>y?x:y);
}
int main()
{
int a=0;
int b=0;
scanf("%d %d",&a,&b);
//int m=get_max(a,20);//实参可以是:常量(20),变量(a)
//int m=get_max(a,2+3);//实参可以是:表达式(2+3)
int m=get_max(a,get_max(3,4));//实参可以是:函数(get_max(3,4)),
printf("%d\n",m);
return 0;
}
这里三个int m只是为前面的概念做例子,实际写代码时应该按照标准 实参的个数、出现的顺序和实参的类型,应该与函数定义的设计一一对应。
注释:
1、形式参数被调用的时候才开辟空间;不被调用时,只是一段代码,不占用内存存储单元。函数调用结束后,形参所占用的内存空间会被收回或者释放。
2、函数的个数、出现的顺序和参数的类型,应该与函数定义中形参的设计一一对应。
1、传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
2、传址调用
(1)、传址调用是把函数外部创建的内存地址传递给函数参数的一种调用函数的方式。
(2)、这种传参的方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
#include
void Swap1(int x, int y)//形式参数
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
void Swap2(int *px, int *py) //被调函数(被调用者)一般为自定义函数或者库函数
{
int tmp = *px; //这个函数内部可以操作函数外部的变量
*px = *py;
*py = tmp;
}
int main() //主调函数(调用者)一般为main()函数
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
//传值调用
//Swap1(a, b); //实参
//传址调用
Swap2(&a, &b); //实参
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
注释: 传址调用和传值调用的使用环境
使用传值调用:把主调函定义的变量传给被调函数,不需要改变变量的值,就可以实现被调函数的内容。
使用传址调用:把主调函定义的变量传给被调函数,可能会改变变量的值,需要传递地址,才可以完成被调函数的内容。
1、函数的嵌套调用
#include
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i=0;
for(i=1;i<=3;i++)
{
new_line();
}
}
int main()
{
three_line()
return 0;
}
结果是:
hehe
hehe
hehe
three_line函数内部嵌套了一个new_line函数。
注释:函数内部可以嵌套调用 但是不能在函数内部再写一个函数。
#include
#include
int main()
{
printf("%d\n",strlen("abcdef"));
return 0;
}
把一个函数的返回值当作一个函数参数。strlen()函数的返回值在这里当作printf()函数的一个参数,这种叫链式访问。
举例:
#include
int main()
{
printf("%d",printf("%d",printf("%d",34)));//printf函数的返回值是打印在屏幕上字符的
//个数
return 0;
}
结果是:4321 每次printf的返回值都会打印在屏幕上
注释:printf函数的返回值是打印在屏幕上字符的个数
所以printf("%d",34)打印的是34
printf("%d",printf("%d",34))打印的是2
printf("%d",printf("%d",printf("%d",34)))打印的是1
1、函数的声明:
1、 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体存不存在,函数声明决定不了。
2、函数声明一般出现在函数的使用之前。要满足先声明后使用。
3、函数的声明一般要放在头文件中,例如test.c。
4、函数的定义在源文件中,例如test.c。
在学习中有时遇到这两种情况需要用到函数的声明。
第一幅图:在编译器中编译时,他会从上到下的扫描代码,函数定义在主函数之后会导致主函数运行时出现报错Add未定义。
第二幅图:当test.c源文件中的代码要使用Add.c源文件中的函数时,需要在头文件test.h中进行声明,在test.c源文件中写入头文件#include"Add.h".。
注释:使用库函数用#include<>,当使用自己写的函数用#include" "。
2、函数的定义
函数的定义是指函数的具体实现,交代函数的功能实现。
1、什么是递归?
程序调用自身的编程技巧成为递归。
递归作为一种算法在程序设计语言中广泛使用。一个过程或函数在其定义或说明中有直接或间接调用其自生的一种方法。递归通常用于将一个复杂的问题层层转化为一个与原问题相似的规模小的问题来求解。
递归地运用只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大的减少了程序的代码量。
递归的只要思考方式在于:把大事化小
2、递归的两个必要条件
(1)、存在限制条件,当满足这个限制条件的时候,递归便不再继续。
(2)、每次递归调用之后越来越接近这个限制条件。
实例:追简单的递归
这个代码 是死递归最终会栈溢出。每一次函数调用都会在栈区申请内存空间。
函数递推的优点:
1、代码简洁
2、易于理解
举例:接受一个整型值(无符号),按照顺序打印他的每一位。
例如:输入:1234、输出:1 2 3 4
思路分析:(1)、 要打印1234中的4,只需要1234%10就可将4拿出来。同理123也可以依次摘出来。
(2)、用4举例,现在将4拿出来,要得到123,只需要1234/10就可以得到123。同理12、1都可以依次得到。
(3)、函数递归,(先递推在回归)通过多次调用(递推)将变量n(1234/10)的值,传给自身调用的print函数做参数。在通过回归依次打印12%10、123%10、1234%10,来实现按照顺序打印每一位1 2 3 4
(3)、没有写1%10,应为在编写代码时有限制条件,在满足限制条件时,将1已经输出。
#include
void main()
{
if (n > 9)
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 0;
scanf("%u", &num);//1 u%打印无符号整数
print(num);//按照顺序打印num的每一位
return 0;
}
注释:
在这个程序中
if(n>9)
{
print(n/10);
}是限制条件,少一个都会让程序死递归。函数每调用(递推)一次,在栈区申请一次空间,随着函数回归相应的栈区空间销毁(释放)。
画图分析:
函数递推的缺点:
1、时间和空间的消耗比较大:
递归由于是函数调用自身,而函数调用是消耗时间和空间的。每一次函数调用,都需要在内存栈中分配空间以保存参数、返回值和临时变量。而往栈中压入和弹出数据也都需要时间,所以降低了效率。
2、重复计算
递归中有很多计算都是重复的,递归的本质是把一个问题分解为多个小问题,多个小问题存在重叠的部分,即存在重复计算。
如斐波那契数列的递归实现。
3、栈溢出
递归可能存在栈溢出,每次调用时都会在内存栈中分配空间。而栈的空间容量是有限的,当调用的次数太多,就可能会超出栈的空间,造成调用栈溢出。
举例:求第n个斐波那契数
斐波那契数列:1 1 2 3 5 8 13 21 34 55 ...... 前两个数相加的和是下一个数。
代码思路:(1)、利用递归的思路写斐波那契数,是从后往前计算,想要知道第n个斐波那契数,就需要知道第n-1个和n-2个斐波那契数。输入n,n<=2时,输出1,n>2时输出fib(n-1)+fib(n-2)。
(2)、利用循环的思路写斐波那契数,是从前往后计算
(1)、递归思想
#define _CRT_SECURE_NO_WARNINGS 1
#include
int count = 0;
int Fib(int n)
{
if (n == 3)//检测第三个斐波那契数重复计算的次数
count++;
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
printf("count = %d\n", count);
return 0;
}
注释:当n=50时,代码运行太慢,有巨大的重复计算。
(2)、循环思想
#include
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n>2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
在这里补充迭代的知识点:
(1)、 迭代又称辗转法,是一种不断用变量旧值进过相同的计算得出新值的过程,常适用于需要重复的去做一组指令的情况:在每次执行完该指令后,都会保存当前的结果值,用于下一次的计算。
(2)、迭代的特点:
一个过程结束后再次进行该过程。他的思路是,从前往后推理。
由于递归的局限性,一些问题难以用递归的方法实现。这时候我们可以考虑迭代或者循环的思路解决。