通常我会尽可能避免转换类型,因为我认为这是不良的编码实践,并且可能会导致性能下降。
但是,如果有人要我解释为什么会这样,我可能会像头灯中的鹿一样看着它们。
那么,为什么/何时铸造不好?
它对于Java,C#,C ++是通用的,还是每个不同的运行时环境都按照自己的方式处理?
欢迎使用任何语言的细节,例如为什么在c ++中不好?
通常我会尽可能避免转换类型,因为我认为这是不良的编码实践,并且可能会导致性能下降。
但是,如果有人要我解释为什么会这样,我可能会像头灯中的鹿一样看着它们。
那么,为什么/何时铸造不好?
它对于Java,C#,C ++是通用的,还是每个不同的运行时环境都按照自己的方式处理?
欢迎使用任何语言的细节,例如为什么在c ++中不好?
Answers:
您已经用三种语言标记了这三种语言,答案在三种语言之间确实有很大的不同。对C ++的讨论或多或少也意味着对C转换的讨论,这(或多或少)给出了第四个答案。
由于这是您没有明确提及的内容,因此我将从C开始。C强制转换存在许多问题。一个是他们可以做很多不同的事情。在某些情况下,强制转换只不过告诉编译器:“闭嘴,我知道我在做什么”,即,即使在进行可能会导致问题的转换时,它也可以确保编译器不会警告您这些潜在的问题。仅举例来说char a=(char)123456;
。定义此实现的确切结果(取决于的大小和签名)char
),并且除非在非常奇怪的情况下使用,否则可能没有用。C强制转换也有所不同,它们是仅在编译时发生的事情(即,您只是告诉编译器如何解释/处理某些数据)还是在运行时发生的事情(例如,从double到的实际转换)长)。
C ++尝试通过添加许多“新”强制转换运算符来至少在某种程度上解决该问题,每个运算符仅限于C强制转换功能的一部分。这使得(例如)意外地执行您确实不想要的转换变得更加困难-如果仅打算在对象上放弃constness,则可以使用const_cast
,并确保它唯一会影响的是一个目的是const
,volatile
或不是。相反,static_cast
不允许a 影响对象是否为const
或。volatile
。简而言之,您具有大多数相同类型的功能,但是将它们归类,因此一个类型转换通常只能执行一种转换,而单个C样式类型转换可以在一个操作中进行两次或三个转换。主要的例外是,至少在某些情况下,您可以使用a dynamic_cast
代替a static_cast
,尽管它被写为a dynamic_cast
,但最终还是以a结束static_cast
。例如,您可以使用它dynamic_cast
来遍历一个类层次结构,但是强制“向上”转换层次结构始终是安全的,因此可以静态完成,而强制“向下”转换层次结构不一定是安全的,因此动态完成。
Java和C#彼此更为相似。尤其是,两者都(实际上是?)强制转换始终是运行时操作。就C ++强制转换运算符而言,就dynamic_cast
实际完成情况而言,它通常最接近-例如,当您尝试将对象强制转换为某种目标类型时,编译器会插入运行时检查以查看是否允许该转换,如果不是,则抛出异常。确切的细节(例如,用于“错误的强制转换”异常的名称)有所不同,但是基本原理仍然基本相似(尽管如果有内存服务,Java的确会将强制转换应用于少数非对象类型,例如int
更接近C的类型)。强制转换-但是很少使用这些类型,以至于1)我不确定这一点,以及2)即使它是真的,也没关系。
从更一般的角度来看,情况非常简单(至少是IMO):强制转换(显然足够)意味着您正在将某种东西从一种类型转换为另一种类型。当/如果您这样做,则会引发“为什么?”的问题。如果您确实希望某种东西是特定类型,为什么不将其定义为开头的那个类型呢?这并不是说没有理由进行这样的转换,但是在任何时候,它都会提示您是否可以重新设计代码,以便在整个过程中使用正确的类型。甚至看似无害的转换(例如,在整数和浮点之间)也应比通常更仔细地检查。尽管他们看起来相似,整数应真正用于“计数”类型的事物,而浮点数应用于“已度量”类型的事物。无视这种区别是导致一些疯狂言论的原因,例如“美国家庭平均有1.8个孩子”。即使我们都能看到这种情况,但事实是没有一个家庭有1.8个孩子。他们可能有1个,或者可能是2个,或者可能更多,但从来没有1.8个。
这里有很多好的答案。这是我的查看方式(从C#角度来看)。
投射通常意味着以下两种情况之一:
我知道此表达式的运行时类型,但编译器不知道。编译器,我告诉您,在运行时,与此表达式相对应的对象实际上就是这种类型的。到目前为止,您已经知道该表达式将被视为这种类型。生成假定对象为给定类型的代码,或者,如果我错了,则引发异常。
编译器和开发人员都知道表达式的运行时类型。在运行时,此表达式将具有与该类型的值关联的另一种不同类型的值。生成从给定类型的值中生成所需类型的值的代码;如果您不能这样做,则抛出异常。
请注意,这些是相反的。有两种类型的演员!在某些类型转换中,您向编译器提供了有关现实的提示 -嘿,这种类型的对象实际上是客户类型-在某些类型中,您告诉编译器执行从一种类型到另一种类型的映射 -嘿,我需要对应于此double的int。
两种类型的转换都是危险信号。第一种类型转换提出了一个问题:“为什么开发人员确切知道编译器不知道的东西?” 如果您处在这种情况下,那么更好的选择通常是更改程序,以使编译器确实掌握实际情况。然后,您不需要演员表;分析在编译时完成。
第二种类型转换引发了一个问题:“为什么首先不对目标数据类型进行操作?” 如果您需要以整数为单位的结果,那么为什么首先要保持双精度?您不应该持有int吗?
这里还有一些其他想法:
http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/
Foo
继承自的对象Bar
,并将其存储在中List<Bar>
,则需要转换时需要强制转换Foo
。也许这表明在体系结构级别存在问题(为什么我们要存储Bar
s而不是Foo
s?),但不一定。并且,如果该对象Foo
也具有有效的强制转换为int
,则它还会处理您的其他注释:您存储的是Foo
,而不是int
,因为an int
并不总是合适的。
Foo
在a中没有问题List<Bar>
,但是在转换时,您正在尝试对a做Foo
不适合的事情Bar
。这意味着通过虚拟方法提供的内置多态性以外的机制来实现子类型的不同行为。也许这是正确的解决方案,但更多时候是一个危险信号。
Tuple<X,Y,...>
元组语法支持,C#中就不可能广泛使用它。这是语言可以更好地将人们推向“成功之坑”的地方。
在Java中,转换错误始终被报告为运行时错误。使用泛型或模板化会将这些错误转换为编译时错误,从而使您更容易发现错误。
正如我上面所说。这并不是说所有转换都是不好的。但是,如果有可能避免,则最好这样做。
投射本身并没有什么坏处,只是它经常被误用作实现某些事情的手段,这些东西要么根本不应该做,要么更优雅地做。
如果它普遍不好,语言将不支持它。像任何其他语言功能一样,它也有自己的位置。
我的建议是专注于您的主要语言,并了解其所有类型和相关的最佳实践。那应该将短途旅行引入其他语言。
相关的C#文档在这里。
在这里的上一个SO问题上,有关于C ++选项的精彩摘要。
Java,c#和c ++是强类型语言,尽管强类型语言可以被视为不灵活的语言,但它们具有在编译时进行类型检查的优势,并可以防止因某些操作的类型错误而导致运行时错误。
基本上有两种类型的类型转换:更通用类型的类型转换或其他类型(更具体的类型)的类型转换。强制转换为更通用的类型(强制转换为父类型)将使编译时间检查保持不变。但是强制转换为其他类型(更具体的类型)将禁用编译时类型检查,并将由编译器替换为运行时检查。这意味着您不太确定自己编译的代码将正确运行。由于额外的运行时类型检查(Java API已执行强制转换!),因此它对性能的影响也可以忽略不计。
dynamic_cast
它通常比慢static_cast
,因为它通常必须执行运行时类型检查(有一些警告:转换为基本类型很便宜,等等)。
某些类型的铸造非常安全高效,以至于甚至根本不认为是铸造。
如果将类型从派生类型转换为基本类型,则通常很便宜(通常-取决于语言,实现和其他因素-成本为零)并且安全。
如果您从一个简单的类型(如int)转换为较宽的类型(如long int),那么它通常又很便宜(通常比分配与该类型相同的类型更便宜),并且再次是安全的。
其他类型则更令人烦恼和/或更昂贵。在大多数语言中,从基本类型强制转换为派生类型要么便宜,但极有可能发生严重错误(在C ++中,如果您将static_cast从基本类型转换为派生类型,将很便宜,但是如果基础值不是派生类型,则行为是不确定的,可能非常奇怪)或相对昂贵,并且有引发异常的风险(在C ++中为dynamic_cast,在C#中为显式的从基类转换为派生类,等等)。用Java和C#装箱是这种方法的另一个例子,并且花费更大(考虑到它们的变化远不只是基础值的处理方式而已)。
其他类型的强制转换会丢失信息(从长整数类型到短整数类型)。
这些风险(无论是例外情况还是更严重的错误)和费用的情况都是避免投放的原因。
一个更具概念性,但也许更重要的原因是,每种转换情况都阻碍了您对代码正确性进行推理的能力:每种情况都是另一个可能出错的地方以及出错的方式。可能会出错,这增加了推论整个系统是否会出错的复杂性。即使每次都证明演员表是安全的,证明这一点也是推理的一部分。
最后,大量使用强制类型转换可能表示在创建对象模型,使用模型或同时使用对象模型时未能充分考虑对象模型:经常在相同的几种类型之间来回投射几乎总是导致无法考虑类型之间的关系用过的。在这里,演员表的坏坏不仅仅在于它们,它是坏事的征兆。
程序员越来越倾向于遵循关于语言功能使用的教条式规则(“从不使用XXX!”,“ XXX被认为有害”等),其中XXX的范围从goto
s到protected
数据成员的指针,从单例到传递对象,值。
按照我的经验,遵循这样的规则可以确保两件事:您将不会是一个糟糕的程序员,也不会是一个优秀的程序员。
更好的方法是挖掘并发现这些全面禁止背后的真相,然后明智地使用这些功能,同时要了解在许多情况下它们是完成工作的最佳工具。
“我通常尽量避免使用类型转换”是此类概括规则的一个很好的例子。在许多常见情况下,强制转换至关重要。一些例子:
typedef
s时)。(例如:GLfloat
<-> double
<-> Real
。)std::size_type
-> int
。)当然,在很多情况下,不适合使用演员表,同样重要的是要学习它们。由于上面的答案已经很好地指出了其中的一些内容,因此我不会赘述。
为了详细说明KDeveloper的答案,它本质上不是类型安全的。使用强制转换不能保证您要转换的对象和强制转换的对象将匹配,并且如果发生这种情况,您将获得运行时异常,这总是一件坏事。
对于C#特别注意,因为它包含is
和as
运算符,所以您有机会(在大多数情况下)确定强制转换是否成功。因此,您应该采取适当的步骤来确定该操作是否成功并正确进行。
在使用C#的情况下,由于在处理值类型时涉及装箱/拆箱的开销,因此在转换时需要格外小心。
如果满足两个条件,则仅将对象转换为某种类型:
这意味着并不是您拥有的所有信息都能在您使用的类型结构中很好地表示出来。这很不好,因为您的实现应在语义上包含您的模型,而在这种情况下显然不是。
现在,当您进行投射时,这可能有两个不同的原因:
在大多数语言中,您经常遇到第二种情况。Java中的泛型对C ++模板系统有更多帮助,但很难掌握,即使那样,某些事情可能还是不可能的,或者不值得付出。
因此,您可以说,强制转换是一种肮脏的技巧,可以规避您的问题,以某种特定的语言表达某种特定的类型关系。应避免脏乱。但是,没有他们,你永远活不下去。
通常,模板(或泛型)比类型转换更安全。在这方面,我要说的是类型转换的问题是类型安全。但是,还有另一个更微妙的问题,尤其是与垂头丧气有关:设计。至少从我的角度来看,垂头丧气是一种代码味道,表明我的设计可能有问题,我应该进一步研究。为什么这么简单:如果您“正确地”获得了抽象,就根本不需要它!顺便问一下好问题...
干杯!