C语言——函数
函数的主要思想是:函数其实是从上到下逐步求解的过程,把一个大的问题拆成多个小的子问题或者说把一个大的功能拆成小的功能模块,通过实现小的功能最终实现大的功能的过程。
函数的语法
类型标识符 函数名(形式参数)
{
函数体语句;
}
其中类型标识符是函数要带出结果的类型也就是返回值的数据类型,注意数组类型不能作为返回值类型。如果函数不需要带出结果时就可以将返回结果的类型标识符设置为void。如果返回结果的类型和函数的类型标识符不一致,其最终结果的类型以类型标识符为准,也就是最终结果的类型都会转换为类型标识符的类型,所以在返回结果时要注意返回结果的类型和类型标识符是不是一致,否则可能会导致数据类型有高精度转为低精度而导致返回结果的精度丢失。还需要注意的是如果不写类型标识符默认是int类型。
函数名,函数名的命名规则和命名标识符的命名规则是一致的,名字必须以字母数字下划线构成、数字不能开头,不能和关键字或库文件重名,在进行函数名的命名时建议起与函数功能有关的名字这样做在后期再查看代码时看到函数名就能大致知道该函数的作用。
形式参数,形式参数表示的是函数体会用到的数据,它的作用是用来接收实际参数的,那么实际参数是怎么传给形式参数的呢?这里暂且只介绍值传递,一般情况下我们会在main函数中定义实际参数然后在调用函数的时候把实际参数传给形式参数,此时需要注意的是main函数定义的实际参数和函数中的形式参数是两个不同的变量,它们存放的内存空间也不同,所以再传参是实际上是把实际参数的数值传给了形式参数,这一个过程就叫做值传递,对于值传递形式参数的改变是不会影响到实际参数的。如果函数不需要接受参数时可以把形式参数设置为void,实际参数和形式参数是有一定的对应关系的:1、实参和形参的类型必须匹配;2、传进函数的参数必须和定义的形式参数个数相同;3、传参的顺序要一 一对应;
形参的写法:
数据类型 形参变量名1,数据类型 形参变量名2 … …
在定义形参变量是要注意:形参变量必须明确指定类型,而不能写成:
int max(int x, y),这里形参y并没有指定类型。
函数体代码,函数体代码就是函数要实现功能的那一部分代码,在编写函数体代码实现功能时要尽量保证函数功能的单一性。
**函数定义的位置,**函数定义的位置有两种:1、定义在main函数之前;2、定义在main函数之后;如果函数定义在main函数之后,需要在使用(函数调用)前作函数声明,函数声明就是函数头+分号。
下面用判断一个数是否为素数的例子来说明函数的定义到调用最后实现功能过程;
#include <stdio.h> int isPrimeNum(int num) { int i = 2; int flag = 1; for (i = 2; i < num; ++i) { if(num % i == 0) { flag = 0; break; } } return flag; } int main(void) { int num = 0; scanf("%d", &num); if(isPrimeNum(num)) { printf("%d is primenumber\n", num); } else { printf("%d is not primenumber\n", num); } return 0; }
这个函数实现了在键盘上输入一个数据并判断该数据是否为素数,其实这个功能可以直接在main函数中实现转换成函数的方式实现可以让代码看起来更加整洁,在后期维护的时候也更加方便。
函数的调用关系
函数的调用关系中只有调用者和被调用者的关系,对于main函数它是整个程序的入口所以它只能是调用者别的函数不能去调用main函数,但是对于其他的函数调用者和被调用者的关系是相对的,一个函数可以去调用别的函数也可以被别的函数去调用。例如:
int isLeapYear(int year) getMonthDays(int year, int month) { isLeapYear(int year); } main() { getMonthDays(year, month); }
这里main函数去调用getMonthDays函数而getMonthDays函数去调用isLeapYear函数,从这里就可以看出函数调用和被调用的关系是相对的。
还需要注意的是:函数是不支持嵌套定义的,但是支持嵌套调用。
函数名代表的是函数的入口地址,在调用的时候可以通过函数名来查找相关函数,那么CPU在执行别的函数的时候再回到main函数CPU是怎么知道从哪一个位置继续往下执行的呢?
其实是这样的当要执行别的函数跳出main函数前做了一个保护现场的动作,当执行完别的函数的时候再回到main函数时恢复现场CPU就能继续执行下去了。那CPU是怎么保护现场的呢?其实是通过栈(是一块内存空间)这种数据结构来保护现场的,在调用别的函数时会把main函数的数据进行压栈再恢复现场时只需要把数据出栈就可以了,上述过程对于其他的函数也适用,如果存在多个函数嵌套调用时,在调用下一个函数前都会把当前函数的数据进行压栈,直到被调用的最后一个函数执行完之后,会把数据出栈利用栈先进后出的特点先进栈的数据会在后面出栈,这特点就能够让程序正确运行,能让现场得到正确的恢复。
栈也是有大小的默认情况下是8M大小但是可以修改,如果一致往栈上放数据直到栈满程序会报段错误。
C语言程序把内存划分了5个区域:
1、栈,主要用来存放自动变量或函数调用的数据 ;
2、堆的特点是:空间大,堆上的空间需要手动申请手动释放 ;
3、字符串常量区,这片内存是只读的;
4、静态区(全局区)用来存放全局变量和静态变量 ;
5、代码区这片区域也是只读的 ;
递归
递归是一种特殊的函数嵌套调用和循环,解决递归问题的思路:要求解决问题n就必须依赖于问题n-1的解决;
递归代码实现思路:
递推关系怎么从问题n到问题n-1再从为题n - 1然后怎么由问题n - 1返回给问题n实现回归的过程,递归对于我来说还是会有一些难理解但是通过话递归展开图能让我清楚的了解递归的过程,下面以一个例子来说吧:
1、递归求1~n的和;
#include <stdio.h> int sumR(int n) { if(n == 1) { return 1; } else { return sumR(n - 1) + n; } } int main(void) { int n = 0, sum = 0; scanf("%d", &n); sum = sumR(n); printf("sum = %d\n", sum); return 0; }
以求1~5的和为例,5传进sumR函数5不等于1会执行else里的语句sumR(5 - 1) + 5,此时调用了sumR函数但参数为4再一次进行判断4不等于1执行else里的语句sumR(4 - 1) + 4,这里再一次调用了sumR函数但参数为3再一次进行判断3不等于1执行else里的语句sumR(3 - 1) + 3,这里再一次调用了sumR函数但参数为2再一次进行判断2不等于1执行else里的语句sumR(2 - 1) + 2,这里再一次调用了sumR函数参数为1,1不等于1执行往上一级返回1,这里的返回是往上一级返回而不是作为最终结果返回,1返回给sumR(1)然后1 + 2作为返回值返回给sumR(2),1 + 2 + 3作为返回值返回给sumR(3),1 + 2 + 3 + 4作为返回值返回给sumR(4),最后返回最终结果1 + 2 + 3 + 4 + 5;
在进行函数传参时数组也可以作为函数参数,数组作为参数总共有两种情况:
1、数组元素作为函数参数 ;
2、数组本身作为函数参数 ;
在一维整型数组本身作为函数的参数时,可能会出现的问题:
int printArray(int a[]) { int len = sizeof(a) / sizeof(a[0]); int i = 0; for(i = 0; i < len; ++i) { printf("%d ", a[i]); } printf("\n"); } int main(void) { int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; printfArray(a) return 0; }
上述程序的输出结果是1 2,原因是printArray(int a[])的形参int a[]在编译时编译器会把它识别为
int *a这是一个指针,指针在64位系统中的大小是占8个字节,所以在计算len时8/4=2,最终的打印结果是1 2,通过上述情况可以知道在把数组作为实参传给函数是同时也要把数组的长度传给函数用作函数中对数组元素的调用。对于一维数组:
int a[10];
数组名 代表类型 —int[10] 这种数组类型 ;
数组名 代表的值 —首元素的地址(数组所占内存空间的首地址) ;
下面以数组作为参数传给函数的方式实现冒泡排序来看程序执行的效果吧:
程序输出结果:
其实从输出结果上看跟在main函数上实现的冒泡排序没有什么区别,只是通过函数的方式去实现且以数组作为参数,但是数组是用作处理大批数据的在我们需要传大量的数据的时候相比与传多个值我只传一个数组就能解决问题这样效率会高得多,不过也要具体问题具体分析,今天先到这了明天还会继续更新的!