NOIP学习小站
西安交通大学附属中学航天学校

自定义函数

只使用内置函数往往是不够的,解决实际问题时,很多时候需要我们根据实际情况自定义函数并调用函数解决问题。自定义函数的优势是一次定义,需要的时候调用即可,可以有效避免大量相同或相似的代码重复出现导致程序臃肿繁琐的情况,也能让程序的逻辑结构更加清晰。

一、最简单的函数:没有参数没有返回值

我们来定义一个没有参数没有返回值的函数,函数的功能是输出"Hello World"

void say(){
	cout<<"Hello World"<<endl;
}

上面定义函数的程序代码中,void表示函数的返回值是空类型(这里表示没有返回值),函数名是say,函数名后面的小括号里没有任何内容(也就是没有任何参数),{}中的内容称为函数体。定义了函数后,在需要使用这个函数的地方直接调用函数即可:

#include<iostream>
using namespace std;

//竞赛时一般将定义函数代码写到main函数前 
void say(){
	cout<<"Hello World"<<endl;
}

int main()
{
	say();   //调用函数 
	for(int i=1;i<=10;i++) say(); 
    return 0;
} 

程序执行过程如下:

可见调用函数时,会转到函数的定义处执行函数体内容,执行完毕后返回到调用函数处。

二、有参数、没有返回值的函数

接下来,我们定义一个函数,能够输出指定行数的"Hello World"

#include<iostream>
using namespace std;

void say(int n){   //有一个int类型参数的函数 
	for(int i=1;i<=n;i++)
		cout<<"Hello World"<<endl;
}

int main()
{
	cout<<"do something"<<endl;
	say(10);   //调用函数
	cout<<"do something else"<<endl; 
    return 0;
}

定义函数代码中函数名后小括号内容int n指明函数有一个int类型的参数n,函数体中也使用了参数n(用来实现n次循环)。定义函数的代码中的参数称为形式参数,没有具体的值,只是形式上指代了一个数据。调用函数的语句say(10);,小括号中的内容称为实际参数,调用过程如下:

可知调用函数时,会转向函数定义语句处,先自动将实际参数的值赋值(拷贝)给形式参数,然后执行函数体内容,最后又返回到调用函数处。

当然可以多次调用这个函数,并且调用时可以指定不同的实际参数:

#include<iostream>
using namespace std;

void say(int n){ 
	for(int i=1;i<=n;i++)
		cout<<"Hello"<<endl;
}

int main()
{
	say(10);
	say(5); 
	say(0); 
    return 0;
} 
#include<iostream>
using namespace std;

void say(int n){ 
	for(int i=1;i<=n;i++)
		cout<<"Hello"<<endl;
}

int main()
{
	int n;
	cin>>n;
	say(n); 
    return 0;
} 

当然函数的参数也可以是多个,下面的函数用来输出\(n\)行\(m\)列由'*'组成的矩形:

#include<iostream>
using namespace std;

void print(int n,int m){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cout<<'*';
		}
		cout<<endl;
	}
}

int main()
{
	print(5,10);
	print(10,24); 
    return 0;
}

调用函数时,会按照调用语句小括号中实际参数的顺序将实际参数的值依次赋值(拷贝)给形式参数。这里甚至还可以再用一个参数指定用哪个字符组成矩形:

#include<iostream>
using namespace std;

void print(int n,int m,char c){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cout<<c;
		}
		cout<<endl;
	}
}

int main()
{
	print(5,10,'*');
	print(10,24,'@'); 
    return 0;
} 

三、有返回值的函数

前面我们调用内置函数,以math库中的数学函数为例:

cout<<pow(2,3)<<endl;
double ans = sqrt(2);
cout<<ans<<endl;
cout<<pow(2,3)+sqrt(2)<<endl;

仔细观察会发现,这里调用pow、sqrt函数与本文前面调用没有返回值的函数很不同,调用pow、sqrt函数会有一个计算结果,我们可以直接将计算结果输出,也可以赋值给变量,甚至参加运算。这样的函数是有返回值的函数,当然也可以自定义有返回值的函数。

下面我们来定义一个很简单的函数,计算两个int整数\(x\)和\(y\)的和,很显然,函数的计算结果(也就是函数的返回值)也应该是int类型:

#include<iostream>
using namespace std;

int sum(int x,int y){
	int z = x+y;
	return z;
}

int main()
{
	cout<<sum(1,2)<<endl;
	int a,b,c;
	cin>>a>>b;
	c = sum(a,b);
	cout<<c<<endl;
	cout<<sum(1,2)+sum(a,b)<<endl;
    return 0;
} 

定义函数语句开始处的int表示函数的返回值是int类型,sum是函数名,函数有两个int类型的参数x、y。函数体中int z = x+y;将x+y的结果赋值给变量z。最重要的语句是return z;表示函数返回值是变量z的值。具体执行过程如下:

调用函数时,会自动转到函数的定义语句处,将实际参数的值依次拷贝给形式参数,然后执行函数体语句。函数体中的return语句用来将该语句后的值返回到调用函数处使用。需要特别注意的是,return语句实际返回的内容应该和函数头指明的函数返回值类型一致,例如本例中return z;返回int类型变量z的值与函数头int sum(int x,int y)中指明函数返回值类型为int一致。

当然,这里还可以进一步将sum函数体中的内容精简:

int sum(int x,int y){
	return x+y;   //返回值是一个表达式(的计算结果)
}

四、main函数

现在我们应该知道,C++程序框架中的main就是我们定义的一个返回值为int类型名称为main的函数,只不过一般不会在程序中再写代码调用main函数,main函数是程序运行是被自动调用的。main函数中的return 0;表示返回 \(0\),其实main函数的返回值是可以被运行本程序的软件(例如Dev C++或者操作系统)捕获到,返回值为 \(0\) 表示程序正常结束,没有任何异常发生:

来看下面的程序:

#include<iostream>
using namespace std;
int main()
{
	cout<<5/0<<endl;    //除数是0,编译时会出现Warning,执行时会出现运行时错误
    return 0;
} 
#include<iostream>
using namespace std;
int main()
{
	int a[5];
	//数组下标溢出,导致程序出现异常
	//严格环境下这里使用a[5]就下标溢出了
	//Dev c++不是严格的运行环境,溢出很多可能都不会出现异常 
	for(int i=0;i<=1000;i++) a[i]=i;
    return 0;
} 

程序运行反馈信息如下(main返回值非 \(0\) 表示程序运行出现异常):

这就是一般情况下(特别是竞赛时)不能在main函数中return一个非 \(0\) 值的原因,这样的话判分程序会认为程序运行出现异常。