目录
概述
为了方便使用,我们往往会将较大的程序分为几个程序模块,每个模块实现特定的功能,组合在一起就可以实现整个程序的功能。这样的程序模块就是函数,因此,函数是C++程序的基本构成单位。C++程序一般由一个主函数main和若干个子函数构成,程序会从主函数开始执行,每当调用到一个子函数时,就跳转到被调函数,执行完后又回到主函数,最后也在主函数结束。
从在程序中的地位上看,函数分为主函数和子函数;由于函数要先定义后才能执行,所以另一方面,从函数的定义方式上看,函数分为系统函数和用户自定义函数。系统函数是系统预定义的,又称为标准库函数;用户自定义函数也就如字面上的意思一样,需要编写程序的人在程序里体现函数的定义过程。
函数的定义
定义函数,就是编写完成函数功能的程序段。在编写之前,需要先了解一下函数的组成部分。
子函数的格式
数据类型 函数名 (形式参数列表) //函数头
{
语句 //函数体
}
函数头
数据类型规定了函数返回值的类型。若数据类型缺省,则表示无返回值,但无返回值一般为void型。
函数名是函数的标识,不允许自定义的函数名与全局变量名相同。
形式参数简称形参。形参列表是包含在圆括号中的0个或多个以逗号分隔的变量声明。规定了函数将从调用函数中接受几个数据及它们的类型。函数以有无形参为划分依据可以分为有参函数和无参函数。
函数体
函数体描述了函数实现一个功能的过程,并要在最后执行一个函数的返回。函数的返回由返回语句实现。
返回语句
一般形式:
return 表达式 ; 或 return (表达式); 或 return ;
实现过程:
- 计算出表达式的值;
- 将该值的类型强制转换为函数的返回值类型;
- 将该值返回到调用处作为调用函数的值;
使用说明:
- 无返回值函数中返回语句可有可无,因为其函数体的右括号有返回功能;
- 一个函数体可以有多个返回语句,但每次只能通过一个返回语句执行返回操作;
- 函数可以只执行一个功能而不返回任何值,如自定义的输出函数;
- 在一个函数定义的内部不允许出现另一个函数定义,即不允许嵌套定义。
函数的调用
在程序中,一个函数调用另一个函数,就是将程序执行流程转移到被调函数。
原型声明
如果调用函数在前,被调函数的定义在后,就必须对被调函数进行原型声明。
原型声明的一般格式如下:
数据类型 函数名 (形式参数类型列表);
函数的原型声明可以出现在程序中的任何位置,且声明次数没有限制,但一般将函数的原型声明放在调用函数的开始部分。
传值调用
在函数调用时,一般要求实参的个数和类型必须与形参的个数和类型一致,即个数相等、类型相同。
实参可以是常量、变量和表达式。
传值调用的实现是系统将实参复制一个副本给形参,形参和实参分别占用不同的存储单元。
传值调用的特点是形参值的改变不影响实参。
形参和实参可以同名,但互不影响。
例题:哥德巴赫猜想
验证哥德巴赫猜想:一个大偶数可以分解成两个素数之和。验证区间为96~100。
#include<iostream>
#include<cmath>
using namespace std;
bool prime(int a) //返回类型为布尔类型;
{
int i,k;
k=(int)sqrt(a);
for(i=2;i<=k;i++)
if(a%i==0)
return false;
return true;
}
int main(){
int a,b,m;
for(m=96;m<=100;m=m+2)
for(a=2;a<=m/2;a++)
if(prime(a))
{
b=m-a;
if(prime(b))
{
cout<<m<<"="<<a<<"+"<<b<<endl;
break;
}
}
return 0;
}
引用调用
引用调用可以使形参的改变影响实参,只需要在形参列表里的形参名前加上一个引用运算符 & ;
嵌套调用
一个子函数可以调用另一个子函数;
递归调用
一个函数的执行过程中可以直接或间接地调用这个函数本身。
使用条件:必须有一个明确的结束递归的条件。
对同一个问题,采用循环的效率高于递归。
总结:不允许嵌套定义,但允许嵌套调用和递归调用;
例题:递归计算阶乘
#include<iostream>
using namespace std;
float fac(int);
int main(){
int n;
float y;
cout<<"Input an integer number:"<<endl;
cin>>n;
y=fac(n);
if(n>=0) cout<<n<<"!="<<y<<endl;
return 0;
}
float fac(int n)
{
float f;
if(n<0)
{
cout<<"n<0,data error!"<<endl;
return 1;
}
else if(n==0||n==1) f=1;
else f=fac(n-1)*n;
return f;
}
函数的参数
在C++中,允许在函数定义或声明时给一个或多个形参指定默认值(也称为缺省值);
在指定了默认值的形参后,其右边不能出现没有指定默认值的形参;
默认值可以是常量、全局变量或函数调用,不能指定引用参数的默认值。
在函数调用时,编译器按自左至右的顺序将实参与形参结合,当实参的数目不足时,编译器按同样的顺序用定义或声明中的默认值来补足所缺少的实参。具体见下例:
例题:设置函数参数的默认值
#include<iostream>
using namespace std;
void fun(int x=1,int y=2,int z=3)
{
cout<<"x="<<x<<","<<"y="<<y<<","<<"z="<<z<<endl;
}
int main(){
fun();
fun(5);
fun(5,4);
fun(5,4,6);
return 0;
}
/*
x=1,y=2,z=3
x=5,y=2,z=3
x=5,y=4,z=3
x=5,y=4,z=6
*/
若函数定义在后,主调函数在前,则应在主调函数的函数原型声明中指定默认值;
同一个函数在同一个文件的不同调用函数中可以设置不同默认值;
内联函数
函数调用时间长,会降低程序运行效率。为了减少运行时间,可以使用内联函数。
编译程序处理内联函数的方法是将程序中出现的内联函数的调用替换为内联函数的函数体语句,因此它是以空间换取时间;
内联函数的定义就是在普通函数的定义前加上关键字 inline ;
注意点:
- 在内联函数内不允许出现switch语句、循环语句和复杂的if语句,若出现,则编译器将该函数视为普通函数;
- 内联函数的定义必须出现在内联函数第一次被调用之前;
函数重载
函数重载是指一个函数名可以对应着多个函数的实现;
同一个函数名,可以作为多个参数类型不同的重载函数,也可以作为多个参数个数不同的重载函数,只是同名不同质的函数需要分别定义;
作用域和存储类别
C++的作用域分为五种:块作用域、文件作用域、函数原型作用域、函数作用域和类作用域。
作用域
块作用域
C++中把用花括号括起来的一部分程序称为块;
在一个块中定义的变量称为局部变量;
具有块作用域的标识符在其作用域内,将屏蔽在本块有效的同名标识符,即局部定义优先;
文件作用域
文件是C++的编译单位。
在所有函数外定义的标识符具有文件作用域,其范围为整个文件;
在函数外定义的变量称为全局变量;
在块作用域内可通过作用域运算符 :: 来引用与局部变量同名的全局变量;
函数原型作用域
在函数原型声明中的标识符可以和函数定义中的标识符不同。
存储类别
局部变量的存储类别
自动变量 定义变量时加上前缀 auto,表明当程序执行到改变量作用域结束处时,撤销变量的定义和赋值;
静态变量 定义变量时加上前缀 static,表明该变量的定义和赋值永不撤销,在下一次调用函数时,静态变量沿用上一次函数调用结束时的值,忽略再一个static变量定义语句;
典例:打印1到5的阶乘值。
#include<iostream>
using namespace std;
int fac(int n);
int main(){
int i;
for(i=1;i<=5;i++)
cout<<i<<"!="<<fac(i)<<endl;
return 0;
}
int fac(int n)
{
static int f=1;
f=f*n;
return f;
}
/*
1!=1
2!=2
3!=6
4!=24
5!=120
*/
寄存器变量 在变量定义前加上前缀 register,由于寄存器的存取速度远大于内存的存取速度,所以如果有一些变量使用频繁,可以将其转化成寄存器变量,以缩短程序运行时间。
全局变量作用域的扩展和限制
全局变量作用域的扩展
- 将 extern 用在函数块内定义的变量前,使其成为全局变量,作用域为整个文件;
- 将 extern 用在第二个使用相同全局变量的文件的全局变量定义前,作为全局变量声明;
全局变量作用域的限制
在希望某些全局变量只限于被本文件引用,而不能被其他文件引用时,可在定义全局变量前加上前缀 static ;
此外,在一个包含多个文件的程序里,若希望一个文件里的函数不被其他文件的函数调用,也需要在这个函数定义前加上前缀 static,一般默认所有函数都可以被其他文件所调用。