我正在编写一个涉及极坐标和笛卡尔坐标的程序。
为每种类型的点创建两个不同的结构是否有意义,一个结构与X
和Y
成员,一个结构与R
和Theta
成员。
还是太多了,最好只有一个结构体,first
并second
作为成员。
我写的内容很简单,不会有太大变化。但是我很好奇从设计的角度来看哪个更好。
我认为第一种选择更好。似乎更具可读性,我将获得类型检查的好处。
我正在编写一个涉及极坐标和笛卡尔坐标的程序。
为每种类型的点创建两个不同的结构是否有意义,一个结构与X
和Y
成员,一个结构与R
和Theta
成员。
还是太多了,最好只有一个结构体,first
并second
作为成员。
我写的内容很简单,不会有太大变化。但是我很好奇从设计的角度来看哪个更好。
我认为第一种选择更好。似乎更具可读性,我将获得类型检查的好处。
Answers:
我已经看到了两种解决方案,所以它绝对取决于上下文。
为了提高可读性,建议使用多个结构非常有效。但是,在某些环境中,您想要对这些结构进行通用操作,并且发现自己正在复制代码,例如matrix * vector操作。当无法按照您的媒介风格进行特定操作时,它可能会令人沮丧,因为没有人将其移植到那里。
极端的解决方案(最终让我们采用)是使用带有功能get <0>()get <1>()和get <2>()的CRTP模板化基类,以通用方式获取元素。然后,在从该基类派生的笛卡尔或Polar结构中定义这些函数。它解决了所有问题,但代价却很可笑:必须学习模板元编程。但是,如果模板元编程对于您的项目已经是公平的游戏,那可能是一个很好的选择。
是的,这很有意义。
结构的价值不仅在于它以方便的名称封装了数据。其价值在于它将您的意图编入了代码,以便编译器可以帮助您验证您有一天没有违反它们(例如,将极坐标集误认为是笛卡尔坐标集)。
人们不善于记住这些琐碎的细节,但善于制定大胆,创新的计划。计算机擅长于细节,而坏于创意计划。因此,将尽可能多的详细维护工作转移到计算机上始终是一个好主意,这样您就可以全心全意地进行总体计划了。
是的,虽然笛卡尔坐标系和极坐标都(在它们的位置上)都是非常明智的坐标表示方案,但理想情况下,绝对不要将它们混合使用(如果您有一个笛卡尔坐标点{1,1},则它与极坐标{1,1有很大不同) }。
根据您的需求,它也可能是值得推行协调界面,类似的方法X()
,Y()
,Displacement()
和Angle()
(或可能Radius()
和Theta()
,取决于)。
最后,编程的目标是切换晶体管位以完成有用的工作。但是,以这样低的级别进行思考将导致难以控制的疯狂,这就是为什么存在高级编程语言来帮助您隐藏复杂性的原因。
如果仅使用成员名为first
和的成员来构建一个结构second
,那么这些名称就没有任何意义。您实际上会将它们视为内存地址。这违反了高级编程语言的目的。
此外,仅因为它们恰好都可以表示为a double
,并不意味着您可以互换使用它们。例如,θ是无量纲的角度,而y具有长度单位。 由于类型在逻辑上不可替换,因此它们应该是两个不兼容的结构。
如果您确实需要玩一些内存重用的技巧,而您几乎肯定不应该这样做,则可以union
在C语言中使用一个类型来明确您的意图。
首先,按照@ Kilian-foth的完全正确答案,明确地将两者都包含。
但是,我想补充一点:
问:当您将对算作成对时,您真的有对两者通用的操作double
吗?请注意,这与说您有一些适用于它们自己的术语的操作不同。例如,“ plot(Coord)”会关心Coord
极性是极坐标还是笛卡尔坐标。另一方面,坚持归档只是按原样对待数据。如果你真的有通用操作,考虑要么定义一个基类,或定义转换到std::pair<double, double>
或者tuple
或者你在你的语言都有。
此外,一种方法可能是将一种坐标类型视为更基本的类型,将另一种坐标类型仅视为对用户或外部交互的支持。
所以,你可以确保所有的基本操作,编码Cartesian
,然后将提供支持Polar
来Cartesian
。这样可以避免实现许多操作的不同版本。
根据语言以及您是否知道两个类都将具有相似的方法和操作,一个可能的解决方案是一次定义该类并使用类型别名,以便以不同的方式显式命名这些类型。
这还有一个优点,就是只要类完全相同,您就只能维护一个,但是一旦您要更改它们,就不需要使用它们来修改代码,因为类型已经使用过了明显地。
再次取决于类的用法(如果需要多态等)的另一种选择是对两种新类型都使用公共继承,因此它们与它们所代表的通用类型具有相同的公共接口。这也允许类型的单独演变。
我认为,在这种情况下,使用相同的成员名称是一个坏主意,因为这会使代码更容易出错。
想象一下场景:您有几个笛卡尔点:pntA和pntB。然后出于某种原因,您决定最好用极坐标表示它们,并更改声明和构造函数。
现在,如果您所有的操作只是方法调用,例如:
double distance = pntA.distanceFrom(pntB);
那你就好了 但是,如果您明确使用成员呢?相比
double leftMargin = abs(pntA.x - pntB.x);
double leftMargin = abs(pntA.first - pntB.first);
在第一种情况下,代码将无法编译。您将立即看到该错误,并将能够修复该错误。但是,如果您具有相同的成员名称,则错误将仅在逻辑级别上,很难检测到。
如果您使用非面向对象的语言编写,则将错误的struct传递给函数甚至更容易。是什么阻止您编写以下代码?
double distance = calculate_distance_polar(cartesianPointA, polarPointB);
另一方面,不同的数据类型将使您能够在编译期间发现错误。