C语言15-枚举、位运算
枚举
在编程的问题中,往往需要用一个变量,来保存某种“状态”,比如之前通过状态机实现语法高亮。
再比如,贪吃蛇中,需要一个变量,来保存他的前进方向。
#define DIR_UP 0
#define DIR_DOWN 1
#define DIR_LEFT 2
#define DIR_RIGHT 3
int nDirection = DIR_UP;
这样做在语法上没有问题,但是在编程逻辑过程中,可能埋下隐患,因为int表示的范围,比我们约定的方向种类,要大的多。
比如说,万一有程序员,无意中写了一下的语句进行赋值:
nDirection = 0;//语法正确,逻辑正确,表示向上
nDirection = 5;//语法正确,逻辑不正确,因为5没有被约定
C语言中,为了解决这种“在限定范围内赋值”的问题,发明了枚举变量类型。
枚举的语法
定义枚举类型:
enum ENUM_DIRECTION/*类型名*/
{
//状态范围
UP,
DOWN,
LEFT,
RIGHT
};
用枚举类型定义变量:
int main(int argc, char* argv[])
{
enum ENUM_DIRECTION eSnakedir;
eSnakedir=DOWN;
return 0;
}
枚举的原理
总之,枚举变量,其实就是在编译时会做范围检查的int类型的变量
enum ENUM_DIRECTION/*类型名*/
{
//状态范围
UP,
DOWN,
LEFT,
RIGHT
};
int main(int argc, char* argv[])
{
enum ENUM_DIRECTION eSnakedir;
eSnakedir=LEFT;
printf("%d, %d", sizeof(eSnakedir), eSnakedir);
return 0;
以上的结果是4, 2
4意味着枚举变量的大小是4字节.
经过测试知道
enum ENUM_DIRECTION/*类型名*/
{
//状态范围
UP, //0
DOWN,//1
LEFT, //2
RIGHT //3
};
我们还可以自己规定枚举对应数字的起点
enum ENUM_DIRECTION/*类型名*/
{
//状态范围
UP=2,
DOWN,
LEFT,
RIGHT
};
枚举只是编译时检查,运行时并不做检查
一下为强制通过指针来改变eSnakedir的值
*(int *)&eSnakedir=5;
位运算
语法
C语言中位运算有:
&
|
^
~
前三种是二元运算符,最后一种是一元运算符。
位运算是指按一个变量中的每一个bit进行运算,用表来总结
实际举例
#define NEGATIVE_ONE (~0)//此处用宏定义了一个-1
~(~char)0x03=0xFC
0x23 ^ 0x11 = 0x32
0x23 & 0x11 = 0x01
0x23 | 0x11 = 0x33
位运算的语法非常简单,掌握怎么运算即可。
对他应用场景的理解,比掌握语法更重要。
应用
标志约定
我们经常用到标志,比如找对象(女找男):可能我们会对男人的属性进行归类,这些归类的属性,我们可以使用“有”或者“没有”来记录。
/* 秃头与否
抽烟与否
有车与否
有房与否
有否腹肌
近视与否
**与否
诚实与否 */
char chIsBare;
char chSmoke;
char chIsCar;
char chIsHouse;
char chIsMuscle;
char chIsGlasses;
char chIsGamble;
char chIsHoneset;
可能会使用多个变量为标志,存储这些特征:
//渣渣
chIsBare = 1;
chIsSmoke = 1;
chIsCar= 0;
chIsHouse = 0;
chIsMuscle = 0;
chIsGlasses = 1;
chIsGamble = 1;
chIsHonest = 0;
以上代码,就定义了一个“人”。但是,从存储角度看,这浪费了内存空间
因为每个标志(与否),只需要一个bit。所以8个标志,严格来说需要8个bit(一个字节)。
所以,可以从位运算的角度去改进:
即,使用一个char8个bit一个字节,来表示不同标志:
//自上往下,分别是第0~7bit位
char chIsBare;
char chSmoke;
char chIsCar;
char chIsHouse;
char chIsMuscle;
char chIsGlasses;
char chIsGamble;
char chIsHoneset;
这样,特征的二进制就变成了:
0110 0011=>0x63
以上的技巧可以说,没有技巧,只是约定。
光有约定,并不利于编程。
比如现在,我想要一个不抽烟,有钱,其他清空和0x63一样的人。
但是,有了位运算,我们就有技巧。
首先,我们可以用不同的数字,表示不同bit的有效位。
#define FLAG_ISBARE 0x01
#define FLAG_ISSOMKE 0x02
#define FLAG_ISCAR 0x04
#define FLAG_ISHOUSE 0x08
#define FLAG_ISMUSCLE 0x10
#define FLAG_ISGLASSES 0x20
#define FLAG_ISGAMBLE 0x40
#define FLAG_ISHONSET 0x80
接着,不同的位运算,就发挥了不同的功能。
|运算符的“打开”标志功能
char chOldOne = ...;
chOldOne = chOldOne | FLAG_ISBARE;
**&运算符的“关闭”标志功能
char chOldOne = ...;
chOldOne = chOldOne &(~FLAG_ISBARE);
**^运算符的“切换”标志功能
char chOldOne = ...;
chOldOne = chOldOne ^FLAG_ISCAR
/* 秃头与否
抽烟与否
有车与否
有房与否
有否腹肌
近视与否
**与否
诚实与否 */
#define FLAG_ISBARE 0x01
#define FLAG_ISSOMKE 0x02
#define FLAG_ISCAR 0x04
#define FLAG_ISHOUSE 0x08
#define FLAG_ISMUSCLE 0x10
#define FLAG_ISGLASSES 0x20
#define FLAG_ISGAMBLE 0x40
#define FLAG_ISHONSET 0x80
int main (int argc, char* argv[])
{
char chPerfectOne=0 |
(FLAG_ISCAR)
| (FLAG_ISHOUSE)
| (FLAG_ISMUSCLE)
printf("%02X", chPerfectOne);
return 0;
}
利用位运算进行无中间变量交换
普通交换:
int nValue1 = 0x1111111;
int nValue2 = 0x222222;
//交换
int nTemp = nValue1;
nValue1= nValue2;
nValue 2=temp;
使用异或,可以省掉中间变量:
int main (int argc, char* argv[])
{
int nValue1=0x1;
int nValue 2=0x2;
printf("%p, %p\r\n", nValue1, nValue2);
nValue1=nValue1^nValue2;
nValue2=nValue1^nValue2;//nValue2变成原来的nValue1;
nValue1=nValue^nValue2;
printf("%p, %p\r\n", nValue1, nValue2);
return 0;
}
通过以下算法进行类比:
nValue1 = nVaulue1 + nValue2;
nValue2 =nValue1 ‐ nValue2;
nValue1 = nValue1 ‐ nValue2 ;
异或在加密解密中的应用
什么是加密算法
加密算法与编码算法最本质的区别在于:加密算法需要“**”(key)。
异或天生适用于加密和解密:
- nValue ^ key //变为密文
- 密文^key//变为原文
void EncryptBlock(char* pData, int nLen, char* pKey, int nKeyLen)
{
for (size_t i = 0; i < nLen; i++)
{
pData[i] ^= pKey[i%nKeyLen];
}
}
int main(int argc, char* argv[])
{
char szData[] = "helloworld";
EncryptBlock(szData, strlen(szData),
"hello", strlen("hello"));
return 0;
}
非常多得加密解密算法中,使用了疑惑运算(比如AES、MDS、SHA256、CRC)