定义和使用自己的子程序——函数的介绍
- 1.函数基础语法
- 1.1.基础语法
- 1.2.例题1——距离函数
- 题目描述
- 输入格式
- 输出格式
- 输入输出样例
- 输入 #1
- 输出 #1
- 提示
- 2.void类型
- 3.变量作用域和参数传递
- 3.1.局部变量和全局变量
- 3.2.形式参数和实际参数
- 3.3.例题2——歌唱比赛
- 题目描述
- 输入格式
- 输出格式
- 输入输出样例
- 输入 #1
- 输出 #1
- 4.递归函数
- 4.1.递归函数介绍
- 4.2.例题3——计算阶乘
- 题目描述
- 输入格式
- 输出格式
- 输入输出样例
- 输入 #1
- 输出 #1
- 提示
- 4.3.递归函数运行详细演示和代码
- 4.4.练习1——赦免战俘
- 题目背景
- 题目描述
- 输入格式
- 输出格式
- 输入输出样例
- 输入 #1
- 输出 #1
- 5.基础宏函数
- 6.课后作业
1.函数基础语法
1.1.基础语法
在编写程序时,我们可能会遇到需要大量实现同一种功能的情况。如果能把它们分装成像ceil
,sqrt
这样的函数该多好啊。我们接下来就会学习函数的相关知识。
函数,又叫子函数,也可以被称为子程序。函数定义的基本语法如下:
返回值类型 函数名(参数类型1 参数名1,参数类型2 参数名2,…参数类型n 参数名n)
{
函数体
return 返回值;
}
1.2.例题1——距离函数
距离函数 - 洛谷
题目描述
给出平面坐标上不在一条直线上三个点坐标 ( x 1 , y 1 ) , ( x 2 , y 2 ) , ( x 3 , y 3 ) (x_1,y_1),(x_2,y_2),(x_3,y_3) (x1,y1),(x2,y2),(x3,y3),坐标值是实数,且绝对值不超过 100.00,求围成的三角形周长。保留两位小数。
对于平面上的两个点 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2),则这两个点之间的距离 d i s = ( x 2 − x 1 ) 2 + ( y 2 − y 1 ) 2 dis=\sqrt{(x_2-x_1)^2+(y_2-y_1)^2} dis=(x2−x1)2+(y2−y1)2
输入格式
输入三行,第 i i i 行表示坐标 ( x i , y i ) (x_i,y_i) (xi,yi),以一个空格隔开。
输出格式
输出一个两位小数,表示由这三个坐标围成的三角形的周长。
输入输出样例
输入 #1
0 0
0 3
4 0
输出 #1
12.00
提示
数据保证,坐标均为实数且绝对值不超过 100 100 100,小数点后最多仅有 3 3 3 位。
题意分析
如果我们直接编写程序,回得到这样的代码:
#include<cstdio>
#include<cmath>
using namespace std;
int main(){double x1,y1,x2,y2,x3,y3,ans;scanf("%lf%lf%lf%lf%lf%lf",&x1,&y1,&x2,&y2,&x3,&y3);ans=sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));//使用公式ans+=sqrt((x3-x2)*(x3-x2)+(y3-y2)*(y3-y2));ans+=sqrt((x3-x1)*(x3-x1)+(y3-y1)*(y3-y1));printf("%.2lf", ans);//保留两位小数return 0;
}
这样太啰嗦了,我们可以将计算两点的距离这个功能分装成一个函数。
#include<cstdio>
#include<cmath>
using namespace std;
double sq(double x)//每个子函数都必须定义在主函数外面
{return x*x;
}
double dist(double x1,double y1,double x2,double y2)//计算两点距离的函数
{return sqrt(sq(x1-x2)+sq(y1-y2));//调用前面定义过的函数
}
int main(){double x1, y1, x2, y2, x3, y3, ans;scanf("%lf%lf%lf%lf%lf%lf",&x1,&y1,&x2,&y2,&x3,&y3);ans=dist(x1,y1,x2,y2);//使用前面分装好的函数ans+=dist(x1,y1,x3,y3);ans+=dist(x2,y2,x3,y3);printf("%.2lf",ans);return 0;
}
注意:函数内的每个参数都必须注明类型,不可以出现这种形式:
double f(int x1,x2)
{return x1+x2;
}
我们应该根据引用的顺序,确定函数定义的顺序。比如上文sq函数应在dist函数前面定义dist函数应在main函数前面定义。
2.void类型
在C++中有一种特殊的类型:空类型。即void
。现阶段可以理解为void
只能用来定义函数,不能用来定义变量。void类型的函数不能有返回值,或者返回一个“空”。void类型的函数通常只用来执行一些操作。比如:
void print_data(int a,int b)
{cout<<a<<" "<<b<<endl;
}void print_division(int a,int b)
{if(b==0){cout<<"除数不能为0";return;//提前返回}double ans=a*1.0/b;cout<<ans<<endl;
}
3.变量作用域和参数传递
3.1.局部变量和全局变量
在C++中,定义在函数外部的就叫做全局变量。反之,定义在函数内部的就叫做局部变量。
全局变量会自动赋值为0。
#include<iostream>
using namespace std;
int n,a[110];//这两个是全局变量
int add(int x)
{return x+1;
}
int main(){int v=0;//这是局部变量cin>>n;a[n]=add(a[n]);cout<<a[n]<<endl;
}
3.2.形式参数和实际参数
全局变量和局部变量统称为为实际参数,简称“实参”。定义函数时括号内的参数都叫做形式参数,简称“形参”。
在函数运行的过程中,并不是直接引用实参。而是将实参的值拷贝一份来使用。函数返回之后,所有的形参都会被销毁。所以函数括号内的参数都叫做形式参数。
#include<iostream>
using namespace std;
int Add(int x,int y)//x和y是形参
{return x+y;
}
int main(){int a,b;//a和b是实参cin>>a>>b;cout<<Add(a,b);return 0;
}
3.3.例题2——歌唱比赛
歌唱比赛 - 洛谷
题目描述
n ( n ≤ 100 ) n(n\le 100) n(n≤100) 名同学参加歌唱比赛,并接受 m ( m ≤ 20 ) m(m\le 20) m(m≤20) 名评委的评分,评分范围是 0 0 0 到 10 10 10 分。这名同学的得分就是这些评委给分中去掉一个最高分,去掉一个最低分,剩下 m − 2 m-2 m−2 个评分的平均数。请问得分最高的同学分数是多少?评分保留 2 2 2 位小数。
输入格式
第一行两个整数 n , m n,m n,m。
接下来 n n n 行,每行各 m m m 个整数,表示得分。
输出格式
输出分数最高的同学的分数,保留两位小数。
输入输出样例
输入 #1
7 6
4 7 2 6 10 7
0 5 0 10 3 10
2 6 8 4 3 6
6 3 6 7 5 8
5 9 3 3 8 1
5 9 9 3 2 0
5 8 0 4 1 10
输出 #1
6.00
题意分析
我们可以使用类似于“打擂台”的方法寻找最大最小值。设置一个初始擂主。遍历数组,如果找到的数比当前擂主大/小就更新擂主。然后我们分装一个函数,用来计算每位同学的分数。
程序如下:
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,s[10010],maxsum=-1;//用maxsum存储最大的分数,注意初值要赋一个较小值
void score_stat(int a[],int m)//传入一个数组,计算分数总和
{int max_score=0,min_score=10,sum=0;//最大分、最小分和分数总和for(int i=0;i<m;i++)//遍历数组{max_score=max(max_score,a[i]);//更新最大分min_score=min(min_score,a[i]);//更新最小分sum+=a[i];//更新分数总和}maxsum=max(maxsum,sum-max_score-min_score);//更新最大分数
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)//循环n组数据{for(int j=0;j<m;j++)//循环读入一组数据scanf("%d",&s[j]);score_stat(s,m);}printf("%.2lf",double(maxsum)/(m-2));//输出答案return 0;
}
注意:这里可以把数组作为参数传递进来。写法如上。当数组作为参数传递时,是直接传递进来的。比如上文的函数中的数组a就是函数外数组s的别名。改变数组a的值时数组s的值也会改变。
4.递归函数
4.1.递归函数介绍
函数可以被主函数和其他函数调用,也可以被自己调用。像这种自己调用自己的函数就叫做递归函数。
4.2.例题3——计算阶乘
计算阶乘 - 洛谷
题目描述
求 n ! n! n!,也就是 1 × 2 × 3 ⋯ × n 1\times2\times3\dots\times n 1×2×3⋯×n。
挑战:尝试不使用循环语句(for、while)完成这个任务。
输入格式
第一行输入一个正整数 n n n。
输出格式
输出一个正整数,表示 n ! n! n!。
输入输出样例
输入 #1
3
输出 #1
6
提示
数据保证, 1 ≤ n ≤ 12 1 \leq n\le12 1≤n≤12。
4.3.递归函数运行详细演示和代码
题意分析
对于递归的题目,我们可以将这个问题拆分成多个子问题,并逐个解决。比如这个问题,我们可以把这个问题拆解成f(n)=n*f(n-1)
其中f(n)
代表n的阶乘。我们就可以得出程序:
#include<iostream>
using namespace std;
long long f(int n)//阶乘的数据较大,所以使用long long类型返回
{return n*f(n-1);
}
int main(){int n;cin>>n;cout<<f(n);return 0;
}
但运行这个程序,我们会发现它结束不了。这是因为,当n=1
时,f(n)
应该返回1。但我们却没有加特殊判断。加上就可以通过这道题了。
#include<iostream>
using namespace std;
long long f(int n)//阶乘的数据较大,所以使用long long类型返回
{if(n==1)return 1;//递归边界return n*f(n-1);
}
int main(){int n;cin>>n;cout<<f(n);return 0;
}
像这种递归函数中递归终止的条件,就叫做递归边界。
当n=5
时,递归函数f(n)
的运行过程如下。
如果看完这张图也不理解也没关系。随着学习的深入,你可能会在某一瞬间领悟。我就是这样子的。所以不用气馁,将一切都交给时间。
4.4.练习1——赦免战俘
赦免战俘 - 洛谷
题目背景
借助反作弊系统,一些在月赛有抄袭作弊行为的选手被抓出来了!
题目描述
现有 2 n × 2 n ( n ≤ 10 ) 2^n\times 2^n (n\le10) 2n×2n(n≤10) 名作弊者站成一个正方形方阵等候 kkksc03 的发落。kkksc03 决定赦免一些作弊者。他将正方形矩阵均分为 4 个更小的正方形矩阵,每个更小的矩阵的边长是原矩阵的一半。其中左上角那一个矩阵的所有作弊者都将得到赦免,剩下 3 个小矩阵中,每一个矩阵继续分为 4 个更小的矩阵,然后通过同样的方式赦免作弊者……直到矩阵无法再分下去为止。所有没有被赦免的作弊者都将被处以棕名处罚。
给出 n n n,请输出每名作弊者的命运,其中 0 代表被赦免,1 代表不被赦免。
输入格式
一个整数 n n n。
输出格式
2 n × 2 n 2^n \times 2^n 2n×2n 的 01 矩阵,代表每个人是否被赦免。数字之间有一个空格。
输入输出样例
输入 #1
3
输出 #1
0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 1
0 0 0 0 0 1 0 1
0 0 0 0 1 1 1 1
0 0 0 1 0 0 0 1
0 0 1 1 0 0 1 1
0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1
题意分析
我们可以将这个方阵分成四个小方阵处理,我们先将这个方阵全部初始化为0。分别处理剩下的三个方阵。递归边界为,处理到最后规模为1的方阵后就直接赋值为1。代码如下:
#include<iostream>
#include<cmath>
using namespace std;
#define MAXN 1030//2^10等于1024
int a[MAXN][MAXN],n,i,j;
/*cal是处理方阵的函数。x和y代表这个方阵的第一个人的坐标,n代表规模即代表这个方阵的大小是n*n的。*/
void cal(int x,int y,int n)
{if(n==0)//如果规模为0,即不可分割{a[x][y]=1;//赋值return;//返回}cal(x+pow(2,n-1),y,n-1);//处理右上角的方阵cal(x,y+pow(2,n-1),n-1);//处理左下角的方阵 cal(x+pow(2,n-1),y+pow(2,n-1),n-1);//处理右下角的方阵
}
int main()
{cin>>n;cal(1,1,n);//从规模为n的方阵开始处理 for(i=1;i<=pow(2,n);i++)//打印方阵 {for(j=1;j<=pow(2,n);j++)cout<<a[i][j]<<' ';cout<<endl;}return 0;
}
5.基础宏函数
还记得我们之前了解过的宏定义吗?既然它是简单粗暴的替换,那可不可以替换函数呢?当然可以。一个最基础的宏函数定义如下:
#define Add(a,b)return (a)+(b);
当然,我们也可以使用\
宏定义转接符,这样就可以定义多行宏函数了。具体使用如下:
#define multiplication(a,b)cout<<a<<'*'<<b<<'='<<(a)*(b);\
return (a)*(b);
注意:在宏定义时一定要勤加括号,因为宏定义是直接简单粗暴的替换。可能会出现这种情况:
multiplication(1+2,3)
不加括号宏编译后就会变成这样。
1+2*3
不符合预期效果。所以要宏定义时要勤加括号。
6.课后作业
函数与结构体 - 洛谷 中函数部分
喜欢就订阅此专辑吧!
【蓝胖子编程教育简介】
蓝胖子编程教育,是一家面向青少年的编程教育平台。平台为全国青少年提供最专业的编程教育服务,包括提供最新最详细的编程相关资讯、最专业的竞赛指导、最合理的课程规划等。本平台利用趣味性和互动性强的教学方式,旨在激发孩子们对编程的兴趣,培养他们的逻辑思维能力和创造力,让孩子们在轻松愉快的氛围中掌握编程知识,为未来科技人才的培养奠定坚实基础。
欢迎扫码关注蓝胖子编程教育