[Go]程序结构——类型

来源:互联网 发布:微信数据备份 编辑:程序博客网 时间:2024/05/22 15:44

变量或表达式的类型定义了对应存储值得属性特征,例如 数值类型在内存的存储大小(或者是元素的bit个数),它们在内部是如何表达的,是否支持一些操作符,以及它们自己关联的方法集等。

在任何程序中都会存在一些变量有着相同的内部结构,但是却表示完全不同的概念。例如,一个int类型的变量可以用来表示 循环的索引、或者一个时间戳、或者一个文件描述符、或者一个月份;一个float64类型的变量可以用来表示移动速度、或者是温度;一个字符串可以用来表示一个密码、或者是一个颜色的名称。

一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。新命名的类型提供了一个方法,用来分隔不同概念的类型,这样即使它们底层类型相同也是不兼容的

type 类型名字 底层类型
类型声明语句一般出现在包级别,因此如果新创建的类型名字的首字母大写,则外部可见。

译注:对于中文汉字,Unicode标志都作为小写字母处理,因此中文的命名默认是不能导出的(即包外不可见);不过国内的用户针对该问题提出了不同的看法,根据RobPike的回复,在Go2中有可能会将中日韩等字符当做大写字母处理。下面是RobPike在Issue 763的回复:

A solution that's been kicking around for a while:

For Go 2 (can't do it before then): Change the definition to “lower case letters and are package-local; all else is exported”. Then with non-cased languages, such as Japanese, we can write 日本语 for an exported name and 日本语 for a local name. This rule has no effect, relative to the Go 1 rule, with cased languages. They behave exactly the same.

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

gopl.io/ch2/tempconv0

// Package tempconv performs Celsius and Fahrenheit temperature computations.package tempconvimport "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))。只有当两个类型的底层基础类型相同时,或者两者都是指向相同底层结构的指针类型,才允许类型转换。这种转换只改变类型而不会影响值本身。

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

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

fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °CboilingF := CToF(BoilingC)fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °Ffmt.Printf("%g\n", boilingF-FreezingC)       // compile error: type mismatch
比较运算符==和<也可以用来比较一个命名类型的变量和另一个有相同类型的变量,或有着相同底层类型的未命名类型的值之间作比较。但是如果两个值有着不同的类型,则不能直接进行比较:

var c Celsiusvar f Fahrenheitfmt.Println(c == 0)          // "true"fmt.Println(f >= 0)          // "true"fmt.Println(c == f)          // compile error: type mismatchfmt.Println(c == Celsius(f)) // "true"!
注意最后那句。尽管看起来像函数调用,但是这是类型转换操作,它并不会改变值,仅仅改变值得类型而已。

一个命名的类型提供书写方便,特别是可以避免一遍又一遍地书写复杂类型(译注:例如用 匿名的结构体定义变量)。虽然对于像float64这种简单的底层类型没有简介多少,但是如果是复杂类型,则会简介很多。

命名类型还可以为该类型的值定义新的行为。这些行为表示为一组关联到该类型的函数集合(类似与C#中的扩展方法),我们称为类型的方法集。

下面的声明语句,Celsius类型的参数c 出现在了函数名的前面,表明声明的是Celsius类型的一个名叫String的方法,该方法返回Celsius类型对象c的°C单位温度的字符串。

func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
许多类型都会定义一个String方法,因为当使用fmt包的打印方法时,将会优先使用该类型对应的String方法返回的结果。

c := FToC(212.0)fmt.Println(c.String()) // "100°C"fmt.Printf("%v\n", c)   // "100°C"; no need to call String explicitlyfmt.Printf("%s\n", c)   // "100°C"fmt.Println(c)          // "100°C"fmt.Printf("%g\n", c)   // "100"; does not call Stringfmt.Println(float64(c)) // "100"; does not call String

原创粉丝点击