CSU计算机学院2021年C语言期末题目思路分享(后两道题)

文章目录

    • E: 实数相加——大数加法的拓展
      • 原题
        • 题目描述
        • 输入
        • 输出
        • 样例输入
        • 样例输出
      • 题目思路
      • 实现步骤
      • 代码和注释
    • F: 谍影寻踪——链表的思想和运用
      • 原题
        • 题目描述
        • 输入
        • 输出
        • 样例输入
        • 样例输出
      • 题目思路
    • 一点感想

E: 实数相加——大数加法的拓展

原题

题目描述

C语言就要期末考试了,经过一学期的学习,小南学会了分数相加、分数相减、大整数相加,可就是没有尝试过将两个最大达400位的实数相加。在调试过程中,小南遇到了很多问题,一直解决不了。你能帮他编写一个程序实现两个实数相加吗?

输入

多组样例。每行输入一个样例包括两个用空格分开的实数_x_和_y_,其中_x_和_y_是长度不大于400位的非负实数。注意,非负实数中也可能包括非负整数,正整数会省略小数点和后面的0,只包括整数部分。

输出

对于每一行输入,输出两个实数_x_和_y_相加的结果。输出结果中整数部分的前面和小数部分的末尾不输出多余的0;如果是整数,不输出小数点和小数点后面的0。每个输出占一行。

样例输入
1.1 2.9
1.1111111111 2.3444323343 
1 1.1
1.1111 1.1889
样例输出
4
3.4555434454 
2.1
2.3

题目思路

给出的数据明显超过任何一个数据类型,属于大数处理的类型,具体可以b站搜索大数加法、大数阶乘先进行学习,基础的大数加法需要熟练,这样遇到这样的类似的进阶题,思路会更清晰。考过了大数加法,大数阶乘会不会考呢?是有可能的,大数阶乘也是大数加法的拓展。

这题是“实数”的大数处理,也就是有可能有小数点了。我一开始想的是先把小数点去掉,然后按大整数的加法来,原来大整数是右对齐进行计算,现在改为“居中对齐”计算,也就是小数点的位置对齐,然后从右往左计算,想法有,但是很难实现,单是这个“对齐”,已经非常麻烦了。

换个思路,毛主席教导我们:“事物都是一分为二的。”,这一串字符串也不是铁板一块,必然也能一分为二,分成“整数”和“小数”两部分,对这两部分分别使用大数加法。其中,整数部分就是最常见的倒序加法。小数部分就是左对齐的大数加法。

下面我们就看一下实现的步骤,从中能看到很多需要注意的地方,和我犯错误的一些经验教训。

实现步骤

读入的是一整串数据,首先需要分成整数和小数部分,分界点就是小数点。这里用for循环是可以解决的。

这两部分数据使用char数组存储。在存储整数的char数组内,我们要使用字符的数字进行存储,而非把原始数据减去'0',但是小数部分却可以把原始数据减去'0'进行存储。为什么呢?这就需要先了解大数是怎么存储的。

大数是怎么存储的呢?大数加法一般用的是char数组存储每一个数字。整个数组的长度是大于输入的数字的长度的,也就是说,输入的数字,或者是计算后的数字,一般是填不满数组的,那么,剩下的空位置怎么办,怎么样能不影响计算和数组长度的判断,就需要思考。惯例是使用空字符填充,并且,读入的原始数据,是'0''1',这样的字符,一般为了方便计算和初始化,会在存储的时候减去'0'。这样,字符串首先是计算方便了许多——直接用数组成员相加就行了,不用担心忘记减'0',其次,这样不影响计算——0加0还是零。

要回答最开始的问题——整数和小数为何要区别对待,这样说还是不够的。我们还需要了解整数和小数相加方式的不同。

整数的相加是常见的题型,具体可以去看b站的视频。由于整数的加法是从个位开始的,也就是右对齐的(想象列竖式的场景),所以需要一种办法,对齐个位数。这里有两条路,一个是右对齐,然后从右加到左,我在期中考试的时候就是这样写的,那时候没有看其他的文章视频,按最直观的办法想到的,而且那个题并不要求计算结果,只要统计进位次数。其实大数处理最好的办法是另一个办法,就是倒序存储后从左往右做加法,然后从右往左读取结果。这里重点说这个倒序的办法。

而小数的相加,明显是左对齐的,但也是从右往左相加。此时为何区别对待小数和整数的原因已经可以窥见一二,整数由于其右对齐的特性,需要倒序,小数因为是左对齐加法的,而数组存储本来就是左对齐的,就不需要倒序。问题就出在这个倒序上。

我们来看看,如果把整数部分用数字,也就是0, 1, 2 ,3这样的数字(不是'0''1''2''3'这样的字符,这四个字符在ASCII中代表48,49,50,51四个数字)然后进行反向,进行相加,会怎么样:我们会发现无法分别出整数末尾的0和本来数组中用来占位的0!这并不奇怪,因为这两个0是一样的,在ASCII码中都代表空字符。我就在这里错了一次。

那么,我们还能把整数的字符转成数字吗,答案是肯定的,但是需要在反转的时候同步进行转换。

void reverse(char buffer[], char a[])
{for (int tmp = 0, i = strlen(buffer) - 1; i >= 0; i--) {a[tmp++] = buffer[i] - '0';  // 边倒序,边变化形式}
}

这样做为什么就没关系了呢,其实,关键在反转时候的strlen上,之前那样的做法,不仅是混淆了两种0的,更是无法确定数组的长度——长度是靠第一个出现的空字符确定的,只要在反转之前,我们可以区分这两个0,也就是使用'0''1''2''3'这样的字符进行存储,我们就能正确读出字符串的长度,此时进行反转,就会在正确的字符串长度的范围内反转。

很抽象,来看看图,假设要计算1300+1400,如果全部用数字存储,就会变成这样:
在这里插入图片描述
反转数组的时候,程序使用的strlen也会把长度算成2而不是4:
在这里插入图片描述
使用正确的办法:
在这里插入图片描述
反转后:
在这里插入图片描述

这里把0放在了前面,虽然strlen依然不准,但没有人让我们用strlen了!之后我们只需要从最右边往左相加,然后结果中,从右往左遍历,找到第一个不为0的数开始输出即可。最后的代码中可以看到这个过程。

其实这已经把整个题的思路说完了。剩下的细枝末节,其实只要把核心思路掌握,就是一些对核心思路的运用,比如小数相加中,结果中结尾有很多0,怎么去除?整数的思路是从右往左,从第一个不为0的数开始输出,明显小数不能这样——小数没有倒序呢。但是可以运用相同的思路,从右往左,找到第一个不为0的数,记下它的数组下标,然后输出的时候,只要从头输出到这个位置即可。

还要注意题目中的许多细节,比如也有整数加整数、整数加小数的情况出现,此时要判断是否有小数点。还要判断小数部分全为0的时候,此时不输出小数点和小数部分,诸如此类就不再赘述,可以参看代码和注释。

代码和注释

代码中包含详细的注释,命名也尽量明确意思。
题外话,在此提几个倡议:

  1. 稍复杂的程序中尽量用一些单词和下划线命名,用一些拼音也行,尽量不要abcd ijk mnopq到底,比较容易混淆
  2. 多用空格!等号、加号、减号两边、逗号右边、中括号之后等地方加上空格,会让代码舒服非常非常多
    当然这是细枝末节的问题,代码风格取决于个人。
#include <stdio.h>
#include <string.h>#define MAX_LEN 410void reverse(const char buffer[], char a[]) // 把前一个数组反向后存储到后一个数组中,因为对前一个数组只读,加一个const
{for (int tmp = 0, i = strlen(buffer) - 1; i >= 0; i--) {a[tmp++] = buffer[i] - '0';}
}int main()
{char a[MAX_LEN] = {0}, b[MAX_LEN] = {0};while (~scanf("%s %s", a, b)) {int alen = strlen(a), blen = strlen(b), adot = alen, bdot = blen, up = 0;                        // 给adot赋值的值是,如果没有小数点,adot的默认值,在这个值下,两个存储小数的循环不会执行,这样小数的数组就全是0,不会影响计算char a_float[MAX_LEN] = {0}, b_float[MAX_LEN] = {0}, a_int[MAX_LEN] = {0}, b_int[MAX_LEN] = {0}, // 这是用来分别存储整数小数部分的,都是正序float_result[MAX_LEN] = {0}, int_result_reverse[MAX_LEN] = {0},                              // 这是用来存储算术结果的,小数是左对齐计算,整数是右对齐计算,因此一个是正序,一个是倒序(右对齐计算采取了倒序再相加的策略,因此结果也是倒序)a_int_r[MAX_LEN] = {0}, b_int_r[MAX_LEN] = {0};                                              // 这是整数部分的倒序,用于上述的倒序计算int is_adot = 0, is_bdot = 0;                                                                    // 是否具有小数点,用于最后判断要不要输出小数点和小数部分(两者都没有小数部分,即不输出小数点和小数部分)// 两个for循环,分别记录小数点的位置,以一分为二for (int i = 0; i < alen; i++) {if (a[i] == '.') {adot = i;is_adot = 1;break;}}for (int i = 0; i < blen; i++) {if (b[i] == '.') {bdot = i;is_bdot = 1;break;}}// 四个for循环,分别存储a、b数的整数、小数部分for (int i = adot + 1; i < alen; i++) {a_float[i - adot - 1] = a[i] - '0'; // 小数可以减去'0'存储}for (int i = bdot + 1; i < blen; i++) {b_float[i - bdot - 1] = b[i] - '0';}for (int i = 0; i < adot; i++) {a_int[i] = a[i]; // 整数在反转的时候在减去'0'存储}for (int i = 0; i < bdot; i++) {b_int[i] = b[i];}// 因为小数是左对齐相加,因此直接从右往左做大数加法计算即可for (int i = MAX_LEN - 1, tmp; i >= 0; i--) {tmp = a_float[i] + b_float[i] + up;up = tmp / 10; // 进位,用这种形式可以不用if语句判断tmp是否大于等于10,更加简洁明了float_result[i] = tmp % 10;}// 因为整数是右对齐相加,因此需要将两个加数倒序后,从左往右对倒序后的数列做大数加法计算reverse(a_int, a_int_r);reverse(b_int, b_int_r);for (int i = 0, up1 = 0, tmp; i < MAX_LEN; i++) {tmp = a_int_r[i] + b_int_r[i] + up + up1;up = 0;up1 = tmp / 10;int_result_reverse[i] = tmp % 10;}// 倒序标记小数,标出从右到左第一个不为0的值,后续从左往右输出到这个位置即可int float_not_zero_index = 0, is_empty = 1; // is_empty判断小数部分是否为空,以决定要不要输出小数点和小数部分for (int i = MAX_LEN - 1; i >= 0; i--) {if (float_result[i] != 0) {float_not_zero_index = i;is_empty = 0; // 但凡执行到这里,就说明小数部分不为空,不执行到这里,is_empty默认是1,也就是为空break;}}// 倒序输出整数结果,从右往左,从第一个不为0的数开始输出int is_int_empty = 1; // 判断整数部分是否为空,如果为空需要手动输出一个0,原理和上面那个控制变量一样for (int i = MAX_LEN - 1; i >= 0; i--) {if (int_result_reverse[i] != 0) {for (i; i >= 0; i--) {printf("%d", int_result_reverse[i]);is_int_empty = 0;}}}if (is_int_empty) { // 如果整数部分为空,手动输出一个0printf("0");}// 任意一个数有小数点,并且小数部分不为空,输出就要有小数点和小数部分if ((is_adot || is_bdot) && !is_empty) {printf(".");for (int i = 0; i <= float_not_zero_index; i++) {printf("%d", float_result[i]);}}// 统一输出换行符printf("\n");}
}

F: 谍影寻踪——链表的思想和运用

原题

题目描述

2020年10月,国家安全机关组织实施“迅雷-2020”专项行动,破获数百起间谍窃密事件,有效维护了国家安全和利益。在行动中,我方安全机关破获了一个情报组织,他们是单线联系的。我方给情报组织中的每个人员一个唯一的代号。同时,情报人员为了隐秘,可能会有不同的姓名编号(设定最多两个姓名编号)。我方顺藤摸瓜,分别截获并返回组织信息。请你帮忙汇总并恢复这条组织链条。

输入

多组样例。每组样例包括多行输入,第一行包含一个正整数_n_(0<n_≤1000),表示截获到的情报数。接下来的_n_行输入形式为A->B,表示A单向联系B,B是A的下线。A和B表示两名组织人员的信息,包括代号_x_和姓名编号_y,用逗号“,”分开。代号_x_和姓名编号_y_为整数,满足1≤_x_,_y_≤999999。除了第一行,A或B的代号信息在之前的行中出现过。

输出

对于每一组样例,按照顺序在一行中输出该组织的所有成员的信息,用“->”间隔。每个输出占一行。如果有一个代号有两个姓名编号,则认为是一个成员,输出代号和对应的两个姓名编号,用“#”分开。如有两个信息1,2345和1,6666,输出时对应一个成员信息1,2345#6666,两个姓名编号先输入的在前面。

样例输入
3
1,1234->5,2236
5,2236->3,7177 
7,3234->1,1234
4
1,258->10,111111
5,4353->1,36900 
7,22->5,4353
10,159->20,220102
2
1,111->2,222
1,101->2,202
样例输出
7,3234->1,1234->5,2236->3,7177 
7,22->5,4353->1,258#36900->10,111111#159->20,220102
1,111#101->2,222#202

题目思路

看到这个题目名字第一时间想到电影谍影重重和碟中谍,还是很喜欢这类片子的。

题目明显是考察链表的运用,这部分知识需要先掌握,可以看各种书,完成一些书后的习题,上b站搜一些视频学习一下,差不多就可以了。

起初没有看到题目中的“除了第一行,A或B的代号信息在之前的行中出现过”,思来想去了很久AB两者都是第一次出现的情况,还好没想出来具体的实现方法,不然写代码的时间恐怕要翻倍了。

我们还是按实现步骤梳理一下。

既然每一次的代号信息都在之前出现过,那么每一行输入就是对链表的增添。那么自然最开始的时候要有一个初始的链表,要有链表,就先要有个结构体,于是在代码开头,我们写一个结构体。

要包含什么数据呢?题目很明确:名字,代号,可能存在的第二代号。仔细一想,我怎么知道有没有第二代号呢?于是加一个新的标识符is_code2,等于1就是有第二代号,等于0就是没有。

顺便说一下,用is开头命名控制变量有个好处,在判断的时候很明确,比如 if (is_code2) ,即可,英文意思很清楚,“如果是有第二代号”嘛。

结构体就是这样,next作为指针指向下一个结构体,构成链表:

struct spy {int name, code1, code2, is_code2;struct person *next;
};

结构体有了,说好了要建一个链表的。具体来说就是用第一行的信息组建一个链表,我们用代码来解释:

        struct spy *head;head = malloc(sizeof(struct spy));head->next = malloc(sizeof(struct spy));scanf("%d,%d->%d,%d", &head->name, &head->code1, &head->next->name, &head->next->code1); // 第一次扫,先建立一个初始的链表head->is_code2 = head->next->is_code2 = 0;                                               // 都还没有第二代号,标识符为假head->next->next = NULL;

开始需要malloc一些内存,因为第一行代码只是建立一个指针,指向哪里还不知道,分配完内存后,本质是一种质变,本来无意义的内存变成了“结构体”性质的内存,所以分配完内存后,就可以把head看作指向一个结构体的指针了。

什么时候不需要malloc呢?知道了原理其实不言而喻了,假设已经有一个指针p指向了某个结构体,此时写struct spy *head = p;,自然是把head也指向那个结构体,显然就不需要再用malloc了。提一句,这个声明变量要看作两部分struct spy *head,表示声明一个变量head,这个变量的类型是“指向某个spy类结构体(具体哪个还不知道,这里的指向只是它的形式,而非内容)的指针”,不要看成struct spy *head,后者容易引起疑惑。

由于输入的第一行给出两个人的信息,所以初始链表也要包含两个人,其形式就是下面这样的:
在这里插入图片描述
接下来的任务就是找和插入了——找到后续输入的a(或b),在a的后面(或b的前面)插入一个新的链表即可 。我采用的是单个指针,使用p和p->next两步判断——p判断是不是a,p->next判断是不是b,最终的结果,都是在p的后面增加指针。这引出了许许多多的问题,修修补补了很多地方才正确,肯定是有大佬有优雅得多的办法的。我就浅谈一下我的思路。可以对照后文的代码看。

首先scanf一些数据,我们把第一行之后的每行数据存入临时的变量a_name a_code b_name b_code中,即我们称前一个人为a,后一个为b,接着我们开始遍历链表。

p从左往右移动,每次做两个判断:p指向的人的name(即p->name)等于输入的a_name吗?p->next的name(即p->next->name)等于输入的b_name吗?如果其中一个判断为真,就创建一个新的spy结构体,插入其中(插入的具体过程可以参看b站视频和后文代码,如果p是a,就在p后插入b;如果p->next是b,就在p后插入a),一旦插入成功,就break退出遍历,防止重复修改链表。遍历的过程大概如图所示:

在这里插入图片描述
在这里插入图片描述
而题目还要求一个第二代号,这就让问题更加复杂,我的想法是在每次对name的判断为真后,接着判断这个位置是否具有第二代号,如果有就写入代号信息。(具体参看后文代码)

这看似很好,其实还是有很多纰漏,即需要考虑到一些特例:

  1. 如果第一次p的位置是b怎么办?此时p判断的是a,察觉不到它和b相等,而即使p往右移动,也不会有判断head这个位置是不是b_name的过程。
  2. 如果遇到了两个判断同时为真怎么办?此时虽然会正确地插入,但是如果此时出现了第二代号的问题,就得不到正确结果(也就是第四个输入样例的情况,说实话,要是没有这个输入样例,我用我的这种办法自己可能很难想到这种情况)
  3. 如果p指向的是最后一个结构体,那么p->next就指向的是NULL空指针,此时访问p->next->name就会出错,怎么避免?

第一个问题,这就需要加一个判断,来判断head指针指向的结构体是否是b,如果是的话,就在链表头部插入a。

第二个问题比较抽象了,就要在判断p为a后,在判断是否有第二代号的时候加入一个判断——即加入p->next->name和b是否相等,且code是否不一样,如果不一样就要写入b的第二代号。

第三个问题比较明确,需要在if语句中使用p->next != NULL && p->next->name == b_name,由于有短路机制的存在,前者判断为假(即指向空指针),就不会判断后者,自然就不会访问p->next->name

核心思想就是这么多。具体还是来看代码和注释吧:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>struct spy {int name, code1, code2, is_code2;struct spy *next;
};int main()
{int n;while (~scanf("%d", &n)) {struct spy *head; // 头指针,它将要指向第一个结构体head = malloc(sizeof(struct spy));                                                       // 分配结构体空间,此时head成为**具体的**指向这个结构体的指针了head->next = malloc(sizeof(struct spy));                                                 // 分配下一个结构体空间(也就是输入中的第二个人)scanf("%d,%d->%d,%d", &head->name, &head->code1, &head->next->name, &head->next->code1); // 第一次读入,先往初始链表中的结构体中写入信息head->is_code2 = head->next->is_code2 = 0;                                               // 因为它们都还没有第二代号,标识符为0(假)head->next->next = NULL;                                                                 // 第二个人指向NULL(空指针)int a_name, a_code, b_name, b_code;                            // 声明四个变量,用于存储每次读入的数据for (int i = 1; i < n; i++) {                                  // 由于第一次读过了,只要再读n-1次即可scanf("%d,%d->%d,%d", &a_name, &a_code, &b_name, &b_code); // 存入临时变量for (struct spy *p = head; p != NULL; p = p->next) {       // 遍历(扫描、搜索)链表,目的就是插入目标的结构体和其所附带的信息if (head->name == b_name) {                            // 这是判断head是否指向b,因为按这个思路,只用p判断的话判断不到这种情况,此作为一种特例的补充if (head->code1 != b_code) {                       // 判断有没有第二个代号head->is_code2 = 1;                            // 有第二代号,标识符为真head->code2 = b_code;                          // 写入第二代号}struct spy *to_insert;                  // 创建一个待插入的结构体的指针to_insert = malloc(sizeof(struct spy)); // 分配内存,和之前一个道理to_insert->name = a_name;               // 给结构体写入name信息to_insert->code1 = a_code;              // 给结构体写入代号信息// 下面两句是插入的操作to_insert->next = head; // 新插入对象指向之前的第一项head = to_insert;       // 现在的head指向新插入对象break;                  // 及时退出循环,防止修改到其他数据}if (p->name == a_name) {      // 这是判断是否要前插(即p是a,即在p->next前插入b)if (p->code1 != a_code) { // 判断并写入第二个代号p->is_code2 = 1;p->code2 = a_code;}if (p->next != NULL && p->next->name == b_name && p->next->code1 != b_code) { // 判断b有没有第二个代号(针对第四个输入样例那样的判断)p->next->is_code2 = 1;                                                    // 标识符为真p->next->code2 = b_code;}if (p->next == NULL || (p->next != NULL && p->next->name != b_name)) { // 字面意思,其中前一个 p->next == NULL 是防止它不读最后一个了struct spy *to_insert;to_insert = malloc(sizeof(struct spy));to_insert->name = b_name;to_insert->code1 = b_code;// 插入的操作to_insert->next = p->next; // 新插入对象的next,赋值为原来p的nextp->next = to_insert;       // 现在p的next是新插入对象}break;}if (p->next != NULL && p->next->name == b_name) {if (p->next->code1 != b_code) { // 判断有没有第二个代号p->next->is_code2 = 1;      // 标识符为真p->next->code2 = b_code;}struct spy *to_insert;to_insert = malloc(sizeof(struct spy));to_insert->name = b_name;to_insert->code1 = b_code;// 插入的操作to_insert->next = p->next; // 新插入对象的next,赋值为原来p的nextp->next = to_insert;       // 现在p的next是新插入对象break;}}}int is_first_one = 1; // 是不是输出第一项呢?结合下面的判断,如果是输出第一项,就不打最开始的->,后续都不是第一项,就都打出->for (struct spy *p = head; p != NULL; p = p->next) {if (!is_first_one)printf("->");is_first_one = 0; // 只有第一次循环才不执行上面那句代码,后续因为有了这句,所以都会执行printf("%d,%d", p->name, p->code1);if (p->is_code2) {printf("#%d", p->code2); // 这里令我非常无语,把一个井号看成两个,导致一直0%}}printf("\n");}
}

一点感想

学习一门学科,是一个从“必然”走向“自由”的过程。必然是客观规律,自由是对必然的认识和对客观世界的改造。比如在学C语言的时候,必然就是许多C语言的特性,大的可能有数据结构、链表、指针这样的内容,小的比如说for循环、while循环的技巧,if判断中的短路规则等等,而自由就是我们对这些特性的认识和运用这些特性解决实际问题的过程。

我们仅仅在书本上了解、认识了客观规律,这还不是自由,只有进一步在实践中正确地运用客观规律,达到了改造世界的目的,才是真正的自由。也就是学习C语言的各种特性,是远远不够的,只有在实践中恰当地运用这些特性,来解决各种实际问题改造世界,才能在C语言的领域中自由遨游,达到“万类霜天竞自由”的境界,自如地运用各种特性,解决各种复杂的问题。恩格斯说:“自由不在于幻想中摆脱自然规律而独立,而在于认识这些规律,从而能够有计划地使自然规律为一定的目的服务。”(《反杜林论》)

这种自由并不是不能达到的,并不是只能对着高深的知识望洋兴叹。必然和自由是对立的统一,在一定条件下是可以互相转化的。刚开始做题,这也错,那也错,这就是受了必然的制约,成了必然的“奴隶”。但是在不断学习和实践中,也就是在一定条件下,必然就会向自由转化,即此时我们了解了C语言的一些特性和它的惯用法、常用技巧等,能运用这些知识解决问题了,这就是得到了自由。我们说一定条件下必然和自由的转化是客观的,是向一加一等于二一样的真理,因此,要相信在一定条件下,必然一定会向自由转化,不要有“学不会”这样的思想。没有什么天生的天才,人的知识都是后天取得的,只要付出了努力,把认识和实践相结合,创造了这其中的“一定条件”,必然就一定能转换成自由,我们也一定能取得成绩。

而对C语言或者其他学科,它们是复杂的,也就是说对这些学科的认识的自由不是一次性就能得到的。必须要经过实践、认识、再实践、再认识的多次反复才能得到。这个规律在人类历史上也是适用的。毛主席说:“人类的历史,就是一个不断地从必然王国向自由王国发展的历史。这个历史永远不会完结。”

另外在学习的时候还想到了实践和认识的问题,要做成一件事,不能只有实践,也不能只有认识,而是在“实践,认识,再实践,再认识”的多次循环反复中实现的。我们说的“知行合一”,绝不是简单的“合一”,说知就是行,行就是知,然后用知代替行,这是唯心主义的“知行合一”。正确的“知行合一”,其实是“知行统一”,毛主席说:“实践、认识、再实践、再认识,这种形式,循环反复以至无穷,而实践和认识之每一循环的内容,都比较地进到了高一级的程度。这就是辩证唯物论的全部认识论,这就是辩证唯物论的知行统一观。”(《实践论》) 我们学习任何学科知识,都要有这样的思想,单纯的看书、单纯的做题都是不行的。而从全局来看,看书和做题其实都是“认识”的步骤,真正的实践还是将知识投入生产,用于改造世界。再从更大的格局来看,这种实践属于个人的实践,不是社会的实践,这就是后话了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/596914.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入探究:使用大型AI模型的实战指南

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 在今天的技术领域&#xff0c;大型AI模型已成为…

2下载Spring,第一个Spring程序+引用Log4j2

https://www.yuque.com/dujubin/ltckqu/kipzgd#&#xff0c;注意的是&#xff0c;现在&#xff08;202401&#xff09;SpringFramework从release搬到了snapshot下&#xff0c;在这下面找到6.0.2下载. 下载后解压到文件夹&#xff0c;整个框架包含非常多jar包。 然后就可以在p…

3个.NET开源简单易用的任务调度框架

前言 今天分享3个.NET开源、简单、易用的任务调度框架&#xff0c;帮助大家在做定时任务调度框架技术选型的时候有一个参考。 Quartz.Net Quartz.NET是一个功能齐全的开源作业调度系统&#xff0c;可用于从最小的应用程序到大规模企业系统。 Quartz.NetUI Quartz.NetUI是一…

2023年兔飞猛进,2024年龙码精神,龙举云兴

一、2023年回顾 从中华传统文化的角度来看&#xff0c;2023年&#xff0c;是一个比较特别的年份。 2023年是癸卯年&#xff0c;这是根据“天干地支”排列而来。2023年是黑兔年&#xff0c;这是一是根据十天支与五行的对应关系&#xff1a;壬癸属水、代表黑色&#xff0c;二是…

Microsoft Word去除页面多余的换行符

大家写论文的时候或者排版的时候可能遇到换行符多出来了导致页面的不美观。像下面这张图一样&#xff0c;虽然latex不会出现这种问题。 处理方式 点击插入然后点击分页 结果展示

Python最基础的对字符串的操作1

1&#xff0c;字符串的查找&#xff1a; find()&#xff1a;查询某个子串是否包含在这个字符串中&#xff0c;如果在返回这个子串开始的位置下标&#xff0c;否则则返回-1 index()&#xff1a;查询某个子串是否包含在这个字符串中&#xff0c;如果在返回这个子串开始的位置下标…

从vue小白到高手,从一个内容管理网站开始实战开发第六天,登录功能后台功能设计--API项目中的登录实现(一)

从vue小白到高手,从一个内容管理网站开始实战开发第五天,登录功能后台功能设计--数据库与API项目-CSDN博客文章浏览阅读348次,点赞9次,收藏7次。本次文章主要讲了开发后台API项目给前台vue调用的话,需要使用的数据库并新建数据库和表、安装开发工具、如何创建API项目以及A…

IOC解决程序耦合

1.什么是IOC IOC (Inverse of Control)即控制反转&#xff1a;由ioc容器来创建依赖对象&#xff0c;程序只需要从IOC容器获取创建好的对象。 我们在获取对象时&#xff0c;都是采用new的方式。是主动的。 我们获取对象时&#xff0c;同时跟工厂要&#xff0c;有工厂为我们查找…

Impala-查询Kudu表详解(超详细)

文章目录 前言一、使用Impala查询kudu表介绍1. 使用Impala与Kudu表的好处2. 配置Impala以使用Kudu3. Kudu副本因子 二、Impala DDL增强功能1. Kudu表的主键列2. Kudu表特定的列属性1. 主键属性2. NULL | NOT NULL属性3. DEFAULT属性4. ENCODING属性5. COMPRESSION属性6. BLOCK_…

Linux之IP地址、主机名、域名解析

一、IP地址 可以通过ifconfig命令查看本机的ip地址&#xff0c;如果无法使用ifconfig命令&#xff0c;可以安装 安装&#xff1a;yum -y install net-tools ens33&#xff1a;主网卡&#xff0c;里面的inet就是ip地址 lo&#xff1a;本地回环网卡&#xff0c;127.0.0.1&…

Apache Doris (六十): Doris - 物化视图

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录

OpenHarmony—开发环境搭建

背景 因为没有实体的开发硬件&#xff0c;且不想破坏原有的Linux环境&#xff0c;所以这里基于 Docker QEMU 搭建开发环境 宿主机Linux系统命令行方式DockerQEMU 6.2 Docker环境准备 安装Docker 在Ubuntu中&#xff0c;可以使用下面的命令来安装Docker&#xff1a; sudo …

详细解读QLC SSD无效编程问题-4

对于这些全部页面被无效化的WL&#xff0c;执行第二次编程实际上是不必要的&#xff0c;但当前的策略并未注意到这一问题。而对于那些既有有效页面又有无效页面&#xff08;图11中显示为1到3个&#xff09;的WL&#xff0c;应当被编程&#xff0c;但可以利用这些无效信息来改进…

springboot实现用户操作日志记录

springboot实现用户操作日志记录 简介&#xff1a;之前写了《aop实现日志持久化记录》一文&#xff0c;主要介绍自定义aop标注方法上&#xff0c;通过切面方法对用户操作插入mysql。思路正确但是实际操作上存在一些小问题&#xff0c;本文将从项目出发&#xff0c;对细节进行补…

【JaveWeb教程】(2)Web前端基础:JavaScript入门不再难:一篇文章教你轻松搞定JavaScript的基础语法与函数

目录 1 介绍2 引入方式3 基础语法3.1 书写语法3.2 变量3.3 数据类型和运算符 4 函数4.1 第一种定义格式4.2 第二种定义格式 html完成了架子&#xff0c;css做了美化&#xff0c;但是网页是死的&#xff0c;我们需要给他注入灵魂&#xff0c;所以接下来我们需要学习JavaScript&a…

普通用户用哪款电脑杀毒软件最好?

前言 各位小伙伴接触到电脑的时候&#xff0c;都一定有听过“电脑一定要安装杀毒软件”这句话。 毕竟在电脑诞生之初到今天&#xff0c;电脑木马和病毒依旧存在。 中了木马或病毒的电脑会出现什么现象&#xff1f;具体得看中了什么样的病毒。 但轻则资料泄漏、电脑瘫痪&…

SSL/TLS 握手过程详解

SSL握手过程详解 1、SSL/TLS 历史发展2、SSL/TLS握手过程概览2.1、协商交换密码套件和参数2.2、验证一方或双方的身份2.3、创建/交换对称会话密钥 3、TLS 1.2 握手过程详解4、TLS 1.3 握手过程详解5、The TLS 1.2 handshake – Diffie-Hellman Edition 1、SSL/TLS 历史发展 可…

自然语言转SQL,一个微调ChatGPT3.5的实例(下)--模型微调及模型性能

提交训练集进行微调 一旦我们创建了JSONL文件&#xff08;可以在这里或ipfs_here找到一个小样本&#xff09;&#xff0c;下一步是使用以下命令将创建的文件上传到OpenAI&#xff1a; openai.api_key os.getenv("OPENAI_API_KEY") print(openai.File.create(fileo…

【水浸传感器】软硬件一体水浸监测整套方案远程监测解决各种环境漏水问题

一、痛点分析 在工业生产中&#xff0c;水浸传感器可以安装在数据中心、半导体厂房、输油管道、车间仓库、变电室等易发生水浸的区域。一旦检测到漏水情况&#xff0c;立即发出信号反馈。然而&#xff0c;水浸传感器分散在各个地点&#xff0c;导致管理不集中、不便捷&#xf…

Fiber Golang 中的路由和中间件

掌握 GoLang Fiber 中的路由和中间件艺术&#xff0c;以进行高效的 Web 开发 在网络开发领域中&#xff0c;创建一个有效地路由和管理各种任务的 Web 应用程序至关重要。路由决定了如何处理传入的请求&#xff0c;而中间件在执行任务&#xff0c;如身份验证、日志记录和请求解…