函数式宏
函数和数据类型
代码清单8-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #include <math.h> int sqr_int (int x) { return x * x; } double sqr_double (double x) { return x * x; } int main (void ) { int n; double x; printf ("请输入一个整数:" ); scanf ("%d" , &n); printf ("该数的平方是%d。\n" , sqr_int(n)); printf ("请输入一个实数:" ); scanf ("%lf" , &x); printf ("该数的平方是%f。\n" , sqr_double(x)); return 0 ; }
若是接二连三地写出这种功能相近、名称相似地函数,程序中就会充斥着这些似是而非地函数。这样会对程序的阅读、修改造成影响。
函数式宏
函数式宏式定义一个类似于函数的宏定义,其本质是编译,将变量带入形参并展开,再带入相应表达式中运算,反汇编中没有相应的函数框架。
函数式宏在使用过程中需要小心谨慎,不然再展开带入表达式的过程中容易出错。
代码清单8-2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define sqr(x) ((x) * (x)) int main (void ) { int n; double x; printf ("请输入一个整数:" ); scanf ("%d" , &n); printf ("该数的平方是%d。\n" , sqr(n)); printf ("请输入一个实数:" ); scanf ("%lf" , &x); printf ("该数的平方是%f。\n" , sqr(x)); return 0 ; }
不带参数的函数式宏
函数式宏也可以不带参数的定义
代码清单8-2a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define alert() (putchar('\a' )) int main (void ) { alert(); return 0 ; }
函数式宏和逗号运算符
代码清单8-3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define puts_alert(str) {putchar('\a' ); puts(str);} int main (void ) { int n; printf ("请输入一个整数:" ); scanf ("%d" , &n); if (n) { puts_alert("这个数不是0。" ); } else { puts_alert("这个数是0。" ); } return 0 ; }
根据编译器的不同,语法会有所不同。在 VS2022 中,宏函数中需要使用大括号。
代码清单8-4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define puts_alert(str) (putchar('\a' ), puts(str)) int main (void ) { int n; printf ("请输入一个整数:" ); scanf ("%d" , &n); if (n) puts_alert("这个数不是0。" ); else puts_alert("这个数是0。" ); return 0 ; }
函数宏的使用逗号代替分号,可正常编译执行。函数式宏如果使用分号容易造成语法错误。
排序
冒泡排序
代码清单8-5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define NUMBER 5 void bsort (int a[], int n) { int i, j; for (i = 0 ; i < n - 1 ; i++) { for (j = n - 1 ; j > i; j--) { if (a[j - 1 ] > a[j]) { int temp = a[j]; a[j] = a[j - 1 ]; a[j - 1 ] = temp; } } } } int main (void ) { int i; int height[NUMBER]; printf ("请输入%d人的身高。\n" , NUMBER); for (i = 0 ; i < NUMBER; i++) { printf ("%2d号:" , i + 1 ); scanf ("%d" , &height[i]); } bsort(height, NUMBER); puts ("按升序排列。" ); for (i = 0 ; i < NUMBER; i++) { printf ("%2d号:%d\n" , i + 1 , height[i]); } return 0 ; }
枚举类型
在计算机编程中,枚举类型是一种数据类型,由一组该类型的元素(枚举常量)组成。
枚举类型定义语法:
enum 枚举名 {枚举常量1,枚举常量2,枚举常量3...}
枚举类型函数声明:
```enum 枚举名 函数名(形参)``
枚举类型变量声明:
enum 枚举名 变量名
枚举类型中各枚举常量默认从左往右依次从0开始赋值。枚举常量类型是int型。另外注意枚举名不是类型名,类型名是 “enum 枚举名”。
这里 “枚举类型函数声明” 和 “枚举类型变量声明” 本质上就是int型的函数和变量声明,这样写主要是为了区别int型的函数和变量,让代码变得易读和易修改。
代码清单8-6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> enum animal { Dog,Cat,Monkey,Invalid};void dog (void ) { puts ("汪汪!!" ); } void cat (void ) { puts ("喵~!!" ); } void monkey (void ) { puts ("唧唧!!" ); } enum animal select (void ) { int tmp; do { printf ("0...狗 1...猫 2...猴 3...结束:" ); scanf ("%d" , &tmp); } while (tmp<Dog || tmp>Invalid); return tmp; } int main (void ) { enum animal selected ; do { switch (selected = select()) { case Dog: dog(); break ; case Cat: cat(); break ; case Monkey: monkey(); break ; } } while (selected != Invalid); return 0 ; }
书中说 “enum animal 型变量 selected 的声明,是定义了变量 selected 的取值范围为 0~3” ,这种说法是错误的。这里只是声明了 “enum animal” 类型的变量,而输出结果和取值范围都是由select()函数和switch语句规定的。
枚举常量
枚举常量的值可以根据需要任意设置,只要在枚举常量的名称后面写上赋值运算符 “=” 和值就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> enum animal { Dog=2 ,Cat,Monkey,Invalid=6 };void dog (void ) { puts ("汪汪!!" ); } void cat (void ) { puts ("喵~!!" ); } void monkey (void ) { puts ("唧唧!!" ); } enum animal select (void ) { int tmp; do { printf ("2...狗 3...猫 4...猴 5...结束:" ); scanf ("%d" , &tmp); } while (tmp<Dog || tmp>Invalid); return tmp; } int main (void ) { int selected; do { switch (selected = select()) { case Dog: dog(); break ; case Cat: cat(); break ; case Monkey: monkey(); break ; } } while (selected != Invalid); return 0 ; }
枚举名和变量名是两个不同的东西需要正确区分。例如 “enum animal animal” 前一个animal是枚举名,后一个animal是变量名。
递归函数
递归函数就是函数调用函数本身,这种调用方式称为递归函数调用。
阶乘
阶乘n!的定义(n为非负整数)
0!=1
若 n>0, 则n! = n * (n-1)!
例如:4的阶乘 4!=4*3!=4*3*2!=4*3*2*1!=4*3*2*1*0!=4*3*2*1*1=24
代码清单8-7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int factorial (int n) { if (n > 0 ) return n * factorial(n - 1 ); else return 1 ; } int main (void ) { int num; printf ("请输入一个整数:" ); scanf ("%d" , &num); printf ("%d的阶乘为%d。\n" , num, factorial(num)); return 0 ; }
输入输出和字符
程序一般会采用某种形式进行字符的输入输出。
getchar函数和EOF
代码清单8-8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int main (void ) { int ch; while ((ch = getchar()) != EOF) putchar (ch); return 0 ; }
从调试结果可以看到,getchar函数是从标准输入流(一个内存缓冲区中)读取下一个字符,并返回该字符,读到文件末尾或发生错误时,返回EOF。在控制台窗口显示字符是其(回显字符控制台)本身的功能。putchar 从标准输入缓冲区中输出下一个字符。
getch():函数从不回显字符的控制台读取单个字符。
数字字符计数
代码清单8-9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int main (void ) { int i, ch; int cnt[10 ] = { 0 }; while ((ch = getchar()) != EOF) { switch (ch) { case '0' : cnt[0 ]++; break ; case '1' : cnt[1 ]++; break ; case '2' : cnt[2 ]++; break ; case '3' : cnt[3 ]++; break ; case '4' : cnt[4 ]++; break ; case '5' : cnt[5 ]++; break ; case '6' : cnt[6 ]++; break ; case '7' : cnt[7 ]++; break ; case '8' : cnt[8 ]++; break ; case '9' : cnt[9 ]++; break ; } } puts ("数字字符的出现次数" ); for (i = 0 ; i < 10 ; i++) { printf ("'%d': %d\n" , i, cnt[i]); } return 0 ; }
字符
代码清单8-10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int main (void ) { int i, ch; int cnt[10 ] = { 0 }; while ((ch = getchar()) != '\n' ) { if (ch >= '0' && ch <= '9' ) { cnt[ch - '0' ]++; } } puts ("数字字符的出现次数" ); for (i = 0 ; i < 10 ; i++) { printf ("'%d': %d\n" , i, cnt[i]); } return 0 ; }
把 EOF 替换为 ‘\n’,输入回车换行后直接输出结果。
代码清单8-11
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int main (void ) { int i; printf ("EOF = %d\n" , EOF); for (i = 0 ; i < 10 ; i++) { printf ("'%d' = %d\n" , i, '0' + i); } return 0 ; }
转移字符
总结
summary1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> enum RGB { Red,Green,Blue};int main (void ) { int color; printf ("0~2的值:" ); scanf ("%d" ,&color); printf ("你选择了" ); switch (color) { case 0 :printf ("红色。\n" ); break ; case 1 :printf ("绿色。\n" ); break ; case 2 :printf ("蓝色。\n" ); break ; } return 0 ; }
summary2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define alert() (putchar('\a' )) #define putchar_ln(c) (putchar(c),putchar('\n' )) int main (void ) { int ch; int sum = 0 ; while ((ch = getchar()) != '\n' ) { if (ch > '0' && ch <= '9' ) sum += ch - '0' ; if (ch == '\n' ) { alert(); putchar ('\n' ); } else { putchar_ln(ch); } } printf ("所有数字之和为%d。\n" , sum); return 0 ; }
练习
练习8-1
1 2 3 4 5 6 7 8 9 10 11 12 13 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define diff(x,y) (x>y?x-y:y-x) int main (void ) { printf ("x和y的差是%d" , diff(8 , 1 )); return 0 ; }
练习8-2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define max(x,y) (x>y ? x:y) int main (void ) { int a = 1 , b = 2 , c = 3 , d = 4 ; printf ("%d\n" , max(max(a, b), max(c, d))); printf ("%d\n" , max(max(max(a, b), c), d)); return 0 ; }
max(max(a, b), max(c, d)):先比较 a,b 得出结果x;再比较 c,d 得出结果y;最后比较 x,y。
max(max(max(a, b), c), d):先比较 a,b 得出结果x;再比较 x,c 得出结果y;最后比较 y,d
练习8-3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define swap(type,a,b) {int temp;temp=a;a=b;b=temp;} int main (void ) { int x = 5 , y = 10 ; swap(int , x, y); printf ("x=%d y=%d" , x, y); return 0 ; }
练习8-4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> #define NUMBER 5 void bsort (int a[], int n) { int i, j; for (i = 0 ; i < n - 1 ; i++) { for (j = n - 1 ; j > i; j--) { if (a[j - 1 ] < a[j]) { int temp = a[j]; a[j] = a[j - 1 ]; a[j - 1 ] = temp; } } } } int main (void ) { int i; int height[NUMBER]; printf ("请输入%d人的身高。\n" , NUMBER); for (i = 0 ; i < NUMBER; i++) { printf ("%2d号:" , i + 1 ); scanf ("%d" , &height[i]); } bsort(height, NUMBER); puts ("按降序排列。" ); for (i = 0 ; i < NUMBER; i++) { printf ("%2d号:%d\n" , i + 1 , height[i]); } return 0 ; }
练习8-5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> enum sex { man,women};enum month { January=1 , February, March, April, May, June, July, August, September, October, November, December };int main (void ) { enum sex sex ; enum month month ; printf ("请选择游戏角色性别(0...男,1...女):" ); scanf ("%d" , &sex); if (sex == man) { printf ("你选择了男性角色。\n" ); } else { printf ("你选择了女性角色。\n" ); } printf ("请选择地图场景的所在月份(整数1~12):" ); scanf ("%d" , &month); switch (month) { case January: printf ("你选择1月\n" ); break ; case February: printf ("你选择2月\n" ); break ; case March: printf ("你选择3月\n" ); break ; case April: printf ("你选择4月\n" ); break ; case May: printf ("你选择5月\n" ); break ; case June: printf ("你选择6月\n" ); break ; case July: printf ("你选择7月\n" ); break ; case August: printf ("你选择8月\n" ); break ; case September: printf ("你选择9月\n" ); break ; case October: printf ("你选择10月\n" ); break ; case November: printf ("你选择11月\n" ); break ; case December: printf ("你选择12月\n" ); break ; } return 0 ; }
练习8-6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int main (void ) { int num,sum; printf ("请输入一个整数:" ); scanf ("%d" , &num); sum = num; for (int i=num;i>1 ;i--) { sum *= i-1 ; } printf ("%d的阶乘为%d。\n" , num, sum); return 0 ; }
练习8-7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int combination (int n, int r) { int c; if (r == 0 || n == r) c = 1 ; else if (r == 1 ) c = n; else c = combination(n - 1 , r - 1 ) + combination(n - 1 , r); return c; } int main (void ) { int n, r; printf ("请输入所有不同的整数个数:" ); scanf ("%d" , &n); printf ("请输入取出的整数个数:" ); scanf ("%d" , &r); printf ("从%d个不同整数中取出%d个整数的组合为%d。\n" ,n,r, combination(n, r)); return 0 ; }
练习8-8
前置知识
辗转相除法
最大公约数: 指两个数字所共同拥有的约数中最大的那一个。
辗转相除法: 又名欧几里得算法,是求两个正数的最大公约数的算法。其可追溯到公元前300年前。
辗转相除法原理: 两个数的最大公约数等于其中较小的数字和二者之间余数的最大公约数。
例如: 求a和b的最大公约数,就用a和b中较大数除以较小数,这个时候就会有一个余数,当余数为0时,那个较小的数就是最大公约数。若余数不是0,那么之前的余数作为除数依次类推直到余数为0
公式: GCD(较大数,较小数)=GCD(较小数,余数)
案例:求 100 和 24 的最大公约数
100/24=4…4
(100,24)=(24,4)
24/4=6…0
(24,4)=(4,0)
4 小于 24,因此4是最大公约数。
更相减损术
我国早期也有求最大公约数问题的算数,就是更相减损数。该算数在《九章算数》第23页,约分部分。《九章算术》是西汉和东汉时期之间的书,约公元前200年左右。
其本意是因为物品的数量不可能全部都是整数,这时就要用分数表示。但是分数作为一个数来说,如果太繁琐就难用,例如 2/4 的几种表现形式 4/8、1/2等等。就比如两个人分一个西瓜,原来两个人每个人分1/2的西瓜,但你强行用4/8的表现形式,将西瓜分成8分,再每人分4个,这样就很麻烦。为了解决这个问题,古人想到求分子和分母的最大公约数,这样得以最大地简化分数。
原文:可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。
第一步:分子分母可简约一半,就简约一半。(分子分母除以2)
第二步:不能简约一半,在旁边计算分子分母,大数减小数,反复相减,直到求出它们的等数。(不能除2,就用分子或分母中的较大数减去较小数,再比较 ”上次计算的减数” 和 “上次计算的差“ 再次用大数减小数,依次循环,直到差值等于减数,最后的结果差值被称为等数)
第三步:则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公因数(这一步是后续增加的优化步骤,根据不同情况可省略)。
第四步:用等数简约(简约就是除的意思)这个分数(用分子分母除以等数)
案例1: 12/18
第一步:(12/18)/ 2 = 6/9
第二步:9-6=3;6-3=3
第三步:2*3=6
第三步:(12/18)/6 = 2/3
案例2: 49/91
第一步:无法除以2,忽略
第二步:91-49=42;49-42=7;42-7=35;35-7=28;28-7=21;21-7=14;14-7=7
第三步:(49/91)/7 = 7/13
辗转相除法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int gcd (int x, int y) { if (y == 0 ) { return x; } else { if (x > y) gcd(y, x % y); else gcd(x, y % x); } } int main (void ) { int x, y; printf ("请输入整数x:" ); scanf ("%d" , &x); printf ("请输入整数y:" ); scanf ("%d" , &y); printf ("%d和%d的最大公约数是%d" , x,y,gcd(x, y)); return 0 ; }
更相减损术
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int gcd (int x, int y) { static count; for (int i=0 ;x % 2 == 0 && y % 2 == 0 ;i++,count = i) { x = x / 2 ; y = y / 2 ; } if (y == x) { if (count != 0 ) { return count * 2 * x; } else { return x; } } else { if (x > y) gcd(y, x - y); else gcd(x, y - x); } } int main (void ) { int x, y; printf ("请输入整数x:" ); scanf ("%d" , &x); printf ("请输入整数y:" ); scanf ("%d" , &y); printf ("%d和%d的最大公约数是%d" , x,y,gcd(x, y)); return 0 ; }
练习8-9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int main (void ) { int i=0 , ch; int cnt[10 ] = { 0 }; while ((ch = getchar()) != EOF) { if (ch == '\n' ) { i++; } } printf ("一共有%d行字符串" ,i); return 0 ; }
练习8-10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <stdlib.h> int main (void ) { int i, ch; int cnt[10 ] = { 0 }; while ((ch = getchar()) != '\n' ) { if (ch >= '0' && ch <= '9' ) { cnt[ch - '0' ]++; } } puts ("数字字符的出现次数" ); for (i = 0 ; i < 10 ; i++) { printf ("'%d': " , i); for (int k = 0 ; k < cnt[i]; k++) { printf ("*" ); } printf ("\n" ); } return 0 ; }
参考
最大公因数学习记录——兼及“更相减损术”与“辗转相除法”
C语言实现求最大公约数的三种方法_C 语言
中国古算与实数系统(一)
更相减损法(求最大公约数)
五十六、从高中碾转相除法、更相减损术算法谈起
更相减损术与辗转相除法的原理是什么?
辗转相除法介绍