函数的定义
一、函数的定义----【函数】
1.函数定义的语法形式
数据类型 函数名(形式参数表)
{
函数体 //执行语句
}
关于函数的定义有如下说明:
-
函数的数据类型是函数的返回值类型(若数据类型为 void ,则无返回值)。
-
函数名是标识符,一个程序中除了主函数名必须为main外,其余函数的名字按照标识符的取名规则可以任意选取,最好取有助于记忆的名字。
-
形式参数(简称形参)表可以是空的(即无参函数);也可以有多个形参,形参间用逗号隔开,不管有无参数,函数名后的圆括号都必须有。形参必须有类型说明,形参可以是变量名、数组名或指针名,它的作用是实现主调函数与被调函数之间的关系。
-
函数中最外层一对花括号“{ }”括起来的若干个说明语句和执行语句组成了一个函数的函数体。由函数体内的语句决定该函数功能。函数体实际上是一个复合语句,它可以没有任何类型说明,而只有语句,也可以两者都没有,即空函数。
-
函数不允许嵌套定义。在一个函数内定义另一个函数是非法的。但是允许嵌套使用。
-
函数在没有被调用的时候是静止的,此时的形参只是一个符号,它标志着在形参出现的位置应该有一个什么类型的数据。函数在被调用时才执行,也就是在被调用时才由主调函数将实际参数(简称实参)值赋予形参。这与数学中的函数概念相似,如数学函数:
f(x)= x 2+x+1
-
这样的函数只有当自变量被赋值以后,才能计算出函数的值。
2.函数定义的例子
定义一个函数,返回两个数中的较大数。
int max(int x,int y)
{
return x>y?x:y;
}
该函数返回值是整型,有两个整型的形参,用来接受实参传递的两个数据,函数体内的语句是求两个数中的较大者并将其返回主调函数。
3.函数的形式
函数的形式从结构上说可以分为三种:无参函数、有参函数和空函数。它们的定义形式都相同。
(1)无参函数
- 无参函数顾名思义即为没有参数传递的函数,无参函数一般不需要带回函数值,所以函数类型说明为void。
(2)有参函数
- 有参函数即有参数传递的函数,一般需要带回函数值。例如:int max(int x,int y) 函数。
(3)空函数
- 空函数即函数体只有一对花括号,花括号内没有任何语句的函数。
- 空函数不完成什么工作,只占据一个位置。在大型程序设计中,空函数用于扩充函数功能。
- 例如:
函数名()
{ }
4. 阶乘之和-案例分析
编写一个阶乘的函数,我们给此函数取一个名字js。
int js(int n) //函数名js,形参int n
{
int s=1;
for (int i=1; i<=n; ++i)
s*=i;
return s;
} //{ }中是函数的函数体
在本例中:
- 函数名叫js
- 只有一个int型的自变量n,函数js属int型。
- 在本函数中,要用到两个变量i,s。
- 在函数体中,是一个求阶乘的语句,n的阶乘的值在s中,最后由return语句将计算结果s值带回,js()函数执行结束,在主函数中js()值就是s的值。
- 函数的参数n是一个接口参数,说得更明确点是入口参数。如果我们调用函数:js(3),那么在程序里所有有n的地方,n被替代成3来计算。在这里,3就被称为实参。又如:sqrt(4),ln(5),这里4,5叫实参。而ln(x),sqrt(x)中的x,y叫形参。
完整的程序如下:
#include<iostream>
using namespace std;
int js(int);//函数的声明
int main(){
int sum=0;
for (int i=1; i<=10; ++i)
sum+=js(i); //函数的调用
cout<<"sum="<<sum<<endl;
return 0;
}
int js(int n) { //定义的函数体
int s=1;
for (int i=1; i<=n; ++i)
s*=i;
return s; //函数的返回值
}
二、函数的声明和调用----【函数】
1.函数的声明
-
调用函数之前先要声明函数原型。在主调函数中,或所有函数定义之前,按如下形式声明:
类型说明符 被调函数名(含类型说明的形参表);下面对js()函数原型声明是合法的。 int js(int n); 也可以: int js(int);
-
可以看到函数原型声明与函数定义时的第一行类似,只多了一个分号,成为了一个声明语句而已。
2.函数的调用
声明了函数原型之后,便可以按如下形式调用函数:
函数名(实参列表) //例题中语句sum+=js(i);
实参列表中应给出与函数原型形参个数相同、类型相符的实参。在主调函数中的参数称为实参,实参一般应具有确定的值。实参可以是常量、表达式,也可以是已有确定值的变量,数组或指针名。函数调用可以作为一条语句,这时函数可以没有返回值。函数调用也可以出现在表达式中,这时就必须有一个明确的返回值。
3.函数的返回值
在组成函数体的各类语句中,值得注意的是返回语句return。它的一般形式是:
return(表达式);// 例题中语句return s;
其功能是把程序流程从被调函数转向主调函数并把表达式的值带回主调函数,实现函数的返回。所以,在圆括号表达式的值实际上就是该函数的返回值。其返回值的类型即为它所在函数的函数类型。当一个函数没有返回值时,函数中可以没有return语句,直接利用函数体的右花括号“}”,作为没有返回值的函数的返回。也可以有return语句,但return后没有表达式。
返回语句的另一种形式是:return;
这时函数没有返回值,而只把流程转向主调函数。
三、函数参数的调用
1、传值调用
这种调用方式是将实参的数据值传递给形参,即将实参值拷贝一个副本存放在被调用函数的栈区中。在被调用函数中,形参值可以改变,但不影响主调函数的实参值。参数传递方向只是从实参到形参,简称单向值传递。举个例子:
#include<iostream>
using namespace std;
void swap(int a,int b)
{
int tmp=a;a=b;b=tmp;
}
int main()
{
int c=1,d=2;
swap(c,d);
cout<<c<<' '<<d<<endl;
return 0;
} //程序输出为:1 2
在此例中,虽然在swap函数中交换了a,b两数的值,但是在main中却没有交换。因为swap函数只是交换c,d两变量副本的值,实参值没有改变,并没有达到交换的目的。
2、传址调用
这种调用方式是将实参变量的地址值传递给形参,这时形参实是指针,即让形参的指针指向实参地址,这里不再是将实参拷贝一个副本给形参,而是让形参直接指向实参,这就提供了一种可以改变实参变量的值的方法。现在用传址调用来实现swap:
#include<iostream>
using namespace std;
void swap(int &a,int &b) //定义swap()函数,形参是传址调用
{
int tmp=a;a=b;b=tmp;
}
int main()
{
int c=1,d=2;
swap(c,d); //交换变量
cout<<c<<' '<<d<<endl;
return 0;
} //程序输出为:2 1
在此例中,因为swap函数的参数为传址调用,&a是指实参变量的地址值传递给形参,所以,在函数swap中修改a,b的值相当于在主函数main中修改c,d的值。
3、引用调用
a)、什么是引用
- 引用,顾名思义是某一个变量或对象的别名,对引用的操作与对其所绑定的变量或对象的操作完全等价
- 语法:
类型 &引用名=目标变量名;
特别注意:
1.& 不是求地址运算符,而是起标志作用
2.引用的类型必须和其所绑定的变量的类型相同
#include<iostream>
using namespace std;
int main(){
double a=10.3;
int &b=a; //错误,引用的类型必须和其所绑定的变量的类型相同
cout<<b<<endl;
}
3.声明引用的同时必须对其初始化,否则系统会报错
#include<iostream>
using namespace std;
int main(){
int &a; //错误!声明引用的同时必须对其初始化
return 0;
}
4.引用相当于变量或对象的别名,因此不能再将已有的引用名作为其他变量或对象的名字或别名
5.引用不是定义一个新的变量或对象,因此内存不会为引用开辟新的空间存储这个引用
#include<iostream>
using namespace std;
int main(){
int value=10;
int &new_value=value;
cout<<"value在内存中的地址为:"<<&value<<endl;
cout<<"new_value在内存中的地址为:"<<&new_value<<endl;
return 0;
}
6.对数组的引用
语法:语法:类型 (&引用名)[数组中元素数量]=数组名;
#include<iostream>
using namespace std;
int main(){
int a[3]={1,2,3};
int (&b)[3]=a;//对数组的引用
cout<<&a[0]<<" "<<&b[0]<<endl;
cout<<&a[1]<<" "<<&b[1]<<endl;
cout<<&a[2]<<" "<<&b[2]<<endl;
return 0;
}
7.对指针的引用
语法:类型 *&引用名=指针名;
//可以理解为:(类型*) &引用名=指针名,即将指针的类型当成类型*
#include<iostream>
using namespace std;
int main(){
int a=10;
int *ptr=&a;
int *&new_ptr=ptr;
cout<<&ptr<<" "<<&new_ptr<<endl;
return 0;
}
b)、引用的应用
1.引用作为函数的参数
#include<iostream>
using namespace std;
void swap(int &a,int &b){//引用作为函数的参数
int temp=a;
a=b;
b=temp;
}
int main(){
int value1=10,value2=20;
cout<<"----------------------交换前----------------------------"<<endl;
cout<<"value1的值为:"<<value1<<endl;
cout<<"value2的值为:"<<value2<<endl;
swap(value1,value2);
cout<<"----------------------交换后----------------------------"<<endl;
cout<<"value1的值为:"<<value1<<endl;
cout<<"value2的值为:"<<value2<<endl;
return 0;
}
特别注意:
-
当用引用作为函数的参数时,其效果和用指针作为函数参数的效果相当。当调用函数时,函数中的形参就会被当成实参变量或对象的一个别名来使用,也就是说此时函数中对形参的各种操作实际上是对实参本身进行操作,而非简单的将实参变量或对象的值拷贝给形参。
-
通常函数调用时,系统采用值传递的方式将实参变量的值传递给函数的形参变量。此时,系统会在内存中开辟空间用来存储形参变量,并将实参变量的值拷贝给形参变量,也就是说形参变量只是实参变量的副本而已;并且如果函数传递的是类的对象,系统还会调用类中的拷贝构造函数来构造形参对象。而使用引用作为函数的形参时,由于此时形参只是要传递给函数的实参变量或对象的别名而非副本,故系统不会耗费时间来在内存中开辟空间来存储形参。因此如果参数传递的数据较大时,建议使用引用作为函数的形参,这样会提高函数的时间效率,并节省内存空间。
-
使用指针作为函数的形参虽然达到的效果和使用引用一样,但当调用函数时仍需要为形参指针变量在内存中分配空间,而引用则不需要这样,故在C++中推荐使用引用而非指针作为函数的参数
-
如果在编程过程中既希望通过让引用作为函数的参数来提高函数的编程效率,又希望保护传递的参数使其在函数中不被改变,则此时应当使用对常量的引用作为函数的参数。
-
数组的引用作为函数的参数:C++的数组类型是带有长度信息的,引用传递时如果指明的是数组则必须指定数组的长度
#include<iostream>
using namespace std;
void func(int(&a)[5]){//数组引用作为函数的参数,必须指明数组的长度
//函数体
}
int main(){
int number[5]={0,1,2,3,4};
func(number);
return 0;
}
- 常引用
语法:const 类型 &引用名=目标变量名;
常引用不允许通过该引用对其所绑定的变量或对象进行修改
#include<iostream>
using namespace std;
int main(){
int a=10;
const int &new_a=a;
new_a=11;//错误!不允许通过常引用对其所绑定的变量或对象进行修改
return 0;
}