1、计算增长率

假如我国国民生产总值的年增长率为7%,计算 10 年后我国国民生产总值与现在相比增长多少百分比。计算公式为
课后习题 - 图1
r 为年增长率,n 为年数,p 为与现在相比的倍数。
解题思路:由公式可知,就是一个幂函数求解过程。

  1. #include <stdio.h>
  2. #include <math.h>
  3. int main() {
  4. float p, r = 0.07;
  5. int n = 10;
  6. p = pow(1 + r, (double)n); // 建议强制转换下,因会隐式转换
  7. printf("p=%f\n", p);
  8. return 0;
  9. }

暴力循环:注意初始化 p = 1.0 即可。

  1. #include <stdio.h>
  2. #include <math.h>
  3. int main() {
  4. float p = 1.0, r = 0.07;
  5. int n = 10;
  6. // p = pow(1 + r, (double)n);
  7. for (int i = 0; i < n; i++) {
  8. p = p * (1 + r);
  9. }
  10. printf("p=%f\n", p);
  11. return 0;
  12. }

编译运行:编译需要链接 <math.h> 需要添加 -lm 选项。 :::success b12@PC:~/chapter3$ gcc -Wall ./src/increaseRate.c -o ./bin/increaseRate -lm
b12@PC:~/chapter3$ ./bin/increaseRate
p=1.967152 :::

2、存款利息的计算

有1000元,想存5年,可按以下5种办法存:

  1. 一次存 5 年期。
  2. 先存 2 年期,到期后将本息再存 3 年期。
  3. 先存 3 年期,到期后将本息再存 2 年期。
  4. 存 1 年期,到期后将本息再存 1 年期,连续存 5 次。
  5. 存活期存款。活期利息每一季度结算一次。

2017 年的银行存款利息如下:

  • 1 年期定期存款利息为 1.5%;
  • 2 年期定期存款利息为 2.1%;
  • 3 年期定期存款利息为 2.75%;
  • 5 年期定期存款利息为 3%;
  • 活期存款利息为 0.35%(活期存款每一季度结算一次利息)。

如果 r 为年利率,n 为存款年数,则计算本息和的公式如下:

  • 1 年期本息和:课后习题 - 图2
  • n 年期本息和:课后习题 - 图3
  • 存 n 次 1 年期的本息和:课后习题 - 图4
  • 活期存款本息和:课后习题 - 图5,其中 课后习题 - 图6 表示一个季度(4 个月)的本息和

解题思路:题目已经规定好很多,浅显易懂,直接编码即可。重点就是活期存款问题。

  1. #include <stdio.h>
  2. #include <math.h>
  3. int main() {
  4. float r5, r3, r2, r1, r0, p, p1, p2, p3, p4, p5;
  5. p = 1000;
  6. r5 = 0.03;
  7. r3 = 0.0275;
  8. r2 = 0.0468;
  9. r1 = 0.021;
  10. r0 = 0.0035;
  11. p1 = p * (1 + r5 * 5); // 5年
  12. p2 = p * (1 + 2 * r2) * (1 + 3 * r3); // 先2年,到期本息和再存3年
  13. p3 = p * (1 + 3 * r3) * (1 + 2 * r2); // 先3年,到期本息和再存2年
  14. p4 = p * pow(1 + r1, 5); // 先1年,到期本息和再存1年,如此重复5次
  15. p5 = p * pow(1 + r0 / 4, 4 * 5); // 活期,4个月结算一次
  16. printf("p1 = %f\n", p1);
  17. printf("p2 = %f\n", p2);
  18. printf("p3 = %f\n", p3);
  19. printf("p4 = %f\n", p4);
  20. printf("p5 = %f\n", p5);
  21. return 0;
  22. }

编译运行: :::success b12@PC:~/chapter3$ gcc -Wall ./src/calInterests.c -o ./bin/calInerests -lm
b12@PC:~/chapter3$ ./bin/calInerests
p1 = 1150.000000
p2 = 1183.821899
p3 = 1183.822021
p4 = 1109.503784
p5 = 1017.646179 ::: 书中讨论: :::info

  • gcc 对上面实数默认 double 转换为 float 变量不会给予 warning,也不需要担心。除非给出警告,那么将上述变量全部定义为 double 即可。
  • 保留两位小数输出:正常生活中钱最精确只能到分和角,因此四舍五入(PS:这种四舍五入在实际中恐怕不行)保留两位小数,只需要将程序格式化输出 %.2f 即可。书中外加 %5.2f 只为输出数字。下面对其格式化输出进行拓展。 :::

    1. #include <stdio.h>
    2. #include <math.h>
    3. int main() {
    4. double r5, r3, r2, r1, r0, p, p1, p2, p3, p4, p5; // 此处更改为double
    5. p = 1000;
    6. r5 = 0.03;
    7. r3 = 0.0275;
    8. r2 = 0.0468;
    9. r1 = 0.021;
    10. r0 = 0.0035;
    11. int n = 5;
    12. p1 = p * (1 + r5 * 5); // 5年
    13. p2 = p * (1 + 2 * r2) * (1 + 3 * r3); // 先2年,到期本息和再存3年
    14. p3 = p * (1 + 3 * r3) * (1 + 2 * r2); // 先3年,到期本息和再存2年
    15. p4 = p * pow(1 + r1, 5); // 先1年,到期本息和再存1年,如此重复5次
    16. p5 = p * pow(1 + r0 / 4, 4 * 5); // 活期,4个月结算一次
    17. printf("p1 = %10.2f\n", p1); // 右对齐输出(数据在右端),不足左边补空格,否则将不会截断
    18. printf("p2 = %-10.3lf\n", p2); // 左对齐输出(数据在左端),不足右边补空格,否则将不会截断
    19. printf("p3 = %3.8f\n", p3); // 左对齐输出(数据在左端),实际长度大于width不会截断
    20. printf("p4 = %10.*f\n", n, p4); // 指定保留 n 位小数
    21. printf("p5 = %f\n", p5); // 默认右对齐
    22. return 0;
    23. }

    编译运行: :::success b12@PC:~/chapter3$ gcc -Wall ./src/calInterests2.c -o ./bin/calInerests2 -lm
    b12@PC:~/chapter3$ ./bin/calInerests2
    p1 = 1150.00
    p2 = 1183.822
    p3 = 1183.82200000
    p4 = 1109.50359
    p5 = 1017.646235 :::

3、计算月供

购房从银行贷了一笔款 d,准备每月还款额为 p,月利率为 r,计算多少月能还清。设 d 为 300000 元,p 为6000 元,r 为 1%。对求得的月份取小数点后一位,对第 2 位按四舍五入处理。
提示:计算还清月数 m 的公式如下:课后习题 - 图7
C 的库函数中有求对数的函数 log10,是求以 10 为底的对数,课后习题 - 图8表示 课后习题 - 图9

4、分析下面的程序

  1. #include <stdio.h>
  2. int main() {
  3. char c1, c2;
  4. c1 = 97;
  5. c2 = 98;
  6. printf("c1=%c,c2=%c\n", c1, c2);
  7. printf("c1=%d,c2=%d\n", c1, c2);
  8. return 0;
  9. }

(1)运行时会输出什么信息?为什么?
对上述程序编译运行: :::success b12@PC:~/chapter3$ gcc -Wall ./src/test4.c -o ./bin/test4
b12@PC:~/chapter3$ ./bin/test4
c1=a,c2=b
c1=97,c2=98 ::: 因为字符型数据是以 ASCII 码的整数形式存在的。

  • 第 1 行是将c1,c2按 %c 的格式输出,97 是字符 a 的 ASCII 码,98 是字符 b 的 ASCII 码。
  • 第 2 行是将c1,c2按 %d 的格式输出,所以输出两个十进制整数。

(2)如果将程序第4,5行改为 c1=197;c2=198; 运行时会输出什么信息?为什么?
照着题目要求修改,编译运行: :::success b12@PC:~/chapter3$ gcc -Wall ./src/test4.c -o ./bin/test4
b12@PC:~/chapter3$ vi ./src/test4.c
b12@PC:~/chapter3$ ./bin/test4
c1=,c2=
c1=-59,c2=-58 ::: 由于 gcc 编译器将字符作为课后习题 - 图10,即认为是有符号的。即此处发生最高位符号位作为整形输出为负数的原因。

%d 198 199
二进制 1100 0110 1100 0111
%c 1100 0110 1100 0111

如果改变 gcc 默认编码字符集呢?其实 shell 下没拓展的 ASCII 字符集,因此终端还是不会输出!gcc 支持 Unicode
(3)如果将程序第 3 行改为 int c1, c2; 运行时会输出什么信息?为什么?
如果给 c1 和 c2 赋的值是 197 和 198,则用 %c 输出时是不可预料的字符。用 %d 输出时,输出整数 197 和 198,因为它们在 int 类型的有效范围内。

5. scanf 输入

用下面的 scanf 函数输入数据,使 a=3,b=7,x=8.5,y=71.82,c1=’A’,c2=’a’。在键盘上应如何输入?

  1. #include <stdio.h>
  2. int main() {
  3. int a, b;
  4. float x,y;
  5. char c1,c2;
  6. scanf("a=%d b=%d",&a,&b);
  7. scanf("%f %e",&x,&y);
  8. scanf("%c%c",&c1,&c2);
  9. printf("a=%d,b=%d,x=%f,y=%f,c1=%c,c2=%c\n",a,b,x,y,c1,c2);
  10. return 0;
  11. }

:::success b12@PC:~/chapter3$ gcc -Wall ./src/scanf5.c -o ./bin/scanf5
b12@PC:~/chapter3$ ./bin/scanf5
a=3 b=7
6.6 9.9Bb
a=3,b=7,x=6.600000,y=9.900000,c1=B,c2=b

b12@PC:~/chapter3$ ./bin/scanf5
a=99 b=66
6.6 9.9 Bb
a=99,b=66,x=6.600000,y=9.900000,c1= ,c2=B

b12@PC:~/chapter3$ ./bin/scanf5
a=99 b=33
6.6 9.9
Bb
a=99,b=33,x=6.600000,y=9.900000,c1=
,c2=B :::

  • 第一次:在浮点数 y 之后紧跟着字符 Bb ,此时因为 scanf("%f %e",&x,&y); 检测到字符 B 表明此次输入结束, scanf 函数调用完毕而后进行 scanf("%c%c",&c1,&c2); 刚好接受输入的字符。
  • 第二次:在浮点数 y 之后空格分隔,后接字符 Bb ,此时因为 scanf("%f %e",&x,&y); 检测到空格表明此次输入结束, scanf 函数调用完毕而后进行 scanf("%c%c",&c1,&c2); 而接受第一个空格入 c1 造成 c2=Bb 字符不见了!。
  • 第二次:在浮点数 y 之后换行,后接字符 Bb ,此时因为 scanf("%f %e",&x,&y); 检测到换行表明此次输入结束, scanf 函数调用完毕而后进行 scanf("%c%c",&c1,&c2); 而接受此时的换行符c1 造成 c2=Bb 字符不见了!。

遇到这种因为 scanf 引起的问题无法消除缓冲区内字符的情况,主要就是“配对”缓冲区或者“清空”缓冲区的内容。下面为了简单起见,使用较为简单的程序进行模拟:

  1. scanf("%f %e\n",&x,&y); 使用格式控制字符在输入浮点数后匹配换行,则这个换行符被此 scanf 函数“吸收”。

    1. #include <stdio.h>
    2. int main() {
    3. float x,y;
    4. char c1,c2;
    5. scanf("%f %e\n",&x,&y);
    6. scanf("%c%c",&c1,&c2);
    7. printf("x=%f,y=%f,c1=%c,c2=%c\n", x, y, c1, c2);
    8. return 0;
    9. }

    编译运行: :::success b12@PC:~/chapter3$ gcc -Wall ./src/scanf5_1.c -o ./bin/scanf5_1
    b12@PC:~/chapter3$ ./bin/scanf5_1
    9.6 6.9
    Ac
    x=9.600000,y=6.900000,c1=A,c2=c :::

  2. scanf("\n%c%c",&c1,&c2); 前面进行格式匹配:跟上面原理类似

    1. #include <stdio.h>
    2. int main() {
    3. float x,y;
    4. char c1,c2;
    5. scanf("%f %e",&x,&y);
    6. scanf("\n%c%c",&c1,&c2);
    7. printf("x=%f,y=%f,c1=%c,c2=%c\n", x, y, c1, c2);
    8. return 0;
    9. }

    编译运行: :::success b12@PC:~/chapter3$ gcc -Wall ./src/scanf5_2.c -o ./bin/scanf5_2
    b12@PC:~/chapter3$ ./bin/scanf5_2
    6.9 9.6
    Kc
    x=6.900000,y=9.600000,c1=K,c2=c :::

  3. 使用 getchar() 函数“消灭”换行。

    1. #include <stdio.h>
    2. int main() {
    3. float x,y;
    4. char c1,c2;
    5. scanf("%f %e",&x,&y);
    6. char ch = getchar();
    7. printf("ch=%d,ch is %c\n", ch, ch);
    8. scanf("%c%c",&c1,&c2);
    9. printf("x=%f,y=%f,c1=%c,c2=%c\n", x, y, c1, c2);
    10. return 0;
    11. }

    编译运行:此时在输入完浮点数之后就会输出 printf("ch=%d,ch is %c\n", ch, ch); :::success b12@PC:~/chapter3$ gcc -Wall ./src/scanf5_3.c -o ./bin/scanf5_3
    b12@PC:~/chapter3$ ./bin/scanf5_3
    9.4 4.9
    ch=10,ch is
    So
    x=9.400000,y=4.900000,c1=S,c2=o :::

  4. 使用 fflush(stdin); 清空缓存区。网上说在 stdlib.h 中,但是它不是 C 标准!很容易出错且不保险。原因

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. int main() {
    4. float x,y;
    5. char c1,c2;
    6. scanf("%f %e",&x, &y);
    7. fflush(stdin); // empty buffer
    8. scanf("%c%c",&c1,&c2);
    9. printf("x=%f,y=%f,c1=%c,c2=%c\n", x, y, c1, c2);
    10. return 0;
    11. }

    :::success b12@PC:~/chapter3$ gcc -Wall ./src/scanf5_4.c -o ./bin/scanf5_4
    b12@PC:~/chapter3$ ./bin/scanf5_4
    5.5 2.3
    Ak
    x=5.500000,y=2.300000,c1=
    ,c2=A :::

综上,最为保险的方式就是使用吸收方法,或者避免产生这种情况(只能说这个函数设计有点意思!)。类似问题还有含空格字符串输入问题等,解决办法可以参考下:

未完!