函数是C/C++语言的核心概念,也是学习的重点所在。然而此函数与数学上的函数有所不同(也有称方法的),要理解掌握必须下些工夫。
函数
C/C++中的函数与C/C++语言的程序设计理念密切相关,在引入函数之前先看一个例子:
例5.1:
输入十个整数,输出他们的绝对值的阶乘
1 |
|
输入:
1 -2 3 -4 5 -6 7 -8 9 -10
输出:
1 2 6 24 120 720 5040 40320 362880 3628800
随着需求功能的增加,将各个功能写在一起的代码将变得不利阅读和修改;而如果能将代码模块化,就可以解决这一问题。C/C++语言为此引入函数。
定义一个函数
函数是一个命名的语句序列,能够返回计算结果(称为返回值)。
函数的定义语法如下:
1 | <返回值类型> 函数名(<参数类型1> 参数1,<参数类型2> 参数2,……){ |
可以抽象为:
1 | <返回值类型> 函数名(参数表)函数体 |
这样,上面的例5.1就可以改写成(请先带着疑惑看完):
1 |
|
可以看到,绝对值模块、阶乘模块被分开实现了,使用时只需调用即可,这样的编程设计思路(编程范式)更接近人类自然的解题思维。
下面将一一解释以上语法的每个部分。
以计算一个数的平方为例:
1 | int square(int x){ |
第一行:
定义一个名为square的函数,需要一个整型参数(并命名为x),返回一个整型返回值
第二行:
函数体部分为一个return语句,即返回值为表达式x * x的结果。
需要注意的是,函数的定义是写在main函数外面的(可以观察例5.1的代码)。毕竟main也是函数,在C/C++代码中的地位与其它函数一样;更一般地、在C/C++语言中,一个函数不能定义在另一个函数内(暂不考虑lambda表达式)。
调用
完成了函数的定义,我们就可以调用(diào)这个函数了。
1 |
|
输入:
6
输出:
36
6
我们分析一下在调用过程中发生了什么:
程序从main函数开始执行,分别执行了int a;cin>>a;两行。
在执行cout<<square(a);时调用函数square,变量a作为被调用的参数。
于是,square函数中的变量x被赋值为变量a的值,并开始执行square函数:
返回x * x的值(即36),函数执行结束,回到调用的地方。
执行cout<<36;最后输出36。
操作后a的值没有改变。
我们称调用时传递给函数的值(上例中的a)为实际参数(简称实参);称接受该值的变量(上例中的x)为形式参数(简称形参)。
形参是函数中接受传入数值的特殊变量:
1 |
|
输入:
6
输出:
7
6
函数调用过程中,a的值被赋给了形参x,x在函数中+1并返回;而a的值仍为6:操作后a的值没有改变。
试试看在main函数中为上例的x赋值,或者输出x,看看会发生什么——
作用域
考察以下代码:
1 | int square(int x){ |
编译器会告诉你,这个代码是错误的:x没有定义。square函数中定义了变量x,到main函数中却没有了,因为main函数超出了变量x的作用域。
要解释作用域这一概念,我认为最好的类比就是数学题中出现的前提条件:
19.已知函数$f(x)=x^2,g(x)$
(1)若$g(x)=2^x$,设$h(x)=…$……
(2)……
发现:
总条件是$f(x)=x^2$,在整道题中不会改变,且都能使用
第一小问中:$g(x)=2^x$,但只能在第一问中使用;另定义了$h(x)$
第二小问中:没有关于$g(x)$的条件,因此它可以为任意函数,$h(x)$甚至没有出现,自然无法使用。
与之类比,C/C++有全局量与局部量的概念,定义在所有函数外的变量为全局量,作用域为从声明变量开始,到代码的末尾;而定义在函数内的变量为局部量(包括形参也是特殊的局部量),作用域仅在该函数内。因此,上例中的main函数无法使用square中的x。
全局量和局部量可以同名,但同一作用域不能出现两个同名全局量或局部量,请看下例:
1 |
|
输出:
11
21
我们先在所有函数外定义了一个变量x,所以这个x为全局量;然后定义了一个函数add1,参数表中也声明了一个变量x作为形式参数,而这个x在函数内,故为局部量,作用域仅在add1函数内;
程序从main函数开始运行:
先将x赋值为20:此时main函数中没有x的定义,因此这个x是指全局量x(全局量x赋值为20)。
调用add1(10):局部量x(形参)被赋值为10,执行add1函数,return x+1;那么这个x是10(局部量)还是20(全局量)呢?输出结果告诉我们,此处的x是函数内的局部量——一般地,如果有同名的局部量和全局量,那个变量名将会优先定向到局部量,只有没有这个局部量时,它才代指那个全局量。
调用add1(x):此时的x是局部量,因此值为20,并传给形参x,最后返回21。
C/C++的这一特性保证了函数是一个安全的小环境,在里面定义、使用任何名字都不用担心与外面的代码冲突,也让我们能够在定义多个函数时放心的使用诸如x等一些简单的名字而不冲突。
return语句
return语句控制着函数的返回值,写在函数体中,语法为:
1 | return 返回值; |
因此,return后的返回值类型必须与函数定义时写的类型一致。
同时,return语句也控制着函数的退出,函数体执行到return语句就会立即退出,销毁所有局部量(包括形参和在函数体内定义的局部量),并将return后的返回值代回调用处:
1 | int f(int t){ |
上面函数f将返回t+1,而后面的语句将永远不会执行。
无返回值函数(过程)
特别地,一个C/C++函数也可以没有返回值,在定义函数时返回值类型为void(空类型),如:
1 | void f(int x){ |
同理,函数也可以没有参数,即参数为void(也可以省略):
1 | void g1(void){ |
那么问题来了,一个没有返回值的函数有什么用呢?
1 |
|
输入:
9
输出:
14
而如例4.5,就可以写成:
1 |
|
我们发现,无返回值函数仍然能实现一段有功用的代码,这也使得代码模块化了,因而无返回值函数也被称为过程(procedure)。因为没有返回值,一个过程中可以在任何地方使用return语句退出,但禁止return语句后带返回值。然而限于目前工具有限,我们目前仅能够通过全局量让过程修改一个变量,给代码的其他部分使用;在学习了指针、引用后,过程将能够发挥更大作用。
我们发现,main函数就是一个无参数(也可以有参数)、返回值为int(正常运行是返回0)的函数。
作业
思考:
1、什么是C/C++函数,它与数学中的函数有什么不同?
2、我们为什么要引入函数?使用函数编程有什么好处?
3、什么是形参?什么是实参?C/C++语言函数为什么需要形参?
4、什么是作用域?为什么要引入作用域?
5、执行一句return语句会发生哪些事情?
6、什么是过程?
习题:
(以下习题,除main外应至少编写一个函数实现)
1、输入整数n,输出$[2,n]$中的所有质数
2、输入整数n,输出$[2,n]$中所有回文质数
3、计算组合数$C^m_n$的值
4、输入整数n,和n个整数,输入两个整数i,j,将位置i到j间的数升序排列,其余不变
例:
输入:
5
3 5 2 8 6
2 4
输出:
3 2 5 8 6
*5、输入十进制正整数n,转化二进制输出