Learning C++ 之1.10b 设计你的第一个程序

现在你已经学习了编程的一些基本理论,让我们进一步看一下怎么设计你的第一个程序。当你坐下来写一段程序的时候,通常来说你是有一段问题需要解决。现在的程序员往往在将需求转化为程序的阶段碰上困难。但是这个已经证明你在日常的生活中积累了很多解决问题的技巧。

最重要的事情就是在编程之前先设计你的程序。在很多方面上,编程就像是建筑。在你还没有设计的时候就直接盖房子会出现什么后果?结果是除非你非常天才,否则建好的房子会有一堆问题。墙不是直的,低级不牢固,等等。同样的,没有设计直接开始写程序,最终的结果是程序会有一堆问题,你需要花费大量的时间去解决问题,而这些其实在设计阶段稍微动一下脑子就可以解决。

提前的一些计划将会节省你大量维护的时间和减少程序长期运行的挫折。

第一步:定义问题

第一个你需要指出的是你需要解决什么问题。理想情况下你可以把这些需求写成一个或者两个句子。把这些作为一个输出表达也很有用,例如:

  • 我想要一个更好的方式去记录朋友的联系方式。
  • 我想生成随机的地下城,向天然洞穴一样有趣。
  • 我想要推荐我想买的股票。
  • 我想要模拟一个球从一个塔上坠落的高度。

尽管这些需求非常明显,但是非常有用。最糟糕的就是你设计的程序和你的需求完全不一致。

第二部:收集需求

当定义了你想要输出的结果之后,仍然是模糊的,这个时候,你应该手机需求信息。

需求对于你的解决方案所受到的约束(预算,时间,空间,内存),以及程序必须满足的需求来说是一个非常花哨的词。同样的你的需求应该关注what,而不是how。比如:

  • 电话号码应该被保存,所以之后方便再次使用。
  • 随机的洞穴必须包含一条进的路和一条出的路。
  • 股票的推荐应该遵循历史数据。
  • 使用者应该可以登上塔的高度
  • 我们7天内需要一个可测的版本

一个单一的问题可能有很多需求,解决方案并没有完成,知道满足了所有的需求之后。

一些列给到工程师的定义好的需求,应该可以让程序员为你在需求的基础上创建程序,但是这个过程是非常枯燥的。

第三:定义你的工具,目标和备选方案

当你是一个有经验的程序员的时候,这一步还有许多其他的事情要做,比如:

  • 定义你的程序运行的目标和操作系统
  • 定义你所需要的开发工具
  • 决定你是单独编写程序还是作为团队的一部分
  • 定义你的测试,反馈和发布策略
  • 定义怎么备份代码

然而作为一个新的程序员,这些问题都比较简单。你在为你自己写程序,操作系统就是你自己的电脑,开发工具就是你手头有的IDE,这个程序除了你之外,别人应该不会使用,所以这个就变得很简单了。

也就说如果你正在做的工作不是很复杂,你应该有一个计划去备份你的代码。单纯的zip或者copy代码到另一个目录是不够的。如果你的电脑崩溃了,你照样会丢失代码。比较好的方式是从你的系统中获取一份副本,有很多简单的方式来实现,比如email给你自己,或者ftp到另一个服务器,或者放到云上比如github等。版本控制系统有一个非常大的优势,不仅可以保存你的代码,而且还可以轻易地回退到之前地版本。

第四部:将困难的问题分解成为简单地问题

在现实生活中,我们经常需要处理非常复杂的问题。尽力指出需要怎么解决这些问题是非常有挑战性的。在这种情况下,我们经常会使用自上而下的方式去解决问题。也就是为了解决这个复杂的问题,我们把它拆解成一些简单的小问题,各个击破。如果这些子问题仍然比较难,我们继续对他进行拆解。通过持续的拆解问题,你会最终可以有效的解决每一个拆解的小问题。

让我们举一个例子,比如我们需要报道一篇胡萝卜。我们的任务如下:

写一份关于胡萝卜的报道

这个是非常大的一个任务,让我们做一下分解:

1.写一份关于胡萝卜的报道

  • 对胡萝卜做调查
  • 写一份大纲
  • 对大纲进行细节填充
  • 添加目录

这样就非常易于管理了,因为我们分好了子任务。然而,在这个子任务中对胡萝卜做调查仍然比较模糊,所以继续分解。

1.写一份关于胡萝卜的报道

    对胡萝卜做调查

  •         去图书馆看关于胡萝卜的书籍
  •         在网上查找胡萝卜的资料
  •         从参考资料中记录相关章节

   写一份大纲

  •         生长的信息
  •         加工的信息
  •         营养的信息

  对大纲进行填充

  添加目录

现在我们有了一个层级管理,每一个层级的任务都不是很难。完成每一个相关的子任务,我们就会完成这个复杂的任务:写一篇胡萝卜的报道

另一种创建层级的方式是从底向上的方法,就是我们从一些简单的细节工作开始,并通过组织他们进行分层。

举个例子,一些人在工作日会去工作。所以,我们提出一个解决起床去工作的方案。如果我们问你怎么起床,你会给出下面的步骤:

  • 拿出衣服
  • 穿好衣服
  • 吃早饭
  • 开车去公司
  • 刷牙
  • 起床
  • 准备早餐‘
  • 取车
  • 洗个澡

通过自下而上的方法,我们可以把相似的步骤组织成一个大的组织,最终分成一个层次结构,如下:

起床去工作

    卧室的事情

        起床

        拿衣服

        穿衣服

    洗浴室的事情

        洗个澡

        刷个牙

    早餐事情

        准备早餐

        吃早餐

    运输的事情

        取车

        开车去公司

事实证明,这种层级结构在编程的过程中是非常重要的。一旦你有了一个分层的任务,你将会比较容易定义程序中的结构体。最上层的东西就是main函数,其他层次的就是一般的函数。

一旦函数中有一个非常难实现的点,你可以再详细拆分。最终你可以简单的实现每一个你定义的子任务。

第五步,指出事件的序列

现在你的程序已经有一个结构体了,现在需要确认怎么把这些结构给链接起来。第一步是确定要执行的事件序列,例如当你起床后,你该如果按照步骤来执行每一步呢?

  • 起床
  • 拿出衣服
  • 洗个澡
  • 穿衣服
  • 准备早餐
  • 吃早餐
  • 刷牙
  • 取车
  • 开车去公司

如果我们写一个计算器,那么步骤如下:

  • 从用户那里获取第一个数字
  • 获取数字上的操作符
  • 从用户那边获取第二个数字
  • 祭祀u按结果
  • 打印输出

下面的函数定义了你的main()函数里面的顺序:

int main()
{
    getOutOfBed();
    pickOutClothes();
    takeAShower();
    getDressed();
    prepareBreakfast();
    eatBreakfast();
    brushTeeth();
    getInCar();
    driveToWork();
}

计算器:

int main()
{
    // Get first number from user
    getUserInput();
 
    // Get mathematical operation from user
    getMathematicalOperation();
 
    // Get second number from user
    getUserInput();
 
    // Calculate result
    calculateResult();
 
    // Print result
    printResult();
}

如果你用这种方式创建你的程序,在你真正写之前对每一个函数进行注释是一个好习惯,然后挨个调试,测试。这样编译器就不会报错了。

第六步:指出你需要输入和希望输出的结果

一旦你有了一个层次和事件序列,接下来的事情就是确认每一层次需要的输入数据和输出结果。如果你已经有了之前步骤的输入数据,那么输入数据就可以作为一个参数。如果你正在使用其他函数的输出作为参数,那么一般就把输出设置成为一个返回值。

当我们完成时,每个函数都有一个函数原型。如果你忘了,函数原型就是函数名,返回类型,参数,但是不包含具体的函数实现。

让我们举几个例子,getUserInput()是一个非常简单的函数。我们从用户那里得到一个数字并且返回给使用者,因此函数类型如下:

int getUserInput();

在计算器的例子里,calculateResult()函数需要3个参数,两个操作数,以及一个操作符。我们需要准备好这三个参数,当我们调用这个函数的时候。所以这三个数值可以作为函数的参数输入。这个函数值只是作为计算结果用,但是并不作为输出使用。因此我们需要设置一个返回值,其他的函数可以使用。

所以最终的函数原型如下:

int calculateResult(int input1, int op, int input2);

第七步,写任务细节

在这一步中,为了每一个子任务你得写详细的任务实现。如果你已经把任务分解成为了足够小的部分,每一个部分需要非常简单直接。如果一个给定的任务过于复杂,或许你该考虑继续分解。

比如:

int getMathematicalOperation()
{
    std::cout << "Please enter which operator you want (1 = +, 2 = -, 3 = *, 4 = /): ";
 
    int op;
    std::cin >> op;
 
    // What if the user enters an invalid character?
    // We'll ignore this possibility for now
 
    return op;
}

第八步:连接数据的输入和输出

最后一步使用合适的方式将输入和输出连接起来。比如,你可能把calculateResult()函数的输出作为printResult()的输入,所以最终的结果会打印出来。这个通常需要临时变量来存储结果,以便在函数间传输。比如:

// result is a temporary value used to transfer the output of calculateResult()
// into an input of printResult()
int result = calculateResult(input1, op, input2); // temporarily store the calculated result in result
printResult(result);

这种方式比起函数直接嵌套调用要清晰的多。

printResult( calculateResult(input1, op, input2) );

这部分对于一个新手程序员来说往往是最难的部分。

下面是一个完整的计算器版本,里面可能包含了一些我们目前还没有学习的概念,可以参考一下:

  • if语句是条件语句,当条件为真时执行
  • ==操作符比较两边的数值是否完全相等

现在你没有必要完全搞懂每一步的意思,只是从整体上看一下整体结构,以及明白数据是怎么传递的。

// #include "stdafx.h" // uncomment if using visual studio
#include <iostream>
 
int getUserInput()
{
    std::cout << "Please enter an integer: ";
    int value;
    std::cin >> value;
    return value;
}
 
int getMathematicalOperation()
{
    std::cout << "Please enter which operator you want (1 = +, 2 = -, 3 = *, 4 = /): ";
 
    int op;
    std::cin >> op;
 
    // What if the user enters an invalid character?
    // We'll ignore this possibility for now
 
    return op;
}
 
int calculateResult(int x, int op, int y)
{
    // note: we use the == operator to compare two values to see if they are equal
    // we need to use if statements here because there's no direct way to convert op into the appropriate operator
 
    if (op == 1) // if user chose addition (#1)
        return x + y; // execute this line
    if (op == 2) // if user chose subtraction (#2)
        return x - y; // execute this line
    if (op == 3) // if user chose multiplication (#3)
        return x * y; // execute this line
    if (op == 4) // if user chose division (#4)
        return x / y; // execute this line
	
    return x + y; // if the user passed in an invalid op, we'll do addition.
 
    // we discuss better error handling in future chapters
}
 
void printResult(int result)
{
    std::cout << "Your result is: " << result << std::endl;
}
 
int main()
{
    // Get first number from user
    int input1 = getUserInput();
 
    // Get mathematical operation from user
    int op = getMathematicalOperation();
 
    // Get second number from user
    int input2 = getUserInput();
 
    // Calculate result and store in temporary variable (for readability/debug-ability)
    int result = calculateResult(input1, op, input2);
 
    // Print result
    printResult(result);
 
    return 0;
}

写程序的几点建议:

让你的程序有个简单的开始:

通常来说一个程序新手往往对他们的程序有一个比较大的愿景。“我想写一个角色扮演的游戏,有图像,声音,以及商店,地牢,你可以在商城里买卖热议你想要的东西”。如果你开始就写这么复杂的程序,你非常容易受打击。相反的,开始的目标确立的简单一点,在你的能力范围之内。比如“我想要画一个2d的图像在电脑上”。

随着时间的推移增加功能:

一旦你的简单的程序运行成功了,你就可以增加功能了。比如,你可以将2d图像显示在屏幕上了,那么增加一个角色可以走来走去。一旦这个角色创建成功了,增加墙来阻挡角色的行动。有墙创建成功了,可以慢慢增加一个城镇了。有城镇了之后,再去增加商人。通过这样一步步地增加功能,最终你的程序会变得非常复杂,来实现你所想地所有功能。

一次集中在一点上:

不需要想着一次同时编写所有地代码部分,不要把你的精力分散在多个任务中。一次集中搞定一个任务,尽可能地完成它。一次完成一个任务,比同时做5个没有完成地任务要好得多。如果你分神了,你很有可能会犯错误,并且注意不到细节。

写程序的过程中,自测你自己地每一部分代码:

新的程序员经常一次写一段完整段代码。然后他们编译地时候会上报一堆错误。这个并不吓人,如果你的程序不能执行很难去具体定位哪里出问题了。因此我们建议一次写一小段代码,然后立即编译,检查有没有错误。如果有问题,你能够很快的定位问题。一旦确认程序没有问题,接着写下一部分,这种不断重复。虽然这可能花费更长的时间,但是一旦写完,就可以立即执行,没有错误。否则你将会花费大量的时间在debugging上。

打多数新的程序员都会抄捷径,不按照上面的建议来。然而长远来看,按照上面的建议来会节省你大量的时间。少的计划将会导致大量的后续debug。

好消息是一旦你适应了上面的建议,你就自然而然地按照上面地建议来做了。最后,你就可以直接写一段完整的代码,而不用任何的提前计划了。当然这个需要一个比较长的熟练的过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值