Go 中的方法

方法声明

方法的声明和普通函数的声明类似,只是在函数名字前面多了一个参数。这个参数把这个方法绑定到这个参数对应的类型上。

指针接收者的方法

命名类型(Point) 与指向它们的指针(*Point)是唯一可以出现在接收者声明处的类型。而且,为防止混淆,不允许本身是指针的类型进行方法声明:

type p *int

func (p) f()  {
	
}   // 编译错误:非法的接收者类型

实际上编译器会对变量进行 &p 的隐式转换。只有变量才允许这么做,包括结构体字段,像 p.X 和数组或者 slice 的元素,比如 perim[0] 。不能够对一个不能取地址的 Point 接收者参数调用 *Point 方法,因为无法获取临时变量的地址。
实参接收者和形参接收者是同一个类型,比如都是 T 类型或都是 *T 类型。
或者实参接收者是 T 类型的变量而形参接收者是 *T 类型。编译器会隐式地获取变量的地址。
或者实参接收者是 *T 类型而形参接收者是 T 类型。编译器会隐式地获取实际的取值。

位向量

在数据流分析领域,集合元素都是小的非负整型,集合拥有许多元素,而且集合的操作多数是求并集和交集,位向量是个理想的数据结构。
位向量使用一个无符号整型值的 slice,每一位代表集合中的一个元素。如果设置第 i 位的元素,则认为集合包含 i 。

package main

import (
	"bytes"
	"fmt"
)

// IntSet 是一个包含非负整数的集合
// 零值代表空的集合
type IntSet struct {
	words []uint64
}

// Has 方法的返回值表示是否存在非负整数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 添加非负整数 x 到集合中
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 将会对s和t 做并集并将结果存在 s 中
func (s *IntSet) UnionWith(t *IntSet)  {
	for i, word := range t.words {
		if i < len(s.words) {
			s.words[i] |= word
		} else {
			s.words = append(s.words, word)
		}
	}
}

// String 方法以字符串 "{1 2 3}" 的形式返回集中
func (s *IntSet)String() string {
	var buf bytes.Buffer
	buf.WriteByte('{')
	for i, word := range s.words {
		if word == 0 {
			continue
		}
		for j := 0; j < 64; j++ {
			if word & (1 << uint(j)) != 0 {
				if buf.Len() > len("{") {
					buf.WriteByte(' ')
				}
				fmt.Fprintf(&buf, "%d", 64 * i + j)
			}
		}
	}
	buf.WriteByte('}')
	return buf.String()
}

在IntSet 集合中,由于每一个字拥有 64 位,因此为了定位 x 的位置,我们使用商数 x/64 作为字的索引,而 x % 64 记做该字内的索引。UnionWith 操作使用按位 “或” 操作符 | 来计算一次64个元素求并集的结果。

	var x, y IntSet
	x.Add(1)
	x.Add(144)
	x.Add(9)
	fmt.Println(x.String())  // "{1 9 144}"

	y.Add(9)
	y.Add(42)
	fmt.Println(y.String()) // "{9 42}"

	x.UnionWith(&y)
	fmt.Println(x.String())   // "{1 9 42 144}"

	fmt.Println(x.Has(9), x.Has(123)) // "true false"

	fmt.Println(&x)   // "{1 9 42 144}"
	fmt.Println(x.String())  // "{1 9 42 144}"
	fmt.Println(x)    // "{[4398046511618 0 65536]}"

fmt 在输出 &x 的时候,输出了 *IntSet 指针,它有一个 String 方法。fmt 在输出 x.String 的时候,编译器会帮我们隐式地插入 &操作符,我们得到指针后就可以获取到 String 方法里了。在 fmt 输出 x 的时候,因为 IntSet 值本身没有 String 方法,所以 fmt 直接输出了结构体。

封装

Go 语言只有一种方法控制命名的可见性:定义的时候,首字母大写的标识符是可以从包中导出的,而首字母没有大写的则不导出。同样的机制也同样作用于结构体内的字段和类型中的方法。结论就是,要封装一个对象,必须使用结构体。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值