Files
fzu-product/技术资源汇总(杭电支持版)/3.编程思维体系构建/3.4.6.3.指明地点.md
2024-08-10 19:46:55 +08:00

275 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 3.指明地点
::: warning <font size=5>某种极其糟糕的编程习惯</font>
<font size=5><strong>Copy-paste</strong></font>
我们很多同学在编程的过程中,可能会写出一大堆重复性很强的代码,在最近看的 pa 中,举了这样一个例子,你不需要看懂只需要感受到就可:
:::
```c
if (strcmp(s, "$0") == 0)
return cpu.gpr[0]._64;
else if (strcmp(s, "ra") == 0)
return cpu.gpr[1]._64;
else if (strcmp(s, "sp") == 0)
return cpu.gpr[2]._64;
else if (strcmp(s, "gp") == 0)
return cpu.gpr[3]._64;
else if (strcmp(s, "tp") == 0)
return cpu.gpr[4]._64;
else if (strcmp(s, "t0") == 0)
return cpu.gpr[5]._64;
else if (strcmp(s, "t1") == 0)
return cpu.gpr[6]._64;
else if (strcmp(s, "s2") == 0)
return cpu.gpr[7]._64;
else if (strcmp(s, "s0") == 0)
return cpu.gpr[8]._64;
else if (strcmp(s, "s1") == 0)
return cpu.gpr[9]._64;
else if (strcmp(s, "a0") == 0)
return cpu.gpr[10]._64;
else if (strcmp(s, "a1") == 0)
return cpu.gpr[11]._64;
else if (strcmp(s, "a2") == 0)
return cpu.gpr[12]._64;
else if (strcmp(s, "a3") == 0)
return cpu.gpr[13]._64;
else if (strcmp(s, "a4") == 0)
return cpu.gpr[14]._64;
else if (strcmp(s, "a5") == 0)
return cpu.gpr[15]._64;
else if (strcmp(s, "a6") == 0)
return cpu.gpr[16]._64;
else if (strcmp(s, "a7") == 0)
return cpu.gpr[17]._64;
else if (strcmp(s, "s2") == 0)
return cpu.gpr[18]._64;
else if (strcmp(s, "s3") == 0)
return cpu.gpr[19]._64;
else if (strcmp(s, "s4") == 0)
return cpu.gpr[20]._64;
else if (strcmp(s, "s5") == 0)
return cpu.gpr[21]._64;
else if (strcmp(s, "s6") == 0)
return cpu.gpr[22]._64;
else if (strcmp(s, "s7") == 0)
return cpu.gpr[23]._64;
else if (strcmp(s, "s8") == 0)
return cpu.gpr[24]._64;
else if (strcmp(s, "s8") == 0)
return cpu.gpr[25]._64;
else if (strcmp(s, "s10") == 0)
return cpu.gpr[26]._64;
else if (strcmp(s, "t2") == 0)
return cpu.gpr[27]._64;
else if (strcmp(s, "t3") == 0)
return cpu.gpr[28]._64;
else if (strcmp(s, "t4") == 0)
return cpu.gpr[29]._64;
else if (strcmp(s, "t5") == 0)
return cpu.gpr[30]._64;
else if (strcmp(s, "t5") == 0)
return cpu.gpr[31]._64;
```
以下是某论文的代码节选,可以说是错误的范例:
```python
fx = torch.cat((xs[0], fs[0], xs[1], fs[1], xs[2], fs[2], xs[3], fs[3], xs[4], fs[4], xs[5], fs[5], xs[6], fs[6], xs[7], fs[7],
xs[8], fs[8], xs[9], fs[9], xs[10], fs[10], xs[11], fs[11], xs[12], fs[12], xs[13], fs[13], xs[14], fs[14], xs[15], fs[15],
xs[16], fs[16], xs[17], fs[17], xs[18], fs[18], xs[19], fs[19], xs[20], fs[20], xs[21], fs[21], xs[22], fs[22], xs[23], fs[23],
xs[24], fs[24], xs[25], fs[25], xs[26], fs[26], xs[27], fs[27], xs[28], fs[28], xs[29], fs[29], xs[30], fs[30], xs[31], fs[31]), 1)
bx = torch.cat((xs[0], bs[0], xs[1], bs[1], xs[2], bs[2], xs[3], bs[3], xs[4], bs[4], xs[5], bs[5], xs[6], bs[6], xs[7], bs[7],
xs[8], bs[8], xs[9], bs[9], xs[10], bs[10], xs[11], bs[11], xs[12], bs[12], xs[13], bs[13], xs[14], bs[14], xs[15], bs[15],
xs[16], bs[16], xs[17], bs[17], xs[18], bs[18], xs[19], bs[19], xs[20], bs[20], xs[21], bs[21], xs[22], bs[22], xs[23], bs[23],
xs[24], bs[24], xs[25], bs[25], xs[26], bs[26], xs[27], bs[27], xs[28], bs[28], xs[29], bs[29], xs[30], bs[30], xs[31], bs[31]), 1)
```
::: tip <font size=5>你想想,你遇到这么长的代码,你愿意看他吗?</font>
更可怕的是,这种编码模式可能会导致意想不到的 bug。
当你发现这些代码有 bug 的时候,噩梦才刚刚开始。也许花了好几天你又调出一个 bug 的时候,才会想起这个 bug 你好像之前在哪里调过。你也知道代码里面还有类似的 bug, 但你已经分辨不出哪些代码是什么时候从哪个地方复制过来的了。
这种糟糕的编程习惯叫 Copy-Paste, 经过上面的分析,相信你也已经领略到它的可怕了。事实上,[周源源教授](https://cseweb.ucsd.edu/~yyzhou/)的团队在 2004 年就设计了一款工具 CP-Miner, 来自动检测操作系统代码中由于 Copy-Paste 造成的 bug. 这个工具还让周源源教授收获了一篇[系统方向顶级会议 OSDI 的论文](http://pages.cs.wisc.edu/~shanlu/paper/OSDI04-CPMiner.pdf), 这也是她当时所在学校 UIUC 史上的第一篇系统方向的顶级会议论文。
后来周源源教授发现,相比于操作系统,应用程序的源代码中 Copy-Paste 的现象更加普遍。于是她们团队把 CP-Miner 的技术应用到应用程序的源代码中,并创办了 PatternInsight 公司。很多 IT 公司纷纷购买 PatternInsight 的产品,并要求提供相应的定制服务,甚至 PatternInsight 公司最后还被 VMWare 收购了。
这个故事折射出,大公司中程序员的编程习惯也许不比你好多少,他们也会写出 Copy-Paste 这种难以维护的代码。但反过来说,重视编码风格这些企业看中的能力,你从现在就可以开始培养。
:::
*传统上,文本冒险是由(许多)不同位置组成的虚拟世界。虽然这不是必需的(一些冒险发生在一个房间里!),但这是解释**数据结构**使用的好方法。*
我们首先定义一个[结构](http://en.wikipedia.org/wiki/Struct_(C_programming_language))来表示一个位置。它包含两个简单的属性开始(稍后可能会有更多的属性)。
1. 描述:对物品进行描述
2. 标记:具体的对其进行标记
```c
struct location {
const char *description;
const char *tag;
};
```
::: warning 🤔 思考题:
我们为什么要用结构体来保存位置?
这样子做有什么好处?
const 又是什么?
:::
接下来,我们定义一个位置数组。目前,我们保持它非常简单:只有两个位置。
```c
struct location locs[2];
```
我们还可以使用初始值设定项立即填充所有静态数据。
```c
struct location locs[2] = {
{"an open field", "field"},
{"a little cave", "cave"}
};
```
让我们把它付诸实践。在上一章(*parsexec.c* 的代码示例中,我们更改了第 4、18 和 22 行)。
## <strong>parsexec.c</strong>
```c
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "location.h"
bool parseAndExecute(char *input)
{
char *verb = strtok(input, " \n");
char *noun = strtok(NULL, " \n");
if (verb != NULL)
{
if (strcmp(verb, "quit") == 0)
{
return false;
}
else if (strcmp(verb, "look") == 0)
{
executeLook(noun);
}
else if (strcmp(verb, "go") == 0)
{
executeGo(noun);
}
else
{
printf("I don't know how to '%s'.\n", verb);
}
}
return true;
}
```
接下来,我们将一个新模块添加到项目中
## <strong>location.h</strong>
```c
extern void executeLook(const char *noun);
extern void executeGo(const char *noun);
```
## <strong>location.c</strong>
```c
#include <stdio.h>
#include <string.h>
struct location {
const char *description;
const char *tag;
}
locs[] = {
{"an open field", "field"},
{"a little cave", "cave"}
};
#define numberOfLocations (sizeof locs / sizeof *locs)
//欸?这个是干啥呢?
static unsigned locationOfPlayer = 0;
void executeLook(const char *noun)
{
if (noun != NULL && strcmp(noun, "around") == 0)
{
printf("You are in %s.\n", locs[locationOfPlayer].description);
}
else
{
printf("I don't understand what you want to see.\n");
}
}
void executeGo(const char *noun)
{
unsigned i;
for (i = 0; i < numberOfLocations; i++)
{
if (noun != NULL && strcmp(noun, locs[i].tag) == 0)
{
if (i == locationOfPlayer)
{
printf("You can't get much closer than this.\n");
}
else
{
printf("OK.\n");
locationOfPlayer = i;
executeLook("around");
}
return;
}
}
printf("I don't understand where you want to go.\n");
}
```
在 C 语言中,你可以使用单个语句来定义类型(*结构位置*),声明变量(*locs*)并用其初始值填充它。
思考题:变量*locs*是[静态分配的](http://en.wikipedia.org/wiki/Static_memory_allocation),什么是静态分配?
静态分配和动态分配有什么不同之处?
复杂思考题13 行宏定义好像实现了一个函数!很神奇!为什么不用函数来做这个知识呢?
提示:这个问题涉及编程从代码到可执行文件的四个步骤,希望你可以认真学习和思考,如果你用 Linux 去完成。你可以尝试用 gcc 逐步输出编译结果。
测试样例:
Welcome to Little Cave Adventure.
You are in an open field.
--> go cave
OK.
You are in a little cave.
--> go field
OK.
You are in an open field.
--> go field
You can't get much closer than this.
--> look around
You are in an open field.
--> go kitchen
I don't understand where you want to go.
--> quit
Bye!