当前位置: 首页 > 面试题库 >

Go编译器对常量表达式和其他表达式的评估是否有所不同

沃威
2023-03-14
问题内容

为什么下面的代码无法编译?

package main

import (
    "fmt"
    "unsafe"
)

var x int = 1

const (
    ONE     int = 1
    MIN_INT int = ONE << (unsafe.Sizeof(x)*8 - 1)
)

func main() {
    fmt.Println(MIN_INT)

}

我得到一个错误

main.go:12:常量2147483648溢出int

以上说法是正确的。是的,2147483648溢出int(在32位体系结构中)。但是移位操作将导致负值,即-2147483648。

但是相同的代码也可以工作,如果我将常量更改为变量并获得了预期的输出。

package main

import (
    "fmt"
    "unsafe"
)

var x int = 1

var (
    ONE     int = 1
    MIN_INT int = ONE << (unsafe.Sizeof(x)*8 - 1)
)

func main() {
    fmt.Println(MIN_INT)

}

问题答案:

在常量和非常量表达式之间的评估存在差异,这是因为常量是精确的:

数字常数表示任意精度的精确值, 并且不会溢出

输入的常量表达式不会溢出;如果结果不能用其类型表示,则为编译时错误(可以在编译时检测到)。

同一件事不适用于非常量表达式,因为在编译时无法检测到(只能在运行时检测到)。对变量的操作可能会溢出。

在第一个示例中, ONE是类型为type的类型常量int。这个常量表达式:

ONE << (unsafe.Sizeof(x)*8 - 1)

是一个常数移位表达式,适用于:Spec:常数表达式:

如果常量移位表达式的左操作数是未类型化的常量,则结果为整数常量;否则,结果为整数。
否则,它是与左操作数相同类型的常量,该常量必须是
整数类型

因此,shift表达式的结果必须适合,int因为这是一个常量表达式。但是由于没有,这是一个编译时错误。

在第二个示例 ONE中,它不是常量,而是type的变量int。因此,此处的移位表达式可能会并且将溢出,从而导致预期的负值。

笔记:

如果您ONE在第二个示例中将其更改为常量而不是变量,则会得到相同的错误(因为初始化器中的表达式将是常量表达式)。如果ONE在第一个示例中更改为变量,则该变量将不起作用,因为变量不能在常量表达式中使用(它必须是常量表达式,因为它会初始化常量)。

查找最小最大值的常量表达式

您可以使用以下解决方案,得出uintint类型的最大值和最小值:

const (
    MaxUint = ^uint(0)
    MinUint = 0
    MaxInt  = int(MaxUint >> 1)
    MinInt  = -MaxInt - 1
)

func main() {
    fmt.Printf("uint: %d..%d\n", MinUint, MaxUint)
    fmt.Printf("int: %d..%d\n", MinInt, MaxInt)
}

输出(在Go Playground上尝试):

uint: 0..4294967295
int: -2147483648..2147483647

其背后的逻辑在于Spec:常量表达式:

一元按位补数运算符^所使用的掩码与非常数规则匹配:对于无符号常量,掩码均为1,对于有符号和无类型常量,掩码均为-1。

因此,类型化常量表达式^uint(0)是类型,uint并且是的最大值uint:它的所有位都设置为1。假设整数使用2的补码表示:将其向左移动,1将获得max的值int,其中min
int值为-MaxInt - 1-1由于该0值)。

推理不同的行为

为什么常量表达式没有溢出,非常量表达式没有溢出?

后者很简单:在大多数其他(编程)语言中,都有溢出。因此,这种行为与其他语言一致,并且具有其优势。

真正的问题是第一个:为什么常量表达式不允许溢出?

Go中的常量不仅仅是类型化变量的值:它们表示 任意精度的精确值 。如果您要为一个 类型化的 常量分配一个值,那么请保持“ 精确
”一词,允许溢出并分配一个完全不同的值实际上并不能达到 精确

展望未来,这种类型的检查和禁止溢出可能会捕获如下错误:

type Char byte
var c1 Char = 'a' // OK
var c2 Char = '世' // Compile-time error: constant 19990 overflows Char

这里会发生什么?c1 Char = 'a'之所以有效,'a'是因为它是一个rune常量,并且rune是的别名int32,并且'a'具有97适合byte的有效范围(即0..255)的数值。

但是会c2 Char = '世'导致编译时错误,因为符文的'世'数值19990不适合byte。如果允许溢出,您的代码将编译并分配22数值('\x16'),c2但显然这不是您的意图。通过禁止溢出,可以很容易地在编译时捕获此错误。

要验证结果:

var c1 Char = 'a'
fmt.Printf("%d %q %c\n", c1, c1, c1)

// var c2 Char = '世' // Compile-time error: constant 19990 overflows Char
r := '世'
var c2 Char = Char(r)
fmt.Printf("%d %q %c\n", c2, c2, c2)

输出(在Go Playground上尝试):

97 'a' a
22 '\x16'


 类似资料:
  • 问题内容: 我有一个在Tomcat 5.5上运行的JSP页面。我有以下代码: 我得到的输出是: 我无法弄清楚为什么forEach循环有效,但输出无效。任何人可能提供的任何帮助都将是巨大的。 问题答案: 我知道默认情况下应该启用它,但是我一次又一次地跨页面(甚至是更改行为的同一页面)运行EL处理不会发生的页面。将以下内容添加到任何此类页面的顶部应可解决此问题: 我将它添加到每个页面中,因为它不会造成

  • 问题内容: 什么是实现将采用字符串并根据运算符优先级输出结果的python程序的最佳方法(例如:“ 4 + 3 * 5”将输出19)。我在谷歌上寻找解决这个问题的方法,但是它们都太复杂了,我正在寻找一个(相对)简单的方法。 澄清:我需要比eval()稍微先进的东西-我希望能够添加其他运算符(例如,最大运算符-4 $ 2 = 4),或者,我对此在学术上比对专业更感兴趣-我想知道 该怎么 做。 问题答

  • 本文向大家介绍评估后缀表达式,包括了评估后缀表达式的使用技巧和注意事项,需要的朋友参考一下 为了求解数学表达式,我们需要前缀或后缀形式。将中缀转换为后缀后,我们需要后缀评估算法来找到正确的答案。 在这里,我们还必须使用堆栈数据结构来解决后缀表达式。 从后缀表达式中,找到一些操作数后,将它们压入堆栈。找到某个运算符后,将从堆栈中弹出两个项目,并按正确的顺序执行操作。之后,结果也被压入堆栈中以备将来使

  • 我在服务类中编写了一个函数,在该函数中,我评估了从控制器传递的参数,但Grails返回了错误的评估结果。 所有这些调试语句按预期打印为null。现在 两个语句在返回true时都应该返回false,这是怎么回事?有什么想法吗? 我正在使用grails 2.4.2

  • 问题内容: 在以下示例中: test.html test1.html script1.js 发生fn testFn执行4次。我希望在控制台中只能看到2条日志。即使,即使我删除 有2个日志,而不仅仅是一个。我怎么了 更新: angular.js test.html 安慰 问题答案: 更新: 在对视图表达式进行插值并运行摘要循环时,Angular会至少对每个表达式重新运行/插值至少两次,以便在渲染之前

  • 本文向大家介绍Python用eval评估表达式,包括了Python用eval评估表达式的使用技巧和注意事项,需要的朋友参考一下 示例