当前位置: 首页 > 知识库问答 >
问题:

创建常量类型并限制类型的值

施自怡
2023-03-14

我有一个关于常量类型的问题,这些常量被限制在特定的值,以及在GO中如何实现这些。假设我创建了一个类型unary,它有两个常量值正(1)负(-1),我希望限制该类型(unary)的用户创建unary类型的其他值。是否通过创建包并使值为正为负可见,并使类型为一元仅限于包含包来实现这一点?例如,请参见下面的代码

package unary

type unary int////not visible outside of the package unary

const (
    Positive unary = 1//visible outside of the package unary
    Negative unary = -1//visible outside of the package unary
)

func (u unary) String() string {//visible outside of the package unary
    if u == Positive {
        return "+"
    }
    return "-"
}

func (u unary) CalExpr() int {//visible outside of the package unary
    if u == Positive {
        return 1
    }
    return -1
}

这是将类型限制为某些常量值的正确方法吗?

共有1个答案

元阳荣
2023-03-14

您提出的解决方案并不是以您希望的方式安全的。可以使用非类型化整数常量创建一元的新值,其int值与1-1不同。请参见此示例:

p := unary.Positive
fmt.Printf("%v %d\n", p, p)

p = 3
fmt.Printf("%v %d\n", p, p)

产出将是:

+ 1
- 3

我们可以更改p的值来存储int3,该值显然不等于。这是可能的,因为spec:可分配性:

在以下任何情况下,值X可赋值给T类型的变量(“X可赋值给T”):

  • ...
  • x是一个非类型化常量,可由t类型的值表示。

3是一个非类型化常量,它可以由unary类型的值表示,该值具有int的基础类型。

由于上面提到的原因,在Go中,您不能使用“局外人”包不能为其创建新值的“安全”常量。因为如果您想在包中声明常量,您只能使用具有“非类型化”版本的表达式--其他包在赋值中也可能使用这些表达式(就像我们的示例中一样)。

如果要实现“安全”部分,可以使用未导出的struct,但不能在常量声明中使用它们。

示例:

type unary struct {
    val int
}

var (
    Positive = unary{1}
    Negative = unary{-1}
)

func (u unary) String() string {
    if u == Positive {
        return "+"
    }
    return "-"
}

func (u unary) CalExpr() int {
    return u.val
}

试图更改其值:

p := unary.Positive

p.val = 3 // Error: p.val undefined (cannot refer to unexported field or method val)

p = unary.unary{3} // Error: cannot refer to unexported name unary.unary
// Also error: implicit assignment of unexported field 'val' in unary.unary literal

请注意,由于我们现在使用的是struct,我们可以通过将值的struct表示形式添加到struct,进一步简化代码:

type unary struct {
    val int
    str string
}

var (
    Positive = unary{1, "+"}
    Negative = unary{-1, "-"}
)

func (u unary) String() string { return u.str }

func (u unary) CalExpr() int { return u.val }
unary.Positive = unary.Negative
var (
    positive = unary{1}
    negative = unary{-1}
)

func Positive() unary { return positive }

func Negative() unary { return negative }

然后获取/使用这些值:

p := unary.Positive()

如果您打算为“常数”使用接口类型,则必须小心。在Kaveh Shahbazian的回答中可以看到一个例子。一个未导出的方法用于阻止其他人实现接口,给你一种其他人真的不能实现的错觉:

type Unary interface {
    fmt.Stringer
    CalExpr() int
    disabler() // implementing this interface outside this package is disabled
}

var (
    Positive Unary = unary(1)  // visible outside of the package unary
    Negative Unary = unary(-1) // visible outside of the package unary
)

type unary int // not visible outside of the package unary

func (u unary) disabler() {}

func (u unary) String() string { /* ... */ }

func (u unary) CalExpr() int { /* ... */ }

然而,情况并非如此。用一个肮脏的把戏,这是可以规避的。可以嵌入导出的一元类型,并且可以使用现有的值来实现接口(以及未导出的方法),我们可以添加导出方法的自己的实现,执行/返回我们想要的任何操作。

下面是它的样子:

type MyUn struct {
    unary.Unary
}

func (m MyUn) String() string { return "/" }

func (m MyUn) CalExpr() int { return 3 }

测试它:

p := unary.Positive
fmt.Printf("%v %d\n", p, p)

p = MyUn{p}
fmt.Printf("%v %d\n", p, p.CalExpr())

产出:

+ 1
/ 3

正如Volker在他的评论中提到的,在您的特殊情况下,您可以使用

type unary bool

const (
    Positive unary = true
    Negative unary = false
)

作为类型,bool有两个可能的值:truefalse,我们都使用了。因此,没有其他值可以被“利用”来创建我们常量类型的其他值。

 类似资料:
  • 问题内容: 我有一个关于将常量类型限制为某些值的问题,以及如何在Go中完成该操作。说我创建类型,其具有两个常数值和与我想限制该类型(用户从创建类型的其它值)。我是否可以通过创建一个程序包并使其值和可见性以及将类型限制为包含的程序包来实现此目的?例如,请参见下面的代码 这是将类型限制为某些常量值的正确方法吗? 问题答案: 缺陷 提议的解决方案以您想要的方式并不安全。可以使用无类型的整数常量来创建新值

  • 问题内容: 说,我有如下通用类型 我想让它只接受特定的类型(字符串/整数/双精度)。我知道有界通配符,但在这里对我没有帮助。在setVar()中,如果类型不是Integer / String等,我可以检查并引发异常。这是最好的方法吗? 在对此类型进行操作时,我遇到同样的问题。根据类型,我想执行不同的操作。继承和有界通配符似乎通常是解决此类问题的方法,但它们是原始包装器。 问题答案: 使用继承: P

  • \Vtiful\Kernel\Chart Class Const 常量 图表类型 CHART_NONE 无 CHART_AREA 面积图 CHART_AREA_STACKED 面积图 - 堆积 CHART_AREA_STACKED_PERCENT 面积图 - 堆积百分比 CHART_BAR 条形图 CHART_BAR_STACKED 条形图 - 堆积 CHART_BAR_STACKED_PERC

  • const TYPE_STRING = 0x01; // 字符串 const TYPE_INT = 0x02; // 整型 const TYPE_DOUBLE = 0x04; // 浮点型 const TYPE_TIMESTAMP = 0x08; // 时间戳,可以将 xlsx 文件中的格式化时间字符转为时间戳

  • 问题内容: 我在MySQL中使用ENUM数据类型,想重用它,但不重新输入值。MySQL中是否有等效于C,C ++定义类型的方法? 我要执行以下操作: 这可能吗? 谢谢 问题答案: 号MySQL不支持或为,例如,PostgreSQL的呢。 您可能必须再次输入所有名称。您可以使用复制和粘贴或SQL脚本来减轻执行此操作所需的工作。 您还可以使用这些表来获取ENUM定义的文本,然后将其内插到新的语句中。

  • Rust 有两种常量,可以在任意作用域声明,包括全局作用域。这两种常量都要显式地标注: const: 不可改变的值(常用类型)。 static: 在 'static 生命周期内可能发生改变的变量。 有个特例就是 "string" 原始类型。可以给它直接赋一个不可改变的 static 变量,是因为它的 类型标记:&'static str 包含了生命周期 'static。其他的引用类型都必须特别注明从