iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Golang学习之无类型常量详解
  • 914
分享到

Golang学习之无类型常量详解

Golang无类型常量Golang 常量 2023-03-20 14:03:14 914人浏览 八月长安
摘要

目录什么是无类型常量无类型常量的特性默认的隐式类型类型自动匹配无类型常量带来的便利无类型常量的坑总结因为虽然名字很陌生,但我们每天都在用,每天都有无数潜在的坑被埋下。包括我本人也犯过

因为虽然名字很陌生,但我们每天都在用,每天都有无数潜在的坑被埋下。包括我本人也犯过同样的错误,当时代码已经合并并发布了,当我意识到出了什么问题的时候为时已晚,最后不得不多了个合并请求留下了丢人的黑历史。

为什么我要提这种尘封往事呢,因为最近有朋友遇到了一样的问题,于是勾起了上面的那些“美好”回忆。于是我决定记录一下,一来备忘,二来帮大家避坑。

由于涉及各种隐私,朋友提问的代码没法放出来,但我可以给一个简单的复现代码,正如我所说,这个问题是很常见的:

package main
 
import "fmt"
 
type S string
 
const (
    A S = "a"
    B   = "b"
    C   = "c"
)
 
func output(s S) {
    fmt.Println(s)
}
 
func main() {
    output(A)
    output(B)
    output(C)
}

这段代码能正常编译并运行,能有什么问题?这里我就要提示你一下了,B和C的类型是什么?

你会说他们都是S类型,那你就犯了第一个错误,我们用发射看看:

fmt.Println(reflect.TypeOf(any(A)))
fmt.Println(reflect.TypeOf(any(B)))
fmt.Println(reflect.TypeOf(any(C)))

输出是:

main.S
string
string

惊不惊喜意不意外,常量的类型是由等号右边的值推导出来的(iota是例外,但只能处理整型相关的),除非你显式指定了类型。

所以在这里B和C都是string。

那真正的问题来了,正如我在这篇所说的,从原类型新定义的类型是独立的类型,不能隐式转换和赋值给原类型。

所以这样的代码就是错的:

func output(s S) {
    fmt.Println(s)
}
 
func main() {
    var a S = "a" 
    output(a)
}

编译器会报错。然而我们最开始的复现代码是没有报错的:

const (
    A S = "a"
    B   = "b"
    C   = "c"
)
 
func output(s S) {
    fmt.Println(s)
}

output函数只接受S类型的值,但我们的B和C都是string类型的,为什么这里可以编译通过还正常运行了呢?

这就要说到golang的坑点之一——无类型常量了。

什么是无类型常量

这个好理解,定义常量时没指定类型,那就是无类型常量,比如:

const (
    A S = "a"
    B   = "b"
    C   = "c"
)

这里A显式指定了类型,所以不是无类型常量;而B和C没有显式指定类型,所以就是无类型常量(untyped constant)。

无类型常量的特性

无类型常量有一些特性和其他有类型的常量以及变量不一样,得单独讲讲。

默认的隐式类型

正如下面的代码里我们看到的:

const (
    A = "a"
    B = 1
    C = 1.0
)
 
func main() {
    fmt.Println(reflect.TypeOf(any(A))) // string
    fmt.Println(reflect.TypeOf(any(B))) // int
    fmt.Println(reflect.TypeOf(any(C))) // float64
}

虽说我们没给这些常量指定某个类型,但他们还是有自己的类型,和初始化他们的字面量的默认类型相应,比如整数字面量是int,字符串字面量是string等等。

但只有一种情况下他们才会表现出自己的默认类型,也就是在上下文中没法推断出这个常量现在应该是什么类型的时候,比如赋值给空接口。

类型自动匹配

这个名字不好,是我根据它的表现起的,官方的名字叫Representability,直译过来是“代表性”。

看下这个例子:

const delta = 1 // untyped constant, default type is int
var num int64
num += delta

如果我们把const换成var,代码无法编译,会爆出这种错误:invalid operation: num + delta (mismatched types int64 and int)。

但为什么常量可以呢?这就是Representability或者说类型自动匹配在捣鬼。

按照官方的解释:如果一个无类型常量的值是一个类型T的有效值,那么这个常量的类型就可以是类型T。

举个例子,int8类型的所有合法的值是[-128, 127),那么只要值在这个范围内的整数常量,都可以被转换成int8。

字符串类型同理,所有用字符串初始化的无类型常量都可以转换成字符串以及那些基于字符串创建的新类型。

这就解释了开头那段代码为什么没问题:

type S string
 
const (
    A S = "a"
    B   = "b"
    C   = "c"
)
 
func output(s S) {
    fmt.Println(s)
}
 
func main() {
    output(A) // A 本来就是 S,自然没问题
    output(B) // B 是无类型常量,默认类型string,可以表示成 S,没问题
    output(C) // C 是无类型常量,默认类型string,可以表示成 S,没问题
    // 下面的是有问题的,因为类型自动匹配不会发生在无类型常量和字面量以外的地方
    // s := "string"
    // output(s)
}

也就是说,在有明确给出类型的上下文里,无类型常量会尝试去匹配那个目标类型T,如果常量的值符合目标类型的要求,常量的类型就会变成目标类型T。例子里的delta的类型就会自动变成int64类型。

我没有去找为什么Golang会这么设计,在c++、rust和Java里常量的类型就是从初始化表达式推导或显式指定的那个类型。

一个猜测是golang的设计初衷想让常量的行为表现和字面量一样。除了两者都有的类型自动匹配,另一个有力证据是golang里能作为常量的只有那些能做字面类型的类型(字符串、整数、浮点数、复数)。

无类型常量的类型自动匹配会带来很有限的好处,以及很恶心的坑。

无类型常量带来的便利

便利只有一个,可以少些几次类型转换,考虑下面的例子:

const factor = 2
 
var result int64 = int64(num) * factor / ( (a + b + c) / factor )

这样复杂的计算表达式在数据分析和图像处理的代码里是很常见的,如果我们没有自动类型匹配,那么就需要显式转换factor的类型,光是想想就觉得烦人,所以我也就不写显式类型转换的例子了。

有了无类型常量,这种表达式的书写就没那么折磨了。

无类型常量的坑

说完聊胜于无的好处,下面来看看坑。

一种常见的在golang中模拟enum的方法如下:

type ConfigType string
 
const (
    CONFIG_XML ConfigType = "XML"
    CONFIG_JSON = "jsON"
)

发现上面的问题了吗,没错,只有CONFIG_XML是ConfigType类型的!

但因为无类型常量有自动类型匹配,所以你的代码目前为止运行起来一点问题也没有,这也导致你没发现这个缺陷,直到:

// 给enum加个方法,现在要能获取常量的名字,以及他们在配置数组里的index
type ConfigType string
 
func (c ConfigType) Name() string {
    switch c {
    case CONFIG_XML:
        return "XML"
    case CONFIG_JSON:
        return "JSON"
    }
    return "invalid"
}
 
func (c ConfigType) Index() int {
    switch c {
    case CONFIG_XML:
        return 0
    case CONFIG_JSON:
        return 1
    }
    return -1
}

目前为止一切安好,然后代码炸了:

fmt.Println(CONFIG_XML.Name())
fmt.Println(CONFIG_JSON.Name()) // !!! error

编译器不乐意,它说:CONFIG_JSON.Name undefined (type untyped string has no field or method Name)。

为什么呢,因为上下文里没明确指定类型,fmt.Println的参数要求都是any,所以这里用了无类型常量的默认类型。当然在其他地方也一样,CONFIG_JSON.Name()这个表达式是无法推断出CONFIG_JSON要匹配成什么类型的。

这一切只是因为你少写了一个类型。

这还只是第一个坑,实际上因为只要是目标类型可以接受的值,就可以赋值给目标类型,那么出现这种代码也不奇怪:

const NET_ERR_MESSAGE = "site is unreachable"
 
func doWithConfigType(t ConfigType)
 
doWithConfigType(CONFIG_JSON)
doWithConfigType(NET_ERR_MESSAGE) // WTF???

一不小心就能把错得离谱的参数传进去,如果你没想到这点而做好防御的话,生产事故就理你不远了。

第一个坑还可以通过把常量定义写全每个都加上类型来避免,第二个就只能靠防御式编程凑活了。

看到这里,你也应该猜到我当年闯的是什么祸了。好在及时发现,最后补全声明 + 防御式编程在出事故前把问题解决了。

最后也许有人会问,golang实现enum这么折磨?没有别的办法了吗?

当然有,而且有不少,其中一个比较著名的是stringer: https://pkg.go.dev/golang.org/x/tools/cmd/stringer

这个工具也只能解决一部分问题,但以及比什么都做不了要强太多了。

总结

无类型常量会自动转换到匹配的类型,这会带来意想不到的麻烦。

一点建议:

  • 如果可以的话,尽量在定义常量时给出类型,尤其是你自定义的类型,int这种看情况可以不写
  • 尝试用工具去生成enum,一定要自己写过过瘾的话记得处理必然存在的例外情况。

这就是golang的大道至简,简单它自己,坑都留给你。

以上就是Golang学习之无类型常量详解的详细内容,更多关于Golang无类型常量的资料请关注编程网其它相关文章!

您可能感兴趣的文档:

--结束END--

本文标题: Golang学习之无类型常量详解

本文链接: https://www.lsjlt.com/news/200555.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Golang学习之无类型常量详解
    目录什么是无类型常量无类型常量的特性默认的隐式类型类型自动匹配无类型常量带来的便利无类型常量的坑总结因为虽然名字很陌生,但我们每天都在用,每天都有无数潜在的坑被埋下。包括我本人也犯过...
    99+
    2023-03-20
    Golang无类型常量 Golang 常量
  • python学习之变量类型
    变量:  变量是保存在内存中的值,根据变量类型开辟不同的内存空间且只允许符合该数据类型的数据才可以被存储在该内存空间中变量赋值:在Python中定义变量时,无需像其他语言一样需要声明数据类型。每个变量在内存中创建的时候都会包含变量的标识、名...
    99+
    2023-01-30
    变量 类型 python
  • Golang有类型常量和无类型常量的区别
    场景 在 Go 语言中,常量分为有类型常量和无类型常量。 // 有类型常量 const VERSION string = "v1.0.0" // 无类型常量 const RELEA...
    99+
    2023-05-14
    Golang 有类型常量 无类型常量
  • Golang无类型常量问题怎么解决
    今天小编给大家分享一下Golang无类型常量问题怎么解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。这个问题是很常见的:p...
    99+
    2023-07-05
  • Golang有类型常量和无类型常量的区别是什么
    本篇内容主要讲解“Golang有类型常量和无类型常量的区别是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Golang有类型常量和无类型常量的区别是什么”吧!场景在 Go 语言中,常量分为有...
    99+
    2023-07-05
  • golang学习之详解“不定参数”
    Golang是一门功能强大的编程语言,它在语法结构上比其他语言更加简单直接,这特别适合于处理大量数据和解决高并发问题。其中一个高级功能就是不定参数,也称为可变参数,这使得函数可以接受不确定数量的参数,进而提高程序的灵活性和可重用性。一、不定...
    99+
    2023-05-14
    Golang go语言 不定参数类型
  • Python学习之异常处理详解
    目录什么是异常与异常处理异常的语法捕获通用异常捕获具体异常如何捕获多个异常捕获多个异常 - 方法1捕获多个异常 - 方法2本章节主要学习 python 中的异常处理,来看一下该章节的...
    99+
    2024-04-02
  • Java基础类学习之String详解
    目录1 String不可变性2 不可变的好处3 String+和StringBuilder效率差异4 String, StringBuffer and StringBuilder5 ...
    99+
    2022-12-27
    Java String类 Java String
  • C++学习之异常机制详解
    目录1. 异常处理机制介绍2. 如何抛出异常和捕获异常2.1 抛出异常2.2 捕获异常3. 如何实现自己的异常4. 注意事项5. 面试常问的题目6. 答案7. 总结1. 异常处理机制...
    99+
    2023-05-15
    C++异常机制 C++异常
  • Python学习之异常断言详解
    该章节我们来学习 异常的最后一个知识点 - 断言 ,断言是判断一个表达式,在表达式为 False 的时候触发异常。表达式我们可以对号入座,可以是条件语句中的声明,也可以是是 whil...
    99+
    2024-04-02
  • Java异常学习之自定义异常详解
    前言哎呀,妈呀,又出异常了!俗话说:“代码虐我千百遍,我待代码如初恋”。小Alan最近一直在忙着工作,已经很久没有写写东西来加深自己的理解了,今天来跟大家聊聊Java异常。Java异常的体系什么的,理论知识啥的我就懒得去BB太多了,是个搞J...
    99+
    2023-05-31
    java 自定义异常 ava
  • Python学习之自定义异常详解
    目录自定义抛出异常关键字 - raise演示小案例 - 1演示小案例 - 2自定义异常类总结在上一章我们学习了 异常的三个关键字,分别是try、except 以及 finally。我...
    99+
    2024-04-02
  • 人工智能学习Pytorch张量数据类型示例详解
    目录1.python 和 pytorch的数据类型区别2.张量①一维张量②二维张量③3维张量④4维张量1.python 和 pytorch的数据类型区别 在PyTorch中无法展示...
    99+
    2024-04-02
  • TypeScript学习笔记之类型缩小
    目录类型缩小什么是类型缩小呢?常见的类型保护有如下几种:typeof平等缩小instanceofin总结类型缩小 什么是类型缩小呢? 类型缩小的英文是 Type Narrowing;...
    99+
    2024-04-02
  • C++学习笔记之类与对象详解
    目录前言:1.访问限定符:【问题】C++中 struct和class的区别是什么?2.封装【问题】在类和对象的阶段,我们只研究类的封装特性,那什么是封装呢?3.类的定义与声明【问题】...
    99+
    2024-04-02
  • Golang学习之反射机制的用法详解
    目录介绍TypeOf() ValueOf()获取接口变量信息事先知道原有类型的时候事先不知道原有类型的时候介绍 反射的本质就是在程序运行的时候,获取对象的类型信息和内存结构,反射是把...
    99+
    2024-04-02
  • Python学习手册之数据类型
     在上一篇文章中,我们介绍了 Python 的异常和文件,现在我们介绍 Python 中的数据类型。 查看上一篇文章请点击:https://www.cnblogs.com/dustman/p/9979931.html 数据类型None 类...
    99+
    2023-01-30
    数据类型 手册 Python
  • java学习之JVM运行时常量池理解
    运行时常量池 运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息时常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将...
    99+
    2024-04-02
  • PHP数据类型详解:学习笔记分享!
    PHP数据类型详解:学习笔记分享! 在PHP编程语言中,数据类型是非常重要的一个概念。数据类型决定了变量可以存储哪些类型的数据,以及可以对这些数据进行哪些操作。本篇文章将对PHP中的常见数据类型进行详细的讲解,并通过演示代码的形式,帮助读者...
    99+
    2023-07-09
    数据类型 学习笔记 http
  • 深入学习Golang并发编程必备利器之sync.Cond类型
    目录1. sync.Cond 的基本概念1.1 条件变量1.2 互斥锁1.3 条件变量的实现原理2. sync.Cond 的基本用法2.1 创建 sync.Cond 对象2.2 等待...
    99+
    2023-05-18
    Golang sync.Cond原理 Golang sync.Cond使用 Golang sync.Cond
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作