函数
随着程序规模的扩大,你的 main()
函数可能变得越来越臃肿了。这时候使用函数将不同的模块分开书写,可以让程序变得条理清晰、简明易懂。例如下面是 OJ 习题 “环形矩阵” 的一段代码:
int n;
int a[100][100];
int main ()
{
scanf("%d", &n);
for (int k = 1; k <= (n + 1) / 2; k++)
for (int i = k; i <= n + 1 - k; i++)
for (int j = k; j <= n + 1 - k; j++)
a[i][j] = k;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
printf("%4d", a[i][j]);
puts("");
}
return 0;
}
读入、矩阵制作、输出三个步骤全部放在 main()
函数中显得有些冗长。我们可以根据功能将上述代码分成三个模块,每个模块写在一个单独的子函数里:
int n;
int a[100][100];
void input()
{
scanf("%d", &n);
}
void matrix_generation()
{
for (int k = 1; k <= (n + 1) / 2; k++)
for (int i = k; i <= n + 1 - k; i++)
for (int j = k; j <= n + 1 - k; j++)
a[i][j] = k;
}
void print()
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
printf("%4d", a[i][j]);
puts("");
}
}
int main ()
{
input();
matrix_generation();
print();
return 0;
}
由于这个程序比较简单,你可能会觉得这样写代码非常拖沓。但随着程序结构的进一步复杂化,你一定会发现将程序分功能分模块书写能帮助你理清思路,也方便 debug。
代码风格
对于初学者来说,养成良好的代码风格极其重要。一方面,条理清晰的代码看起来赏心悦目,方便自己 debug;另一方面,如果你将来与别人合作开发项目,良好的代码风格可以让他人快速理解你书写的模块,方便沟通交流。大家或许听说过一些类似于 “老程序员一走,整个项目就玩不转了,因为没人知道他写了什么” 的笑话,撇开工程复杂性的客观原因,这样的现象也说明很大一部分程序员的代码书写习惯极其糟糕。
所以无论你是否有编程基础,请一定重视自己的代码风格。我们建议你至少在以下几个方面注意:
- 合理的变量命名:不要使用无意义的
a
b
c
_
__
,而应该使用arrayLength
stuCount
等。有意义的变量名可以让你/他人立刻理解这个变量的用途。至于变量的命名格式,我们建议你搜索驼峰命名法。 - 合理的函数使用:不要将一大堆代码塞在一起。
- 合理书写注释,对程序的功能做一点解释。
- 合理的符号使用 (括号,换行符 etc.)
- 合理的缩进
- ……
如果你感兴趣,你可以上网搜索 Google 的代码风格规范。
最后是一个小笑话:
世界上有两件最讨厌的事情,一个是写注释,一个是别人的代码不写注释。
定义
函数的基本语法如下:
int func(int a, float b, char c, ...)
{
}
其中
- 第一个
int
指明了函数的返回值类型为int
。你当然可以选择别的类型,如果你的函数不需要返回值,可以用void
。 func
是函数的名称。这是你可以自由指定的部分。(int a, float b, ...)
是函数的参数。你可以指定任意个数的参数,但每个参数都要有类型。
下面是一个简明的例子:
int abs(int x) {
int res = x >= 0 ? x : -x;
return res;
}
int main ()
{
int x = -1;
x_abs = abs(x); // x_abs = 1
return 0;
}
注意所有的子函数要写在 main 函数的上方 (这是 C/C++ 的要求)。如果你想要将子函数写在 main 函数的下方,需要在 main 函数之前先声明该子函数。声明简单来说就是将函数的定义抄一遍:
int abs(int x);
int main ()
{
int x = -1;
x_abs = abs(x);
return 0;
}
int abs(int x) {
int res = x >= 0 ? x : -x;
return res;
}
变量的作用域
有了函数的概念后我们就必须要区分“全局变量“和“局部变量”。顾名思义在函数内部定义的变量是局部变量,该变量只能在当前函数内使用。在所有函数外定义的变量是全局变量,该变量可以在任何函数中使用 (注:一个函数的参数也是该函数的局部变量)。下面是一个例子:
int n = 0;
void func()
{
printf("%d\n", n); // 输出结果为 0。
printf("%d\n", m); // 编译错误:m 是 main() 的局部变量,在 func() 中不可用。
}
int main ()
{
int n = 1, m = 2;
printf("%d\n", n); // 输出结果为 1,全局变量和局部变量重名优先使用局部变量。
}
按值传递和按引用传递
按值传递和按引用传递是函数中必须提及的另一个重要概念。很多初学者会疑惑:如果我在子函数中对参数的值做了改动,主函数里相应的变量会变吗?你可以做一个小实验探索这一点:
void func(int x) { x = 2; }
int main ()
{
int x = 1;
func(x);
printf("%d\n", x); // 输出结果为 1
}
事实证明子函数的改动不会影响主函数。这是因为 C/C++ 中函数变量参数的传递默认使用按值传递,即子函数的参数变量 x
和主函数的变量 x
是两个独立的主体,在函数调用时主函数的 x
的值会被复制到子函数的 x
中,在子函数中对 x
的所有改动都是针对这个“复印件”的,对原件没有影响。
那么有没有办法可以让子函数直接修改主函数中的变量的值呢? C++ 提供了引用变量的机制,一个例子如下:
void func(int x, int &y) { x++; y++; }
int main ()
{
int x = 1, y = 1;
func(x, y);
printf("%d %d\n", x, y); // 输出结果为 1 2
}
在这个例子中,x
是按值传递的,y
是按引用传递的 (注意函数参数前的 &
符号)。按引用传递可以理解为直接将原件交给了子函数,子函数的改动会影响主函数的值。