登录以参加训练计划
第一节 函数
前言
在程序设计中,我们学习了三种基本的控制结构:顺序、分支、循环。这些结构可以组合成任何程序。然而,在实际应用中,经常需要用到子程序结构。
子程序的概念
通常,在程序设计中,我们会遇到一些程序段在程序的不同地方反复出现。这时,可以将这些程序段作为相对独立的整体,用一个标识符给它起一个名字。在程序中出现该程序段的地方,只需简单地写上其标识符即可。这样的程序段称为子程序。
子程序的使用不仅缩短了程序,节省了内存空间及减少了程序的编译时间,而且有助于结构化程序设计。因为一个复杂的问题可以分解成若干个子问题来解决。如果子问题仍然很复杂,还可以继续分解,直到每个子问题都是一个具有独立任务的模块。这样编制的程序结构清晰,逻辑关系明确,无论是编写、阅读、调试还是修改,都会带来极大的便利。
在一个程序中可以只有主程序而没有子程序,但不能没有主程序,也就是说不能单独执行子程序。
在此之前,我们已经介绍了C++提供的各种标准函数,如 abs()
, sqrt()
等等。这些系统提供的函数为编写程序提供了很大的方便。例如:计算 sin(1) + sin(2) + ... + sin(100)
的值。但这些函数仅限于常用的基本函数,编程时经常需要自定义一些函数。
我们来看下面的一个例子:求 1! + 2! + 3! + … + 10!
的值。
如果要编写程序,我们会看到求阶乘的操作要执行10次,只不过每次所求的数不同。我们不必编写10遍求阶乘的程序!我们希望有一个求阶乘的函数,假设为 js(x)
,那么就可以这样求解这个问题。
示例:求 1! + 2! + 3! + … + 10!
#include <iostream>
using namespace std;
int main() {
long long sum = 0;
for (int i = 1; i <= 10; ++i) {
sum += js(i);
}
cout << sum << endl;
return 0;
}
现在的问题是:C++不提供js(x)
这样一个标准函数,上面程序是通不过的。没关系,我们编写自己的函数。如果是C++的标准函数,可以直接调用,如abs(x)
, sqrt(x)
等。而C++调用的标准函数需要在程序中通过#include
指令加入相应的库即可。
一、函数的定义
1. 函数定义的语法形式
数据类型 函数名(形式参数表){
函数体 // 执行语句;
}
关于函数的定义有如下说明
:
- 1,
函数的数据类型
是函数的返回值类型(若数据类型为void,则无返回值)。 - 2,
函数名
是标识符,一个程序中除了主函数名必须为main外,其余函数的名字按照标识符的取名规则可以任意选取,最好取有助于记忆的名字。 - 3,
形式参数(简称形参)
表可以是空的(即无参函数),也可以有多个形参,形参间用逗号隔开。不管有无参数,函数名后的圆括号都必须有。形参必须有类型说明,形参可以是变量名、数组名或指针名,它的作用是实现主调函数与被调函数之间的关系。 - 4,
函数中最外层一对花括号“{}”
括起来的若干个说明语句和执行语句组成了一个函数的函数体。由函数体内的语句决定该函数功能。函数体实际上是一个复合语句,它可以没有任何类型说明,而只有语句,也可以两者都没有,即空函数。 - 5,函数不允许嵌套定义。在一个函数内定义另一个函数是非法的,但是允许嵌套使用。
- 6,函数在没有被调用的时候是静止的,此时的形参只是一个符号,它标志着在形参出现的位置应该有一个什么类型的数据。函数在被调用时才执行,也就是在被调用时才由主调函数将实际参数(简称实参)值赋予形参。这与数学中的函数概念相似,如数学函数:
f(x) = + x + 1 这样的函数只有当自变量被赋值以后,才能计算出函数的值。
2. 函数定义的例子
定义一个函数,返回两个数中的较大数。
int max(int x, int y) {
return x > y ? x : y;
}
该函数返回值是整型,有两个整型的形参,用来接受实参传递的两个数据。函数体内的语句是求两个数中的较大者并将其返回主调函数。
3. 函数的形式
都相同,函数的形式从结构上说可以分为三种:无参函数、有参函数和空函数。它们的定义形式如下:
(1) 无参函数
数据类型 函数名()
函数体
(2) 有参函数
数据类型 函数名(类型 参数名, ...)
函数体
(3) 空函数
函数名()
函数体
二、函数的声明和调用
1.函数的声明
调用函数之前先要声明函数原型。在主调函数中或所有函数定义之前,按如下形式声明:
类型说明符 被调函数名(含类型说明的形参表);
如果是在主函数定义之前声明了函数,那么该函数原型在本程序文件中任何地方都有效。如果是在某个主调函数内部声明了被调用函数原型,那么该原型就只能在这个函数内部有效。
下面对js()函数原型声明是合法的。
int js(int n);
也可以:
int js(int);
可以看到函数原型声明与函数定义时的第一行类似,只多了一个分号,成为了一个声明语句而已。
2.函数的调用
声明了函数原型之后,便可以按如下形式调用函数:
函数名(实参列表)
//例题中语句
sum+=js(i);
实参列表中应给出与函数原型形参个数相同、类型相符的实参。在主调函数中的参数称为实参,实参一般应具有确定的值。实参可以是常量、表达式,也可以是已有确定值的变量、数组或指针名。函数调用可以作为一条语句,这时函数可以没有返回值,函数调用也可以出现在表达式中,这时就必须有一个明确的返回值。
3.函数的返回值
在组成函数体的各类语句中,值得注意的是返回语句return。它的一般形式是: return(表达式);// 例题中语句return s;
其功能是把程序流程从被调函数转向主调函数并把表达式的值带回主调函数,实现函数的返回。所以,在圆括号表达式的值实际上就是该函数的返回值,其返回值的类型即为它所在函数的函数类型,当一个函数没有返回值时、函数中可以没有return语句,直接利用函数体的右花括号“)”,作为没有返回值的函数的返回,也可以有return 语句,但return后没有表达式,返回语句的另一种形式是: return; 这时函数没有返回值,而只把流程转向主调函数。
三、函数的传值调用
函数传值调用的特点是将调用函数的实参表中的实参值依次对应地传递给被调用函数的形参表中的形参,要求函数的实参与形参个数相等,并且类型相同。
函数的调用过程实际上是对栈空的操作过程,因为调用函数是使用栈空间来保存信息的。函数在返回时,如果有返回值,则将它保存在临时变量中。然后恢复主调函数的运行状态,释放被调用函数的栈空间,按其返回地址返回到调用函数。
在C++语言中,函数调用方式分传值调用和传址调用。
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;
return 0;
}//程序输出为:21
在此例中,因为swap函数的参数为传址调用,&a是指实参变量的地址值传递给形参,所以,在函数swap 中修改a,b的值相当于在主函数main中修改c.d的值。
四、函数的应用举例
例6.2
计算组合数C(m,n)的值(n≤m≤10)
[分析] 组合数C(m,n)可以理解为从m个数中任意取出n个数的所有情况数。求这个数值,有一个经典的计算方法: C(m,n) = m! / ((m-n)! * n!)
。
程序如下:
#include<cstdio>
using namespace std;
int fac(int x); // 阶乘函数的声明
int main() {
int m, n;
scanf("%d %d", &m, &n);
printf("%d\n", fac(m) / (fac(m - n) * fac(n))); // 阶乘函数的调用
return 0;
}
int fac(int x) { // 定义阶乘函数
int s = 1;
for (int i = 1; i <= x; i++) {
s *= i;
}
return s; // 阶乘函数的值返回
}
例6.4
检查数字d是否出现在正整数n的某位中
定义一个函数
check(n, d)``,让它返回一个布尔值,如果数字 d
在正整数 n
的某位中出现则返回 true
,否则返回 false
。
例如:
check(325719, 3)
返回true
check(77829, 1)
返回false
程序如下:
#include <iostream>
using namespace std;
bool check(int n, int d);
int main() {
int a, b;
cout << "input n, d" << endl;
cin >> a >> b;
if (check(a, b)) {
cout << "true" << endl;
} else {
cout << "false" << endl;
}
return 0;
}
bool check(int n, int d) {
while (n) { // C++中非0为真
int e = n % 10;
n /= 10;
if (e == d) {
return true;
}
}
return false;
}
例6.5
使用冒泡法对数组元素按从小到大的顺序排序(数组作为函数参数)
程序如下:
#include <iostream>
using namespace std;
void bubble(int[], int);
int main() {
int array[10] = {11, 4, 55, 6, 77, 8, 9, 0, 7, 1};
cout << "排序前: ";
for (int i = 0; i < 10; ++i) {
cout << array[i] << ',';
}
cout << endl;
bubble(array, 10);
cout << "排序后: ";
for (int i = 0; i < 10; ++i) {
cout << array[i] << ',';
}
cout << endl;
return 0;
}
void bubble(int a[], int n) {
for (int i = 1; i < n; ++i) {
for (int j = 0; j < n - i; ++j) {
if (a[j] > a[j + 1]) { // 判断并交换变量
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
说明:
数组名作为参数
在前面我们已经知道数组名是该数组在内存中的首地址。将数组名作为参数传给函数,实际上是把数组的地址传给函数,形参数组和实参数组的首地址重合,因此在被调用函数中对数组元素值进行改变,主调函数中实参数组的相应元素值也会改变。
五、全局变量、局部变量及它们的作用域
- 在函数外部定义的变量称为外部变量或全局变量。
- 在函数内部定义的变量称为内部变量或局部变量。
- 参加人数
- 4
- 创建人