个人go语言学习 | 青训营笔记

56 阅读6分钟

package main

import (
"fmt"
"image/color"
"math"
)

//在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。

type Myclass struct {
a, b int
str string
}

func (m Myclass) Print() { //m是方法的接收器
fmt.Println(m.a, m.str, m.b) //我们可以任意的选择接收器的名字
}

//p.Distance的表达式叫做选择器

var Ma Myclass = Myclass{1, 2, "hello"}

//Ma.Print()
//每种类型都有其方法的命名空间

// Path是一个命名的slice类型,而不是Point那样的struct类型,然而我们依然可以为它定义方法
// Go语言里,我们为一些简单的数值、字符串、slice、map来定义一些附加行为
type Myint int

var Mi Myint = Myint(2)

func (m Myint) print() {
fmt.Println(m)
}

//基于指针对象的方法
//当调用一个函数时,会对其每一个参数值进行拷贝,如果一个函数需要更新一个变量,或者函数的其中一个参数实在太大我们希望能够避免进行这种默认的拷贝,这种情况下我们就需要用到指针了。

type p int

func (q *p) print() {
fmt.Println(*q)
}

//只有类型(Point)和指向他们的指针(*Point),才可能是出现在接收器声明里的两种接收器。
//如果一个类型名本身是一个指针的话,是不允许其出现在接收器中

//编译器会将指针和本身隐式转换,所以用指针也能调用结构体方法,结构体也能调用指针方法
//差别
//
//如果命名类型T(译注:用type xxx定义的类型)的所有方法都是用T类型自己来做接收器(而不是*T),那么拷贝这种类型的实例就是安全的;
//调用他的任何一个方法也就会产生一个值的拷贝//拷贝构造?

//但是如果一个方法使用指针作为接收器,你需要避免对其进行拷贝
//进行了拷贝,那么可能会引起原始对象和拷贝对象只是别名而已,实际上它们指向的对象是一样的。
//指向同一块内存

// Nil也是一个合法的接收器类型
//就像一些函数允许nil指针作为参数一样,方法理论上也可以用nil指针作为其接收器,尤其当nil对于对象来说是合法的零值时
//注释中指出nil变量代表的意义
//nil的字面量编译器无法判断其准确类型
//使用 类名(nil)进行转换,将nil转换为类型的0值

// 通过嵌入结构体来扩展类型
type point struct {
a, b int
}

type colorpoint struct {
point //匿名字段
Color color.RGBA
}
type colorpoint2 struct {
p point
Color color.RGBA
}

// 将point对象命名
type colorpoint3 struct {
point
color.RGBA //多个匿名字段
} //这种类型的值便会拥有Point和RGBA类型的所有方法
//使用如果选择器有二义性的话编译器会报错

// Point这个类型嵌入到ColoredPoint
func (p point) print() {
fmt.Println(p.a, p.b)
}

// 可以直接认为通过嵌入的字段就是ColoredPoint自身的字段,而完全不需要在调用时指出Point,
func (cp colorpoint) print() {
cp.print() //调用point方法
fmt.Println(cp.Color)
} //直接使用cp而不是point来使用point变量
//Point类的方法也被引入了ColoredPoint

//方法只能在命名类型(像Point)或者指向类型的指针上定义
//有些时候我们给匿名struct类型来定义方法

// 方法值和方法表达式
// 我们经常选择一个方法,并且在同一个表达式里执行,比如常见的p.Distance()形式,实际上将其分成两步来执行也是可能的。
// p.Distance叫作“选择器”
// 选择器会返回一个方法“值”->一个将方法(Point.Distance)绑定到特定接收器变量的函数
// 这个函数可以不通过指定其接收器即可被调用
// 调用时不需要指定接收器(译注:因为已经在前文中指定过了
// 只要传入函数的参数即可:
func (p point) Diatance(q point) {
t := (p.a-q.a)(p.a-q.a) + (p.b-q.b)(p.b-q.b)
a := float64(t)
fmt.Println(math.Sqrt(a))
}
func cmtest() {
p := point{1, 1}
q := point{2, 2}
fun := p.Diatance
fun(q)
}

//在一个包的API需要一个函数值、且调用方希望操作的是某一个绑定了对象的方法的话,方法“值”会非常实用

// 方法表达式
// 当调用一个方法时,与调用一个普通的函数相比,我们必须要用选择器(p.Distance)语法来指定方法的接收器。
// 当T是一个类型时,方法表达式可能会写作T.f或者(*T).f,会返回一个函数“值”,这种函数会将其第一个参数用作接收器
// 不写选择器)的方式来对其进行调用:
func cmtest2() {
distance := point.Diatance //方法表达式
p := point{1, 1}
q := point{2, 2}
distance(p, q) //必须指出接收器p以及参数q
}

//当你根据一个变量来决定调用同一个类型的哪个函数时,方法表达式就显得很有用了。你可以根据选择来调用接收器各不相同的方法。
//// 译注:这个Distance实际上是指定了Point对象为接收器的一个方法func (p Point) Distance(),
//// 但通过Point.Distance得到的函数需要比实际的Distance方法多一个参数,
//// 即其需要用第一个额外参数指定接收器,后面排列Distance方法的参数。

// Bit数组
// 一个bit数组通常会用一个无符号数或者称之为“字”的slice来表示,
// 每一个元素的每一位都表示集合里的一个值。当集合的第i位被设置时,我们才说这个集合包含元素i。
// An IntSet is a set of small non-negative integers.
// Its zero value represents the empty set.
type IntSet struct {
words []uint64
} //无符号数或者称之为“字”的slice来表示

// Has reports whether the set contains the non-negative value x.
func (s *IntSet) Has(x int) bool {
word, bit := x/64, uint(x%64)
return word < len(s.words) && s.words[word]&(1<<bit) != 0
}

// Add adds the non-negative value x to the set.
func (s *IntSet) Add(x int) {
word, bit := x/64, uint(x%64)
for word >= len(s.words) {
s.words = append(s.words, 0)
}
s.words[word] |= 1 << bit
}

// UnionWith sets s to the union of s and t.
func (s *IntSet) UnionWith(t *IntSet) {
for i, tword := range t.words {
if i < len(s.words) {
s.words[i] |= tword
} else {
s.words = append(s.words, tword)
}
}
}

//封装
//一个对象的变量或者方法如果对调用方是不可见的话,一般就被定义为“封装”。封装有时候也被叫做信息隐藏,同时也是面向对象编程最关键的一个方面。
//Go语言只有一种控制可见性的手段:大写首字母的标识符会从定义它们的包中被导出,小写字母的则不会。这种限制包内成员的方式同样适用于struct或者一个类型的方法。因而如果我们想要封装一个对象,我们必须将其定义为一个struct。
//这种基于名字的手段使得在语言中最小的封装单元是package
//一个struct类型的字段对同一个包的所有代码都有可见性,无论你的代码是写在一个函数还是一个方法里。
//优点
//因为调用方不能直接修改对象的变量值,其只需要关注少量的语句并且只要弄懂少量变量的可能的值即可
//隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现,
//是阻止了外部调用方对对象内部的值任意地进行修改
//只用来访问或修改内部变量的函数被称为setter或者getter

//Go的编码风格不禁止直接导出字段。当然,一旦进行了导出,就没有办法在保证API兼容的情况下去除对其的导出,