Golang关键字--type 类型定义

栏目: Go · 发布时间: 5年前

内容简介:参考type有如下几种用法:1.定义结构体

参考 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
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法新解

算法新解

刘新宇 / 人民邮电出版社 / 2016-12-1 / CNY 99.00

本书分4 部分,同时用函数式和传统方法介绍主要的基本算法和数据结构。数据结构部分包括二叉树、红黑树、AVL 树、Trie、Patricia、后缀树、B 树、二叉堆、二项式堆、斐波那契堆、配对堆、队列、序列等;基本算法部分包括各种排序算法、序列搜索算法、字符串匹配算法(KMP 等)、深度优先与广度优先搜索算法、贪心算法以及动态规划。 本书适合软件开发人员、编程和算法爱好者,以及高校学生阅读参考......一起来看看 《算法新解》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具