C#整形之间的转换问题

sunshmcsdn 2013-09-10 10:23:26
在msdn上short关键字的描述中有这样一段话:
short x = 32767;
在以上声明中,整数 32767 从 int 隐式转换为 short。 如果整数的长度超过了 short 存储位置的大小,则将产生编译错误。
后半句我理解,前半句“从 int 隐式转换为 short”却让人糊涂了,int怎么能隐式转换到short?后面还有这么一段话“不能将存储大小更大的非文本数值类型隐式转换为 short 类型”,int类型只能隐式转换到long 、float、double 或 decimal类型,这不是前后矛盾吗?还是中文翻译的问题?
我对整形变量的赋值一直不太理解,32767默认应该是int类型,赋值给short类型变量,应该发生强制转换才对,为什么这里说是隐式转换?请高手解答。
...全文
405 29 打赏 收藏 转发到动态 举报
写回复
用AI写文章
29 条回复
切换为时间正序
请发表友善的回复…
发表回复
threenewbee 2013-09-12
  • 打赏
  • 举报
回复
你应该说,都没有发生类型转换。 加L和不加是一样的。
sunshmcsdn 2013-09-12
  • 打赏
  • 举报
回复
引用 23 楼 caozhy 的回复:
隐式类型转换也好,显式也罢,都是在C#语法层面来说的。 和IL层面的转换毫无关系。 代码层面有转换,编译器可以优化掉,没有产生对应的IL转换指令。 代码层面没有转换,编译器也可以加上转换的IL指令。 你的问题就是,文档写错了,你被错误的文档误导了。在代码层面,没有任何的转换发生。 道理很简单,你没有写short = (short)32767;所以没有显式的转换。 如果你理解为隐式的转换,那么int不能转化给short。所以结论是既没有显式转换,也没有隐式转换。 IL不是代码语义的等价物,只是代码的执行结果的等价物。 这个东西,是一种思维方式,只能靠你理解,什么叫等价: 比如 a > b和a < b等价么? 显然在代码层面不等价。甚至它们的语义相反 但是如果你这么写: if (true || (a > b)) { ... } 和 if (true || (a < b)) { ... } (在理想编译器中)会产生一样的IL。也就是他们在特定的上下文下,它们的语义无关紧要。 我们看 short x = 32767; 如果你理解为32767是一个int,靠隐式转换变成了short,那么必须给C#语法增加一条不必要的规则: 当隐式转换出现在变量初始化的时候,允许将int隐式转换为short,在非变量初始化的时候,不允许这么做。 从编程语言设计的角度来说,这违反了最简规约的原则。 那么语法必然是将short x = 32767;理解为这没有任何转换发生,同时移除那条不必要的规则。
版主,我又尝试了这个 long l = 123; short s = 456; 这两句代码生成的IL如下: .locals init ([0] int64 l, [1] int16 s) IL_0000: nop IL_0001: ldc.i4.s 123 IL_0003: conv.i8 IL_0004: stloc.0 IL_0005: ldc.i4 0x1c8 IL_000a: stloc.1 long l = 123;发生了类型转换 short s = 456;没有转换,的确如你所说,可我怎么向学生解释呢?“整形变量的赋值不会发生类型转换”这样说吗?但为什么又允许对long类型加L后缀,如果说一个整数常量123没有类型,为什么又可以加L后缀,岂不是多此一举?
maysoft 2013-09-12
  • 打赏
  • 举报
回复
版主解释的好清楚。。。。学习。。。
sunshmcsdn 2013-09-12
  • 打赏
  • 举报
回复
但是赋值给long类型变量时,不是有这句吗?IL_0003: conv.i8,这不就是转换吗?
threenewbee 2013-09-11
  • 打赏
  • 举报
回复
引用 16 楼 sunshmcsdn 的回复:
还是有些糊涂,这么说吧,下面两句话有区别吗? long a = 123L; long b = 123;
看你怎么定义区别。 如果我写一个编译器,将它编译成不同的结果,那么这就是有区别的。 但是如果C#编译器对这两者产生的IL代码完全一样,你无法从生成的目标代码中获知,你当初是写的是前者还是后者,那么就是没有区别。
sunshmcsdn 2013-09-11
  • 打赏
  • 举报
回复
还是有些糊涂,这么说吧,下面两句话有区别吗? long a = 123L; long b = 123;
  • 打赏
  • 举报
回复
你可以试试
int a = 32788;
short b = (short)a;
  • 打赏
  • 举报
回复
引用 楼主 sunshmcsdn 的回复:
我对整形变量的赋值一直不太理解,32767默认应该是int类型,赋值给short类型变量,应该发生强制转换才对,为什么这里说是隐式转换?请高手解答。
short b = (short)a; 这才叫做强制转换。 而 short b = a; 这就是隐式转换。 这只是就事论事说语法,所谓强制还是隐式,看语法。
threenewbee 2013-09-11
  • 打赏
  • 举报
回复
隐式类型转换也好,显式也罢,都是在C#语法层面来说的。

和IL层面的转换毫无关系。

代码层面有转换,编译器可以优化掉,没有产生对应的IL转换指令。
代码层面没有转换,编译器也可以加上转换的IL指令。

你的问题就是,文档写错了,你被错误的文档误导了。在代码层面,没有任何的转换发生。
道理很简单,你没有写short = (short)32767;所以没有显式的转换。
如果你理解为隐式的转换,那么int不能转化给short。所以结论是既没有显式转换,也没有隐式转换。

IL不是代码语义的等价物,只是代码的执行结果的等价物。

这个东西,是一种思维方式,只能靠你理解,什么叫等价:
比如
a > b和a < b等价么?
显然在代码层面不等价。甚至它们的语义相反
但是如果你这么写:
if (true || (a > b))
{
...
}

if (true || (a < b))
{
...
}
(在理想编译器中)会产生一样的IL。也就是他们在特定的上下文下,它们的语义无关紧要。

我们看
short x = 32767;
如果你理解为32767是一个int,靠隐式转换变成了short,那么必须给C#语法增加一条不必要的规则:
当隐式转换出现在变量初始化的时候,允许将int隐式转换为short,在非变量初始化的时候,不允许这么做。

从编程语言设计的角度来说,这违反了最简规约的原则。

那么语法必然是将short x = 32767;理解为这没有任何转换发生,同时移除那条不必要的规则。
threenewbee 2013-09-11
  • 打赏
  • 举报
回复
引用 21 楼 sunshmcsdn 的回复:
ok,所以回到最初的问题,既然默认整数是int类型,赋值给long类型变量会发生类型转换,那么赋值给short类型自然也应该发生类型转换。而int范围比short范围大,不应该通过隐式转换完成,而应该通过显式(强制)转换完成才对。但为什么short x = 32767;不报错?
类型转换彼类型转换。 short x = 32767;就是将一个short的32767传给x,没有发生任何类型转换。 如果要我写这篇MSDN文档,应该这么写。 你之所以混乱,是你没有正确区分IL层面上的类型转换和C#语法上的类型转换。 你还是被那个文档误导了。估计写文档的人自己没有理解这两者的区别。如果你要根据编译器来理解语法,这个文档写起来就复杂了。 比如+运算符。 也许文档就一句话,将左边和右边的表达式相加作为整个表达式的结果。 但是从编译器的角度看,这个文档就得这么写了: 如果表达式的两边都是常量,那么+运算符构成的表达式表示一个左右两边之和的常量。 如果表达式右边是1,那么+运算符表示左边的变量自增1后作为表达式的值。 如果表达式的左右有0,那么这条语句表示什么操作也不做。 如果这个表达式的两边都是加法表达式,那么表示同时对两边相加再取结果。 …… 这个文档基本可以写无限长了。
sunshmcsdn 2013-09-11
  • 打赏
  • 举报
回复
ok,所以回到最初的问题,既然默认整数是int类型,赋值给long类型变量会发生类型转换,那么赋值给short类型自然也应该发生类型转换。而int范围比short范围大,不应该通过隐式转换完成,而应该通过显式(强制)转换完成才对。但为什么short x = 32767;不报错?
threenewbee 2013-09-11
  • 打赏
  • 举报
回复
IL_0001: ldc.i4.s 123 // int的123 IL_0003: conv.i8 //转化为long, conv表示convert,转换 IL_0004: stloc.0 IL_0005: ldc.i4.s 123 IL_0007: conv.i8 IL_0008: stloc.1 IL1 3 4和IL 5 7 8是一样的。 就表明默认一个整数就是int类型,被赋值给long类型变量时发生了类型转换。是的。
想名费脑 2013-09-11
  • 打赏
  • 举报
回复
short(int16)<int(32)<long(int64)、float、double
sunshmcsdn 2013-09-11
  • 打赏
  • 举报
回复
.locals init ([0] int64 a, [1] int64 b) IL_0000: nop IL_0001: ldc.i4.s 123 IL_0003: conv.i8 IL_0004: stloc.0 IL_0005: ldc.i4.s 123 IL_0007: conv.i8 IL_0008: stloc.1 这是生成的IL,我对IL不太熟悉,说的可能不对,请指正。里面有两个conv.i8,是否表明这两个数值都被转换成64位整数,然后才赋值给变量?如果是这样,就表明默认一个整数就是int类型,被赋值给long类型变量时发生了类型转换。
threenewbee 2013-09-10
  • 打赏
  • 举报
回复
并且如果能引导学生学会通过阅读IL了解编译器的意图,就更好了。这样他们可以抛开那些似是而非的论述,获得精确的认识。
threenewbee 2013-09-10
  • 打赏
  • 举报
回复
引用 13 楼 sunshmcsdn 的回复:
大概理解了,不过由于工作需要,我是教授编程语言的,学生会提这样的问题,该如何解释呢,我不能说这个无所谓,只看结果就好了。学生会认为我也不懂。用什么方式解释比较好呢。
msdn说的不对。你可以结合几段代码和产生的IL,讲清楚这个机制。
sunshmcsdn 2013-09-10
  • 打赏
  • 举报
回复
大概理解了,不过由于工作需要,我是教授编程语言的,学生会提这样的问题,该如何解释呢,我不能说这个无所谓,只看结果就好了。学生会认为我也不懂。用什么方式解释比较好呢。
threenewbee 2013-09-10
  • 打赏
  • 举报
回复
理论上说,你看到conv.i2,在IL层面上才真正有“转换”发生。至于别处,你可以说存在对结果没有任何影响的转换(被编译器优化移除了),也可以说根本没有转换,这两种说法都对。

另外,在机器代码层面,又未必所有的conv.i2都会产生一样的机器码。甚至在CPU内部,一样的机器码又未必转换为一样的微代码,在相同的电路上执行相同的操作……但是这一切是透明的。只要结果和你的设想预期,你就可以视作它是如此工作。
threenewbee 2013-09-10
  • 打赏
  • 举报
回复
或者这么说,“转换”有两种,一种是对你的输出有影响的,一种是没有影响的。后者对你来说就没有意义。 好比,对于代码: int i = 3 + 5; 一种编译器编译为 int i = 3; i = i + 5; 另一种编译器编译为 int i = 8; 还有一种编译器编译为 int 匿名变量 = 3; int i = 5; i = 匿名变量 + i; 这都没有关系。
threenewbee 2013-09-10
  • 打赏
  • 举报
回复
编译器是数学等价的艺术。 什么叫等价的艺术?就是说,你把它的实现当作一个黑盒,你的输入输出如果不变,具体怎么实现的是无所谓的。 C#内部尽量使用int32(因为int32对于x86处理器来说是效率最高的本机类型),但是保证你,如果你使用了short,那么它可以被复制到2字节的内存中,它遇到超过32767的值就溢出,它可以匹配short的函数重载参数……一切你认为会表现为short的,它都如此表现。至于内部,你说它转换了也好,没转换也罢,有什么区别呢?
加载更多回复(9)

110,537

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧