前言
- 函数是完成特定任务的独立程序代码单元。C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。
- 函数可以提前保存起来,并给它起一个独一无二的名字,只要知道它的名字就能使用这段代码。函数还可以接收数据,并根据数据的不同做出不同的操作,最后再把处理结果反馈给我们。
函数的原型和调用
-
无参数函数的定义:
- 如果函数不接收用户传递的数据,那么定义时可以不带参数。形式如下:
return_type function_name( ) { body of the function }
- return_type是返回值类型,它可以是C语言中的任意数据类型;function_name是函数名,它是标识符的一种,命名规则和标识符相同,函数名后面的括号()不能少;body of the function是函数体,它是函数需要执行的代码,是函数的主体部分,即使只有一个语句,函数体也要由{}包围;如果有返回值,在函数体中使用 return 语句返回。return 出来的数据的类型要和return_type一样。如:
void hello_demo() { printf ("Hello World! \n"); //没有返回值就不需要 return 语句 } double circle_demo() { int r = 1; double cir = 2*3.14*r; return cir; }
-
有参数函数的定义:
- 如果函数需要接收用户传递的数据,那么定义时就要带上参数。形式如下:
return_type function_name( dataType1 param1, dataType2 param2 ... ) { body of the function }
- dataType1 param1,dataType2 param2...参数列表。函数可以只有一个参数,也可以有多个,多个参数之间由‘,’分隔。参数本质上也是变量,定义时要指明类型和名称。
- 数据通过参数传递到函数内部进行处理,处理完成以后再通过返回值告知函数外部。如:
double circle_demo(double r) { double cir = 2*3.14*r; return cir; }
- 注意:C语言不允许函数嵌套定义;也就是说,不能在一个函数中定义另外一个函数,必须在所有函数之外定义另外一个函数。main() 也是一个函数定义,也不能在 main() 函数内部定义新函数。
-
函数的声明:
- 注意:C语言代码由上到下依次执行,原则上函数定义要出现在函数调用之前,否则就会报错。但在实际开发中,经常会在函数定义之前使用它们,这个时候就需要提前声明。
- 注意:当在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,应该在调用函数的文件顶部声明函数。
- 函数声明会告诉编译器函数名称及如何调用函数。形式如下:
return_type function_name(dataType1 param1, dataType2 param2 ... );
- 在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明:
return_type function_name(dataType1, dataType2 ... );
- 当声明某个函数,其参数是长度可变数组(variable-length array),除了在函数定义的头部,其他地方都可以使用星号(*)来表示数组长度。如果使用非常量的整数表达式来定义数组长度,编译器会将它视为星号。如下:
double maximum( int nrows, int ncols, double matrix[nrows][ncols] ); double maximum( int nrows, int ncols, double matrix[ ][ncols] ); double maximum( int nrows, int ncols, double matrix[*][*] ); double maximum( int nrows, int ncols, double matrix[ ][*] );
-
函数的调用:
- 所谓函数调用(Function Call),就是使用已经定义好的函数。一般形式如下:
function_name(param1, param2, param3 ...);
- function_name 是函数名称,param1,param2,param3...是实参列表。实参可以是常数、变量、表达式等,多个实参用逗号
,
分隔。 - 注意:函数不能嵌套定义,但可以嵌套调用,也就是在一个函数的定义或调用过程中允许出现对另外一个函数的调用。
#include <stdio.h> /***函数的定义*****/ int add_demo(int a, int b) { int c = a+ b; printf("%d\n", c); return c; } void hello_demo(); // 函数的声明,定义在调用后面,需要声明 int main() { int i = 1; int j = 2; int c = add_demo(i, j); // 函数的调用(有参数) hello_demo(); // 函数的调用(无参数) return 0; } /*****函数的定义*******/ void hello_demo() { printf("Hello World!\n"); }
函数的形参和实参
-
形参和实参定义:
- C语言函数的参数会出现在两个地方,分别是函数定义处和函数调用处,这两个地方的参数是有区别的。
- 在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
- 函数被调用时给出的参数包含了实实在在的数据(也就是传递的数据),会被函数内部的代码使用,所以称为实际参数,简称实参。
- 形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参,相当于一次赋值操作。
- 当调用函数时,有两种向函数传递参数的方式:默认情况下,C 使用传值调用来传递参数。
调用类型 描述 传值调用 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。 引用调用 通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
-
区别和联系:
- 形参在未出现函数调用时,他们并不占用内存单元,只有在发生函数调用的时候数形参才被分配内存,函数调用完成后,形参所占的内存被释放。所以形参变量只有在函数内部有效,不能在函数外部使用。
- 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。
- 在定义函数时,一定要指定形参的数据类型。
- 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。
- 在C语言中,实参和形参的数据传递是“值传递”,即单向传递,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。
函数的返回类型和返回值
-
return用法:
- 函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。一般形式为:
return 表达式; 或 return (表达式);
-
返回值:
- 如果函数的返回值为void,可以不需要return语句。
- 函数return语句中的返回值数据类型应该与函数定义时相同。
- return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值。
- 函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。
- 如果函数中没有return语句,那么函数将返回一个不确定的值。
#include <stdio.h> /*******返回最大值*******/ int max_demo(int a, int b) { return a > b ? a : b; } int main() { int k = max_demo(10, 20); printf("%d\n", k); return 0; }
- 在main函数中执行return语句,程序终止,但在子函数中执行return只是子函数终止了,main函数仍在运行。exit是C语言的库函数,也代表程序终止,需要头文件stdlib.h,但不管在程序的任何位置调用exit,所有进程就马上终止了。在main函数使用return和调用exit都一样。如下程序终止。
#include <stdio.h> #include <stdlib.h> int max_demo(int a, int b) { exit(0); return a > b ? a : b; } int main() { int k = max_demo(10, 20); printf("%d\n", k); return 0; }
函数的递归
-
定义:
- 一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。如下程序:
#include <stdio.h> /*****先序递归***** ******输出结果:0 1 2 3 4 5 6 7 8 9 10 ****/ void test_demo(int n) { printf("n = %d\n", n); if(n < 10) { test_demo(n + 1); } } /*****后序递归***** ******输出结果:10 9 8 7 6 5 4 3 2 1 ****/ void test1_demo(int n1) { if(n1 < 10) { test1_demo(n1 + 1); } printf("n1 = %d\n", n1); } int main() { test_demo(0); test1_demo(0); return 0; }
-
递归形式:
- 尾递归:如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作。如下:
#include <stdio.h> /*****求n的阶乘*****/ long factorial(int n) { if (n == 0 || n == 1) { return 1; } else { return factorial(n - 1) * n; // 递归调用 } } int main() { int a; scanf("%d", &a); printf("Factorial(%d) = %ld\n", a, factorial(a)); return 0; }
- 中间递归:发生递归调用的位置在函数体的中间。
- 多层递归:在一个函数里面多次调用自己。
-
递归优缺点:
- 递归可以为某些编程问题提供了最简单的解决方案,比如上述的后序递归比循环实现倒序更简单;但一些递归算法会快速消耗计算机的内存资源。另外,递归也不方便阅读和维护。
-
例子:
- 将十进制转化为二进制:根据十进制转化为二进制特点,采用后序递归方法可实现。
#include <stdio.h> /****后序递归方式***** *****输入13后,输出结果:1101 ****/ void to_bin_demo(unsigned int n) { int i = n % 2; if(n >= 2) to_bin_demo(n / 2); printf("%d", i); } int main() { int a; scanf("%d", &a); to_bin_demo(a); printf("\n"); return 0; }
- 斐波那契数列:0,1,1,2,3,5,8,13,21,34,55,89,144...
#include <stdio.h> int fib_demo(int n) { if(n == 1) return 1; if(n == 2) return 2; return fib_demo(n - 1) + fib_demo(n - 2); } int main() { int a; int i; printf("Please input num:"); scanf("%d", &a); for(i = 1; i < a; i++) printf("%d, ",fib_demo(i)); return 0; }
多个源代码文件程序的编译
-
头文件的使用:
- 如果把main函数放在第一个文件中,而把自定义函数放在第二个文件中,那么就需要在第一个文件中声明函数原型。
- 如果把函数原型包含在一个头文件里,那么就不必每次使用函数时都声明其原型。
-
#include意义:
- #include是预编译指令,代表头文件包含。#include 的用法有两种,如下所示:
#include <stdio.h> #include "mydemo.h"
- 使用尖括号< >,编译器会到系统路径下查找头文件;而使用双引号" ",编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
- 注意:一个 #include 命令只能包含一个头文件,多个头文件需要多个 #include 命令。同一个头文件可以被多次引入,多次引入的效果和一次引入的效果相同。文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。
-
不带参数#define意义:
- #define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。一般形式为:
- 宏定义一般形式为:其中,#表示这是一条预处理命令,所有的预处理命令都以 # 开头。
宏名
是标识符的一种,命名规则和变量相同。字符串
可以是数字、表达式、if 语句、函数等。#define 宏名 字符串
- 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换(有时候括号不能省,否则会发生歧义)。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
/**** 未去括号:sum=2*(2*n+n^2);去括号:sum=2*2*n+n^2 ****/ #include <stdio.h> #define M (2*n + n^2) int main() { int sum = 0; int n = 2; sum = 2*M ; printf("sum=%d\n", sum); return 0; }
- 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
- 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。
/****表示LOVE只能在test_demo子函数使用****/ #include <stdio.h> #define LOVE 520 int test_demo() { return LOVE; } #undef LOVE int main() { int i = test_demo(); printf("%d\n", i); printf("GUN\n"); return 0; }
-
带参数#define意义:
- 带参宏定义的一般形式为:
#define 宏名(形参列表) 字符串
- 带参宏调用的一般形式为:
宏名(实参列表);
- 例子:
/**** 用宏定义实现2数最大值 ****/ #include <stdio.h> #define MAX(a,b) (a>b) ? a : b // 带参数宏定义 int main() { int x = 10; int y = 20; int max = MAX(x, y); // 宏调用 printf("max=%d\n", max); return 0; }
- 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。如:
#define MAX(a,b) (a>b)?a:b // 正确 #define MAX (a,b) (a>b)?a:b // 错误
- 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。和函数不同,带参宏定义只是符号的替换,不存在值传递的问题。
- 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。
- 带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。
-
#ifdef(#ifndef)与#endif:
- #ifdef是个预编译指令,代表只要定义了一个常量(必须用宏名表示),那么就预编译后面代码。表现形式:#else可省略
#ifdef 宏 code1 #else code2 #endif
- #ifndef是个预编译指令,代表只要没有定义了一个常量,那么就预编译后面代码。表现形式:#else可省略
#ifndef 宏 code1 #else code2 #endif
- 举例:
/**** 该程序输出hello world 和 DO NOT ****/ #include <stdio.h> #define TEST int main() { #ifdef TEST printf("hello world\n"); #endif printf("DO NOT\n"); } /**** 该程序输出 DO NOT ****/ #include <stdio.h> #define TEST int main() { #ifndef TEST printf("hello world\n"); #endif printf("DO NOT\n"); }
- 一般用在头文件中,防止不管头文件被包含多少次,只有一次生效。如下2程序,a.h只会预编译一次:
/**** 文件a.h ****/ #ifndef A_H #define A_H int add_demo(int a, int b); int max_demo(int a, int b); #endif /**** 文件test.c ****/ #include <stdio.h> #include "a.h" #include "a.h" #include "a.h" #include "a.h" int main() { int a = 10; int b = 20; printf("%d\n", max_demo(a, b)); printf("%d\n", add_demo(a, b)); return 0; } int max_demo(int a, int b) { return a > b ? a : b; } int add_demo(int a, int b) { return a + b; }
-
#if用法:
- #if 用法的一般形式:
#if 整型常量表达式1 code1 #elif 整型常量表达式2 code2 #elif 整型常量表达式3 code3 #else code4 #endif
- 注意:#if 命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,而且结果必须是整数;而 if 后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。
- 常用预处理指令:
指令 说明 # 空指令,无任何效果 #include 包含一个源代码文件 #define 定义宏 #undef 取消已定义的宏 #if 如果给定条件为真,则编译下面代码 #ifdef 如果宏已经定义,则编译下面代码 #ifndef 如果宏没有定义,则编译下面代码 #elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码 #endif 结束一个#if……#else条件编译块