跳入C++:计算未知数

上次,我写了一篇 C++程序 数数单词 在文本文件中。这一次,编写一些代码来计算对象的运动,以此作为创建类、使用函数指针和处理一些新容器的借口。感谢我的同事Ale Contenti和Andy Rich提供了灵感和原创的Simple Physics计算器应用程序,James McNellis提供了草稿和代码片段,Stephan Lavavej提供了额外的反馈。

null

要求

这一次,我需要开发一个类来提供有关对象运动的信息。要求包括:

  • 有五个方程变量: 距离 , 时间 , 加快 , 初速度 ,和 最终速度 .
  • 使用 运动学方程 当其他三个变量已知时,计算两个未知方程变量。
  • 提供一种将变量清除或重置回未设置状态的方法。
  • 避免使用除零、负平方根或不完全存在两个未知值和三个已知值的计算。

例如,如果该类给定距离(250米)、时间(7.3秒)和加速度(3.2米/秒的平方),则应计算未知值初始速度(22.5666米/秒)和最终速度(45.9266米/秒)。重置类使其准备好接受一组新的已知值。如果在计算未知量时出现问题,则操作将失败并显示错误消息。对于本练习,代码在控制台应用程序中使用,但它应该足够通用,可以用来驱动这样的UI:

图片[1]-跳入C++:计算未知数-yiteyi-C++库

作为额外的要求,代码应该是可移植的。对于那些迫不及待的人,抓住 代码 挖进去[ 更新日期:2/25/2013 :代码也位于GitHub上 https://github.com/ebattalio/calculate-unknowns ]

第一版:一般问题

我的第一次尝试解决了用三个已知值求解两个未知数的一般问题。最终的解决方案仍然需要使用运动学方程,但是一般的解决方案对我来说更简单(我不需要担心 微妙之处 属于 物理学 和方程),并可用作 起点 适用于其他领域或变量较多的类似应用。数学并不难,但我是来C++的。真正的方程式将在稍后混合(谢谢!)在打开visualstudio之前,我想知道什么代码可以处理已知值和未知值的组合。我脑海中浮现的第一件事是“如果语句”;使用一堆级联if语句来挑选已知值并计算未知值。每个if语句将检查未知值的特定组合,每个组合一个。有多少种组合?

图片[2]-跳入C++:计算未知数-yiteyi-C++库

我涂鸦了一张图表,发现只有10个未知的组合是重要的。对角线上的组合(带圆圈)使用同一变量两次,因此可以忽略。右上三角形中的组合与左下三角形中的组合重复,因此它们也可以忽略。剩下的是左下角的三角形,十种不同的未知组合。if语句不太多,但我更喜欢使用两个未知数的组合作为键值(例如,“ae”或“bd”)以及switch语句来执行正确的计算。切换语句仍然很酷,对吧?它们易于遵循,效率高,并且可以轻松扩展。解决了这个问题,我想到了如何存储方程值。公式值是一个数字,但可能为空(未设置且未知),因此单个变量不起作用。我需要两个,一个跟踪值,一个跟踪值的存在。在我的第一个版本中,我是新手,使用了简单的成员变量(每个变量一对)和定制的助手函数(get、set和has value)。这种实现并不理想,但它很简单,易于测试,并且易于发展。我已经从公式1.h中抄了几个部分。第一个示例显示如何声明变量以跟踪方程值,第二个示例显示如何计算键代码。我还复制了一个get函数。

双重的 _a、 u b,u c,u d,u e;

布尔 _aúhasval、úbúhasval、úcúhasval、údúhasval、úeúhasval;

未签名 未知密钥() 常数

{

返回 !hasA()+(!hasB()<<1)+(!hasC()<<2)+(!hasD()<<3)+(!相(<<4);

}

双重的 格塔() 常数

{

如果 (哈萨()) 返回 _一个; 公式1例外 ( “缺少一个” );

}

当没有值时,代码抛出一个异常来告诉调用者“嘿,这个变量没有值”。这比重写函数将值作为输出参数传递并在值存在时返回true好吗?在这两种情况下,调用方仍有责任跟踪变量的状态。如果调用者失败,代码将失败,结果将相同。草药萨特抓住了 何时以及如何使用异常 :

区分错误和非错误。失败是一个错误,当且仅当它违反了函数满足其被调用方的先决条件、建立自己的后置条件或重新建立它分担维护责任的不变量的能力时。其他一切都不是错误。

我会把这当作最佳实践。

第二个版本:使用有序映射和函数指针

第一个版本在调用计算时使用switch语句来解决这两个未知数。默认情况下,处理未识别的未知组合,如果没有糟糕的编码,就永远不会发生这种情况。考虑到这一点,switch语句真正做的就是将一个键(一组未知项)映射到一个值(一个代码块或函数)。在代码的第二个版本中,我用存储整数键和函数指针值的映射替换了开关:

类型定义 标准: 地图 < 未签名 内景 ,标准: 功能 < 无效 ()>> 公式2 ;

公式2 _公式;

它在构造函数中使用一个静态函数进行初始化,该函数返回一个填充的formulas对象,这是一个我以后将再次使用的“巧妙技巧”。在某些情况下,也可能是这样 更快 . 为此,初始化函数需要绑定到右边 指针。在代码中,它是从调用者传递的:

FormulaV2():_公式(创建u函数u映射)( ))

{

}

静止的 公式2 创建函数映射( 公式2 * p )

{

公式2 公式;

公式[3]=std::bind(& 公式2 ●计算u ab, p );

公式[5]=std::bind(& 公式2 ●计算交流, p );

// ..

公式[24]=std::bind(& 公式2 ●计算数据, p );

返回 公式;

}

原始版本使用了 无序地图 ,但在与Stephan交谈之后,最佳实践是默认使用有序映射,仅当需要非常特殊的特性时才使用无序映射。堆栈溢出时, 格曼尼克 主要地 同意 但这说明 无序地图 当需要纯查找速度时。我在这个版本中没有使用lambdas,但是我可以使用。它会使代码更容易扩展,因为处理映射和计算的所有代码都将位于一个位置,而不是作为离散类方法分散在一起。它还将消除使用std::bind的需要。我可能应该使用lambdas,但是通过避免使用它们,代码更易于调试和维护。对于那些寻找lambda指导的人,请考虑 兰姆达斯,到处都是兰姆达斯 ( 第九频道,赫伯·萨特,2010年 ), C++中的lambda表达式 (MSDN) , C++中的lambda表达式与函子 (被接受的回答,疯狂的埃迪,斯塔克弗利), 匿名函数 (维基百科) 而对于硬模风扇,C++标准。还有一个显著的变化。异常类已修改为从 std::运行时错误 . 以前的异常类定义缺少用户定义的析构函数。詹姆斯·麦克内利斯在《纽约时报》 异常规范如何影响虚拟析构函数重写 ?“堆栈溢出。

第三个版本:处理方程变量的更好方法

在第三个版本中( 公式3 ),表达式变量在容器中管理,而不是在单个成员变量中管理,并使用一组辅助函数(使用枚举器标记)和一对实用函数进行操作,以访问正确的项:

枚举 结构 标签 : 未签名 { , b , c , d , e , 计数 };

类型定义 标准: 数组 < 双重的 , 静态浇铸 < 未签名 >( 标签 :: 计数 )> 值u序列 ;

类型定义 标准: 位组 < 静态浇铸 < 未签名 >( 标签 :: 计数 )> 存在u序列 ;

静止的 未签名 作为基础( 标签 常数 t型 ) { 返回 静态浇铸 < 未签名 >( t型 ); }

静止的 未签名 作为u位( 标签 常数 t型 ) { 返回 1<<作为基础( t型 ); }

作为基础 采用标记名(如 标签::a )并返回其基础值(如0)。返回值用作 值u序列 数组。 作为u位 返回相应位设置为无符号整数的值,便于处理 存在u序列 .通过这种方式处理公式变量,代码更易于阅读,更易于扩展,并且要测试的函数更少。采用代码和修改代码一样简单 标签 定义计算函数。

最终版本:物理混合

配方V4b 是我代码的最终版本。上一版本的设计是通过自定义来处理对象运动计算的:

  • 公式变量名在 标签 枚举。
  • 用物理方程计算未知数。
  • 由于某些等式可能导致试图除以零或取负平方根,因此可能会引发更多异常。

在我要追查的所有错误中,最阴险的是其中一个等式中使用的常量的小数点放错了位置。我没有写“0.5”,而是写了“.05”,结果就不一样了。结果指出了问题所在,但我的眼睛和大脑在大约15分钟的时间里没有看到错误,还有一大堆战略性的输出语句。编译器警告是一种相对的乐趣。此版本和以前的版本是 附属的 . [ 更新日期:2/25/2013 : 代码也打开了 github ] . 我还附上 配方V4a 代码由一个简单的物理计算器XAML/C++应用程序编写,由ALE CalpTeNC和Andy Rich编写。它使用lambda并以与我的实现不同的方式管理等式变量。在编写自己的应用程序之前,我浏览了一下这个实现,这是欺骗,但实际上并不是因为我使用了这个应用程序来获得灵感,并从我自己的新手角度来编写它。代码包括一个调用每个版本的示例。

吃点爆米花!

  • 使用位来跟踪未知值:“这通常是有问题的,因为它设置了一个限制–不管您选择的整数中有多少位。当空间是在溢价,你不能击败位,但否则避免不必要的位旋转是一个好主意… L
  • 有关未知值的详细信息:“ 增强。可选 如果我没有避开外部库,Boost.Optional看起来是个不错的解决方案。作为奖励,不要再胡闹了。
  • 重新讨论位旋转:“但是,有一个折衷:boost::optional不能从位字段的压缩表示中获益,它可能会将等式对象的大小增加N字节,其中N是变量的数量。如果N<=64,我认为位域解决方案更可取,特别是因为无论如何,在编译时实现时必须知道变量的数量,因此可以很容易地使用模板来为N>64使用效率较低、更通用的代码。”
  • 使用术语“hash”来描述表示两个未知项的值(由 获取未知密钥 ):“这里使用“哈希”一词可能会让人困惑。”“hash”是一个表示力量的词,通常表示碰撞是可能的。“如果我是Humpty Dumpty,我可以说(用相当轻蔑的语气)“hash”这个词的意思正是我选择它的意思——既不多也不少”,但我不是。我修正了密码。
  • 担心是否会有例外的投掷约定:“幸运的是,有足够的约定,特别是在这一领域,不管你做什么,你都可能遵守某种约定。”我怀疑这在生命中是真实的,就像C++代码一样。
  • 为if语句的礼仪而烦恼:“[if]不是一个函数。我强烈建议在后面加个空格。不坏的人应该感到不好。我修好了。
  • 命名布尔函数:“返回bool但命名方式不使其返回值明显的函数是错误的。在这种情况下,不清楚真假是什么意思 计算 . 我把它改成了一个void函数,并依赖异常来传递计算失败。调用者必须跟踪设置了多少变量。
  • 我几乎不好意思把这个包括在内。我在提出一个例外时错误地使用了“抛出新的”一词:“危险!危险!错了!”扔掉新的“C++是假的”。

每一次都是一次学习经历。

当你学习C++时,你做了哪些小项目?你对以后的文章有什么建议吗?这个代码可以改进吗?你想写一篇“跳进C++”的文章吗?在通常的地方让我们知道- 脸谱网 , 推特 , ebattali@microsoft.com 或下面的评论。 计算未知.zip

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享