📄 ch01s02.html
字号:
<html><head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>2. CPS猜数字游戏</title><link rel="stylesheet" href="html.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.69.1"><link rel="start" href="index.html" title="Java网络程序员看Continuation"><link rel="up" href="ch01.html" title="Chapter 1. CPS与网络程序流程控制"><link rel="prev" href="ch01s01.html" title="1. 简单的CPS例子"><link rel="next" href="ch01s03.html" title="3. CPS与goto"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2. CPS猜数字游戏</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch01s01.html">Prev</a> </td><th width="60%" align="center">Chapter 1. CPS与网络程序流程控制</th><td width="20%" align="right"> <a accesskey="n" href="ch01s03.html">Next</a></td></tr></table><hr></div><div class="section" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="d0e61"></a>2. CPS猜数字游戏</h2></div></div></div><p>熟悉了最简单最基本的CPS程序,我们接下来看一个比较复杂的例子。我们要写一个猜数字的游戏。电脑会自动生成1到100之间的一个数字,然后用户给出自己的猜测,电脑会回答Number too big或Number too small。游戏会重复这个过程,直到用户猜出答案为止。用普通的C写出来是这样的:</p><pre class="programlisting">#include <stdio.h>#include <stdlib.h>void main() { int ans = random() % 100 + 1; int win = 0; int guess; while (!win) { printf("Guess a number [1, 100]: "); scanf("%d", &guess); if (guess < ans) printf("Number too small.\n"); else if (guess > ans) printf("Number too big.\n"); else win = 1; } printf("Congratulations!");}</pre><p>这个程序的逻辑可以说非常清楚了。不过,现在我们假设系统没有printf,只有对应的CPS函数print,其定义相当于:</p><pre class="programlisting">void print(const char *message, void (*continuation)(void)) { printf("%s\n", message); continuation(); exit(0);}</pre><p>注意到该函数不会返回给母函数,而是会直接执行自己的第二个参数 。这时我们的游戏应该怎么写呢?</p><p>为了简单起见,我们只要求把printf用CPS的print代替,程序的其他地方还是可以用普通的函数调用的。因为print不会返回,所以我们要在所有调用到printf的地方把程序“断开”,并把断开后该print之后的所有部分变成一个函数。然后我们把这个函数传给print作为第二个参数就行了。</p><p>上面说得似乎很简单,但是如果您试一试,您马上就会发现一个问题,while结构怎么办?答案也很简单,我们不能用while了。不过即使没有while我们还是能把CPS的程序写出来,您只要需要不断地询问自己,“程序执行到现在剩下的操作是什么?”就行了。而且对于这样简单的例子,其对应的CPS版本并不算太复杂。</p><p>我希望您不要马上看下面的答案,试着自己把上面的代码转换成CPS。您会有比较深刻的印象为什么写出来的代码只能是下面这种模式。</p><pre class="programlisting">/* 请不要马上看答案!先自己试试看。 */#include <stdio.h>#include <stdlib.h>void print(const char *message, void (*continuation)(void)) { printf("%s\n", message); continuation(); exit(0);}int ans;void finishgame();void guess();void prompt();void finishgame() { /* do nothing */}void guess() { int guess; scanf("%d", &guess); if (guess < ans) print("Number too small.", prompt); else if (guess > ans) print("Number too big.", prompt); else print("Congratulations!", finishgame);}void prompt() { print("Guess a number [1, 100]: ", guess);}void main() { ans = random() % 100 + 1; prompt();}</pre><p>您的做法和上面的一样吗?</p><p>如果我们仔细看看这个程序,我们会注意到它有至少三个问题:</p><div class="orderedlist"><ol type="1"><li><p>一部分本地变量变成了全局变量,例如上面的ans,因为不止一个函数要用到它,而我们又不能使用普通的变量参数。这是因为print接受的continuation的定义是void (*continuation)(void),也就是一个不接受任何参数,也不返回任何值的函数。</p></li><li><p>控制结构例如while不能使用了。上面的例子中,prompt和guess两个函数会互相传递对方为continuation,以此来模拟while。</p></li><li><p>虽然在本例中看不出来,但是如果原程序中有递归及回溯的话,那么单单把本地变量变成全局变量就不够了。这时,我们需要自己写一个call stack,也就是,重新发明一个compiler可以自动帮我们做的轮子。我们后面还会详细讲到这个问题。</p></li></ol></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch01s01.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="ch01.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="ch01s03.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">1. 简单的CPS例子 </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> 3. CPS与goto</td></tr></table></div></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -