函数
1. 函数声明
简单地,函数声明可以有以下形式:返回类型 标识符 ( 形式参数列表 )
其中,如果这个函数不返回任何东西,则 返回类型 的位置填 void;如果不接受参数,形式参数列表(以下简称 形参列表)里面填 void。
例如,要编写一个函数 say_hello(),让它在屏幕上输出一段话(这个函数既不接受参数,也不返回值):
void say_hello(void) {
puts("Hello, World!");
}2
3
可能的输出(示例):
<输出与输入或平台相关,请以实际运行为准>
例如,编写一个函数 print_add(),让它在屏幕上输出两个数的和(这个函数接受两个参数,不返回值):
void print_add(double a, double b) {
printf("%g + %g = %g\n", a, b, a + b);
}2
3
可能的输出(示例):
<输出与输入或平台相关,请以实际运行为准>
例如,编写一个函数 add(),让它返回两个数的和(这个函数接受两个参数,返回一个值):
double add(double a, double b) {
return a + b;
}2
3
2. 函数调用
使用 函数名 ( 实际参数列表 ) 的形式调用函数。每个实参表达式会先被求值,再把得到的值赋给对应的形参对象。
当函数的形参列表为空的时候,实际参数列表(以下简称实参列表)什么都不填。
函数调用发生在表达式求值阶段,处理的是值、类型、形参对象和控制流。宏替换发生在预处理阶段,处理的是记号。把这两层分开,后面阅读函数、宏和求值顺序时会清楚很多。形参是被调函数内部的对象,对形参赋新值不会直接改写调用方传入的对象;如果想让函数修改调用方对象,通常需要传入指向该对象的指针。
例如,调用 say_hello():
#include <stdio.h>
void say_hello(void) {
puts("Hello, World!\n");
}
int main(void){
say_hello();
say_hello();
say_hello();
return 0;
}2
3
4
5
6
7
8
9
10
11
12
运行结果:
Hello, World!
Hello, World!
Hello, World!2
3
例如,调用 print_add():
#include <stdio.h>
void print_add(double a, double b) {
printf("%g + %g = %g\n", a, b, a + b);
}
int main(void){
print_add(1.2, 2.3);
return 0;
}2
3
4
5
6
7
8
9
10
运行结果:
1.2 + 2.3 = 3.5例如,调用 print_add() 和 add():
#include <stdio.h>
double add(double a, double b) {
return a + b;
}
void print_add(double a, double b) {
printf("%g + %g = %g\n", a, b, a + b);
}
int main(void){
print_add(add(3.5, 7.4), add(4.5, 4.4));
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
可能的输出(示例):
<输出与输入或平台相关,请以实际运行为准>
add(3.5, 7.4) 返回 10.9,add(4.5, 4.4) 返回 8.9。如同调用 print_add(10.9, 8.9),会输出:
10.9 + 8.9 = 19.83. 形参与实参的边界
下面的例子说明“形参对象得到的是实参的值”:
#include <stdio.h>
void set_to_zero(int x) {
x = 0;
}
int main(void) {
int n = 5;
set_to_zero(n);
printf("%d\n", n);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
可能的输出(示例):
<输出与输入或平台相关,请以实际运行为准>
set_to_zero 内部修改的是形参对象 x,main 中的对象 n 不会因此改变。若接口语义要求修改调用方对象,应显式传入地址:
void set_to_zero(int *p) {
*p = 0;
}2
3
这样写把“允许修改哪个对象”放进了函数类型和调用形式里,读者不需要猜。
4. 实参求值顺序
函数调用中,各个实参的求值顺序通常不由书写顺序决定。标准保证的是:函数设计符和所有实参都求值完成后,才进入被调用函数。因此,不要在同一次函数调用的多个实参里依赖彼此的求值先后。
int i = 0;
/* 不要写依赖实参求值先后的调用 */
/* print_add(i++, i++); */
print_add(i, i + 1);2
3
4
5
6
如果一个调用需要多个有副作用的步骤,先拆成多条语句,再调用函数。这样既符合标准语义,也更容易调试。
5. 习题
编写函数 void fill_area(char ch, int width, int height),实现功能:打印 ch 字符 height 行 width 列。
编写四个函数,近似模拟 ++x、x++、--x、x-- 对 int 对象的效果。
要求:
- 通过指针修改调用方对象;
- 前缀形式返回修改后的值;
- 后缀形式返回修改前的值;
- 说明为什么这些函数只是近似模拟,而不是和运算符完全等价。
输入四个整数,判断它们是否满足“24 点”:可以构建出一个只含这四个整数、加减乘除和括号的表达式,且该表达式的求值结果为 24。
示例:1 5 5 5 满足,因为 (5-(1/5))*5=24。
括号生成:输入一个整数 n,生成所有可能的并且有效的 n 对括号组合。
示例:输入:3,输出:((())) (()()) (())() ()(()) ()()()。
全排列生成:输入一个整数 n,生成 1~n 这 n 个整数的全排列。
示例:输入:3,输出:123 132 213 231 312 321。