包(Package)
精密计算和 big 包
整数的高精度计算 Go 语言中提供了 big 包
其中包含了 math 包:有用来表示大整数的 big.Int
和表示大有理数的 big.Rat
类型(可以表示为 2/5 或 3.1416 这样的分数,而不是无理数或 π)
这些类型可以实现任意位类型的数字,只要内存足够大。缺点是更大的内存和处理开销使它们使用起来要比内置的数字类型慢很多
大的整型数字通过 big.NewInt(n)
来构造
大有理数通过 big.NewRat(N,D)
方法构造
所有大数字类型都有像是 Add()
和 Mul()
这样的方法实现
类似 Java 中的 bigDecimal
自定义包和可见性
包名使用短小不含有下划线_
的小写单词命名
即使程序与包处于同一路径下,也不建议使用./pack
来进行引入
主程序利用的包必须在主程序编译之前被编译
当使用.
来做为包的别名时,可以不通过包名来使用其中的项目
import _ "pack"
:pack 包只导入其副作用,也就是说,只执行它的 init 函数并初始化其中的全局变量
同一源码文件中可包含多个init
函数,执行是无序的
init
函数是不能被调用的
导入的包在包自身初始化前被初始化,而一个包在程序执行中只能初始化一次
结构体与方法
结构体
type identifier struct {
field1 type1
field2 type2
...
}
如果字段在代码中从来也不会被用到,那么可以命名它为 _
结构体的字段可以是任何类型,包括结构体本身、函数或者接口
使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:
var t *T = new(T)
var t *T
t = new(T)
// 更加常用
t := new(T)
// 结构体字段的值是它们所属类型的零值
声明 var t T
也会给 t
分配内存,并零值化内存,但是这个时候 t
是类型 T
在这两种方式中,t
通常被称做类型 T 的一个实例(instance)或对象(object)
表达式 new(T)
和 &T{}
是等价的,都是对应类型的指针
表达式var t T
与T{}
是等价的,都是类型 T
结构体类型实例和一个指向它的指针的内存布局:
也可以通过解指针的方式来设置值
pers := new(Person) // 指针类型
pers.firstName = "Chris"
pers.lastName = "Woodward"
(*pers).lastName = "Woodward" // 这是合法的
Go 语言中,结构体和它所包含的数据在内存中是以连续块
的形式存在的,即使结构体中嵌套有其他的结构体
(性能很牛逼)
按惯例,工厂的名字以 new 或 New 开头
如果想知道结构体类型 T 的一个实例占用了多少内存,可以使用:size := unsafe.Sizeof(T{})
结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记
标签的内容不可以在一般的编程中使用,只有包 reflect
能获取它
type TagType {
f1 bool "An import answer"
f2 string "The name of the thing"
f3 int "How much there are"
}
func refTag(tt TagType, ix int) {
ttType := reflect.TypeOf(tt)
// ix 是索引
ixField := ttType.Field(ix)
// 获取标签内容
fmt.Printf("%v\n", ixField.Tag)
}
结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有必须的字段类型,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体
在一个结构体中对于每一种数据类型只能有一个匿名字段
在 Go 语言中,相比较于继承,组合更受青睐
type innerS struct {
in1 int
}
type outerS struct {
b int
c float32
int // 匿名字段
innerS // 匿名字段
}
func main() {
outer := new(outerS)
// 初始化设值
// 类型名称就是字段名称
fmt.Printf("outer.int is: %d\n", outer.int)
fmt.Printf("outer.innerS is: %d\n", outer.innerS)
// 获取内嵌结构体的属性值
fmt.Printf("outer.in1 is: %d\n", outer.in1)
fmt.Printf("outer.innerS.in1 is: %d\n", outer.innerS.in1)
}
当两个字段拥有相同的名字(可能是继承来的名字)时该怎么办呢?
- 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式;
- 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发一个错误(不使用没关系)。(没有办法来解决这种问题引起的二义性,必须由开发者自己修正)
方法
Go 方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数
接收者类型可以是(几乎)任何类型,不仅仅是结构体类型(函数、int、string、bool等);
接收者一定不能是接口
接收者不能是一个指针类型,但它可以是任何其他允许类型的指针
一个类型加上它的方法等价于面向对象中的一个类
在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:必须在同一个包下
// 通过类型别名实现跨包类的方法定义
type myTime struct {
time.Time // anonymous field
}
func (t myTime) first3Chars() string {
return t.Time.String()[0:3]
}
// 如果继续在 myTime 类型上定义与 time.Time 中相同名称的方法,会实现覆盖
m := myTime{time.Now()}
fmt.Println("Full time now:", m.String()) // 调用原始类的方法
fmt.Println("First 3 chars:", m.first3Chars()) // 调用自定义方法
方法不允许重载
但如果基于接收者类型,可以重载
type T1 struct {}
func (t *T1) me() {}
// 方法名如果不绑定接收者,是不允许重载的
// 方法绑定了接收者,允许重载
type T2 struct {}
func (t *T2) me() {}
别名类型不能有它原始类型上已经定义过的方法
// 方法定义
// 如果 recv 一个指针,Go 会自动解引用
func [(recv receiver_type)] methodName(parameter_list) (return_value_list) { ... }
// 如果方法不需要使用 recv 的值,可以用 _ 替换它
func [(_ receiver_type)] methodName(parameter_list) (return_value_list) { ... }
鉴于性能的原因,recv
最常见的是一个指向 receiver_type 的指针
对于类型 T,如果在 *T 上存在方法 Meth()
,并且 t
是这个类型的变量,那么 t.Meth()
会被自动转换为 (&t).Meth()
指针方法和值方法都可以在指针或非指针上被调用
setter & getter 方法:setter 方法使用 Set 前缀,对于 getter 方法只使用成员名
当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这在效果上等同于外层类型 继承 了这些方法:将父类型放在子类型中来实现亚型
和内嵌类型方法具有同样名字的外层类型的方法会覆写内嵌类型对应的方法
// 相当于实现了多重继承
type Child struct {
Father
Monther
}
主要有两种方法来实现在类型中嵌入功能:
A:聚合(或组合):包含一个所需功能类型的具名字段。
B:内嵌:内嵌(匿名地)所需功能类型
在类型中嵌入所有必要的父类型,可以很简单的实现多重继承
在 Go 中,代码复用通过组合和委托实现,多态通过接口的使用来实现:有时这也叫 组件编程(Component Programming)
当广泛使用一个自定义类型时,最好为它定义 String()
方法
不要在 String()
方法里面调用涉及 String()
方法的方法(各种*print*
都算) – 无限递归