golang里面的枚举值进行或操作后,如何实现它的String()方法?

参考了一些代码,研究了一下,我们举出一个实际的例子,用网卡的属性来做枚举演示,一个网卡可以有up状态、广播状态、本地loopback状态、点对点pointToPoint、multicast多播状态,一共5个,当然了,还可以定义更多,比如down状态等等

golang本身并没有enum关键字,没有办法直接声明一个枚举类型,但是枚举肯定是必须要的,枚举在写程序的时候还是很方便的,而且可以起到编译期检查的作用,比如你用一个int作为参数,和用一个枚举作为参数传递进去,用枚举的话,直接编译器就可以检查参数的合法性,如果用int,传入的话,可以乱传,编译器无法发现错误。

golang是这样搞的,先用一个type关键字,给现有的数据类型取个别名:

// NicFlag 定义一个枚举,用于表示网卡的状态,golang里面并没有enum关键字,
//枚举的本质上是整数,考虑到种类有限,所以只需要uint,占用1个字节即可
type NicFlag uint
//有了NicFlag这个枚举类型,就可以定义枚举常量了
const (
	FlagUp			NicFlag = 1 << iota	// FlagUp=1
	FlagBroadcast	//2
	FlagLoopback	//4
	FlagPointToPoint	//8
	FlagMulticast	//16
)

//如何实现把枚举类型的值翻译为string呢?多个NicFlag是
//可以通过或操作组合起来的,比如:FlagUp | FlagBroadcast | FlagLoopback
var flagNames = []string {
	"up",
	"broadcast",
	"loopback",
	"pointtopoint",
	"multicast",
}

在定义常量的时候,刻意用移位来让5个Flag常量的二进制是错位的,你看,这5个分别是1、2、4、8、16,对应的二进制分别是0001、0010、0100、1000、00010000,也就是说在1个字节中只有1位是1,其它全是0,这里是有深意的,因为这几个Flag是支持或操作的,也就是说一个网卡支持同时具备这几种状态,比如这样写是没有问题的,因为它们的二进制位是错开的,所以按位或运算,并不会导致二进制位上的1出现重叠

FlagUp | FlagBroadcast | FlagLoopback

所以上面的或表达式最终得到就是:0001 | 0010 | 0100 = 0111,这种写法就是用一个字节的3个bit同时表示3种状态。

这属于位带操作技巧,在嵌入式开发中用的比较普遍,但是也对写程序稍稍有了一点高要求,但是呢也无所谓,也不是什么特别难懂的东西。

现在问题是:既然NicFlag是一个通过type定义的新类型,那么要实现它的String()方法改怎么搞呢?

因为NicFlag支持或操作,本来呢,FlagUp、FlagBroadcast、FlagLoopback、FlagPointToPoint、FlagMulticast在没有或运算的情况下,简简单单定义一下它们的字符串表示即可,比如:

//如何实现把枚举类型的值翻译为string呢?
//多个NicFlag是可以通过或操作组合起来的,
//比如:FlagUp | FlagBroadcast | FlagLoopback
var flagNames = []string {
	"up",
	"broadcast",
	"loopback",
	"pointtopoint",
	"multicast",
}

为NicFlag枚举类型定义String()方法,这里有一个很关键的地方,就是f可能是多个枚举的或组合,比如:FlagUp | FlagBroadcast | FlagLoopback,就要返回:"up" | "broadcast" | "loopback"

func (f NicFlag) String() string  {
	//先把思路写清楚
//因为NicFlag通过|或运算进行组合后,其实就是二进制位上相应的位置被置为1,比如本质上从二进制的角度,这几个枚举值的值是这样的
	//0001
	//0010
	//0100
	//1000
	//0001 0000
	//要判断一个传入的NicFlag到底是哪几位上是1,比如传入的是0011, 那就是 FlagUp | FlagBroadcast,然后我们返回 "up" | "broadcast"
	//具体怎么判断,我们可以让f(本质是一个uint)和0001、0010、0100、1000、00010000 依次进行按位与操作,如果与操作的结果值 不等于0,就把对应的枚举值字符串添加到最后的输出中
	var finalValue = ""

	//这里为什么会对flagNames进行range循环呢,因为range循环会得到数组的索引以及数组的具体值,而数组的具体值就是我们的内容
	//同时range后得到的数组索引index正好是0是开始的,从0到4,对这5个数字,向左移1位,正好得到1、2、4、8、16,和NicFlag枚举的整数值是一模一样的
	//这样的话,就可以用uint(index)和NicFlag进行按位与操作了,只要结果不为0,就直接取flagNames数组中的当前索引对应的name
	for index, name := range flagNames {
		//index是从0开始的一直到4,最后就会变成
		//1 << 0 结果是1
		//1 << 1 结果是2
		//1 << 2 结果是4
		//1 << 3 结果是8
		//1 << 4 结果是16
		//uint(index)的取值是1、2、4、8、16,正好与NicFlag的整数取值一样,那么关键来了,进行按位与操作,如果f的二进制相应位上=1,那么和uint(index)进行
		//按位与操作后,最终的结果必然不为0,因为 1&1=1,就这么简单
		if f & (1 << uint(index)) != 0 {
			if finalValue != "" {
				finalValue += "|"
			}
			finalValue += name
		}
	}
	if finalValue == "" {
		finalValue = "0"
	}
	return finalValue
}

为什么这样实现的核心思想,已经详细地写在程序的代码注释中了,总结一下:

  1. 要知道这里定义的NicFlag枚举类型的本质上是一个字节的整数,而且用了左移,确保这里定义的5个具体的值,在二进制上相应位重叠,每个值是前一个值的2倍,所以是1、2、4、8、16,分别对应的是0001、0010、0100、1000、0001 0000,看到没?
  2. 要判断一个NicFlag中的二进制相应位是不是1,就要进行与操作,分别和0001、0010、0100、1000、0001 0000进行与操作,只要与出来的结果不是0,就表示相关的枚举值参与了或操作,我们只要把这个枚举值对应的字符串append到最终的字符串中即可

测试一下:

func Test1(t *testing.T) {
	fmt.Printf("FlagUp is : %v\n", FlagUp)
	fmt.Printf("FlagBroadcast is : %v\n", FlagBroadcast)
	fmt.Printf("FlagLoopback is : %v\n", FlagLoopback)
	fmt.Printf("FlagPointToPoint is : %v\n", FlagPointToPoint)
	fmt.Printf("FlagMulticast is : %v\n", FlagMulticast)
}
=== RUN   Test1
FlagUp is : up
FlagBroadcast is : broadcast
FlagLoopback is : loopback
FlagPointToPoint is : pointtopoint
FlagMulticast is : multicast
--- PASS: Test1 (0.00s)
PASS

通过这个案例学习,对golang的枚举以及位操作更加加深印象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值