具有相同成员但命名不同的两个结构,这是一个好主意吗?


49

我正在编写一个涉及极坐标和笛卡尔坐标的程序。

为每种类型的点创建两个不同的结构是否有意义,一个结构与XY成员,一个结构与RTheta成员。

还是太多了,最好只有一个结构体,firstsecond作为成员。

我写的内容很简单,不会有太大变化。但是我很好奇从设计的角度来看哪个更好。

我认为第一种选择更好。似乎更具可读性,我将获得类型检查的好处。


11
我总是为每个目的创建一个新的结构/类。需要一个3d向量,用三个浮点数创建一个结构3d_vector。需要一个uvw表示,用三个浮点数创建一个struct texture_coords。在3d环境中需要一个位置,创建一个具有三个浮点的结构位置。你明白了。与使用相同的东西相比,它提供了更好的可读性。如果您多次定义相同的东西而感到困扰,请使用基本的3浮点结构并将多个名称定义为相同的结构。
凯文

如果他们有一些通用的方法,那么也许是一种。您是否需要比较两者的相等性?
狗仔队2015年

8
@Sidney除非您绝对需要我不会提供的功能。您需要执行sin / arcsin操作以在两种表示形式之间进行转换。每次进行转换时,这都会在最低有效位中引入一点bitrot。我几乎可以肯定,您遇到的痛苦与尝试处理提供某种事件的发生频率和事件之间的时间间隔(x和1 / x)的类类似。跟踪类中的典型代表并处理所有四舍五入的麻烦并不是我想再做的事情。
Dan Neely 2015年

3
可以表示很多事情的数据类型的一个很好的例子是字符串,但是“字符串型”是反模式。写你的例子。尝试为支持两种坐标系的类型实现点积。
内森·库珀

1
您应该努力在适用的情况下使用不同的类型,以获得类型安全的好处-这将防止您向函数发送错误的类型/从函数发送错误的类型(使用编译时正确性检查,具体取决于您的编程语言)。这将简化维护,因为您可以找到某种类型的所有真实用法(而不会由于类型被合并而导致错误用法)。
Erik Eidt

Answers:


17

我已经看到了两种解决方案,所以它绝对取决于上下文。

为了提高可读性,建议使用多个结构非常有效。但是,在某些环境中,您想要对这些结构进行通用操作,并且发现自己正在复制代码,例如matrix * vector操作。当无法按照您的媒介风格进行特定操作时,它可能会令人沮丧,因为没有人将其移植到那里。

极端的解决方案(最终让我们采用)是使用带有功能get <0>()get <1>()和get <2>()的CRTP模板化基类,以通用方式获取元素。然后,在从该基类派生的笛卡尔或Polar结构中定义这些函数。它解决了所有问题,但代价却很可笑:必须学习模板元编程。但是,如果模板元编程对于您的项目已经是公平的游戏,那可能是一个很好的选择。


1
您的答案很有趣。有可能提供一个例子吗?
全能的骆驼Moha 2015年

1
我可以举一个我做​​过的例子:FIR过滤极,笛卡尔及其向量。数学非常相似,没有角度(无)包装,出于性能方面的原因,代码中的某些部分仍然重复,我们在相同的地方使用了模板。所有内容使用不同的名称。Cort的“极端解决方案”本可以保存一些重复项,但几乎不会全部重复。
Eugene Ryabtsev

1
我的第一个反应是,通过铸造可以更好地解决这种情况,但事实证明这样做有些冒险
200_success 2015年

114

是的,这很有意义。

结构的价值不仅在于它以方便的名称封装了数据。其价值在于它将您的意图编入了代码,以便编译器可以帮助您验证您有一天没有违反它们(例如,将极坐标集误认为是笛卡尔坐标集)。

人们不善于记住这些琐碎的细节,但善于制定大胆,创新的计划。计算机擅长于细节,而坏于创意计划。因此,将尽可能多的详细维护工作转移到计算机上始终是一个好主意,这样您就可以全心全意地进行总体计划了。


6
+1完美地描述了使用计算机来做计算机擅长的事情,并使您的大脑专注于自己的工作。
BrianH 2015年

8
“应该编写程序供人们阅读,而只能偶然使机器执行。” -摘自Abelson和Sussman的“计算机程序的结构和解释”。
hlovdal 2015年

18

是的,虽然笛卡尔坐标系和极坐标都(在它们的位置上)都是非常明智的坐标表示方案,但理想情况下,绝对不要将它们混合使用(如果您有一个笛卡尔坐标点{1,1},则它与极坐标{1,1有很大不同) }。

根据您的需求,它也可能是值得推行协调界面,类似的方法X()Y()Displacement()Angle()(或可能Radius()Theta(),取决于)。


如果OP从这些结构中进行分类,则更为重要,因为对笛卡尔坐标和极坐标的操作不同。
Mindwin 2015年

1
最后一段+1,这是理想的解决方案。点是空间就是物体;点就是空间。这一点的内部表示形式无关紧要。当然,现实世界中的顾虑(性能,舍入错误)可能会导致问题。这完全取决于它的用途。
BlueRaja-Danny Pflughoeft 2015年

而且,对于此示例,它不太可能更改,但是如果它们是另外两个类,则没有任何内容告诉您它们可能在某些时候有所不同。
染料

8

最后,编程的目标是切换晶体管位以完成有用的工作。但是,以这样低的级别进行思考将导致难以控制的疯狂,这就是为什么存在高级编程语言来帮助您隐藏复杂性的原因。

如果仅使用成员名为first和的成员来构建一个结构second,那么这些名称就没有任何意义。您实际上会将它们视为内存地址。这违反了高级编程语言的目的。

此外,仅因为它们恰好都可以表示为a double,并不意味着您可以互换使用它们。例如,θ是无量纲的角度,而y具有长度单位。 由于类型在逻辑上不可替换,因此它们应该是两个不兼容的结构。

如果您确实需要玩一些内存重用的技巧,而您几乎肯定不应该这样做,则可以union在C语言中使用一个类型来明确您的意图。


最佳答案,恕我直言
Dean Radcliffe

2

首先,按照@ Kilian-foth的完全正确答案,明确地将两者都包含。

但是,我想补充一点:

问:当您将对算作成对时,您真的有对两者通用的操作double吗?请注意,这与说您有一些适用于它们自己的术语的操作不同。例如,“ plot(Coord)”会关心Coord极性是极坐标还是笛卡尔坐标。另一方面,坚持归档只是按原样对待数据。如果你真的有通用操作,考虑要么定义一个基类,或定义转换到std::pair<double, double>或者tuple或者你在你的语言都有。

此外,一种方法可能是将一种坐标类型视为更基本的类型,将另一种坐标类型仅视为对用户或外部交互的支持。

所以,你可以确保所有的基本操作,编码Cartesian,然后将提供支持PolarCartesian。这样可以避免实现许多操作的不同版本。


1

根据语言以及您是否知道两个类都将具有相似的方法和操作,一个可能的解决方案是一次定义该类并使用类型别名,以便以不同的方式显式命名这些类型。

这还有一个优点,就是只要类完全相同,您就只能维护一个,但是一旦您要更改它们,就不需要使用它们来修改代码,因为类型已经使用过了明显地。

再次取决于类的用法(如果需要多态等)的另一种选择是对两种新类型都使用公共继承,因此它们与它们所代表的通用类型具有相同的公共接口。这也允许类型的单独演变。


两个类中成员的名称不同。实际上,名字是两类之间的唯一区别
全能骆驼Moha

@ Mhd.Tahawi您可以使用适当的名称实现getter和setter,以确保您使用的类是相同的,但要为要使用的操作提供适当的名称。它变得更加冗长,但是您将需要减少重复的代码。
Svalorzen 2015年

0

我认为,在这种情况下,使用相同的成员名称是一个坏主意,因为这会使代码更容易出错。

想象一下场景:您有几个笛卡尔点: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);

另一方面,不同的数据类型将使您能够在编译期间发现错误。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.