wujr5 / c-and-cpp-language-learning

C和C++编程语言学习 - 2015级
67 stars 34 forks source link

软件:week5 实验问题反馈 #10

Open ghostbody opened 8 years ago

ghostbody commented 8 years ago

实验主要问题

大家可能是上大学以来第一次写实验报告,可能问题挺多,下面将详细介绍。

另外,对于大家提出的建议进行一一回答。

不要抄袭,不要抄袭,不要抄袭

很遗憾你们遇到我这个大一实训时候的ta,我们那时候检查抄袭是通过py的正则表达式和距离算法判断相似度,你可以去问问你们14级的师兄师姐。为什么不能抄袭?我想这个问题不是问题吧。

还是那句话,你做不出来,或者直接说不想做,跟我说,我直接给你及格(不过不要每次都来这样),你做不出来,可以缓交。除此之外,要不就不做,要不就好好做。

抄袭一次,本次实验0分;抄袭两次,实验作业全部零分;抄袭三次,直接挂科重修。

实验报告问题

软件 = 程序 + 数据 + 文档 仅仅的代码不是软件,这个实验的实验代码就是程序,文档就是实验报告,文档和程序同样重要。有下列几个普遍问题: 1、 不按照模板自由发挥。 既然实验报告给了模板,你就应该按照这个来写,不要自己重新编一个,这样不好。 2、格式不正确 要求说了用pdf格式,不少word的格式,这个不好。 3、主要内容太吹水 前两部分是给你写你做了什么事情的,比如你用了什么函数,他是什么功能,是写这些的,很多同学就开始水他学习这个经历了九九八十一难,这个应该在心得那里写。

以后的课程基本上很多都要写报告的,所以大家还是学习好怎么写实验报告

优秀报告请参考:优秀报告

代码问题

1、普遍问题,没有输入输出提示:哎呀,师兄不是西西里,而且大家写的函数五花八门,你不提示我你在做什么,我要去一句一句翻看源代码非常痛苦。另外,有些同学实验报告也是没写干了什么,所以我就完全不知道你做了什么。

所以,程序应该有简单的输入输出的提示,告诉我输入什么,输出什么。退一步,实验报告里也总有个程序使用说明。

2、代码编译问题。我用的编译器是dev c++ 很多同学会用vs编译,可以你要考虑兼容的问题。比较多的问题是两个,第一个是使用_s后缀的vs专用函数,第二个就是使用for循环时候直接在里面申明自增变量i。前者是通用性很差,只支持vs,后者是只有c99标准才支持那样子的申明,大家注意。建议还是使用GCC为内核的编译器,如dev,CFREE,当然vs是可以用的,不过你要注意通用性。

优秀代码请参考:优秀代码

附上一份我们一个小project的实验报告:sinew

问题反馈

1、西西里题目太难的问题, 以及西西里理论课和实验课脱节: 说实话,我们在西西里上放的题目真的不难,而且这个是每周老师根据教学进度放的题目,恰恰是和理论课结合很紧密的题目。如果不会做的同学最好多和其他同学交流,和TA交流,和老师交流,特别是在实验课上,你可以准备很多问题来问老师和TA,因为这个时间TA和老师的关注点是大家。

2、扩展题太难,以及扩展题希望能出更具思考的题目: 首先,扩展题的来源有三个,第一个是往年的期中期末考试题,第二个是15教务四班每周的作业题,第三个是我自己出的题目。一个是供大家检考试的例子以及督促大家往新的知识学习,第二种是对比其他班学习的进度弥补我们因为班级的差异带来的教学不足,第三种是我自己出的,对于算法要求低,对语言本身要求极高的题目以及一些考验思维严密性的问题,还有就是应用题(读数字,贪吃蛇)。每个题后面都有难度,一般1~4都是不难的。

而且这个题目做不做随大家,不计入成绩。而且这些题目一般都不偏向经典算法(除了个别期中期末考试题),而是重于对语言本身的应用,逻辑思维的严密性等。如果想做算法题,1、以后算法课肯定会有,2、可以考虑去ACM队,3、找我要,我很多

3、代码风格问题以及衍生的关于软件工程的问题: 我们要时时刻刻记住我们学的是软件工程,也就是说我们的代码是给我们合作的程序员看的,我们的产品(程序、文档、数据)是给我们的用户看的。工程重在的就是协同完成一个很大的项目。比如假设我们现在要100个人来完成一个软件,假如别人和你合作却读不懂你的代码,或者很难读懂怎么办?有的同学说很容易的,如果我是1万行代码呢?

另外提到一点,工程的思想。你要验证问题的解决方法对不对,可能没办法通过数学证明。这时候你需要做什么?

测试

这是在西西里通过的关键。不是说我过了那个example就好了,这远远不够,你需要做的事对于那些数据的很多情况,自己去考虑更多的数据输入输出,从而找到自己程序的问题。因为很多时候我们都会有一种错觉,认为自己的逻辑是正确的,所以测试是解决工程问题最好的办法。

4、看不懂报错,看不懂风格报错: 首先要说一点就是,英文必须要学会看,看不懂就是因为不会翻译。翻译出来正确了,你自然就懂了。要学会适应英语阅读,以及表达,这就是为什么要大家用英文解释理论选择题。

5、感觉老师上课讲的天花乱坠,云里雾里: 我当时也是杨老师教的,上课也是这种感觉,那时候实验题目是竞赛队的TA出的(大家可以在扩张体里看到变态的期中考试题),那时候我们学的更加吃力。我想说的是,老师讲的东西即使你听不懂你也要听进去,以后会有一次恍然大悟的感觉,哦原来是这样的。你会知道这些理论知识多么重要,会贯穿软件工程大学的四年,因为这门课是基础中的基础。

讲点题外话,很多13级的同学就是那时候被题目虐的太惨了,恐惧编程,害怕编程,所以13级整体两级分化是很严重的,很多同学至今仍然不理解当初程序设计的思想方法和理论,很抗拒打代码。我想说,一件事就算再难你也要坚持下来,难不是你放弃的理由,有问题找TA,找老师,找同学,找师兄师姐。

6、时间太紧,TA回复时间太久: 我们TA的时间确实很少,所以希望同学们能够集中在实验课问问题。平时的话如果发qq我可能会不小心忽略或者什么的。

建议大家发邮件给我们,或者在群上和其他同学讨论(不要害羞,有些大神比较高冷,其实他们内心还是很善良的),大家也应该积极去解决其他同学的问题,这样你也可以学到不少。

或者,你可以直接在这下面留言

还有个人时间问题,有人会说我参加了很多社团什么的。 首先,我要说,这里是中山大学,不是中山大社团,所以你应该以学为主。另外,你在社团的活动其实也是一种大学的学习,可是你需要学会安排时间,这是一种能力。我大一也参加过社团,大二是学生会副秘书长,但这些你要安排好。

7、学习编程要到怎样的程度才算好: 这没有一个明确的界限。就像你问,什么样的作家才是好作家? 学习就是一个漫长的,潜移默化的过程,我现在也还在学c语言。 在这个过程,第一,你最需要的是总结。你学了什么,你遇到了什么问题,怎么解决的,什么思路?这些事学习中最关键的积累。第二,理论是很抽象的,你需要做的是两件事,首先将理论具体化,通过实践做题去得到经验,当经验到达一定程度,你可以试着把具体的经验抽象成理论,这才是一个完整的学习过程。比如,老师说c语言的数学计算不同于数学的表达式,那我就使用几个个math函数,模仿别人怎么用的,最后可以抽象出,c语言的数学计算是需要库函数的使用。

后面的期中考试就是一次检验的机会

8、思维混乱: 对于一个编程的题目你不能晕,你要做的思考,不要害怕题目。 先读懂题目,输入时什么,输出是什么。 再者,细分问题,将问题减小。千万不急着写代码。 搞清楚逻辑关系,逐个小问题解决。 测试,修改错误。 (这个以后会再讲)

9、公布答案 这个建议很好,那以后每周题目的答案就公布在git上这个仓库。

总结

可能还有些同学的问题没有解答。总结起来,大家学习的过程中需要多提问题,不要自己憋着;多和大家讨论;多思考,深入的思考。

本周扩展题:

扩展题

chenxy296 commented 8 years ago

【简陋版table】求教。。。求大腿。。输入最后一个email按回车之后,输出最后一个email的时候怎么把回车删了??或者应该怎样换种输出??下面是代码。。【逃

#include <stdio.h>
int main(void) {
    char uid[20][10] = {' '}, name[20][10] = {' '}, email[40][10] = {' '};
    int i, m, n, k;
    printf("请输入uid,username和email各10个,用空格隔开,输入完毕后输入一个空格后回车.\n");
    for (i = 0; i < 10; i++) {         //输入uid,username,email
        for (m = 0; m < 20; m++) {
            scanf("%c", &uid[i][m]);
            if (uid[i][m] == 32 || uid[i][m] == '\n') 
                break;
        } for (n = 0; n < 20; n++) {
            scanf("%c", &name[i][n]);
            if (name[i][n] == 32 || name[i][n] == '\n')
                break;
        } for (k = 0; k < 40; k++) {
            scanf("%c", &email[i][k]);
            if (email[i][k] == 32 || email[i][k] == '\n')
                break;
        }
    } printf("+-----+-------------------+---------------------------------------+\n");
    printf("| uid | username          | email                                 |\n");
    printf("+-----+-------------------+---------------------------------------+\n");
    for (i = 0; i < 10; i++) {         //输出信息 
            printf("| %-4s", uid[i]);
            printf("| %-18s", name[i]);
            printf("| %-38s|\n", email[i]);
    } printf("+-----+-------------------+---------------------------------------+\n");
    return 0;
}
ghostbody commented 8 years ago

其实你可以试一下使用 %s扫描整个字符串的。:smile:

HillCJL commented 8 years ago

讲道理,输入是char输出却用s? 然而devc++测试代码并没有吞掉回车啊。

HillCJL commented 8 years ago

总的来说,这个代码有两个要改进的地方,一个明显的错误。 一个如学长所讲,尝试用%s扫描。 另一个就是代码风格要改,那个for好歹换个行。。。 错误的地方在假如用%c读入,麻烦在字符数组末尾输入‘\0’(自己打代码完成),否则日后的输出很容易出事。

chenxy296 commented 8 years ago

orz好的,谢谢~~~orz我会改进的,再次感谢!~是这样吗??= =

#include <stdio.h>
int main(void) {
  char uid[20][10] = {' '}, name[20][10] = {' '}, email[40][10] = {' '};
  int i, m, n, k;
  printf("请输入uid,username和email各10个.\n");
  for (i = 0; i < 10; i++) {     // 输入uid,username,email
    scanf("%s", &uid[i]);
    scanf("%s", &name[i]);
    scanf("%s", &email[i]);
  }
  printf("+-----+-------------------+---------------------------------------+\n");
  printf("| uid | username          | email                                 |\n");
  printf("+-----+-------------------+---------------------------------------+\n");
  for (i = 0; i < 10; i++) {     // 输出信息
    printf("| %-4s", uid[i]);
    printf("| %-18s", name[i]);
    printf("| %-38s|\n", email[i]);
  }
  printf("+-----+-------------------+---------------------------------------+\n");
  return 0;
}
HillCJL commented 8 years ago

没错。还有关于&uid[i]这种表达,uid[i]默认返回指针,也就是uid[i]的首地址,即&uid[i]和uid[i]是等价的。选择哪种表达都是可以的。

HillCJL commented 8 years ago

关于拓展题上面强制转换的问题。 之前便有同学用(double)或者(int)进行强制转换,Google style check报错。 这是我拓展题的一道代码,其中用到了强制转换(int*)

AlreadyCity = (int*)(calloc((time + 1), sizeof(int)));

提示:

Using C-style cast.  Use reinterpret_cast<int*>(...) instead  [readability/casting] [4]

然而改成

AlreadyCity = reinterpret_cast<int*>(calloc((time + 1), sizeof(int)));

之后,系统报错:

error: ‘reinterpret_cast’ undeclared (first use in this function)
         AlreadyCity = reinterpret_cast<int*>(calloc((time + 1), sizeof(int)));
                       ^

英语的意思是说“未定义” 上网查了下,reinterpret_cast<int*>(...)是c++定义下的强制转换,而且reinterpret_cast是关键字(虽然不知道为什么Online Judge上说是C-style),用C的编译器是无法是别的。 改了下编译器成c++,就通过了。 所以以后在遇到强制转换的时候,要注意使用check中的格式,并且换c++编译器编译。以上。

guoxiongfeng commented 8 years ago

关于拓展1022(无向无负权图最短路)的问题。

这道搜索题我原来在书上看到过类似的,然后首先一个思路就是:递归暴力搜索。从起点开始,递归寻找可能路径,找到终点返回上一层搜索,记录下总路程并与当前最小值比较,更新最小值。 结果这种思路交上去runtime error 呵呵。。。 然后我换了一种思路:依次以每个点作为中间顶点,枚举起点终点的所有情况,比较当前记录的i-->j的距离与通过中转点到达的距离 ,不断更新最小值。 这次没有runtimeerror了,也没超时(说实话n^3的复杂度没超时我也有点小激动了...)但是,WA是什么鬼。。。


    #include<stdio.h>
    int main() {
    int l[250][250], m, n, i, j,
    x, y, k, length, min, max = 1000000, t, a;
    scanf("%d", &t);
    for (a = 1; a <= t; ++a) {
    scanf("%d%d", &m, &n);
    for (i = 0; i <= m-1; ++i)
      for (j = 0; j <= m-1; ++j)
        if (i != j) l[i][j] = max;//将没有路置为max
        else l[i][j] = 0;
    for (i = 1; i <= n; ++i) {
        scanf("%d%d%d", &x, &y, &length);
        l[x][y] = length; l[y][x] = length;//将路用数组表示,并初始化。
    }
    for (i = 0; i <= m-1; ++i)
      for (j = 0; j<= m-1; ++j)
        for (k = 0; k <= m-1; ++k)
          if  (l[j][k] > l[j][i]+l[i][k]) l[j][k] = l[j][i]+l[i][k];//三重循环更新出任意两点最短路长。
    scanf("%d%d", &x, &y);
    if (l[x][y] < 1000000) printf("%d\n", l[x][y]);
    else printf("-1\n");//读入始末点,并输出对应值。
}
    return 0;
}

求大神或各位师兄帮忙......

guoxiongfeng commented 8 years ago

大神你好...我没有完全听懂你说的,但是我认为你说的那个例子的话,应该是早在以9做节点时,更新了15-->3, 最后15做节点时,更新的是1-->15-->3(3-15之前更新过)所以我还是认为没错的啊...能给我一个你认为我会错的样例吗,谢谢~ 另外,递归的最坏情况200层我想时间空间都扛不住吧...你是怎么解决的....

guoxiongfeng commented 8 years ago

@LinTheLegend

HillCJL commented 8 years ago

一开始我也没仔细看,我之后专门查了下这个算法,这个算法是有原型的,名字叫Floyd算法。 然而我也不知道为什么会wrong answer。我也在找原因。 至于递归,直接时间久爆掉了。。。。

HillCJL commented 8 years ago

@guoxiongfeng 至于递归的话,可以参考无向图的广度算法,大致是用数组或者堆来记录路径,把所有从起点开始的路摸一遍。 然而太费时。。。。

guoxiongfeng commented 8 years ago

是的..原来就是看过这个算法记住了,所以我觉得算法是没问题的.. 递归我在想剪枝的办法,只会深度不会广度,也不会堆的我只能等着爆内存了....orz

HillCJL commented 8 years ago

@guoxiongfeng 比较粗糙的递归算法:

#include <stdio.h>

int I[1000], J[1000], L[1000], M, N, ST, ED, Length = -1, p[200];

void Judge(int NowCity, int time, int Distance) {
    int NextDis[1000], NextCity[1000], NCCount = 0;
    int i, j, k, l;
// 判断是否到达终点,并与当前长度做比较,假如没到终点,则判断到达的城市之前是否有走过
    for ( i = 0; i < N; i++ ) {
        if ( I[i] == NowCity ) {
            if ( J[i] == ED ) {
                if ( Length == -1 ) {
                    Length = Distance + L[i];
                } else {
                    l = Distance + L[i];
                    Length = (l < Length) ? l : Length;
                }
            } else {
                k = 0;
                for ( j = 0; j < time; j++ ) {
                    if ( *(p + j) == J[i] ) {
                        k++;
                        j = time;
                    }
                }
                if ( k == 0 ) {
                    NextCity[NCCount] = J[i];
                    NextDis[NCCount] = L[i];
                    NCCount++;
                }
            }
        }
        if ( J[i] == NowCity ) {
            if ( I[i] == ED ) {
                if ( Length == -1 ) {
                    Length = Distance + L[i];
                } else {
                    l = Distance + L[i];
                    Length = (l < Length) ? l : Length;
                }
            } else {
                k = 0;
                for ( j = 0; j < time; j++ ) {
                    if ( *(p + j) == I[i] ) {
                        k++;
                        j = time;
                    }
                }
                if ( k == 0 ) {
                    NextCity[NCCount] = I[i];
                    NextDis[NCCount] = L[i];
                    NCCount++;
                }
            }
        }
    }
    for ( i = 0; i < NCCount; i++ ) {
// 入栈(依据局部变量的生存周期来做栈指针,可以达到栈的效果,只要函数结束便自动出栈了)
        p[time + 1] = NextCity[i];
        Judge(NextCity[i], time + 1, Distance + NextDis[i]);
    }
}

int main() {
    int T;
    int i, c[1];
    scanf("%d", &T);
    while (T) {
        scanf("%d%d", &M, &N);
        for ( i = 0; i < N; i++ ) {
            scanf("%d%d%d", &I[i], &J[i], &L[i]);
        }
        scanf("%d%d", &ST, &ED);
        Length = -1;
        p[0] = ST;
        Judge(ST, 1, 0);
        printf("%d\n", Length);
        T--;
    }
}

超时的原因可能是读写过多,反正读写是十分耗时的。。。

HillCJL commented 8 years ago

用二维数组的方式优化了下。。

#include <stdio.h>

int I, J, L, M, N, ST, ED, Length = -1, p[200], RDistance[250][250];

void Judge(int NowCity, int time, int Distance) {
    int NextDis[1000], NextCity[1000], NCCount = 0;
    int i, j, k, l;
    for ( i = 0; i < N; i++ ) {
        if ( RDistance[NowCity][i] < 1000000 ) {
            if ( i == ED ) {
                if ( Length == -1 ) {
                    Length = Distance + RDistance[NowCity][ED];
                } else {
                    l = Distance + RDistance[NowCity][ED];
                    Length = (l < Length) ? l : Length;
                }
            } else {
                k = 0;
                for ( j = 0; j < time; j++ ) {
                    if ( *(p + j) == i ) {
                        k++;
                        j = time;
                    }
                }
                if ( k == 0 ) {
                    NextCity[NCCount] = i;
                    NextDis[NCCount] = RDistance[NowCity][i];
                    NCCount++;
                }
            }
        }
    }
    for ( i = 0; i < NCCount; i++ ) {
        p[time + 1] = NextCity[i];
        Judge(NextCity[i], time + 1, Distance + NextDis[i]);
    }
}

int main() {
    int T;
    int i, j;
    const int max = 1000000;
    scanf("%d", &T);
    while (T) {
        scanf("%d%d", &M, &N);
        for ( i = 0; i < 250; i++ ) {
            for ( j = 0; j < 250; j++ ) {
                RDistance[i][j] = max;
            }
        }
        for ( i = 0; i < N; i++ ) {
            scanf("%d%d%d", &I, &J, &L);
            RDistance[I][J] = L;
            RDistance[J][I] = L;
        }
        scanf("%d%d", &ST, &ED);
        Length = -1;
        p[0] = ST;
        Judge(ST, 1, 0);
        printf("%d\n", Length);
        T--;
    }
}
yyh15331004 commented 8 years ago

想请教下多个for循环的运算思路,没怎么弄清楚。

include

main() { int i, j, k; for(i=0 ; i <= 10 ; i++) for(j=0 ; j <=5 ; j++) for(k=0 ; k <= 2 ; k++) if(i+j_2+k_5 == 10) printf("一角%d个,贰角%d个,五角%d个\n", i, j, k); return 0; } 这个运算过程是怎么样,想不明白,小白求指导

ghostbody commented 8 years ago

1.你询问的for循环思路是指什么? 2.这个代码贴上来是干嘛的?

for循环和while循环,do while循环其实是等价的。

for(初始化表达式;循环条件;运算表达式) 等价于 初始化表达式 while(循环条件) { 循环体语句; 运算表达式; }

也就是说for循环的初始化表达式只执行一次,循环条件是判断循环是否结束,运算表达式则是在第一次循环最后执行,每次循环结束执行一次。

for循环多数应用于循环语句和循环次数有关的,比如数组的遍历。

for(i = 0; i < n; i++);

yyh15331004 commented 8 years ago

谢谢,我的说的思路是指,那个代码中用到了3个for循环,最终用if判断得到了1元钱的组合。计算机是怎么算的,比如是先令i=0,j=0和k所有情况都判断一次,还是先令k=0,j=0把i的所有情况判断一次。贴这个代码主要就是想问这个问题,和实验没有关系,谢谢您