- 函数的介绍
- 函数的种类
- 库函数
- 自定义函数
- 函数的定义和声明
- 定义
- 声明
- 函数的参数
- 实际参数
- 形式参数
- 实参与形参的区别
- 函数的调用
- 传值调用
- 传址调用
- 一个例子(二分查找)
- 嵌套调用
- 链式访问
- 函数的递归
- 解释
- 求正整数n的阶乘n!
- 求第n个斐波那契数列
1. 函数的介绍
函数是一个程序的部分代码,用来实现某些特定的功能,与主main函数分离,使程序结构模块化,代码更加清晰。
思想是高内聚低耦合。
2. 函数的种类 2.1 库函数
C语言中包含了许多种类的库函数,把一些实现特定功能的代码(如:输入输出、字符串比较、数学中的一些函数实现、申请内存等)封装成一个个函数,方便我们使用。在使用某个函数时只需要知道它在哪个库函数中,然后在自己程序的开始添加相应的库函数即可。
.h结尾的文件是头文件。
| 输入输出库函数 | stdio.h |
|---|---|
| 字符处理库函数 | ctype.h |
| 字符串处理库函数 | string.h |
| 数学库函数 | math.h |
| 内存分配库函数 | stdlib.h |
| 时间处理库函数 | time.h |
| 布尔库函数 | stdbool.h |
| 其他库函数 |
除了C语言提供的基本的库函数,我们还可以自己实现一个个函数,即自定义函数,以此来满足更具体的、专一的需求,同时也可以了解函数实现的原理,增长自身的能力。
3. 函数的定义与声明 3.1 定义
对函数的返回值类型、函数名、具有的参数、实现的功能进行定义。定义之后,便具有了一个可以实现一定功能的函数。
格式:
函数返回值类型 函数名(变量数据类型 变量名1,...... ,变量数据类型 变量名2){
函数功能实部分
}
一个具体的函数定义:
//实现两个整数的相加并以整型返回结果
int Add_sum(int a, int b){
int ret = a + b;
return ret;
}
函数一般都会有一个返回值,void除外。
void为返回类型意为函数没有返回值,可以在程序的末尾写上return;,或者不写return;,对这个函数无影响。
void*为返回值意为,函数返回一个不指向任何类型的为"空"的指针。
| 一些返回值类型举例 | |
|---|---|
| char | 字符型 |
| int | 整型 |
| float | 单精度浮点型 |
| double | 双精度浮点型 |
| char* | 字符指针 |
| int* | 整型指针 |
| float* | 浮点型指针 |
| double* | 浮点型指针 |
3.2 声明函数名的命名与变量的命名相同,由大小写字母,数字和下划线组成,且开头不能是数字。
函数的定义可以放在程序的开头,但函数的定义一般会跨越多行,当有多个函数被定义时main函数前面将会变得繁杂,不利于我们写程序。函数一般满足先声明后使用。
#include//函数定义 - 两个整数相加 int Add_sum(int a, int b){ return a + b; } int main(){ int a = 3; int b = 4; int sum = Add_sum(a,b); printf("%dn",sum); return 0; }
运行结果:
- 一种写法是把函数定义均放在main函数后面,但由于程序代码是从上往下依次进行,所以在main函数后面的自定义函数不能被main函数有效调用。解决方法是在main函数之前进行相应的函数声明。
- 函数的声明一般放在程序的main函数之前,放在程序的开头部分,与函数定义不同,只需要由函数头和结尾分号组成。
#include//函数声明 int Add_sum(int a, int b); int main(){ int a = 3; int b = 40; int sum = Add_sum(a,b); printf("%dn",sum); return 0; } //函数定义 - 两个整数相加 int Add_sum(int a, int b){ return a + b; }
运行结果:
另一种写法是:
把所有的函数定义都写在一个.c文件中,把所有的函数声明都写在一个.h文件中。
程序更加模块化
程序易于与他人协作
4. 函数的参数 4.1 实际参数(实参)
4.2 形式参数(形参)传递给函数的具有确定的值的参数称为实参。
实参可以是常量、变量、函数、表达式等。
函数名后括号内定义的各种变量。
4.3 实参与形参的区别函数声明时函数返回类型、函数名、函数的形参的数据类型是必需的,而形参中的变量名是可有可无的。也就是说函数声明关心的是函数返回类型、函数名、函数的形参的数据类型,不关心形参的变量名是什么,可以省略,但一般与函数头保持一致。
- 在函数被调用、实参把值传递给形参时,形参才在内存中被创建,才开始有效。在被调函数执行完返回时,包括形参在内的、在被调函数内有效的所有变量均被销毁,释放被占用的内存空间。
- 也就是说实参传递给形参时形参占用了新的内存空间,即实参与形参具有相互独立的储存空间,形参值得改变不会对实参的值产生影响,形参是实参的一份临时拷贝。
一个失败的交换两个整数的值的代码
#include//交换变量x与y的值 void swap(int x, int y); int main(){ int a = 0; int b = 0; scanf("%d%d",&a, &b); swap(a, b); printf("a = %d b = %dn", a, b); return 0; } void swap(int x, int y){ int t = x; x = y; y = t; }
实际运行结果:a与b并没有交换!
若想通过形参改变实参的值,需要得到实参的地址,所以需要使用类型为指针的形参来接收实参的地址,通过间接访问操作符*通过地址改变实参的值。
对交换两个数代码的改进:
#include//交换变量x与y的值 void swap(int *px, int *py); int main(){ int a = 0; int b = 0; scanf("%d%d",&a, &b); swap(a, b); printf("a = %d b = %dn", a, b); return 0; } //函数swap的形参为两个整型指针 void swap(int *px, int *py){ int t = *px; *px = *py; *py = t; }
运行结果:a与b完成了交换
5. 函数的调用
- 传值调用
- 传址调用
5.2 传址调用实参的值传递给非指针的形参,由于实参与形参具有不同的储存空间,形参也不知道实参的地址,所以形参无法通过实参的地址影响实参的值。
形参与实参相互隔绝,没有任何关系。
5.3 一个例子(二分查找)传址调用实际上也是传值调用,只不过有些特殊,传递的是实参的地址的值。
实参的地址传递给指针类型的形参,实参与形参也具有不用的储存空间,但是形参中存放的是实参的地址,所以可以通过储存的实参的地址来影响实参的值。
形参可以通过实参的地址访问实参,形参与实参便联系了起来。
对有序数组的元素进行排序并输出
#include//函数声明 int Binary_search(int arr[], int sz, int input); int main(){ int arr[] = {1,3,5,7,9,10,13,15,17,19}; int sz = sizeof(arr) / sizeof(arr[0]); int input = 0; scanf("%d", &input); //函数调用 int index = Binary_search(arr, sz, input); if(index != -1){ printf("找到了,下标为:%dn",index); } else{ printf("没找到n"); } return 0; } //函数定义 //二分查找(折半查找),找到了返回数组下标,找不到返回-1 int Binary_search(int arr[], int sz, int input){ //数组左下标 int left = 0; //数组右下标 int right = sz - 1; //数组中间下标 int middle = 0; while(left <= right){ middle = left + (right - left)/2; //待查找input小于数组中间元素,则input只可能在数组左半边 if(input < arr[middle]){ right = middle - 1; } //待查找input大于数组中间元素,则input只可能在数组右半边 else if(input > arr[middle]){ left = middle + 1; } //待查找input等于于数组中间元素 else{ return middle; } } //左下标大于右下标还没有找到则一定找不到了 return -1; }
一些运行结果:
函数可以嵌套调用
#includevoid two_num_swap(int* px, int* py); void arr_print(int arr[], int sz); void compare_sort(int arr[], int sz); int main() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; //求数组的长度 int sz = sizeof(arr) / sizeof(arr[0]); printf("排序前:n"); arr_print(arr, sz); comment_sort(arr, sz); printf("排序后:n"); arr_print(arr, sz); return 0; } // void compare_sort(int arr[], int sz) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; for (j = i + 1; j < sz; j++) { if (arr[i] > arr[j]) { //嵌套调用 //main函数调用compare_sort函数,compare_sort函数调用two_num_swap函数 two_num_swap(&arr[i], &arr[j]); } } } return; } // void two_num_swap(int* px, int* py) { int t = *px; *px = *py; *py = t; } // void arr_print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("n"); }
运行结果:
一个函数的返回值作为这个函数或另一个函数的参数。
一个例子:
#includeint main(){ printf("%d",printf("%d",printf("%d", 1514))); return 0; }
运行结果:
最内层的printf打印1514,返回值为4,。
第二层的printf打印4,返回值为1。
最外层的printf打印1,返回值为1。
scanf的返回值为接受的成功输入个数。
printf返回值是其打印字符的个数,包括空白符(换行符、空格符、水平制表符、回车符)。
6. 递归 6.1 解释:
6.2 函数递归的条件把复杂的问题按照一定的方法一直分解,每次都把问题复杂度降低,最终分解成简单的问题。
函数自己调用自己,满足条件时停止调用。
只需要少量的代码,就可以实现复杂问题的求解。
6.3 一个例子(计算字符串的个数)
- 有停止递归的条件
- 每次递归都更接近停止递归的条件
#includeint My_strlen(char *pstr); int main() { //字符串,末尾为' ' char str[] = "Hello world!"; int ret = My_strlen(str); printf("%dn", ret); return 0; } int My_strlen(char *pstr) { //指针pstr指向的字符不是' ',即字符串没到末尾,字符数+1 if (*pstr != ' ') { return 1 + My_strlen(pstr + 1); } //指针pstr指向的字符是' ',即到达了字符串的末尾,字符数不变 else { return 0; } }
运行结果:
6.4.2 求正整数n的阶乘n!循环是迭代的一种,但迭代不一定是循环
一些问题既可以用递归实现,也可以用循环实现。
相同的问题,递归实现往往比循环实现会占用更多的时间和更多的内存,如求一个正整数的阶乘,斐波那契数列。
相同的问题,递归实现一般比循环代码简洁。
而一些问题只能用递归实现,比如汉诺塔问题。
每次函数调用都会在内存的栈上占用内存,但栈的内存是有限的,递归多次调用自身过多时可能会使栈的内存溢出,即栈溢出。
#includeint main(){ unsigned int n = 0; scanf("%u", &n); int ret = Factorial(n); printf("%dn", ret); return 0; } //递归的实现 int Factorial(unsigned int n){ if(n > 1){ return n * Factorial(n - 1); } else{ return 1; } }
一个运行结果:
循环实现:
#includeint main() { int n = 0; int ret = 1; int i = 0; scanf("%d", &n); for (i = 1; i <= n; i++) { ret = ret * i; } printf("%dn", ret); return 0; }
运行结果:
6.4.3 求第n个斐波那契数列在不考虑数据超出in范围的情况下,求正整数n的阶乘n!递归运行速度慢于循环。
#includeint Fibonacci(int n); int main(){ int n = 0; scanf("%d", &n); int ret = Fibonacci(n); printf("%dn", ret); return 0; } //递归实现 int Fibonacci(int n){ if(n > 2){ return Fibonacci(n - 1) + Fibonacci(n - 2); } else{ return 1; } }
运行结果:
#includeint Fibonacci(int n); int main(){ int n = 0; scanf("%d", &n); int ret = Fibonacci(n); printf("%dn", ret); return 0; } //循环实现 int Fibonacci(int n){ int a = 1; int b = 1; int c = 1; while(n >= 3){ c = a + b; a = b; b = c; n--; } return c; }
在不考虑数据超出in范围的情况下,求第n个斐波那契数列递归运行速度慢于循环。
END



