内容:汉诺塔递归、整型一维数组作函数参数、字符一维数组作函数参数、二维数组作函数参数。
一、回顾
- 函数思想:高内聚、低耦合,把功能拆开;输入-处理-输出;
- 函数定义、函数调用、函数声明;
- 递归思想;
- 内存 5 个区:栈、堆、全局/静态区、字符串常量区、代码区。
二、汉诺塔(递归经典题)
2.1 递归两步
写递归必须先想清楚:
- 问题 n 和问题 n-1 之间的递推关系;
- 结束条件。
2.2 思路
把 n 个盘子从 A 移到 C,借助 B:
1. 将 n-1 个盘子 从 A → B // 递归 2. 将剩下的那个盘子 从 A → C 3. 将 n-1 个盘子 从 B → C // 递归结束条件:n == 1,直接A → C。
注意三个柱子的身份会变:
起始 辅助 目标 n 个盘子 A B C n-1 A C B2.3 递归展开(n=3)
hanoi(3,'A','B','C') ├─ hanoi(2,'A','C','B') │ ├─ hanoi(1,'A','B','C') → move('A','C') // 1:A→C │ ├─ move('A','B') // 2:A→B │ └─ hanoi(1,'C','A','B') → move('C','B') // 3:C→B ├─ move('A','C') // 4:A→C └─ hanoi(2,'B','A','C') ├─ hanoi(1,'B','C','A') → move('B','A') // 5:B→A ├─ move('B','C') // 6:B→C └─ hanoi(1,'A','B','C') → move('A','C') // 7:A→C2.4 代码(hanoi.c)
#include<stdio.h>voidmove(inta,intb){printf("%c-->%c\n",a,b);}// 起始 辅助 目标voidhanoi(intn,intpole1,intpole2,intpole3){if(n==1){move(pole1,pole3);// 只有一个盘,直接从起始挪到目标}else{hanoi(n-1,pole1,pole3,pole2);// ① 把 n-1 个挪到辅助柱move(pole1,pole3);// ② 把第 n 个挪到目标柱hanoi(n-1,pole2,pole1,pole3);// ③ 把 n-1 个从辅助柱挪到目标柱}}intmain(void){intn;printf("Input a num:");scanf("%d",&n);hanoi(n,'A','B','C');return0;}带步骤号的详细版(hanoi_all_info.c):
#include<stdio.h>voidmove(intn,intpole1,intpole2){staticintstep=1;printf("%03d:[disk %d] : %c --> %c\n",step++,n,pole1,pole2);}// 起始柱 辅助柱 目标柱voidhanoi(intn,intA,intB,intC){if(1==n){move(n,A,C);}else{hanoi(n-1,A,C,B);// n-1 先挪走puts("-------");move(n,A,C);// 第 n 个挪到目标柱puts("-------");hanoi(n-1,B,A,C);}}intmain(void){intn=0;printf("Input numbers of disk: ");scanf("%d",&n);hanoi(n,'A','B','C');return0;}三、数组作为函数参数
3.1 问题引入
inta[10]={1,2,3,4};- 单个元素
a[0]就是 int 型变量,传给int max(int a, int b)没问题; - 但要把整个数组传进去怎么办?
3.2 整型一维数组作参数
// 形参写法(形式上是数组)voidprintArray(intx[10],intlen){}// 编译器本质上看成voidprintArray(int*x,intlen){}数组在内存中是一片连续空间,数组名从"代表的值"角度,代表的是首元素的地址:
[ a[0] ] ← 首元素的起始地址 [ a[1] ] [ a[2] ] ...小结:
| 写法 | |
|---|---|
| 形参 | printArray(int a[], int len)(数组形式 + 数组长度) |
| 本质 | printArray(int *a, int len)(指针,接收首元素地址) |
| 实参 | printArray(数组名, 数组长度) |
注意:函数内部
sizeof(x)拿不到数组长度(x 已经退化成指针),所以长度必须单独传。
3.3 值传递 vs 地址传递
- 实参给形参,本质是把实参的值拷贝给形参变量;
- 普通变量是值传递(函数内改形参不影响外面);
- 数组传的是首地址,函数内通过地址操作,改的就是主函数里那块数组空间——所以
reverseArray、selectSort不需要 return 就能改原数组。
3.4 基础代码(array.c)
#include<stdio.h>voidprintArray(intx[],intlen)// 编译器看成 int *x{inti=0;for(i=0;i<len;++i){printf("%d ",x[i]);}putchar('\n');}voidinputArray(intx[],intlen){inti=0;for(i=0;i<len;++i){scanf("%d",&x[i]);}}intmain(void){inta[]={1,2,3,4,5,6};intlen=sizeof(a)/sizeof(a[0]);inputArray(a,len);printArray(a,len);return0;}3.5 数组常用操作封装(test_array.c)
#include<stdio.h>voidprintArray(intx[],intlen){inti=0;for(i=0;i<len;++i){printf("%d ",x[i]);}putchar('\n');}voidinputArray(intx[],intlen){inti=0;for(i=0;i<len;++i){scanf("%d",&x[i]);}}// 求最大值intmaxOfArray(intx[],intlen){intmax=x[0];inti=0;for(i=1;i<len;++i){if(x[i]>max){max=x[i];}}returnmax;}// 逆序voidreverseArray(intx[],intlen){inti=0;intj=len-1;while(i<j){intt=x[i];x[i]=x[j];x[j]=t;++i;--j;}}// 选择排序voidselectSort(intx[],intlen){inti=0,j=0;for(i=0;i<len-1;++i){for(j=i+1;j<len;++j){if(x[i]>x[j]){intt=x[i];x[i]=x[j];x[j]=t;}}}}// 二分查找:找到返回下标,没找到返回 -1intbinaryFind(intx[],intlen,intn){intbegin=0;intend=len-1;intmid=0;while(begin<=end){mid=(begin+end)/2;if(x[mid]>n){end=mid-1;}elseif(x[mid]<n){begin=mid+1;}else{break;}}returnbegin<=end?mid:-1;}intmain(void){inta[10]={1,2,3,4,5,6};intlen=sizeof(a)/sizeof(a[0]);inputArray(a,len);selectSort(a,len);printArray(a,len);intn=0;printf("Input a num:");scanf("%d",&n);intret=binaryFind(a,len,n);printf("ret = %d\n",ret);return0;}四、字符型一维数组作函数参数
字符数组用来存字符串,字符串本身有 ‘\0’ 结束标志,所以作参数时不需要再传长度。
chars[]="hello";| 写法 | |
|---|---|
| 形参 | void Strcpy(char dest[], char src[]) |
| 实参 | 直接传数组名 |
完整代码(char.c):手写 string.h 函数
#include<stdio.h>// 手写 putsvoidPuts(chars[]){inti=0;while(s[i]!='\0'){putchar(s[i]);++i;}putchar('\n');}// 手写 strlenlongStrlen(chars[]){inti=0;while(s[i]!='\0'){++i;}returni;}// 手写 strcpy:把 src 拷到 dest,最后补 '\0'voidStrcpy(chardest[],charsrc[]){inti=0;while(src[i]!='\0'){dest[i]=src[i];++i;}dest[i]='\0';}// 手写 strcat:把 src 拼到 dest 后面voidStrcat(chardest[],charsrc[]){inti=0;// 1. 先定位到 dest 的 '\0'while(dest[i]!='\0'){++i;}// 2. 从该位置开始把 src 逐个拷过来intj=0;while(src[j]!='\0'){dest[i]=src[j];++i;++j;}// 3. 保证 dest 是字符串dest[i]='\0';}// 紧凑写法:dest[i++] = src[j++] 会把 '\0' 一起拷过去voidStrcat1(chardest[],charsrc[]){inti=0;while(dest[i]!='\0'){++i;}intj=0;while(dest[i++]=src[j++]);}// 手写 strcmp:返回停止位置上两个字符的差值intStrcmp(chars1[],chars2[]){inti=0;while(s1[i]==s2[i]&&s1[i]!='\0'&&s2[i]!='\0'){++i;}returns1[i]-s2[i];}intmain(void){chars[20]="hello";gets(s);chars1[20];gets(s1);printf("ret = %d\n",Strcmp(s,s1));return0;}五、二维数组作函数参数
inta[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};二维数组本质是"一维数组的一维数组",作参数时:
// 形式上写成二维数组voidprintArray(inta[3][4]);// 编译器本质上看成:指向一维数组的指针voidprintArray(int(*a)[4]);小结:
| 写法 | |
|---|---|
| 形参 | printArray(int a[][4], int row)(第二维列数必须写死,行数用变量传) |
| 实参 | printArray(数组名, 行数) |
代码(2d_array.c)
#include<stdio.h>voidprintArray(intx[][4],introw){inti=0,j=0;for(i=0;i<row;++i){for(j=0;j<4;++j){printf("%2d ",x[i][j]);}putchar('\n');}}intmain(void){inta[][4]={1,2,3,4,5,6,7,8,9,10,11,12};introw=sizeof(a)/sizeof(a[0]);printArray(a,row);return0;}六、作业参考:双胞胎素数(test_prime.c)
#include<stdio.h>intisPrime(intn){inti=0;intret=1;for(i=2;i<n;++i){if(n%i==0){ret=0;break;}}returnret;}// 打印 n 以内所有素数voidprintPrimeInX(intn){inti=0;for(i=2;i<=n;++i){if(isPrime(i)==1){printf("%d ",i);}}putchar('\n');}// 双胞胎素数:两个素数相差为 2,如 (3,5) (5,7)voidprintTwinsPrimeInX(intn){inti=0;for(i=2;i<=n-2;++i){if(isPrime(i)==1&&isPrime(i+2)==1){printf("(%d,%d)\n",i,i+2);}}}intmain(void){intn=0;printf("Input a num:");scanf("%d",&n);printTwinsPrimeInX(n);return0;}