函数
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.8运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
3. 形参与实参的边界
下面的例子说明“形参对象得到的是实参的值”:
#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. 习题
[0.1] 编写函数
void fill_area(char ch, int width, int height),实现功能:打印ch字符height行width列。[2.3] 输入四个整数,判断它们是否满足“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。