C语言基础(有关三个数比较大小、冒泡排序、最大公约数、和有关某个数x的绝对值的n次方除于n的阶乘问题的函数求解法;和阶乘、一个整形求每个位数数字的函数递归方法;和一维数组,二维数组作函数参数的有关二分查找、杨氏矩阵、奇偶数组按左右顺序排序的函数方法等一系列问题的求解!)
多的不吹,少的不唠,开头还得是我的校园生活,今天是10月29周六,周末就是美好的,早上直接10点起床(一起床就看见我们宿舍少了个人,是谁呢?不用猜都知道是那个赌王),但是我隐约好像记得8点时某些人就出发去了图书、馆,然后我就睡过去啦,还是睡觉香,然后下午干了啥,哦,对,我看了一部电影(神秘海域),荷兰弟的作品,感觉就是还行吧(应该是资金有限,本来这种题材可以拍的很好的,估计是另一个演员钱拿多了),然后看完就睡着了,睡到了下午5点,所以等于睡了一天。
一、我们今天就来讲讲函数(OK,等我一下 ,让我先去看一下书,不然我也不知道怎么讲)(干货满满哦!)
1.首先让我们讲一下C语言中的函数类型:C语言的函数不像是数学中的函数,所以认为自己数学函数不好的同学并不用害怕,C语言中进行模块化的设计时,总是讲较为复杂的问题分解为若干个相对简单的子问题,然后对其分别求解。(这句话的意思也就是告诉我们在我们做算法题的时候也应该套用这种模式,就是先搞明白题目的意思,然后进行分解,一步一步思考,把我们想要实行的程序一步一步的实现,这样就可以使我们不至于无从下手了),所以当我们在解决一个子问题的时候,我们就可以利用那些已经被写好的函数(如之前的printf函数scanf函数和max函数),这些都可以直接进行使用,所以他们有一个好听的名字叫:库函数。但是我们不可能什么都依赖于库函数,否则就不需要什么 程序员了,所以在C语言中(自定义函数)才是关键,自定义函数就非常考察我们个人的能力了,所以先用一个代码让我们见识一下什么叫,自定义函数。(先用一个简单的比大小为例!)
//比大小正常版 #include<stdio.h> int main() { int a, b, c;//定义变量,使我可以输入三个数 scanf("%d%d%d", &a, &b, &c);//输入三个数 if (a < b)//判断,因为我要让a里面放的是三个变量里面的最大值,b里面放的是第二大,c中放最小 { int tmp = a;//所以为了达到上面那个小目的,我就要进行三次的判断,然后进行三次的数字交换 a = b; b = tmp; } if (a < c)//为了使a最大 { int tmp = a; a = c; c = tmp; } if (b < c)//为了使b第二大 { int tmp = b; b = c; c = tmp; } printf("%d %d %d\n",a,b,c);//按从小到大的顺序打印 return 0; }
int my_get_max(int x, int y, int m) 这个就是我自定义的函数 { int max; 这下面的就是为了实现找到最大值,我们应该写的基础代码 if (x > y) max = x; else max = y; if (m > max) max = m; return max; } #include<stdio.h> int main() { int a, b, c; scanf("%d%d%d",&a,&b,&c); int max = my_get_max(a, b, c);这个就是函数的调用 printf("max=%d\n",max); return 0; }
#include<stdio.h> my_get_max(int x, int y) { return x > y ? x : y;这个就是一个三目操作符的使用而已,看不懂不怕 } int main() { int a, b, c; scanf("%d%d%d",&a,&b,&c); int max = my_get_max(my_get_max(a, b),c);这个只是一个函数的嵌套使用而已,意思就是比完一对数的大小后,再拿那个数与第三个数比大小,这样就可以得到3个数的最大数 printf("max=%d ",max); return 0; }
2.由上述的三个代码比较我们可以清晰的知道什么是自定义函数,什么是函数的调用,什么是函数的嵌套使用,第一个代码就是正常的数字调换来获得最大值,第二个就是使用了函数的方法进行获得最大值,不过就只是把获得最大值的代码,重新包装一下,然后放到函数中。所以我们这边再介绍一下怎样包装函数,首先包装函数的时候需要进行传参,就是把你想要完成的步骤,所需要的元素给传过去,例:我想比较a 和 b 的大小,此时我就应该把 a 和 b 给传过去,此时在函数内部就可以完成 a 和 b 的比较了(但是传参时一定要注意,你是否需要返回你最终得到的那个值,比如你想在函数使用完后,获得你的那个最大值,把它打印出来,此时你就一定要把 a 和 b 的地址转到函数内部,在传参时写成 &a 和 &b )然后再用一个变量把函数是最终返回值给接收,就好比是上述代码中的max,只要这样此时我才有资格去打印函数的返回值,否则你永远都拿不到此函数的返回值(这种方法就叫做传址调用),然而也还有另一种情况,那就是当你不需要返回值的时候,此时你就只是单独进行函数的调用,中需要其工作一次,没有别的要求是,此时你就可以使用传值调用,就是可以让形参获得实参的一份临时拷贝,当我们改变形参时此时并不会改变外部的实参,(这边解释一下什么 叫形参和实参)(形参就是我用来接收实参时的空间,例如:int x,int y,这个就叫形参),(而实参就是我传给函数的内容,例如:a,b,这两个变量),所以总结在进行传参时有两种方法(1.传值,2.传址),下面 再把这两种方式的区别做小点分析:
1.传值:把 a 的数值传到 b,改变 b,a 不会跟着变,b 存的是跟 a 一样的值;就是相当于一份临时拷贝;在函数内进行使用,出了函数的范围就会销毁,也就是不影响外部任何操作;
2.传址:把 a 的地址传到 b,改变 b,a 同时跟着变,b 存的只是a 的地址;此时就可以让函数和函数外部建立真正的联系,函数内部此时就可以直接操作函数外部的变量了;所以当我要设计一个函数(这个函数可能要改变外部变量时,我就要考虑使用传址调用,否则程序就不能正常使用);
3. 这边在给各位总结一下如何写函数(1.我应该先要想这个函数是用来干什么的;2.然后才去想这个函数应该怎么实现),比如获得最大值函数(1.我应该先想这个函数是用来比较两个数的大小的,然后我就把两个数给传到函数中,并且我是需要打印这个函数的返回值的,所以函数类型设置为int类型,这样我就能返回一个int类型的返回值了,与void类型的函数进行比较,假如你的函数不需要返回值时,你就可以设置为void类型的,且使用传值的方法,比如此时你只是想进行比较;2.然后第二步就是要思考你应该如何去实现这个函数,比如是像第一个代码那样先进行判断然后再进行数字的交换;或者就是像第二种方法那样,直接进行判断,然后直接就赋值,然后再比较再判断,所以只要你牛,爱怎么写怎么写)。
2.接下来我们用一个高级一点的代码,再对函数进行一轮的解析:
//.冒泡排序课上练习,函数进阶版 #include<stdio.h> void maopaohanshu_wu(int arr[], int sz)//这个就是我函数的名字,和参数的接收,和函数的返回类型(void)的函数 { int m = 0; for (m = 0; m < sz-1; m++)//这步的意思就是我想要进行冒泡排序,我就要进行数字交换(但是是数字个数-1次交换),因为当我进行最后一次交换的时候(此时是同时交换了两个数字的位置,所以可以少一次交换) { int n = 0; for (n = 0; n < sz - 1 - m; n++)//这个for循环的意思就是我每次循环的数字的个数,(n < sz - 1 - m),所以这步就有点关键了,就代表冒泡排序的终极奥义: //就是每进行一次循环,我所要交换的数字就会少一个,例:第一次我需要进行9个数字的交换(也就是我要交换9次,第二次我就需要交换8次,以此类推) { int tmp = 0; if (arr[n]<arr[n+1])//这个判断语句就是为了交换我的数字(配合上上面那个for循环,我此时就可以进行我需要的次数次交换) { tmp = arr[n]; arr[n] = arr[n +1]; arr[n + 1] = tmp; } } } } void dayinhanshu_wu(int arr[], int sz)//这个就是arr数组的打印(切记只要你想要打印数组,你就一定要使用循环),所以这边就非常抽象的把一个打印函数给重新包装出来,自己成为一个调用函数,而不是库函数 { int i = 0; for (i = 0; i < sz; i++)//这个训话就是为了打印我arr数组中的每一个元素(但是注意是通过下标来获取元素) { printf("%d ",arr[i]); } } int main() { int i = 0; int arr[] = {1,2,3,4,5,6,7,8,9,10}; int sz = sizeof(arr) / sizeof(arr[0]); maopaohanshu_wu(arr, sz);//这边就是两个函数的调用(这样的写法使得main函数内部就非常的干净的感觉) dayinhanshu_wu(arr, sz); return 0; }
如上这个代码,就使用了两个函数的设计(打印函数和冒泡排序函数),冒泡函数的设计看不看的懂无所谓,想看懂就认真去理解注释,主要是理解什么东西是函数的调用,函数说来说去就是那点东西(1.就是函数的名字,例:maopaohanshu_wu;2.就是函数的返回 类型,例:void,写在函数名的前面;3.就是函数参数的接收类型,例:(int x,int y);4.就是如何实现函数作用的代码设计(当然也就是最关键的一步)就是函数内部的所有东西)。多的不说,代码和注释里面自己慢慢悟!
3.上述我们讲的都是又关于外部函数的实现,所以接下来讲一讲更加具有代表性的函数实现
1.首先先给大家把内部函数和外部函数做一个区分
(一、)内部函数其实就是只限于本文件的函数调用,不允许其他文件的调用;
(二、)外部函数就是除了内部函数,其余都可以被同一程序中其他的源文件的main函数调用;
2.OK!还是那句话多的不说,少的不唠,大家看代码:
#include<stdio.h> #include<math.h> double fact(int n)//这个就是为了实现n的阶乘效果的函数,阶乘就是那样,没什么好讲的了 { int i = 0; double ret = 1;//但是切记算阶乘累乘的时候一定要把ret赋值为1. for (i = 1; i <= n; i++)//这个就是为了获得(1到n的数字)的累乘,为什么会有这个效果呢?(重点就是因为我的i每次都是从 1 开始的,这点尤为的重要) { ret *= i;//这个就是一个累乘而已 } return ret; } int main() { int n = 1;//这个n从1开始就非常的细节了,可以让第一个数字x为1次方,保留其本身,而不是0次方改变其本身 double x = 0; double item = 0; double s = 0; printf("请输入x的值:"); scanf("%lf", &x); item = x;//这步有两个意思(1.是为了把x给保护起来,防止在使用过程中发生改变;2.是为了实现第一个数字x可以加到s之中) while (fabs(item) >= 0.00001)//这个是题目要求,并且fabs是一个求绝对值的函数(此时就是想求item的绝对值) { //且这个循环的意思是只要item的绝对值大于0.00001这个循环就可以执行,题目要求 s += item;//这步就是累加的意思,把每次循环过来的item都给加在一起 n++; //这步也是关键,只有这样才能改变n的值(从而实行x的n次方,从1到n的各个次方的数字) item = pow(x, n) / fact(n);//这步就是整个代码的核心,就是为了算x的n次方除于n的阶乘,然后把每一次循环的item都加在一起 } printf("s = %lf\n", s); return 0; }
题目附上!
上面那个代码就是通过两个循环就能实现,但是要有很强的基础
并且上述代码,充分使用了变量和循环还有函数,如下再给各位来一个代码(求最大公约数的求解)
//最大公约数求解 #include<stdio.h> int gcd(int x, int y) { int g = 0; g = x % y; while (g)//这个循环要注意,下面的交换也要注意一下(目的就是为了一直%,然后得到最大公约数,具体原理小学课本上有) { x = y; y = g; g = x % y;//这边千万不敢傻傻的打印g,因为此时g还不等于0,循环还会继续,所以g的值最后的赋给了y,所以要打印y; } return y; } int main() { int a, b, c; printf("请输入两个你想求数:"); scanf("%d%d",&a,&b); c = gcd(a, b); printf("整数%d和整数%d的最大公约数是:%d\n",a,b,c); return 0; }
4.总的来说函数就是那样,想要理解这些代码中的算法就认真看注释就行。
5.现在再来讲一下什么是函数的递归(算是比较难理解的东西)
1.首先我们定义一下函数递归的概念:就是一个函数程序调用自身的编程技巧我们就叫做递归(通俗的来说就是一个函数自己调用自己)。
2.当我们在使用函数递归的时候,我们一定要小心一个错误(栈溢出),简单来说栈溢出的意思就是,main函数一直往栈区放变量,最后导致内存不足,所以就会有栈溢出的现象。
3.所以当我们使用哪个递归的时候,我们就一定要有两个最基本的条件(1.一定要有限制条件,使这个递归不满足条件的时候,递归可以停止;2.再每次进行完这个递归的时候都应该要越来越接近我的这个条件),所以这就是递归使用的注意事项,(且要明白这个递归的条件的重要性)
4.OK!多的不说,少的不唠,请看代码如下:
//n的阶乘递归版 #include<stdio.h> int Fac1(int n) { if(n<=1) return 1; else return n*Fac1(n-1); } int main() { int n = 0; int ret =0; scanf("%d",&n); ret = Fac1(n); //循环的方式 printf("ret=%d\n",ret); return 0; }
//函数递归 练习1,接受一个整形值,按照顺序打印它的每一位 #include<stdio.h> // 例:输入:1234,输出1 2 3 4 *主要是用好% 和 / void print(int n) { if(n>=10) //这个就是递归的限制条件 { //这个叫print函数中直接调用print函数 print(n/10); //有点像循环,就是一直调用的感觉 } printf("%d ",n%10);//当输入123经过两次调用之后,就会剩下一个1,然后进行1%10这个,然后就于1,最后打印出1 } int main() { unsigned int num = 0; scanf("%d",&num); print(num); return 0; }
5.看完上述代码,作何感想(是难难难呢? 还是不难呢?),反正本人觉得好难啊!好了不搞笑了,注释有部分解释,但是我还是再做一些总结:
(一)以n的阶乘的那个代码为例:可以清楚的看书其实main函数内部并没什么重要的东西,关键的东西在于函数中的 if (n<=1) ,return nFac1(n-1) 这两步,剩下的都是划划水啦,只要弄明白这两步就行了,首先是if (n<=1) 这个判断条件,这个就是上述第三点的内容,这个条件就是这个递归能够成功实现的关键(且表达的意思就是只有当我输入的数字大于等于 1 时才可以进行n的阶乘的计算,否则就无法计算,(其实这也就不过是小学的逻辑,算阶乘当然是要大于等于1的数),但是放到递归中那种小学的逻辑反而就能成为一个递归很好的判断条件,有时候真的是很关键的), 然后就是 return nFac1(n-1) 这步,这步也是异常的关键,(不要以为表面看起来好理解,就是这个数乘以它的下一个数),其实这步想理解是有困难的,原因如下图:(以当n=6时为例)
这个是被调函数中的return语句,当值为6时,执行,得到的是6* fac(5),这个值作为结果会返回给主调函数,主调函数执行到fac(5)时,又调用了int fac()这个函数,接着执行得到5* fac如此循环,直到2* fac(1),此时被调函数 int fac(1)的值为1。即2fac(1)=2 * 1=1,因为规定了 if(n<1)时 return 1;得到确定的值之后,或是说,递归函数达到结束边界条件,值又通过return返回给主调函数,然后回溯计算 6 fac(5)。以此类推,最后算出n=720时的值。所以这就是递归的难啊(就像是男人一样,难啊),不好理解,各位慢慢来,不要着急啦!
(二)理解完上面那个阶乘的代码,我们现在看一下这个更简单的递归算法(关于按顺序打印位数的),递归反正想理解就要费一番功夫就对了,所以这个代码的意思就是:假如我输入1234时,根据 print(n/10) 这步我就可以很轻松的得到123,printf("%d “,n%10) 然后通过这步我就可以很轻松的得到 余数(4),所以此时我就得到了1234的个位了对吧!所以此时通过递归的方法 和我的 if (n>=10)这个条件,我就可以把我刚刚得到的 123给重新调用print这个函数,进行递归思想,然后又通过print(n/10) 这个得到12,通过这个printf(”%d ",n%10)得到(3)十位,如此类推,我就可以得到1234 的每一位,1, 2,3, 4 了,有没有感觉递归非常神奇呢?(然而这边还要特别注意 ,就是 1 % 10 是得 1 ,不敢想住是 得 0 就行)。
6.函数的递归用法我都给你们讲完了,函数基本上也就差不多了(但是我很想凑一凑,看一下能不能凑到8000个字),所以我在想现在讲啥比较好一点。好的,我们现在来看一下什么叫 (数组作为函数的参数)!
1.首先我们先看一下什么叫:一维数组作为函数的参数,在这个问题之前先让我给你们介绍一下什么是什么是一维数组,什么是二维数组,例:一维数组 int arr[10]= {1,2,3,4,5,6,7,8,9,10} 这个就是一维数组的定义;二维数组,例:int arr[3][4]={{1,2,3,4},{5,6,7,8}}这个就是二维数组的定义(先简单介绍一下,具体的数组使用和细节处理有空再说,懂得什么是数组就行)(多说一句,其实二维数组就是2个一维数组,以后再跟你说是原理),所以我们知道什么见一维数组了,现在就让我们看一下代码,代码如下:
#include<stdio.h>//有关二分查找法 int erfenzhazhaofa_wu(int num,int arr[], int sz) { int left = 0; int right = sz - 1; int mid = (left + right) / 2; if (arr[mid] > num) { left++; } else if (arr[mid] < num) { right--; } else { return mid;//不符合上面两个条件就只剩下正确答案了 } return 10; // 结束标志 } int main() { int arr[] = {1,2,3,4,5,6,7,8,9,10}; int num = 5; int sz = sizeof(arr) / sizeof(arr[0]); int ret = erfenzhazhaofa_wu(num,arr,sz);//每次定义完函数一定要考虑,要不要赋一个变量; if (ret == 10) { printf("找不到该数字\n"); } else { printf("找到了,该数字是:%d\n",ret); } return 0; }
这个代码就是一个二分查找法的代码,想完全理解就自己结合代码和注释慢慢悟,看不明白也没关系,这边主要是跟大家讲一下什么是一维数组做函数参数,从上面代码可以清晰的看出在main函数内部有一个一维数组arr,我想把这个一维数组传到函数erfenzhazhaofa_wu中去,所以此时我需要用一个int arr[ ]来接收(其实还可以用一个指针,但是我们还没讲到,所以先不讲,所以这就是数组的简单的传参方法。只有这样把arr给传到函数中去,我才能实现我想对数组进行的操作,讲数组中的元素进行改变,也就是进行编程,实现目的。
2.一维数组介绍完了,我们来看一下本节最牛的代码(有关二位数组作为函数参数的),代码如下:
//9.这个题目是关于(杨氏矩阵)的代码题(就是去寻找右上角或左下角的数然后去将它和我要找的数进行比较)通用就是(当我以后搞不定返回值时,我就可以利用这种返回型的参数,参考下面) #include<stdio.h> int FindNum(int arr[][3], int k, int* px, int* py) { int x = 0; int y = *py - 1;//x,y的坐标就搞定了 while (x <= *px - 1 && y >= 0)//这个循环的意思就是不能出去我的二维数组的范围 { if (arr[x][y] > k)//在进行比较的时候用的就一定是那个数的坐标,不然拿不到我要的那个数字(左上角,右下角) { y--; } else if (arr[x][y] < k) { x++; } else { *px = x; *py = y; //printf("该数下标是:(%d,%d)",x,y);虽然可以在这里写,但是在函数内写,就不是很好 return 1; } } //找不到的话这边也可以给一定的参数例: *px = -1; *py = -1; return 0; } int main() { int arr[4][4] = { {1,2,3,4,} ,{5,6,7,8,}, {9,10,11,12,}, {13,14,15,16} }; int k = 11; int x = 4;//这个数组的元素一定要传进去(就看是用什么样子传,可以用常数4,4;也可以用地址(&x,&y的形式,但是默认此时的x,y就是4,4)) int y = 4; int ret = FindNum(arr,k, &x, &y);//这样子就可以具有传入和传出两种作用了,(传入时传4,4)(传出时就是传改变后的值(让我在函数外面也可以使用改变后了的值,这就可以让我在外面打印)) if (ret == 1) { printf("找到了\n"); printf("该数的下标是:(%d,%d)\n",x,y); } else { printf("找不到该数\n"); } return 0; }
这个代码是关于杨氏矩阵问题的代码,算法不怎么样(只是思路有一点复杂而已),算法难度在我学过的里面还不是最难的,想懂就自己看注释,看不懂不怕,我们主要还是为了介绍一下二维数组作为函数参数时的使用,如上面代码一样,二维数组进行传参时,跟一维数组是一样的,具体我就不多做解释,但是还是有几个注意点的,例:
1.我们学过的传值和传址的区别
2.就是在传二维数组的时候一定要把二维数组的(行和列)给传过去给函数内使用,不然就有大问题
3.并且二维数组的使用还是是依赖于下标
4.当我们想找某个二维数组中的元素的时候,用的方法还是去寻找它的坐标(arr [x] [y]),且是下标的坐标,切记!切记!
7.总结:本想总结,但是一看字数,不舍得(一想凑个万字)
所以给大家在展示一个代码,关于调换数组中元素的(相比于上面两个代码好理解的很)(具体案列见(C语言程序设计176页)),代码如下:
//这个就是一个奇数偶数的交换,且交换后奇数在一边,偶数在一边 #include<stdio.h> void jioujiaohuanhanshu_wu(int arr[],int sz) { int i = 0; int left = 0; int right = sz - 1;//这个区分一下字符串交换时的那个strlen-1; while(left<right) { while ((left < right) && (arr[left] % 2 == 1))//这个意思就是从左开始找奇数 { left++; } while ((left < right) && (arr[right] % 2 == 0))//从右开始找偶数 { right--; } if(left<right)//这个判断和底下的交换的意思就是:我找到了左边的所有奇数,找到了右边的所有偶数,现在我就需要交换这些数字之后,我就得到了,奇数在一边,偶数在一边了 { int tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; } } } int main() { int arr[10] = { 21,34,224,25,367,41,736,37,42,456 }; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]);//这边可以一眼就看书元素个数,所以这步可以直接省略,然后循环就直觉写小于10也行 for (i = 0; i < sz; i++)//如果想练习怎么使用函数,你尽管可以把这个打印数组拿到外面去重新包装一个函数出来 { printf("%d ", arr[i]); } printf("\n"); jioujiaohuanhanshu_wu(arr,sz);//这个位置写函数主要为了区别一下原数组和改变之后的数组,使我的程序效果更加明显 for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
这个代码相对好理解,自己看注释 就能明白,反正只要明白了数组交换的原理就行
8.真正的总结(小命要紧):对于函数就是干,别怂你能行,相信自己,加油!(还有就是大家写函数的时候不敢像我一样,写成这个样子,其实翻译过来就是奇偶交换函数加上我的姓(jioujiaohuanhanshu_wu),所以不能这样写,这样不好,最好是去看一下英文是怎样的。