一、函数及其应用
1.函数的概念
函数是程序中一段相对独立的代码,这段代码能够实现某一项具体、独立、完整的功能。
函数分为系统函数和自定义函数。
常用的系统函数包括
- 主函数:main
- 数字函数(cmath):fabs、sqrt、powceil、floor...
- 字符串函数(cstring):strlen、strcmp...
- 算法函数(algorithm): max、min...
2.函数的定义
返回类型 函数名(参数列表)
{
函数体
}
例:计算两个数的较大者
double imax(double a, double b){
double ans;
if(a>b) ans=a; else ans=b;
return ans;
}
说明:
- 自定义函数类似于系统函数,参数列表相当于输入;
- 函数名类似于变量名,命名规则一致,注意知名见意;
- 参数列表可以为空,即无参函数,也可以有多个参数,参数之间用逗号隔开,每个参数包括参数类型和参数名;
- 返回类型除了可以是int、double、bool、char等以外,也可以没有返回值(void);
- 如返回类型不是void,函数体至少包含一个return语句,其返回值类型应与返回类型一致。
3.函数的作用
- 代码复用
同一个函数可以被一个或多个函数调用多次,从而减少重复代码的编写。
- 问题分析
按照模块化思想,一个大的应该分解成若干结构清晰、功能独立、调试方便的小程序段。
例1:计算阶乘和
计算3!+ 5!+ 14!(其中n!=1x2x3x...xn )
分析:
只要实现一个自定义函数fac用来计算n!即可。
#include<bits/stdc++.h>
using namespace std;
int fac(int x){
int s=1;
for(int i=1;i<=x;i++) s*=i;
return s;
}
int main(){
int sum=fac(3)+fac(5)+fac(14);
cout<<sum<<endl;
return 0;
}
例2: 输出如图所示的图形
#include<bits/stdc++.h>
using namespace std;
void pic(int n){
int i,j,k;
for(i=1;i<=n;i++){
for(j=1;j<=40-i;j++)
cout<<" ";
for(k=1;k<=2*i-1;k++)
cout<<"*";
cout<<<endl;
}
}
int main({
pic(4);pic(6);
return 0;
}
4.函数的调用
程序中对函数的使用称为函数调用。
函数可以在表达式中被调用,也可以在其他函数体内被调用,还可作为另一个函数的参数被调用。
函数调用的一般形式为:
函数名(实参1,实参2,...)
4.1形式参数与实际参数
(1)实际参数:在调用函数名后面的()内的变量或表达式称为实际参数(简称实参)。
(2)形式参数:在被调用函数名后面的()内的变量称为形式参数(简称形参)。
例如:
#include<bits/stdc++.h>
using namespace std;
int maxint(int x,int y){ // 函数定义的x,y变量是函数调用的形参
if(x>y)
return x;
else
return y;
}
int main(){
int a,b;
cin>>a>>b;
cout<<maxint(a,b)<<endl; // 函数定义的a,b变量是函数调用的实参
return 0;
}
注意:
(1)函数的实参与形参必须数量一致、顺序一致、数据类型一致。
(2)参数的类型可以是常量、变量、表达式、数组或函数等。
4.2调用方法
对于没有返回值的函数,调用时直接单独一行写“函数名(参数);”
对于有返回值的函数,调用时必须以值的形式出现在表达式中。
程序可以调用任何前面已定义的函数,如果我们需要调用在后面定义的函数,就要先声明该被调用的函数。
4.3函数调用的执行过程
(1)计算实际参数的值。
(2)将实际参数传递给被调用函数的形式参数,程序执行跳到被调用的函数中。
(3)执行函数体,执行完后如果有返回值,则把返回值返回给调用该函数的地方继续执行。
5.参数的传递
5.1传值调用
传值调用方式将实参的值复制给形参(实参可以是变量、常量、或表达式)。本质上,形参是实参的一个副本,改变形参的值不会影响实参中值的内容。实参与相对应的形参甚至可以是相同的参数名,但它们是不同的内存存储地址。
5.2引用调用
引用是使用变量的内存存储地址来访问操作变量的特殊形式。
引用变量类型定义:
数据类型& 变量
数据类型 &变量
引用类型定义中的数据类型称为引用的 “基类型” ,如int&是int类型的引用。
5.3数组作为参数
将数组名作为参数传给函数,实际上是把数组的地址传给函数。形参数组和实参数组的首地址重合,因此在被调用函数中对数组元素值进行改变,主调函数中实参数组的相应元素值也会改变。
6.变量的作用域
变量按其在程序中作用的范围,分为全局变量和局部变量。
6.1全局变量
全局变量是指在程序代码开始执行前初始化并分配存储空间的变量,它在整个程序中(主程序和子程序)都起作用,整个程序执行结束后释放存储空间。
6.2局部变量
局部变量是指在函数内部定义的变量,它只在函数内部起作用,在函数调用是动态初始化,函数调用完毕后释放存储空间。
7.函数的应用
例1:最大公约数
输入两个数,输出它的最大公约数
#include<bits/stdc++.h>
using namespace std;
int f(int x,int y){
for(int i=x;i>=1;i--)
if(x%i==0 && y%i==0)
return i;
}
int main(){
int x,y;
cin>>x>>y;
cout<<f(x,y)<<endl;
return 0;
}
例2:阶乘(factorial)
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。
计算3!+7!+11!
#include<bits/stdc++.h>
using namespace std;
int fac(int x){
int s=1;
for(int i=1;i<=x;i++)
s*=i;
return s;
}
int main(){
cout<<fac(3)+fac(7)+fac(11)<<endl;
return 0;
}
例3:孪生素数
输出1000以内所有的孪生素数对(差值为2)以及总对数。
#include<bits/stdc++.h>
using namespace std;
bool f(int x){
bool s=true;
for(int i=2;i<=x-1;i++)
if(x%i==0)
s=false;
return s;
}
int main(){
int s=0;
for(int i=3;i<=997;i+=2)
if(f(i)&&f(i+2))
s+=1;
cout<<s<<endl;
return 0;
}
例4:数字统计
统计在给定范围[L,R]的所有整数中,数字2出现的次数。
比如在给定范围[2,22],数字2在2中出现了1次,在12中出现了1次,在20中出现了一次,在21中出现了1次,在22中出现了两次,所以一共出现了6次
#include<bits/stdc++.h>
using namespace std;
int f(int x){
int s=0;
while(x){
if(x%10==2)
s+=1;
x=x/10;
}
return s;
}
int main(){
int l,r;
cin>>l>>r;
int s=0;
for(int i=l;i<=r;i++)
s+=f(i);
cout<<s<<endl;
return 0;
}
例5:火柴数字
火柴数字如下图:
现用6根火柴摆数字,请列出所有能摆出的自然数,要求每个数火柴全用上,不多不少。
#include<bits/stdc++.h>
using namespace std;
int tj(int x){
int f[]={6,2,5,5,4,5,6,3,7,6}; //0~9分别需要多少根火柴棒
int s=0; //统计总数目
int ti=x; //因x会在内层循环时发生改变,在此对i进行保存以便后续使用
while(ti){ //计算ti需要多少根火柴棒,ti非0才会执行循环,所以0不在计算范围
s+=f[ti%10]; //个位所用的数量
ti=ti/10; //整除10,除去个位数
}
return s;
}
int main(){
cout<<0<<endl;
for(int i=1;i<112;i++) //穷举范围
if(tj(i)==6)
cout<<i<<endl;
return 0;
}
二、递归及其应用
8.递归函数
递归是计算科学领域中一种重要的计算思维模式。它既是一种抽象表达的手段,也是一种问题求解的重要方法。直接或间接地调用自身的方法称为递归,递归分为递推和回归。指一种通过重复将问题分解为同类的子问题,从而解决问题的方法。
在数学与计算机领域中,递归函数是指用函数自身来定义该函数的方法。如著名的斐波那契数列"1 1 2 3 5 8 13 …"
,可以递归定义为
1(n=1或n=2)
F(n)=
F(n−1)+F(n−2) (n>2)
递推关系是递归的重要组成,例如上式中的 F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n)=F(n-1)+F(n-2)F(n)=F(n−1)+F(n−2);而边界情况是递归的另一要素,例如上式中当 n 为 1 或 2 时的 F ( n ) = 1 F(n)=1F(n)=1,保证递归能在有限次的计算后得出结果,而不会产生无限的情况。
【样例研习】
以下为用递归算法求斐波那契数列第n项的C++函数
int fibo(int n){
if(n==1 || n==2)
return 1;
else
return fibo(n-1)+fibo(n-2);
}
本题的递推公式为 fibo( n ) = fibo ( n−1 ) + fibo ( n−2 ),边界条件为 fibo ( 1 or 2 ) = 1
求解过程如下:
fibo(5)
=fibo(4)+fibo(3)
=(fibo(3)+fibo(2))+(fibo(2)+fibo(1))
=((fibo(2)+fibo(1))+1)+(1+1)=(1+1)+1)+(1+1)
=5
递归算法需确定的两个条件:
1、递推关系
2、边界条件(即递归退出的条件)
常用的递归代码框架:
函数类型 函数名(形式参数):
if(边界条件)
语句组
else
递推公式
递归与迭代:
(1)递归与迭代算法都需要重复执行某些代码
(2)递归是重复调用函数自身,遇到满足终止条件时逐层返回;迭代是重复反馈过程,其目的是逼近所需目标或结果,通常使用计数器结束循环。
9.递归的应用
1、斐波那契数列:1 1 2 3 5 8 13 21 34 55 89 ...
已知前两项为1,之后每一项等于前两项之和。
现输入n,请输出兔子数列的第n项。
2、用递归法求4!+5!+6!+7!的值。
1 ( n = 0 )
F ( n ) =n ∗ F ( n − 1 ) ( n > 0 )
本题的递推公式为 fac ( n ) = fac ( n−1 ) ∗ n,边界条件为 fac ( 0 ) = 1 。
fac(4)
=4∗fac(3)
=4∗(3∗fac(2))
=4∗(3∗(2∗fac(1)))
=4∗(3∗(2∗(1∗fac(0))))
=4∗(3∗(2∗(1∗1)))
3、用递归法求1+2+3+…+100的值。
4、输入两个数,求其最大公约数。
根据上文中所讲的辗转相除法可得公式如下:
b(a%b=0)
gcd(a,b)=gcd(b,a%b) (a%b=0)
5、花果山上有一洞,小猴每次采取跳1阶或者跳3阶的办法从山下跳跃上台阶进洞,编程输入台阶数,输出有多少种不同的跳法。