C#-面向对象:枚举和位运算

大家在学习DateTime的时候有没有发现一个DayOfWeek类型,表示星期几。使用F12就可以看到它的定义:

C#-面向对象:枚举和位运算_第1张图片

这里就出现了:

枚举(Enum)

枚举(enum)是和类(class)、结构(struct)并列的一种类型。它的成员又被称之为枚举值,枚举值不能有任何修饰符,只能包含两部分:

  • 名称,比如DayOfWeek中的Sunday、Monday等,名称一般是“有意义的”,以方便开发人员调用
  • 底层数据,比如DayOfWork中的0、1、2等。默认底层数据是int类型,还可以使用其他整数类型,比如byte、short等,如下所示:

    enum Grade : short //默认是int类型,现在是short类型 { }

底层数据也可以不写,不写的话默认从0开始,从上到下依次增加1:

enum Grade : short //默认是int类型 { Excellent, //不写底层数据默认为O Passed, //默认以1的步长递增底层数据 Failed = 8 //指定底层数据 }

枚举可以像类型一样使用:

//声明Grade属性 public Grade grade { get; set; } //作为方法的返回值 static Grade GetBy(int score){} //作为方法的参数 static int GetBy(Grade score){}

它的值直接枚举名点(.)出即可:

//声明变量/字段 Grade grade = Grade.Excellent;

因为枚举的整型底层数据,所以可以在枚举和整型之间进行强制转换:

Console.WriteLine((short)Grade.Excellent); //int类型也行(可以和short转换) Console.WriteLine((int)Grade.Passed); Console.WriteLine((int)Grade.Failed); Console.WriteLine((Grade)0); //Excellent Console.WriteLine((Grade)16); //无法转换时也不会报错,直接输出16

注意:枚举的默认值是:

  • 0所对应的枚举值,或者
  • 如果找不到0所对应的枚举值,就直接为0

演示:

为什么要使用枚举呢?

以DayOfWork为例,如果没有枚举,我们如何传递“星期几”呢?比如我们有一个方法,可以根据星期几返回bool值,如果是星期三和星期六,就返回true,表示当天不上课,^_^

怎么声明这个方法呢?参数用什么类型?int,string?比如这样:

static bool IsRest(int dayofWork) { return dayofWork == 3 || dayofWork == 6; }

这样写,有两个问题:

  1. 开发人员怎么知道3代表星期三,6代表星期六?你说这个还可以对应着来,星期天你怎么表示?0,还是7?不同的文化可能有不同的定义,这就是问题。
  2. 有没有可能传入一个参数:8?因为int类型的取值是没有限制的。

@想一想@:如果用string类型呢?

所以我们用DayOfWork的枚举来做:

static bool IsRest(DayOfWeek dayofWork) { return dayofWork == DayOfWeek.Wednesday || dayofWork == DayOfWeek.Saturday; }

首先,DayOfWeek.Wednesday表义非常清楚;然后,参数是枚举类型,所以你没办法传入一个不存在的枚举值的。这些,都极大的提高了代码的可读性和健壮性(少bug)。

枚举通常和

switch...case

配合使用,比如:

Grade grade = Grade.Excellent; switch (grade) { case Grade.Excellent: Console.WriteLine("发10个红包"); break; case Grade.Passed: Console.WriteLine("发5个红包"); break; case Grade.Failed: Console.WriteLine("没有红包了"); break; default: Console.WriteLine("怎么回事?"); break; }

整段代码的意思就是,根据grade的值进行分支:

  1. 如果grade的值等于Grade.Excellent,输出"发10个红包";
  2. 如果grade的值等于Grade.Passed,输出"发5个红包";
  3. 如果grade的值等于Grade.Failed,输出"没有红包了";
  4. 如果grade的值不等于上面的任何的一个枚举值,输出"怎么回事?"

这种逻辑其实用if...else...也可以实现,但是switch...case看起来更整洁一些。但要注意以下几点:

  • 只能进行“等值”运算,也就是说只能判断switch()中的值是否等于case后面的值,不能进行大于小于等其他运算。
  • 代码执行时从上往下比较,所以顺序很重要。尤其是default,相当于if...else...中最后兜底的else,应该是在上述所有case条件都不满足的情况下才执行的,所以只能放在最后。同时强烈建议总是带上default,哪怕用于报异常!
  • 不要忘记break,break意味着跳出switch域。如果两个case之间没有break只有业务逻辑语句,就会报编译错误;如果两(多)个case之间没有任何其他语句,这些case条件构成一种“或”的关系,比如上面周三周六休息的需求我们就可以这样实现:

    static bool IsRest(DayOfWeek dayofWork) { switch (dayofWork) { case DayOfWeek.Saturday: case DayOfWeek.Thursday: return true; //此处可以用return代替break default: return false; } }

因为枚举的底层数据是整数类型,它还可以进行

位运算

首先要把传入的数转化成二进制,然后按二进制对齐,对每一个相同的位的值进行相应运算——这就被称之为位运算。

我们这里介绍三种位运算:

  • &:读作“位与”,只有两个1才为1
  • |:读作“位或”,只要一个1就为1
  • ^:读作“异或”,两个值相同才为1

    Console.WriteLine(1 | 2); // 01 | 10 => 11 (3) Console.WriteLine(2 | 4); // 010 | 100 => 110 (6) Console.WriteLine(1 & 2); // 01 & 10 => 00 (0) Console.WriteLine(2 & 4); // 010 & 100 => 000 (0) Console.WriteLine(1 ^ 2); // 01 ^ 10 => 11 (3) Console.WriteLine(2 ^ 4); // 010 ^ 100 => 110 (6)

(除|和&以外,还有><等位移运算、补位运算,项目开发中用得非常少,这里不予讲述)

常见面试题:|和||,&和&&的区别

当|和&应用于逻辑运算时,得到的结果同||和&&是一致的。但是后者是有短路操作进行优化的。具体来说,是指:在组合逻辑运算中,一旦能够得到最终结果,就立即终止运算,直接返回结果。比如:

  • 在逻辑或运算中,只要有一个条件为真,整个运算结果必然为真;
  • 在逻辑且运算中,只要有一个条件为假,整个运算结果必然为假,

就不用再计算后面的条件了。

static bool condition1() { Console.WriteLine("condition1()..."); return true; //或者 false } static bool condition2() { Console.WriteLine("condition2()..."); return false; //或者 true } Console.WriteLine(condition1() | condition2()); Console.WriteLine(condition1() || condition2()); Console.WriteLine(condition1() & condition2()); Console.WriteLine(condition1() && condition2());

断点演示:短路运算时相应condition不会被运行……

规律

如果所有参与运算成员的数值都是:

  • 2的整数次方,比如1、2、4、8、16……,或者
  • 2的整数次方数之和,比如3(=1+2)、6(=2+4)、7(=1+2+4)……

他们就会有如下规律:

  • 位或(|)相当于“加”,比如:2|4=6,2|4|1=7……,以下我们将位或(相加)的结果简称为“位或结果”,参与位或运算的成员简称为“成员”,未参与运算的称之为“非成员”
  • 然后,将位或结果和任一成员进行异或(^)运算,其效果就相当于“减”,比如:6^2=4,7^4=3,
  • 最后,将位或结果和任一成员进行位与(&)运算,其结果就等于该成员,比如:6&2=2,7&4=4;且与任何非成进行位与(&)运算,其结果不会等于该非成员,比如:6&1=0,7&8=0

严格的数学证明,飞哥也不会,^_^,同学们就这样记住结论吧……

@想一想@,这可以用来干嘛?

作用

进行权限管理啊!

比如源栈的学生,可以是:

  • 学生(Student),还可以是
  • 老师助理(TeacherAssist),以及
  • 小组长(TeamLeader)和
  • 寝室长(DormitoryHead)

每个同学可以是上面一种或多种角色,而且可以随时增减。让你在Student类中添加一个属性记录同学的角色,你怎么办?常见的会用数组(或者以后我们会学习的集合),但是,更“专业”更有“逼格”的方法是:

使用枚举(即整数)代表角色,如下所示:

enum Role { Student = 1, TeacherAssist = 2, TeamLeader = 4, DormitoryHead = 8 }//注意所有枚举值必须是2的整数次方

然后对他们进行位运算:

  • | :添加权限
  • ^:剥夺权限
  • &:检测用户是否具有某项权限

    //Role为int类型 public int Role { get; set; } Student tlzz = new Student { Role = (int)Role.Student | (int)Role.TeamLeader }; tlzz.Role = tlzz.Role | (int)Role.DormitoryHead; tlzz.Role = tlzz.Role ^ (int)Role.Student; Console.WriteLine((tlzz.Role & (int)Role.TeamLeader) == (int)Role.TeamLeader);

作业:

  1. 声明一个令牌(Token)枚举,包含值:SuperAdmin、Admin、Blogger、Newbie、Registered。
  2. 声明一个令牌管理(TokenManager)类:
  3. 使用私有的Token枚举_tokens存储所具有的权限
  4. 暴露Add(Token)、Remove(Token)和Has(Token)方法,可以添加、删除和判断其有无某个权限
  5. User类中添加一个Tokens属性,类型为TokenManager

你可能感兴趣的:(c#)