定义和使用自己的子程序——函数的介绍

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=(x2x1)2+(y2y1)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(n100) 名同学参加歌唱比赛,并接受 m ( m ≤ 20 ) m(m\le 20) m(m20) 名评委的评分,评分范围是 0 0 0 10 10 10 分。这名同学的得分就是这些评委给分中去掉一个最高分,去掉一个最低分,剩下 m − 2 m-2 m2 个评分的平均数。请问得分最高的同学分数是多少?评分保留 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 1n12

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(n10) 名作弊者站成一个正方形方阵等候 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.课后作业

函数与结构体 - 洛谷 中函数部分

喜欢就订阅此专辑吧!

【蓝胖子编程教育简介】
蓝胖子编程教育,是一家面向青少年的编程教育平台。平台为全国青少年提供最专业的编程教育服务,包括提供最新最详细的编程相关资讯、最专业的竞赛指导、最合理的课程规划等。本平台利用趣味性和互动性强的教学方式,旨在激发孩子们对编程的兴趣,培养他们的逻辑思维能力和创造力,让孩子们在轻松愉快的氛围中掌握编程知识,为未来科技人才的培养奠定坚实基础。

欢迎扫码关注蓝胖子编程教育
在这里插入图片描述

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值