前言
函数是C语言的基本单位,学好函数有利于程序的模块化以及避免写出重复的代码从而减少代码量,并且可以提高程序的复用性与可读性。
一、引入
引例:定义6个整型变量a,b,c,d,e,f,并对它们任意赋值。分别输出a,b的最大值,c,d的最大值和e,f的最大值
#includeint main() { int a, b, c, d, e, f; a = 5, b = 3, c = 6, d = 87, e = 12, f = 49; if (a > b) printf("%d\n", a); else if (a < b) printf("%d\n", b); if (c > d) printf("%d\n", c); else if (c < d) printf("%d\n", d); if (e > f) printf("%d\n", e); else if (e < f) printf("%d\n", f); return 0; }
通过观察可以发现,代码中有大量重复的部分,如果我们定义一万个变量,并两两比较求出其中的最大值,那么if...else if...else语句就要写一万次,这显然非常累赘。在编程过程中,经常会发现,虽然数据不一样,但是对这些数据的操作却是一样的,例如,引例中求a,b中的最大值与求c,d的最大值所做的操作是一样的,唯独只有被操作的数据不同而已。所以如果程序中有大量重复的作操,但只是针对的数据不一样时,我们可以通过定义函数来解决代码重复的问题。上述引例可以通过如下代码实现对应的功能:
#includevoid get_max(int i, int j) // 自定义的求最大值的函数 { if (i > j) printf("%d\n", i); else if (i < j) printf("%d\n", j); else printf("Equal!\n"); } int main() { int a, b, c, d, e, f; a = 5, b = 3, c = 6, d = 87, e = 12, f = 49; get_max(a, b); get_max(c, d); get_max(e, f); return 0; }
二、认识函数
void表示该函数没有返回值,get_max是该函数的名字,函数名后括号中的变量i和j被称为该函数的形式参数(简称形参)。
void get_max(int i, int j) { if (i > j) printf("%d\n", i); else if (i < j) printf("%d\n", j); }
所有的语言程序的入口都是main函数,从main函数进入则开始顺序逐行执行main函数中的代码。下面代码块中定义完变量并完成赋值后,程序执行到get_max(a, b);,此时程序便会从main函数的上方查找一个名叫get_max函数并将括号中的变量a和b的值分别传输到get_max函数名后括号中的形参i和j中,然后程序会跳转到get_max函数的内部执行,待get_max函数执行完毕后,再跳转回main函数继续执行main函数中的下一条语句。
int main() { int a, b, c, d, e, f; a = 5, b = 3, c = 6, d = 87, e = 12, f = 49; get_max(a, b); get_max(c, d); get_max(e, f); return 0; }
三、函数的作用
函数是一种工具,它是能够完成特定功能的独立代码块,它不是为某个特定的问题设计的,而是为解决大量同类型的问题设计的;虽然函数处理的数据是不同的,但是对这些数据的操作是相同的;函数的使用可以避免写大量重复的代码,减少程序员的编码量;同时函数的存在有利于整个程序的模块化,函数可以将复杂的问题剖解为一个个简单的问题;函数可以被视为一个“黑匣子”,我们只需要知道某个函数的用法即可使用该函数,但我们并不一定知道该函数是怎么实现的,例如,我们虽然经常使用的printf函数,但我们并不知道其中到底是如何实现输出功能的。同样的,当我们编写函数的时候,也可以把具体实现的过程隐藏起来,因为这可能是商业机密。
四、函数的返回值
函数不仅可以接收数据,并对接收到的数据进行处理,而且也可以将数据处理的结果返回,例如下述程序:
#includeint f(void) // int表示函数的返回值是int类型的值;括号中的void表示该函数不能接收任何数据 { return 10; // return语句表示向主调函数返回一个值 } int main() { int i = 88; i = f(); // 调用f函数后,f函数就会返回10这个数据,所以这行代码就相当于把10赋值给变量i printf("%d\n", i); return 0; }
思考:下述程序是否正确?
#includevoid g(void) // 函数名前的void表示该函数没有返回值 { } void h(void) { return 10; // 编译时如果没有调用该函数,则不会报错,一旦调用便会报错,因为h函数的返回值为空 } int main() { int i = 88; i = g(); // 由于g函数没有返回值,所以不能将其赋值给变量i,编译时会报错 printf("%d\n", i); return 0; }
五、定义函数
六、函数的类型
函数返回值的类型也称为函数的类型,如果函数名前的返回值类型与函数执行体中的return表达式的返回的类型不同,则最终函数返回值以函数名前的返回值类型为准。
思考:下述程序输出的结果是多少?
#includeint f() // 如果函数名后的括号里是空的,等同于在括号里写void { return 10.5; } int main() { double x = 6.6; x = f(); printf("%lf\n", x); return 0; }
通过程序可得,变量x输出的值为10.000000,这就说明,函数最终的返回值与函数名前的返回值类型相同,而不是以return表达式为准。所以,函数的类型取决于函数名前的返回值的类型。
七、return语句与break语句的区别
break语句的作用是终止当前循环和switch语句,而return语句与break语句有本质性的差别,return语句的作用是终止当前正在执行的函数。当被调函数的返回值为空时,则直接终止被调函数然后跳回主调函数继续顺序执行主调函数中的代码;如果被调函数的返回值不为空时,则先将返回值返回给主调函数,然后再终止被调函数并跳回主调函数继续顺序执行主调函数中的代码。
思考:以下两个程序的输出结果是否相同?如果不同,这两个程序的输出结果分别是什么?
#incldudevoid f() { for (i = 0; i < 5; i++) { printf("AAAA\n"); break; } printf("BBBB\n"); } int main() { f(); return 0; }
#incldudevoid f() { for (i = 0; i < 5; i++) { printf("AAAA\n"); return; } printf("BBBB\n"); } int main() { f(); return 0; }
八、函数的分类
根据是否有形参可以将函数分为,有参函数和无参函数;根据是否有返回值可以将函数分为有返回值函数和无返回值函数;同时,也可以将函数分为库函数和自定义函数,例如,printf函数就属于库函数,因为是系统提供给我们的,而这篇文章中的get_max函数,f函数,g函数等均属于自定义函数。
九、主函数
不管一个程序中有多少个函数,但主函数(也就是main函数)只能有一个,并且main函数是整个程序的入口,也是整个程序的出口,主函数可以调用任何其他函数,其他函数之间也可以互相调用,但是其他函数不能调用主函数。
十、函数使用举例
定义6个整型变量a,b,c,d,e,f,并对它们任意赋值。分别输出a,b的最大值,c,d的最大值和e,f的最大值
要求:自定义函数并且不能与引例中的函数执行体相同
#includeint get_max(int i, int j) { if (i > j) return i; else if (i < j) return j; } int main() { int a, b, c, d, e, f; a = 5, b = 3, c = 6, d = 87, e = 12, f = 49; printf("%d\n", get_max(a, b)); printf("%d\n", get_max(c, d)); printf("%d\n", get_max(e, f)); return 0; }
输入三个int类型的数字,并判断这三个数字是否是素数。如果是素数输出Yes,如果不是输出No
要求:使用自定义函数实现
提示:素数(又称质数)是大于1的自然数并且素数只能被1和它本身整除
#includeint is_prime(int val) { int i; for (i = 2; i < val; i++) if (val % i == 0) break; if (i == val) return 1; else return 0; } int main() { int val1, val2, val3, i; scanf("%d%d%d", &val1, &val2, &val3); if ( is_prime(val1) ) printf("Yes!\n"); else printf("No!\n"); if ( is_prime(val2) ) printf("Yes!\n"); else printf("No!\n"); if ( is_prime(val3) ) printf("Yes!\n"); else printf("No!\n"); return 0; }
十一、函数的声明
观察可以发现,文章中所有自定义的函数都放在了main函数之上,那我们如果将自定义的函数放在main函数的下面会出现什么情况呢?如果我们在main函数中调用了自定义函数,并且将自定义的函数放在了main函数的下方,此时整个程序就会编译报错,这是因为main函数执行到调用自定义函数的语句时,它只会向上查找对应的函数,而如果在它上面没有对应的函数,程序就会出错。
思考1:下面两个程序是否可以运行?
#includeint main() { f(); return 0; } void f() { printf("AAAA\n"); }
那如果我们要把被调函数放在主调函数之后,我们应该怎么办呢?这需要在主调函数的上方加上函数声明即可,如下程序所示:
#includevoid f(); // 这是函数声明,后面的分号不可以省略 int main() { f(); return 0; } void f() { printf("AAAA\n"); }
思考2:下面的程序是否可以运行?如果有错误应该如何改正?
#includevoid g(void) { f(); } void f(void) { printf("AAAA\n"); } int main() { g(); return 0; }
十二、函数的形参与实参
形参是指定义函数时括号中定义的变量,而实参是指在调用函数时向被调函数传输的具体的数据或变量。如下代码所示,变量i就是形参,而主函数中调用f函数时在后面的括号中写的5就是实参。需要注意的是,形参和实参的个数必须是对应的,数据类型也必须相互兼容。
#includevoid f(int i) { printf("%d\n", i); } int main() { f(5); return 0; }
十三、合理设计函数
在掌握了以上知识之后,我们该如何设计函数让整个程序更像是开发软件?答案很简单,如果仅需要使用一次某个功能,则不需要将该功能设计成一个函数,但如果需要多次使用该功能,则设计一个该功能对应的函数是不二选择。这段话通过以下三个程序体会:
示例:输入一个int类型的数字n,求1到n之间(包括n)所有的素数并输出。不考虑输入的值小于等于1的情况
解法1:不定义其它函数,仅在main函数中实现
#includeint main() { int n; int i, j; scanf("%d", &n); for (i = 2; i <= n; i++) { for (j = 2; j < i; j++) if (i % j == 0) break; if (j == i) printf("%d\n", i); } return 0; }
这样写程序虽然实现了功能,但代码的重用性非常低,比如,我们要分别判断100个不同数字从1到它本身之间的所有素数,则需要将下面的代码块重复写100次,这样就会导致代码大部分是重复的,而且代码量会显得非常大。
for (i = 2; i <= n; i++) { for (j = 2; j < i; j++) if (i % j == 0) break; if (j == i) printf("%d\n", i); }
考虑到要将1到n之间所有的数字都进行判断素数这一操作,所以可以单独设计出一个函数实现判断素数这个功能,从而解决代码重复的问题。
解法2:自定义一个函数实现
#includeint is_prime(int val) { int i; for (i = 2; i < val; i++) if (val % i == 0) break; if (i == val) return 1; else return 0; } int main() { int n; int i; scanf("%d", &n); for (i = 2; i <= n; i++) if ( is_prime(i) ) printf("%d\n", i); return 0; }
相比较第一个程序,该程序更容易让人理解,代码的可重用性也得以提高,并且多次判断时所需要写的代码也相对较少,但存在的问题与第一个程序相同,如果要分别判断100个不同数字从1到它本身之间的所有素数,则需要将下面的代码块重复写100次。
for (i = 2; i <= n; i++) if ( is_prime(i) ) printf("%d\n", i);
又考虑到判断完是否是素数后,输出也是重复性操作,所以同样可以将输出这一操作通过设计函数来实现。
解法3:自定义两个函数实现
#include// 本函数的功能是判断val是否是素数 int is_prime(int val) { int i; for (i = 2; i < val; i++) if (val % i == 0) break; if (i == val) return 1; else return 0; } // 本函数的功能是将1到n之间(包括n)所有的素数输出 void traverse(int n) { int i; for (i = 2; i <= n; i++) if ( is_prime(i) ) printf("%d\n", i); } int main() { int n; int i; scanf("%d", &n); traverse(n); return 0; }
第三个程序相较前两个程序而言,代码量更少,且可重用性更高,如果需要判断多个数字并输出的话,只需要多次调用traverse函数即可实现功能,而且整个程序更容易让人理解。
总结:一个函数的功能尽可能独立单一,不要将多个功能放在一个函数中实现,这样不仅可以避免代码的重复而且还可以增加整个程序的可重用性与可读性。
十四、变量的作用域
变量按作用域划分可以分为全局变量和局部变量。
全局变量
在所有函数外部定义的变量被称为全局变量。如下程序所示,变量k就是一个全局变量,需要注意的是,全局变量只能在定义全局变量的位置之后的函数中使用,在全局变量之上的函数不能使用。
#includeint k = 10; void f() { printf("%d\n", k); } int main() { f(); return 0; }
局部变量
在函数内部定义的变量,以及函数的形参都属于局部变量。如下程序所示,f函数中的形参i以及f函数中定义的变量j都是f函数的局部变量,main函数中定义的变量i也是main函数的局部变量。除此之外,初学时可能会有这样的困惑:f函数中定义了i变量,main函数中也定义了i变量,这样程序是否存在问题?答案是不存在问题的,因为main函数中变量i只在main函数中发挥作用,而f函数中的变量i只在f函数中发挥作用,所以它们并不会起冲突,也就意味着,所有的局部变量只能在函数的内部使用,这也正是局部变量的含义。
#includevoid f(int i) { int j = 20; } int main() { int i = 10; return 0; }
全局变量名与局部变量名冲突的问题
#includeint i = 99; void f(int i) { printf("i = %d\n", i); } int main() { f(8); return 0; }
推测1:程序中有错误,因为全局变量名与局部变量名一样,无法分辨f函数中到底是全局变量i还是局部变量i
推测2:程序无误,如果输出结果为99,则此时的变量i是全局变量;如果输出结果为8,则此时的变量i是局部变量
结果:输出结果为8
结论:如果全局变量名与局部变量名相同,则局部变量会屏蔽全局变量的存在
十五、函数内存的分配
在使用函数之前,操作系统会给函数中所有的变量分配内存空间,但当函数被执行完毕后,其中所有变量的内存均会被释放掉,等到再次使用时,会重新给函数中的变量分配内存空间。大家可以思考一下这样做的合理性。
以上就是C语言学习之函数知识总结的详细内容,更多关于C语言 函数 的资料请关注脚本之家其它相关文章!