定义:
原码: 正数的二进制即为原码,负数的二级制为正数的反码再补码
反码:将二进制数按位取反(1变0, 0变1)
补码: 对反码加1
eg : -5
原码:00000000 00000000 00000000 00000101-> 5
反码:11111111 11111111 11111111 11111010
补码:11111111 11111111 11111111 11111011-> - 5
运算:
1.<< 左移运算符:各二进位全部左移若干位,高位丢弃,低位补0
eg : 6 << 2 = 24-> 2 ^ 2 + 2 ^ 1 = 2 ^ 4 + 2 ^ 3-> 2 << n = 2 ^ n
0000 0000 0000 0000 0000 0000 0000 0110-> 6
0000 0000 0000 0000 0000 0000 0001 1000-> 6 << 2 = 24
数学意义:在数字没有溢出的前提下,对于正数和负数,二进制左移n位就相当于乘以2的n次方
2.>> 右移运算符:各二进位全部右移若干位,正数高位补0,负数高位补1,低位丢弃。
eg: 12 >> 2 = 3
0000 0000 0000 0000 0000 0000 0000 1100-> 12
0000 0000 0000 0000 0000 0000 0000 0011-> 12 >> 2 = 3
eg: -12 >> 2 = -3
1111 1111 1111 1111 1111 1111 1111 0100-> - 12
1111 1111 1111 1111 1111 1111 1111 1101-> - 12 >> 2 = -3
数学意义:在数字没有溢出的前提下,对于正数和负数,二进制右移n位就相当于除以2的n次方
3.& 位与:当运算符两边相同位置都是1时,结果返回1,其他情况都返回0。
eg: 3 & 5 = 1
0000 0000 0000 0000 0000 0000 0000 0011-> 3
0000 0000 0000 0000 0000 0000 0000 0101-> 5
0000 0000 0000 0000 0000 0000 0000 0001-> 3 & 5 = 1
4.| 位或: 当运算符两边相同位置都是0时,结果返回0,其他情况都返回1。
eg: 3 | 5 = 7
0000 0000 0000 0000 0000 0000 0000 0011-> 3
0000 0000 0000 0000 0000 0000 0000 0101-> 5
0000 0000 0000 0000 0000 0000 0000 0111-> 3 | 5 = 7
5.~位非: 将运算符后二进制数反转,0变1,1变0
eg: ~3 = -4
0000 0000 0000 0000 0000 0000 0000 0011-> 3
1111 1111 1111 1111 1111 1111 1111 1100-> ~3 = -4
6.^ 位异或: 当运算符两边相同位置都是相同,结果返回0,不相同时返回1。
eg: 3 ^ 5 = 6
0000 0000 0000 0000 0000 0000 0000 0011-> 3
0000 0000 0000 0000 0000 0000 0000 0101-> 5
0000 0000 0000 0000 0000 0000 0000 0110-> 3 ^ 5 = 6
Exercise:
判断奇偶
交换两个数
取余
相反数
绝对值
平均数
...
趣味练习:
有 1000 个一模一样的瓶子,其中有 999 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。
现在,你只有 10 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?
答:
把所有的瓶子按照二进制标号,10个老鼠对应二进制10个位,将所有瓶子位的值为1的液体喂给对应位的老鼠,最后将死亡的老鼠组合起二进制数就可以判断哪一瓶是毒药
0 1 2 3 4 5 6 7 8 9
0000 0000 0000 0000 0000 0000 0000 0001
0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0011
.
.
.
0000 0000 0000 0000 0000 0011 1110 1000
不知道为什么?数学中最简单的解决方法就是先看简单的例子:
假设有8个瓶子,里面只有一瓶是毒药,外观与其他7瓶一致,老鼠吃下毒药后1个小时会死亡,
问要多少只老鼠才能够才一个小时内找出哪一瓶是毒药?具体怎么操作?
一只老鼠有两种状态:1.生存、2.死亡
2 ^ 3 = 8,所以用3只老鼠就可以
操作:
瓶子:
000(0)
001(1)
010(2)
011(3)
100(4)
101(5)
110(6)
111(7)
老鼠:
321
编号1老鼠(1 - 3 - 5 - 7)、编号2老鼠(2 - 3 - 6 - 7),编号3老鼠(4 - 5 - 6 - 7)
假设编号为5(101)的瓶子有毒
下面分析结果:
如果1号老鼠死了(老鼠1),则表明毒药存在于编号二进制中第一位为1的瓶子,那么毒瓶子的编号为(XX1),X表示尚未确认。
然后分析第二只老鼠,如果第二只老鼠(老鼠2)没死,那就说明毒药瓶子不存在于编号二进制中第二位为1的瓶子,即毒药编号为(X01)。
最后分析第三只老鼠,如果老鼠3死了,那就表名毒药存在于编号二进制位中第三位为1的的瓶子之中,即编号为(101)。
最后得出结论:编号为101的瓶子有毒。
假如三只老鼠都没有死亡,则表明有毒的瓶子是0号瓶子
游戏开发中的应用:
1.在Unity中内置的应用:
public class LayerOperator : MonoBehaviour
{
public LayerMask layer;
public void Open()
{
Camera.main.cullingMask |= layer; //开启某层级的渲染
}
public void Close()
{
Camera.main.cullingMask &= ~layer; //关闭某层级的渲染
}
}
2.用于多条件的筛选:
public struct MyLayerMask
{
public int value { get; private set; }
public string bitStr { get { return System.Convert.ToString(value, 2); } }
public MyLayerMask(int defaultValue = 0)
{
value = defaultValue;
}
public void Add(int value)
{
this.value |= value;
}
public void Remove(int value)
{
this.value &= ~value;
}
public bool Contains(int index)
{
return bitStr.Length > index && bitStr[bitStr.Length - 1 - index] == '1';
}
public static List<int> GetContainLayer(int value, int enumCount)
{
string bitStr = string.Empty;
if (value == -1)
{
bitStr = System.Convert.ToString(GetLayerMaxValue(enumCount), 2);
}
bitStr = System.Convert.ToString(value, 2);
List<int> layers = new List<int>();
for (int i = 0; i < bitStr.Length; i++)
{
if(bitStr[i] == '1')
{
layers.Add((int)Mathf.Pow(2, bitStr.Length - 1 - i));
}
}
return layers;
}
public static int GetLayerMaxValue(int count)
{
int value = 0;
for (int i = 0; i < count; i++)
{
value += (int)Mathf.Pow(2, i);
}
return value;
}
}
public enum NumScreen
{
能整除3的数 = 1,
能整除4的数 = 2,
能整除5的数 = 4,
能整除6的数 = 8,
}
public class ScreenOperator : MonoBehaviour
{
private MyLayerMask myLayer = new MyLayerMask(0);
private void Screen(int index)
{
if (IsPress) //伪码
{
myLayer.Remove((int)Mathf.Pow(2, index));
}
else
{
myLayer.Add((int)Mathf.Pow(2, index));
}
ScreenNum();
}
private void ScreenNum()
{
for (int i = 1; i <= 100; i++)
{
nums[i - 1].gameObject.SetActive(CanDivide(i)); //伪码
}
}
private bool CanDivide(int value)
{
int length = Enum.GetNames(typeof(NumScreen)).Length;
bool canDivide = true;
for (int i = 0; i < length; i++)
{
if (myLayer.Contains(i))
{
if (value % (i + 3) != 0)
{
canDivide = false;
break;
}
}
}
return canDivide;
}
}
3.用于多状态的记录:具体代码的实现原理和多条件筛选一样
比如在游戏中的奖励系统,游戏中有很多种奖励,每日登录,排位赛,竞技场,公会奖励,在线奖励,签到奖励等,策划需要每一种奖励可以领取的时候,客户端在相应的功能按钮都需要光效表现来引起玩家注意,让玩家知道某某奖励现在是可以领取的。我看到很多开发人员都是客户端把所有系统的数据都拿到了,然后再根据数据的相应情况来决定是否让这个按钮开启光效。其实我们只需要用一个整数,在服务器端算好每一种奖励是否可以被领取,客户端收到这个数据后,根据每一个状态的情况来开启相应的光效,让玩家点击进入相应的系统的时候,才去拿相应的数据。
同样在做智能体的状态时,对于多状态的完全可以用位运算来记录,和上面的原理是一样的。这样相当于做了一个简单的分层状态机。例如:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum EnemyState
{
LosePlayer = 1,
SeePlayer = 2,
NearFromPlayer = 16,
FarFromPlayer = 32,
HighHealth = 4,
LowHealth = 8,
Happy = 64,
Angry = 128,
}
public class StateOperator : MonoBehaviour
{
[EnumMaskField("EnemyStates")]
public EnemyState enemyStates;
public float currentDistance;
public Transform player;
public Transform enemy;
private MyLayerMask myLayerMask;
private void OnDrawGizmos()
{
if (player == null || enemy == null) return;
currentDistance = Vector3.Distance(player.position, enemy.transform.position);
myLayerMask = new MyLayerMask();
if (currentDistance < 1f)
{
myLayerMask.Add((int)EnemyState.SeePlayer);
}
if (currentDistance > 1f)
{
myLayerMask.Add((int)EnemyState.LosePlayer);
}
if (currentDistance < 0.5f)
{
myLayerMask.Add((int)EnemyState.NearFromPlayer);
myLayerMask.Add((int)EnemyState.Angry);
}
if (currentDistance > 3f)
{
myLayerMask.Add((int)EnemyState.FarFromPlayer);
myLayerMask.Add((int)EnemyState.Happy);
}
enemyStates = (EnemyState)myLayerMask.value;
}
}
EnumMaskField是一个自定义特性,可以像LayerMask一样查看多选的枚举:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Enum, Inherited = true)]
public class EnumMaskFieldAttribute : PropertyAttribute
{
public string propertyName;
public EnumMaskFieldAttribute(string propertyName)
{
this.propertyName = propertyName;
}
}
[CustomPropertyDrawer(typeof(EnumMaskFieldAttribute))]
public class EnumMaskFieldAttributeDrawer : PropertyDrawer
{
public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _lable)
{
EnumMaskFieldAttribute enumMask = attribute as EnumMaskFieldAttribute;
_property.intValue = EditorGUI.MaskField(_position, enumMask.propertyName, _property.intValue, _property.enumDisplayNames);
}
}
在C#内部有很多地方都用到了位运算:
[AttributeUsage(AttributeTargets.Field, Inherited = true)] 可以看看AttributeTargets