做好闭环(一):不看答案可能就白学了

第一个程序:教你输出彩色的文字

在这一篇里面呢,我们接触了如何在 Linux 环境下输出彩色文字的编程知识。初步学习了 scanf 和 printf 函数的基础用法,两者一个负责读入,一个负责输出。如果你对这篇文章的内容有点陌生,可以再回去看看《第一个程序:教你输出彩色的文字》。最后围绕着这两个函数,给你出了两个思考题。这两个思考题做的怎么样?下面来看看我的参考答案吧。

思考题(1):位数输出

#include <stdio.h>

int main() {
    int n;
    scanf("%d", &n);
    printf(" has %d digits\n", printf("%d", n)); // 有多余输出
    char output[50];
    int ret = sprintf(output, "%d", n);
    printf("%d\n", ret); // 无多余输出
    return 0;
}

运行如上程序,如果输入 123,程序会输出如下两行内容:

123 has 3 digits  
3

你会看到,第 1 行除了数字的位数信息以外,还有多余的输出,第 2 行则是没有多余的输出。而两个信息,都是单纯利用 printf 一族函数完成的。这个问题的解题关键是,理解 printf 函数是有返回值的,而其返回的含义是打印了多少个字符。

那么,当我们使用 printf 打印数字 n 的时候,printf 函数的返回值,就是代表了 n 的位数。类似的,sprintf 也是 printf 一族函数中的一员,它的返回值与 printf 含义相同。

思考题(2):读入一行字符串

#include <stdio.h>
char str[100];
int main() {
    scanf("%[^\n]s", str);
    printf("%s\n", str);
    return 0;
}

这段代码展现了如何使用 scanf 读入一行包含空格的字符串信息。其中,要读入字符串,就需要使用 %s 格式占位符。可是这道题目中,在 % 和 s 中间有一对中括号[],这个[] 代表了一个集合,用来控制 %s 在读入过程中可以读入的字符集合的,例如:%[a-z]s,是可以输入小写字母 a 到 z,那么一旦遇到了非小写字母,就会停止。

而上述代码中的 ^ 上尖号,读作非,“^\n” 就是非换行符,也就是说,只要不是换行符,就可以继续读入。这也就达到了我们想要用 scanf 读入一行的功能要求。你可以自己试一下换成 %[a-z]s,然后输入 “abcd12efeee”,看看程序的输出,你就能明白了。

判断与循环:给你的程序加上处理逻辑

在这篇文章《判断与循环:给你的程序加上处理逻辑》中呢,我们学习了除了顺序结构以外的两种程序执行结构:分支结构和循环结构。知识点的话,主要涉及:条件表达式、if 语句、for 语句等知识内容。我们说到,任何表达式都有返回值,条件表达式的值,就是 1 或者 0 代表“真”或者“假”,“成立”或者“不成立”。并且,介绍了条件判断的时候,实际上遵循的原则是“非零即为真”。最后呢,给你留了一个和循环相关的思考题“打印乘法表”,下面就看看我的参考答案吧。

思考题:打印乘法表

#include <stdio.h>
int main() {
    for (int i = 1; i <= 6; i++) {
        for (int j = 1; j <= i; j++) {
            j == 1 || printf("\t");          
            printf("%d * %d = %d", j, i, i * j);
        }
        printf("\n");
    }
    return 0;
}

这段代码中,采用两层循环,外层循环控制行数,内层循环控制每一行的列数,第 i 行应该有 i 列,所以内层循环是从 1 循环到 i 为止。其中最值得琢磨的是“j == 1 || printf("\t");”这句代码,其实这句代码就是用来实现行尾无多余 \t 字符这个要求的。代码中采用了在每一列的前面输出一个 \t 字符,可是在第一列的前面不输出 \t 字符,这样就保证了行尾无 \t 字符。

那么“j == 1 || printf("\t");”这句代码是如何工作的呢?首先看 || 条件或运算符。|| 运算符的工作逻辑是,左右两侧只要有一个条件成立,那么最终结果就是成立的。这个工作逻辑,还值得细细思考,|| 运算符,从左到右依次判断两个条件是否成立,那么如果第一个左边的条件就成立了呢?作为一个聪明人,还需要判断第二个右边的条件么?你会发现,根本不需要再判断右边的条件了,也就是说不需要执行右边的代码了。

看完了条件“或”的这个特性之后,我们再看看“j == 1 || printf("\t");”这句代码,也就是说,当 j==1 成立时,也就是第一列的时候,右边的 printf("\t") 代码就根本不会执行。这也就意味着,第一列前面不会多输出一个 \t 字符。而其他的情况呢,均会执行 printf("\t") 代码,这也就实现了题目中的要求。

随机函数:随机实验真的可以算 π 值嘛?

这一篇文章里面《随机函数:随机实验真的可以算 π 值嘛?》,我们介绍了程序里面随机函数的基本原理,说明了“真随机”和“伪随机”的本质区别。看了一些留言以后,我来给你总结一下,所谓“真随机”与“假随机”,只要你不太清楚下一个产生的值是什么,那么对于你来说,就是随机的,而“真”或者“假”,讨论的是随机方法的本质。如果随机过程可以保证,下一次产生的每个值都有一定的概率,那么这个就是“真随机”,如果不能保证,那就是“伪随机”。

理解程序中的“伪随机”,你需要在你的脑袋中,构建一个由值组成的环形序列图,设置随机种子,就是选择图中的某个点作为起始点,在我们一次次地获得随机值的过程中,其实程序就是依次地输出了这个环形序列中的每个状态的值。

最后呢,给你留了一个设计随机函数过程的思考题,关于这个思考题,我要提前先跟你道歉,因为这个思考题,并不是想让你做出来的。下面来看看我的参考答案吧。

思考题:设计迷你随机函数

#include <stdio.h>
int main() {
    int n = 5;
    for (int i = 1; i <= 100; i++) {
        printf("%2d ", n);
        if (i % 10 == 0) printf("\n");
        n = (n * 3) % 101;
    }
    return 0;
}

当你运行这个程序的时候,就会看到程序的输出,正如原文中我给你的样例输出一样。要是想理解这段程序,你需要一些数论方面的基础知识,其中包括:欧拉函数,欧拉定理、费马小定理、取余循环节等知识。

在这里,我要再次因为设置这个你可能做不出来这个题,而向你道歉。不过,当你看到上面的那些知识以后,你会发现,这是一道初学者很大概率不可能完成的题目,尽管代码很简单,可背后的原理却看似不简单。其实,我就是想跟你说明,程序的灵魂在算法,算法的灵魂在数学。

数组:一秒钟,定义 1000 个变量

这一篇中,我们学习了数组的基本用法,学会了定义一组数据存储区的方法。并且,围绕着数组知识,完成了“计算数字二进制表示中 1 的个数”的递推程序的设计与实现。

相关的课后思考题呢,也是希望你使用数组来完成相关任务,如果有好奇的朋友,可以到原文章看看《数组:一秒钟,定义 1000 个变量》。

最后让我们来看看这篇文章的参考答案吧。

思考题:去掉倍数

#include <stdio.h>
int check[1005] = {0};
int main() {
    int n, m, num;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%d", &num);
        for (int j = num; j <= m; j += num) {
            check[j] = 1;
        }
    }
    for (int i = 1; i <= m; i++) {
        if (check[i] == 1) continue;
        printf("%d ", i);
    }
    return 0;
}

这段代码中,使用一个 check 数组作为标记,check[i] 等于 0,代表 i 这个数字不是 n 个数字中的任何一个数字的倍数。check[i] 等于 1,代表 i 这个数字能够被 n 个数字中的某个数字整除。其中第 7 行到第 10 行代码,是需要特别关注的。这段代码中,首先读入 n 个数字中的某一个,存储在 num 变量中,之后循环 m 以内所有 num 的倍数,把每个数字的 check 值标记为 1。最后我们循环把 1 到 m 中没有被标记的数字输出,就是符合题目要求的所有数字。

字符串:彻底被你忽略的 printf 的高级用法

这篇《字符串:彻底被你忽略的 printf 的高级用法》的文章中,我们认识了 scanf 和 printf 家族中的两员猛将:sscanf 函数和 sprintf 函数。这两者操作的是字符串,可以理解其本质,就是以字符串为中介做数据类型之间的转换。并且我们还介绍了字符串的相关知识,字符串的相关知识中,比较重要的就是那个 \0 字符,这是一个标记字符串结束的字符,虽然看不到,可作用非常重要,并且这个 \0 字符,也是需要占用存储空间的。

这篇文章中的两个思考题,都是帮助你打开脑洞的,主要就是想告诉你,知识点是死的,而理解知识点和应用知识点是活的,也就是我们常说的活学活用。下面就来看看这篇文章中的两个思考题的参考答案吧。

思考题(1):体验利器

#include <stdio.h>
char str1[1000], str2[1000];
int main() {
    scanf("%s%s", str1, str2);
    printf("str1 = %s\tstr2 = %s\n", str1, str2);
    sprintf(str1, "%s", str1);   // strlen(str1)
    sprintf(str1, "%s", str2);   // strcpy(str1, str2)
    printf("str1 = %s\tstr2 = %s\n", str1, str2);
    sprintf(str1, "%s%s", str1, str2);   // strcat(str1, str2)
    printf("str1 = %s\tstr2 = %s\n", str1, str2);
    return 0;
}

在这段代码中,首先读入两个字符串,str1 和 str2。然后使用 sprintf 分别替代 strlen、strcpy 以及 strcat 三个函数的功能。具体如下:

首先,使用 sprintf(str1, “%s”, str1); 代替 strlen(str1) 的功能,正如你所知道的,sprintf 返回值代表输出了多少个字符,这行代码中也就是 str1 字符串中的字符数量。

其次,使用 sprintf(str1, “%s”, str2); 代替 strcpy(str1, str2) 的功能。使用 sprintf 函数,将 str2 中的内容,输出到 str1 的存储空间中,其实就相当于把 str2 的内容复制到了 str1 中。

最后,使用 sprintf(str1, “%s%s”, str1, str2); 代替 strcat(str1, str2) 的功能。这里,我们将 str1 和 str2 的值,依次性的输出到 str1 中以后,str1 的内容,就是原 str1 和 str2 内容连接以后的总内容了。

思考题(2):优美的遍历技巧

#include <stdio.h>
int main() {
    char str[1000];
    scanf("%s", str);
    for (int i = 0; str[i]; i++) {
        printf("%c\n", str[i]);
    }
    return 0;
}

这段代码中,最值得思考的是循环的终止条件。当循环条件成立的时候,循环会一直执行,不成立的时候,循环就会终止。那么 str[i] 你可以看成是字符,也可以看成是一个整型值,因为任何信息在底层都是二进制存储的,那么其余字符均为非零值,也就是代表条件成立。

只有一个字符的值是零值,就是我们之前所说的字符串中的最后一个特殊的,看不见的字符,\0 字符,这个字符所对应的整型值就是 0,也就是我们所谓的假值。那么这个循环,就会一直循环到字符串的最后一位,才会停止。



本文深入介绍了在Linux环境下输出彩色文字的编程知识,以及如何使用scanf和printf函数的基础用法。此外,还涵盖了条件表达式、if语句、for语句等知识内容,并给出了相关思考题。另外,文章还介绍了程序中随机函数的基本原理,区分了“真随机”和“伪随机”的本质区别,并留下了一个设计随机函数过程的思考题。整体而言,本文内容涵盖了程序设计的基础知识,包括输入输出、条件判断、循环结构和随机函数,适合初学者快速了解程序设计的基础概念。文章还包括了两个思考题,分别涉及位数输出和读入一行字符串,以及打印乘法表的思考题。此外,还介绍了字符串的高级用法,以及相关的思考题。整体而言,本文内容涵盖了程序设计的基础知识,包括输入输出、条件判断、循环结构和随机函数,适合初学者快速了解程序设计的基础概念。 

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值