Golang关键字--type 类型定义

懒皮 · · 1281 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

参考Go关键字--type,原文除类型定义外,还介绍了type其它几种使用场合。本文只说类型定义。

type有如下几种用法:
1.定义结构体
2.定义接口
3.类型定义
4.类型别名
5.类型查询

一、类型定义--------节选自《Go语言圣经》第69页

为了说明类型声明,我们将不同温度单位分别定义为不同的类型:

// Package tempconv performs Celsius and Fahrenheit temperature computations.
package tempconv

import "fmt"

type Celsius float64 // 摄氏温度
type Fahrenheit float64 // 华氏温度

const (
    AbsoluteZeroC Celsius = -273.15 // 绝对零度
    FreezingC Celsius = 0 // 结冰点温度
    BoilingC Celsius = 100 // 沸水温度
)

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }

我们在这个包声明了两种类型:Celsius和Fahrenheit分别对应不同的温度单位。它们虽然有着相同的底层类型float64,但是它们是不同的数据类型,因此它们不可以被相互比较或混在一个表达式运算。刻意区分类型,可以避免一些像无意中使用不同单位的温度混合计算导致错误;因此需要一个类似Celsius(t)或Fahrenheit(t)形式的显式转型操作才能将float64转为对应的类型。Celsius(t)和Fahrenheit(t)是类型转换操作,它们并不是函数调用。类型转换不改变值本身,但是会使它们的语义发生变化。另一方面,CToF和FToC两个函数则是对不同温度单位下的温度进行换算,它们会返回不同的值。

对于每一个类型T,都有一个对应的类型转换操作T(x),用于将x转为T类型(译注:如果T是指针类型,可能会需要用小括弧包装T,比如 (*int)(0) )。只有当两个类型的底层基础类型相同时,才允许这种转型操作,或者是两者都是指向相同底层结构的指针类型,这些转换只改变类型而不会影响值本身。如果x是可以赋值给T类型的值,那么x必然也可以被转为T类型,但是一般没有这个必要。

数值类型之间的转型也是允许的,并且在字符串和一些特定类型的slice之间也是可以转换的,在下一章我们会看到这样的例子。这类转换可能改变值的表现。例如,将一个浮点数转为整数将丢弃小数部分,将一个字符串转为 []byte 类型的slice将拷贝一个字符串数据的副本。在任何情况下,运行时不会发生转换失败的错误(译注: 错误只会发生在编译阶段)。

底层数据类型决定了内部结构和表达方式,也决定是否可以像底层类型一样对内置运算符的支持。这意味着,Celsius和Fahrenheit类型的算术运算行为和底层的float64类型是一样的,正如我们所期望的那样。

fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C
boilingF := CToF(BoilingC)
fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F
fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch

比较运算符 == 和 < 也可以用来比较一个命名类型的变量和另一个有相同类型的变量,或有着相同底层类型的未命名类型的值之间做比较。但是如果两个值有着不同的类型,则不能直接进行比较:

var c Celsius
var f Fahrenheit
fmt.Println(c == 0) // "true"
fmt.Println(f >= 0) // "true"
fmt.Println(c == f) // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"!

注意最后那个语句。尽管看起来想函数调用,但是Celsius(f)是类型转换操作,它并不会改变值,仅仅是改变值的类型而已。测试为真的原因是因为c和g都是零值。

二、类型定义让代码更加简洁

使用类型定义定义出来的类型与原类型不相同,所以不能使用新类型变量赋值给原类型变量,除非使用强制类型转换。下面来看一段示例代码,根据string类型,定义一种新的类型,新类型名称是name:

type name string

为什么要使用类型定义呢?类型定义可以在原类型的基础上创造出新的类型,有些场合下可以使代码更加简洁,如下边示例代码:

package main

import (
    "fmt"
)

// 定义一个接收一个字符串类型参数的函数类型
type handle func(str string)

// exec函数,接收handle类型的参数
func exec(f handle) {
    f("hello")
}

func main() {
    // 定义一个函数类型变量,这个函数接收一个字符串类型的参数
    var p = func(str string) {
        fmt.Println("first", str)
    }
    exec(p)

    // 匿名函数作为参数直接传递给exec函数
    exec(func(str string) {
        fmt.Println("second", str)
    })
}

输出信息是:

first hello
second hello

上边的示例是类型定义的一种简单应用场合,如果不使用类型定义,那么想要实现上边示例中的功能,应该怎么书写这段代码呢?

// exec函数,接收handle类型的参数
func exec(f func(str string)) {
    f("hello")
}

exec函数中的参数类型,需要替换成func(str string)了,咋一看去也不复杂,但是假如exec接收一个需要5个参数的函数变量呢?是不是感觉参数列表就会很长了。

func exec(f func(str string, str2 string, num int, money float64, flag bool)) {
    f("hello")
}

从上边的代码可以发现,exec函数的参数列表可读性变差了。下边再来看看使用类型定义是怎么实现这个功能:

package main

import (
    "fmt"
)

// 定义一个需要五个参数的函数类型
type handle func(str string, str2 string, num int, money float64, flag bool)

// exec函数,接收handle类型的参数
func exec(f handle) {
    f("hello", "world", 10, 11.23, true)
}

func demo(str string, str2 string, num int, money float64, flag bool) {
    fmt.Println(str, str2, num, money, flag)
}

func main() {
    exec(demo)
}
三、详解 Go 语言中的 time.Duration 类型

在 Time 包中,定义有一个名为 Duration 的类型和一些辅助的常量:

type Duration int64

const (
    Nanosecond Duration = 1
    Microsecond = 1000 * Nanosecond
    Millisecond = 1000 * Microsecond
    Second = 1000 * Millisecond
    Minute = 60 * Second
    Hour = 60 * Minute
)

然后是测试代码:

func Test() {
    var waitFiveHundredMillisections int64 = 500

    startingTime := time.Now().UTC()
    time.Sleep(10 * time.Millisecond)
    endingTime := time.Now().UTC()

    var duration time.Duration = endingTime.Sub(startingTime)
    var durationAsInt64 = int64(duration)

    if durationAsInt64 >= waitFiveHundredMillisections {
        fmt.Printf("Time Elapsed : Wait[%d] Duration[%d]\n", 
        waitFiveHundredMillisections, durationAsInt64)
    } else {
        fmt.Printf("Time DID NOT Elapsed : Wait[%d] Duration[%d]\n",
        waitFiveHundredMillisections, durationAsInt64)
    }
}

运行了这段测试代码,然后得到了下面的输出,从输出内容来看,我定义的 500 毫秒的时间已经用完了,但怎么可能。

Time Elapsed : Wait[500] Duration[10724798]

从上面的知识很容易看出问题, Duration 类型中时间的基本单位是 Nanosecond ,所以当我将一个表示 10 毫秒的 Duration 类型对象转换为 int64 类型时,我实际上得到的是 10,000,000。

改成这样测试一下

func Test() {
    var waitFiveHundredMillisections time.Duration = 500 * time.Millisecond

    startingTime := time.Now().UTC()
    time.Sleep(600 * time.Millisecond)
    endingTime := time.Now().UTC()

    var duration time.Duration = endingTime.Sub(startingTime)

    if duration >= waitFiveHundredMillisections {
        fmt.Printf("Wait %v\nNative [%v]\nMilliseconds [%d]\nSeconds [%.3f]\n", 
        waitFiveHundredMillisections, duration, duration.Nanoseconds()/1e6, duration.Seconds())
    }
}

实际上, Duration 类型拥有一些便捷的类型转换函数,它们能将 Duration 类型转化为 Go 语言的内建类型 int64 或 float64 ,像下面这样:

func Test() {
    var duration_Seconds time.Duration = (1250 * 10) * time.Millisecond
    var duration_Minute time.Duration = 2 * time.Minute

    var float64_Seconds float64 = duration_Seconds.Seconds()
    var float64_Minutes float64 = duration_Minute.Minutes()

    fmt.Printf("Seconds [%.3f]\nMinutes [%.2f]\n", float64_Seconds, float64_Minutes)
}

我也迅速注意到了在时间转换函数中,并没有转换毫秒值的函数,使用 Seconds 和 Minutes 函数,我得到了如下输出:

Seconds [12.500]
Minutes [2.00]

但我需要转换毫秒值,为什么包里面单单没有提供毫秒值的转换呢?因为 Go 语言的设计者希望我有更多的选择,而不只是将毫秒值转换成某种单独的内建类型。下面的代码中,我将毫秒值转化为了 int64 类型和 float64 类型:

func Test() {
    var duration_Milliseconds time.Duration = 500 * time.Millisecond

    var castToInt64 int64 = duration_Milliseconds.Nanoseconds() / 1e6
    var castToFloat64 float64 = duration_Milliseconds.Seconds() * 1e3
    fmt.Printf("Duration [%v]\ncastToInt64 [%d]\ncastToFloat64 [%.0f]\n",
    duration_Milliseconds, castToInt64, castToFloat64)
}

我将纳秒值除以 1e6 得到了 int64 类型表示的毫秒值,将秒值乘以 1e3 ,我得到了 float64 类型表示的毫秒值,上面代码的输出如下:

Duration [500ms]
castToInt64 [500]
castToFloat64 [500]

参考一下内置包time中的源码,很容易理解:

func (d Duration) Seconds() float64 {
    sec := d / Second
    nsec := d % Second
    return float64(sec) + float64(nsec)/1e9
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:懒皮

查看原文:Golang关键字--type 类型定义

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1281 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传