说起位运算,那得先说原码反码补码。
说起原码反码补码,那得先说计算机进制…
算了,我们就从头来,一次撸透这兄弟几个!

一.计算机进制

1. 十进制 :

范围 0-9
就是我们日常使用的进制方式,数字范围0-19,满10进1,就是说呢,10+1=11, 这个大家都懂就不详细举例了哈。

	var num int = 3
	fmt.Printf("num的十进制:%d\n", num)		//打印3

2. 二进制:

这玩意就是计算机使用的进制方式, 范围只有0和1,满2进1,例如:

0000 0000 + 1 = 0000 0001
0000 0001 + 1 = 0000 0010
0000 0010 + 1 = 0000 0011

0000 0011 + 1 = 0000 0100	
最后一位进1等于0
最后一位进的1加在倒数第二位
倒数第二位的1又进1等于0,把进的位加在倒数第3位
倒数第三位就等于了1
以此类推......
	var num int = 3		// 十进制的数字 3
	fmt.Printf("num的二进制:%b\n", num)	// 打印11, 具体请看本文的进制对照图

3. 八进制:

范围0-7,满8进1, 以数字0开头表示,例如 :

	var n1 int = 7		// 十进制的数字 7
	fmt.Printf("7的八进制:%o\n", n1)		// 打印7

	var n2 int = 9		// 十进制的数字 9
	fmt.Printf("9的八进制:%o\n", n2)		// 打印11
    
    n3 := 8
    fmt.Printf("8的八进制:%o\n", n3)		// 打印10

4. 十六进制:

范围 0-9及A-F(a-f), 满16进1,以0x或0X开头表示
0-9对应十进制的0-9,
A-F对应十进制的10-15

	var n1 int = 10
	fmt.Printf("10的十六进制:%X\n", n1)	// 打印A

	var n2 int = 12
	fmt.Printf("12的十六进制:%X\n", n2)	// 打印C

	var n3 int = 15
	fmt.Printf("15的十六进制:%X\n", n3)	// 打印F

	var n4 int = 16
	fmt.Printf("16的十六进制:%X\n", n4)	// 打印10, 满16进1

5. 进制对照图

十进制二进制十六进制八进制
00000 000000
10000 000111
20000 001022
30000 001133
40000 010044
50000 010155
60000 011066
70000 011177
80000 1000810
90000 1001911
100000 1010A12
110000 1011B13
120000 1100C14
130000 1101D15
140000 1110E16
150000 1111F17
160001 00001020
170001 00011121
以此类推

二.各种进制之间的转换

1. 各种进制 转 十进制

  • 二进制转十进制
    方法:从二进制的最低位(最右边的那一位)开始,最低位看作第一位,依次往左把每一位的数拿出来,乘2的位数-1次方,然后求和。
    例:把二进制1110转为十进制
    2的1-1次方 = 1
    2的2-1次方 = 2
    2的3-1次方 = 4
    2的4-1次方 = 8
    1110 => (0 * 2^0) + (1 * 2^1) + (1 * 2^2) + (1 * 2^3)
    1110 => (0 * 1) + (1 * 2) + (1 * 4) + (1 * 8) => 0 + 2 + 4 + 8 = 14

  • 八进制转十进制
    方法:从八进制的最低位(最右边的那一位)开始,最低位看作第一位,依次往左把每一位的数拿出来,乘8的位数-1次方,然后求和。
    例:把八进制0123转为十进制
    0123 => (3 * 8^0) + (2 * 8^1) + (1 * 8^2) + (0 * 8^3)
    0123 => (3 * 1) + (2 * 8) + (1 * 64) + (0 * 512) => 3 + 16 + 64 + 0 = 83

  • 十六进制转十进制
    方法:从十六进制的最低位(最右边的那一位)开始,最低位看作第一位,依次往左把每一位的数拿出来,乘16的位数-1次方,然后求和。
    例:把十六进制0x25C(就是25C)转为十进制
    25C => (12 * 16^0) + (5 * 16^1) + (2 * 16^2)
    0123 => (12 * 1) + (5 * 16) + (2 * 256) => 12 + 80 + 512 = 604

2. 十进制 转 各种进制

  • 十进制转二进制
    方法:把十进制数一直除2,知道商为0为止,把每一次除完之后的余数反过来拼在一起,就是它对应的二进制数
    例:把十进制数67转二进制

    所以十进制67 => 二进制1000011

  • 十进制转八进制
    方法:把十进制数一直除8,知道商为0为止,把每一次除完之后的余数反过来拼在一起,就是它对应的八进制数
    例:把十进制数157转八进制

    所以十进制157 => 八进制235

  • 十进制转十六进制
    方法:把十进制数一直除16,知道商为0为止,把每一次除完之后的余数反过来拼在一起,就是它对应的十六进制数
    例:把十进制数367转十六进制

    所以十进制367 => 十六进制16F

3. 二进制 转 各种进制

  • 二进制转八进制
    方法:从最低位开始,把每3个二进制数编队为一组,不够的补0,每组进行转化为八进制
    例:把二进制 11010101 转为 八进制
    先转十进制:011 => 3, 010 => 2, 101 => 5
    11010101 编组之后 (011)(010)(101) =>八进制 325

  • 二进制转十六进制
    方法:从最低位开始,把每4个二进制数编队为一组,不够的补0,每组进行转化为十六进制
    例:把二进制 11010101 转为 十六进制
    先转十进制:1101 => 13, 0101 => 5
    11010101 编组之后 (1101)(0101) =>十六进制 D5

4. 各种进制 转 二进制

  • 八进制转二进制
    方法:把八进制的每一位数,转化为对应3位的二进制数, 不够的补0,或者不用补,自己看得出来也行
    例:把八进制 325 转为二进制
    3 => 二进制 011
    2 => 二进制 010
    5 => 二进制 101
    所以:325 =>二进制 11010101

  • 十六进制转二进制
    方法:把十六进制的每一位数,转化为对应4位的二进制数, 不够的补0,或者不用补,自己看得出来也行
    例:把十六进制0xD5 转为 二进制
    D => 十进制 13 => 二进制 1101
    5 => 二进制 0101
    所以:D5 => 二进制 11010101

5. go实现十进制和二进制的转换


// 十进制转二进制
func decimalToBinary(num int) int {
	var binary []int

	for num != 0 {
		binary = append(binary, num%2)
		num = num / 2
	}
	res := 0
	if len(binary) == 0 {
		fmt.Printf("%d\n", 0)
	} else {
		for i := len(binary) - 1; i >= 0; i-- {
			res = res*10 + binary[i]
		}
	}
	return res
}

// 二进制转十进制
func binaryToDecimal(num int) int {
	var remainder int
	index := 0
	decimalNum := 0
	for num != 0 {
		remainder = num % 10
		num = num / 10
		decimalNum = decimalNum + remainder*int(math.Pow(2, float64(index)))
		index++
	}
	return decimalNum
}

三.原码,反码,补码

众所周知,1个字节=8位,即 0000 0000 - 1111 1111。
当一个字节为 1111 1111 :表示的数字是 255.
当一个字节为 0000 0000 :表示的数字是0.
所以一个字节可以存储0~255范围内的数字,总共256个值,负数的话可以存储 -128 ~ +127范围内的数值。
但是负数怎么表示呢?
所以原码,反码,补码就出来了:

类型说明存储范围
原码第一位为符号位,0代表正,1代表负,剩下7位表示数字本身-127 ~ +127
反码正数反码与原码一致,负数反码 按照原码 按位取反,第一个符号位不变-127 ~ +127
补码正数补码与原码一致,负数补码是该数反码+1-128 ~ +127

原码和反码 : 第一位用来表示符号位,所以少了一位。
补码 : 第一位既表示符号,也表示数值范围

例如:

数字5-5-128
原码0000 01011000 0101表示不了
反码0000 01011111 1010表示不了
补码0000 01011111 10111000 0000

总结:
1.正数的原码,反码,补码都是一样的。
2.0的反码和补码都是0。
3.计算机都是以补码的方式进行计算的。

  • 为啥计算机以补码来运算呢?
    因为补码可以在不缺少位的情况下表示负数,例如 1-1在计算机内运算的时候其实是 1+(-1)

四.golang位运算

快了快了,最后亿个知识点了,肝起来朋友们!

1. 三种位运算:

符号运算规则
按位与 &二进制中比较的两位,都是1,结果为1,否则为0
按位或 |二进制中比较的两位,其中有一个是1,结果为1,否则为0
按位异或 ^二进制中比较的两位,一位为0另一位为1,结果为1,否则为0

例1:

2&3		(正数的原码,反码,补码都一样)
2的原码 => 0000 0010
2的反码 => 0000 0010
2的补码 => 0000 0010	

3的原码 => 0000 0011
3的反码 => 0000 0011
3的补码 => 0000 0011	

因为计算机都是以补码方式来计算的
2&3 => 
2的补码  0000 0010 
&
3的补码  0000 0011
----------------
算出补码  0000 0010 =>十进制 2
所以 2&3 = 2

例2:

2|3		(正数的原码,反码,补码都一样)
2|3 => 
2的补码  0000 0010 
|
3的补码  0000 0011
----------------
算出补码  0000 0011 =>十进制 3
所以 2|3 = 3

例3:

2 ^ 3		(正数的原码,反码,补码都一样)
2 ^ 3 => 
2的补码  0000 0010 
^
3的补码  0000 0011
----------------
算出补码  0000 0001 =>十进制 1
所以 2 ^ 3 = 1

来一个麻烦的,负数
例4:

-2^2		
-2的原码 => 1000 0010
-2的反码 => 1111 1101
-2的补码 => 1111 1110	

-2^2 => 
-2的补码  1111 1110 
^
2的补码   0000 0010
----------------
算出补码  1111 1100 减1位=> 反码1111 1011 符号为不变按位取反=> 原码1000 0100 =>十进制 -4
所以 -2^2 = -4

2. 两种移位运算符:

同样是以补码方式进行运算

符号运算规则
右移 >>二进制中底位溢出,符号为不变,用符号位补溢出的高位
左移 <<二进制中符号位不变,低位补0

例1:

1 >> 2  右移2位
1的补码 0000 0001 低位向右溢出两位, 最后的两位01溢出了, 再用符号位补溢出高位
所以 1 >> 2 => 0000 0001 >> 2 => 0000 0000 =>十进制 0

func TbOne() {
	numA := 3
	fmt.Printf("3:    %08b\n", numA)
	fmt.Printf("3<<1: %08b\n", numA<<1)
	fmt.Printf("3<<2: %08b\n", numA<<2)
}
/*
打印 : 
3:    00000011
3<<1: 00000110
3<<2: 00001100
*/

例2:

1 << 2  左移2位
1的补码 0000 0001 符号位不变, 低位补0
所以 1 << 2 => 0000 0001 << 2 => 0000 0100 =>十进制 4

func TbOne() {
	numA := 120
	fmt.Printf("120:    %08b\n", numA)
	fmt.Printf("120>>1: %08b\n", numA>>1)
	fmt.Printf("120>>2: %08b\n", numA>>2)
}
/*
120:    01111000
120>>1: 00111100
120>>2: 00011110
*/

一些具体使用

func TbOne() {
	var numA, numB, numC int
	numA = 0
	// 打印 十进制0 的二进制数
	fmt.Printf("0:    %08b\n", numA)	// 0:    00000000
	numB = numA | 1
	fmt.Printf("numB(numA|1): %08b\n", numB)// numB(numA|1): 00000001
	numC = numB | 1<<1
	fmt.Printf("numC(numB|1<<1):%08b\n",numC)//numC(numB|1<<1):00000011
	numD = 3
	fmt.Printf("3:%08b\n", numD)// 3: 00000011
	numE = 3 + (1 << 2)  // 3的二进制 + 1<<2的二进制
	fmt.Printf("3+(1<<2): %08b\n", numE) //3+(1<<2): 00000111
	// 打印十进制数
	fmt.Println("abc: ", numA, numB, numC) // abc:  0 1 3
	fmt.Println("十进制数5的二进制=", decimalToBinary(5)) // 101
	fmt.Println("二进制数11的十进制数=", binaryToDecimal(11)) // 3
	
	// 遍历int类型变量
	num := 10086
	numStr := strconv.Itoa(num)
	for k, v := range numStr {
		fmt.Printf("k,v:%d,%d", k, v)
		fmt.Println()
	}
}

// 二进制数转十进制数
func binaryToDecimal(num int) int {
	var remainder int
	index := 0
	decimalNum := 0
	for num != 0 {
		remainder = num % 10
		num = num / 10
		decimalNum = decimalNum + remainder*int(math.Pow(2, float64(index)))
		index++
	}
	return decimalNum
}

// 十进制数转二进制数
func decimalToBinary(num int) int {
	var binary []int

	for num != 0 {
		binary = append(binary, num%2)
		num = num / 2
	}
	res := 0
	if len(binary) == 0 {
		fmt.Printf("%d\n", 0)
	} else {
		for i := len(binary) - 1; i >= 0; i-- {
			res = res*10 + binary[i]
		}
	}
	return res
}

打完收工,朋友们学费了吗!