玩编程,你得记住这么一条:只要一门语言是图灵完备的,那么它当然就能够做到任何事。
没错。图灵完备=万能。无非是有时候你需要绕个弯子、有时候又需要额外付出点性能或别的什么代价而已。
C/C++系:函数名本身就是一个函数指针,可以放进函数数组。你的需求已经被直接满足。
所有面向对象语言:多态本来就是用来做这个的。你只要让所有角色都实现同一个接口,使得它们都能提供一个攻击方法即可(注意每个角色的攻击方法可以有不同实现,比如火球、暗影箭之类),然后这样调用就够了:
foreach 角色 in 角色数组: 角色.攻击()
面向过程但又不支持函数指针的老奶奶语言(比如BASIC):你需要使用一个叫做dispatch的思路。
//把技能ID和执行这个技能的函数联系起来 bool dispatchSpell(spellID id) { switch(id){ case spellFireball: return Fireball(); case spellFirebolt: return Firebolt(); //依此类推,列出其他法术 ... //如果法术id非法,返回false default: ASSERT(false); //debug版还是直接崩掉吧 return false; } } //给角色数据结构里面声明一个defaultSpell变量: roleType { ... spellID defaultSpell; ... } //现在,直接循环调用角色数组里面每个角色的defaultSpell: foreach role in rolielist: dispatchSpell(role.defaultSpell);
各种语言的各种“奇技淫巧”,其他答主提到的更多。
总之就一句话,只要语言是图灵完备的,那么你需要的各种程序内效果就一定有办法实现,只是未必那么自动化而已。
或者说,如果你需要自动语法检查之类辅助性功能,那么你或者等编译器支持,或者就只能通过第三方程序实现;但在程序里面,你实现的一切功能,只要它是可计算的,那么就一定有办法实现。
唯一的问题,就是你自己知不知道该怎么实现、知不知道常见问题的更优化的解决方案、能否因地制宜的创造出最适合自己所面对的问题的最优化解决方案——你需要冷静、睿智的通盘考虑问题,千万别陷在局部细节中。尤其不要只看到那些细节上的好处。它们不仅不解决问题,很多时候反而是搅乱项目的行家里手。
举例来说,玩多了面向对象的,很容易直觉的“用继承来消除if语句”;然后呢,不同角色从角色基类继承;法师术士都是施法者,都要从“施法者”继承……如果这样使得你需要写if,那么就说明你需要增加继承层次,直到所有的if被消除……
很遗憾。但这是胡闹。
事实上,鼓吹这些的人压根就没长程序员脑子。
要称得上“程序员脑子”,你需要看透问题,不要浮在“法师术士牧师都是施法者”这样的表面。
事实上,在计算机里面,“施法”是什么呢?
施法是这样一件事:
1、施法往往需要消耗一些资源(魔力、能量、怒气、体力、hp等)
不管这些资源叫什么,它就是一个数字,保存于一个变量;消耗资源就是变量减去一个值。
2、施法可能需要某些先决条件(比如被施法者的状态限制:浮空,特定buf/debuf,和施法者之间的距离等)
3、施法需要角色执行一些动画(骨骼动画,典型如unity的avatar)
4、施法需要一些光影效果(手部发光/气团、面部表情、眼睛发光等)
5、施法会制造一些游戏物体(抛射体、地面动画、粒子效果等)
6、施法会有一些影响范围(单一目标、多目标、传染等)
7、被法术影响的对象会出现一些状态改变(扣血、扣蓝、加血、加速、减速等等,归根结底是修改了角色的某个变量所存储的数值)
……
基本就是这样。
那么,一个施法框架应该是这样:
bool cast(spellID id) { //根据法术id读取规则 spellConfig = loadSpellConfig(id); //执行资源检定操作 resourceCheck(id); //判断先决条件 ... //执行角色动画 ... //处理光影效果 ... //制造法术实体(火球、暴风雪、子弹乃至刀光剑影,全都可以是法术实体) ... //计算影响范围 ... //被击中目标效果处理(身上带火、呼叫、乱跑等) ... //伤害计算 ... }
然后,这些效果可以整理成若干种出来、存入数据库(所谓的技能数据化);那么无论你能想出多少个法术、如何千变万化,这套框架都可以无需修改的支持下来——反正就是那么几个效果的排列组合嘛,至多法阵啥的需要换张图片。那么,以后无论调什么,改数据库就完了,何必动程序代码呢。
类似的,当你不再把法术释放的细节和角色绑定之后,你的要求就更容易完成了:
foreach role in roleList { cast(role.defaultSpell); }
动用某个技术的目的是让程序更好写、更井井有条、更能应对需求变更。
不妨想一想,看看是这个方案更简单、更清爽、更稳固呢;还是那些“面向对象带师”们忽悠你的、用海量继承消除if的方案更不浪费生命。
绝大多数编程语言根本没有你说的“为什么不能把函数当做一个变量”这种问题,这个问题大多数情况下和语言本身没有什么关系,单纯的是个人技艺达不达标的问题。我们以常见的C语言为例,把一个函数当作一个变量使用在日常编程中简直再正常不过了。比如:
#include <stdio.h> #include <unistd.h> struct FuncTable { void (*begin)(void); void (*run)(unsigned int s); void (*end)(void); }; void begin_f() { printf("Begin
"); } void run_f(unsigned int s) { printf("Run %d seconds
", s); sleep(s); } void end_f() { printf("End
"); } int main(int argc, char *argv[]) { struct FuncTable ft = { .begin = begin_f, .run = run_f, .end = end_f, }; ft.begin(); ft.run(3); ft.end(); return 0; }
编译执行:
$ gcc -o mytest mytest.c -Wall $ ./mytest Begin Run 3 seconds End
再说你描述中的例子,又是在纠结if...else的问题。首先面对多英雄不同技能的这样一个需求,我们首先想到的是面向对象的编程思想,即总体英雄角色这个大类以及每个角色不同所继承和派生出的子类以及对象不同。用C++等面向对象的语言我们都知道很容易实现,那要是用不是面向对象语言的C语言呢?
我们举个简单的例子,假设所有英雄都可以做移动Move, 普通攻击Hit,和一个技能SkillA。然后每个不同的英雄可能有不同的技能和攻击范围,比如Karma这个英雄有一个SkillB的特别技能,还有一个叫Jax的英雄攻击都带范围攻击,且他也有一个特别的技能SkillB(区别于Karma的SkillB)。
这样的需求你怎么实现?难道要用一堆if...else吗?
我简单的实现了下面一个例子,来用C语言模拟一次简单的面向对象的编程。首先我们要有一个名为Hero的大类,这个大类里有英雄的名字,有一些功能方法,有可供使用的英雄数据。我们简单的让每个英雄都能有四个基础功能——设置移动/攻击坐标,移动,攻击,基础技能攻击,如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> /* abstract interface declaration */ struct Hero { char name[256]; void *funcTable; void *privateData; }; struct HeroFuncTable { void (*SetTarget)(struct Hero * obj, int x, int y); void (*Move)(struct Hero * obj); void (*Hit)(struct Hero * obj); void (*SkillA)(struct Hero * obj); }; struct HeroPrivateData { int x, y; }; void HeroSetTarget(struct Hero * obj, int newx, int newy) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; rdata->x = newx; rdata->y = newy; } void HeroMove(struct Hero * obj) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; printf("%s move to: [%d, %d]
", obj->name, rdata->x, rdata->y); } void HeroHit(struct Hero * obj) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; printf("%s hit target at: [%d, %d]
", obj->name, rdata->x, rdata->y); } void HeroSkillA (struct Hero * obj) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; printf("%s is using skillA on: [%d, %d]
", obj->name, rdata->x, rdata->y); }
然后我们现在需要一个叫Karma的英雄,他除了具备基本英雄技能以外还有一个自己特有的SkillB技能:
/* Class Karma */ void KarmaSkillB (struct Hero *obj, int arg) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; printf("Karma is using skillB * %d at: [%d, %d]
", arg, rdata->x, rdata->y); } struct KarmaFuncTable { void (*SetTarget)(struct Hero * obj, int x, int y); void (*Move)(struct Hero * obj); void (*Hit)(struct Hero * obj); void (*SkillA)(struct Hero * obj); void (*SkillB)(struct Hero * obj, int arg); } karmaFuncTable = { HeroSetTarget, HeroMove, HeroHit, HeroSkillA, KarmaSkillB }; struct Hero * MakeKarma (int initx, int inity) { struct Hero * obj = malloc (sizeof(struct Hero)); struct HeroPrivateData * rdata = malloc (sizeof(struct HeroPrivateData)); strcpy(obj->name, "Karma"); obj->funcTable = (struct KarmaFuncTable*) &karmaFuncTable; obj->privateData = rdata; rdata->x = initx; rdata->y = inity; return obj; }
如上代码,我们让Karma继承所有的Hero的方法和类型,然后在给他提供一个SkillB的特殊技能,最后我们提供一个Karma英雄的构造函数。
下面我们再给出一个Jax英雄,让Jax英雄拥有范围攻击效果,且也有一个特殊技能:
/* Class Jax */ struct JaxPrivateData { int x, y; int range; }; void JaxSetRange(struct Hero * obj, int rg) { struct JaxPrivateData * rdata = (struct JaxPrivateData*)obj->privateData; rdata->range = rg; } void JaxHit(struct Hero * obj) { struct JaxPrivateData * rdata = (struct JaxPrivateData*)obj->privateData; printf("Jax hit target at: %d[%d, %d]
", rdata->range, rdata->x, rdata->y); } void JaxSkillA(struct Hero * obj) { struct JaxPrivateData * rdata = (struct JaxPrivateData*)obj->privateData; printf("Jax is using SkillA at: %d[%d, %d]
", rdata->range, rdata->x, rdata->y); } void JaxSkillB(struct Hero * obj) { struct JaxPrivateData * rdata = (struct JaxPrivateData*)obj->privateData; printf("Jax is using SkillB at: %d[%d, %d]
", rdata->range, rdata->x, rdata->y); } struct JaxFuncTable { void (*SetTarget)(struct Hero * obj, int x, int y); void (*SetRange)(struct Hero * obj, int rg); void (*Move)(struct Hero * obj); void (*Hit)(struct Hero * obj); void (*SkillA)(struct Hero * obj); void (*SkillB)(struct Hero * obj); } jaxFuncTable = { HeroSetTarget, JaxSetRange, HeroMove, JaxHit, JaxSkillA, JaxSkillB }; struct Hero * MakeJax (int initx, int inity, int initr) { struct Hero * obj = malloc (sizeof(struct Hero)); struct JaxPrivateData * rdata = malloc (sizeof(struct JaxPrivateData)); strcpy(obj->name, "Jax"); obj->funcTable = (struct JaxFuncTable*) &jaxFuncTable; obj->privateData = rdata; rdata->x = initx; rdata->y = inity; rdata->range = initr; return obj; }
如上,我们实现了Jax英雄类,我们让他继承了SetTarget和Move的方法,同时因为其特有的范围攻击我们给它提供一个额外的range成员变量,并提供了一个SetRange的方法,接着重构了其普通攻击和技能攻击的方法,让这些攻击都带范围效果,然后还实现了一个他特有的SkillB技能,最后我们给出一个Jax英雄的构造函数。
来让我们测试一下上面的代码:
/* Do some test */ void KarmaDoSomething(struct Hero *h) { struct KarmaFuncTable *f = h->funcTable; f->SetTarget(h, 10, 20); f->Move(h); f->Hit(h); f->SkillA(h); f->SkillB(h, 100); } void JaxDoSomething(struct Hero *h) { struct JaxFuncTable *f = h->funcTable; f->SetTarget(h, 50, 88); f->SetRange(h, 8); f->Move(h); f->Hit(h); f->SkillA(h); f->SkillB(h); } int main(int argc, char *argv[]) { struct Hero * heros[2]; heros[0] = MakeKarma(0, 0); heros[1] = MakeJax(0, 0, 5); printf("-- Welcome Karma do something for us --
"); KarmaDoSomething(heros[0]); printf("
"); printf("-- Welcome Jax do something for us --
"); JaxDoSomething(heros[1]); return 0; }
如上,我们通过funcTable的类型转换,实现一个多态的效果,让Hero去使用它自己的方法集合。我们看一下执行效果:
$ gcc -o hero hero.c -Wall $ ./hero -- Welcome Karma do something for us -- Karma move to: [10, 20] Karma hit target at: [10, 20] Karma is using skillA on: [10, 20] Karma is using skillB * 100 at: [10, 20] -- Welcome Jax do something for us -- Jax move to: [50, 88] Jax hit target at: 8[50, 88] Jax is using SkillA at: 8[50, 88] Jax is using SkillB at: 8[50, 88]
可以看出不同的英雄展现出了不同的动作和技能。
当然这只是我花了二十分钟写的一个极其简单的程序,实际的项目设计肯定要比这个复杂的多,而且也能比我这个写的好的多。我这里只是想告诉一些初学者,很多初学者钻牛角尖的“复杂问题”往往是因为知识还没有学到位而陷入的自我纠结。我已经碰到过很多人以不同形式问过我类似优化if...else...、优化判断条件、优化内存结构……等等等等的问题。这些自己以为自己正在面对架构、优化等问题的问题,很多时候根本不是你以为的做架构和优化的样子。
学习更多的计算机知识,多阅读优秀的项目代码,很多时候回看很多问题时才会知道自己当时根本只是维度还不够高而过早的陷入高维度的纠结而已,包括我自己也是经常这样。
这个问题不应该用面向对象的多态来解决吗?还要自己写一堆的if else?
任何一种现代编程语言,哪怕是c,都有现成的解决方案。
你这种需求,在OO就用多态,在FP就用模式匹配,只有在命令式,才需要if...else。
而if...else和函数能不能当变量用也没有半毛钱关系。在OO和FP的解决方案都不需要把函数当变量来用。本质上来说,你的想法里面两种代码是同构的。因为就算你把函数放个数组里面,这个数组不需要初始化?初始化代码和if else是同构的。
初始化代码可能长这样:
skill[0] = function() { xxx }; skill[1] = function() { xxx }; skill[2] = function() { xxx };
但事实上,这等价于:
if ( a == 0 ) xxx else if ( a == 1 ) xxx else if ( a == 2 ) xxx
可以看出来两者的抽象结构是一样的。
其实从编程的角度,这种设计是需要严格禁止的。
换句话说:外表相似但实质表现不同的函数,我们需要把它的接口或者名称有意做得不同,避免误用。
所以:这个商品的设计师,它肯定不懂编程。
如果是我,可能会刻意的改变USB插座的位置布局,让它看起来长得不一样。——至少改变一下USB插口的颜色。
--
老插线板名义上是 5V2A 的 10W 插线板,实际上是单口输出最大2A,多口同时输出的情况下,它的实际表现大约是 5V1A,也就是苹果那个万年诟病充电头的水平。——换句话说,旧插线板的USB接口,几乎是没有什么用处的鸡肋。
新插线板,号称单口27W,多口的情况下肯定会缩水,但我猜测至少QC3.0快充是可以用的。相比旧板子应该说是从协议层面有了很大进步。一个典型的例子是接无线充电器不需要浪费充电头了。
解释一下:小米目前的无线充是 30W,需要30W的充电头,但商城只有 18W 跟 65W 的充电头出售,小米商城根本没有适配无线充的 30W 充电头。如果插这个接线板,大概能当 27W 用吧?
我是一名基层派出所民警。
可以说当今中国警察普遍羡慕美国警察可以采取暴力手段绝对的镇压不法分子。
但是,不得不说,这次这位美国警察,太过分了,不仅是过分,而且我的理解是那已经构成了犯罪行为。那黑人已经制服了就可以正常上拷带走了,没必要一直压着脖子压那么长时间。没能置身其中不知现场那美国警察的所思所想,反正我个人挺不理解他为啥那样干的。
只能说无论什么地方,无论什么行业,只要是人的社会,都有像样的也有操蛋的吧。
_________此处为分割线 _________
以下为统一答复评论中有些人质疑的我所讲的羡慕二字。
能够出现这种质疑在我料想之中,因为中国警察也有过过分的时代,据我所知就是在七十八十九十年代,就如同地痞流氓,看谁不顺眼就能打谁对老百姓而言没王法可讲,那时候的警察说好听点可以说是威风凛凛说难听点儿是横行霸道。
但我想表明的是,时过境迁,现在的中国警察无论是受舆论约束还是因为法治社会建设制度规范都已经变得逐步文明与规范起来,起码我认为从我们现在开始从公安司法院校毕业参加公务员考试考进来的新一代警察已经具备新的面目,当然不可否认的是在这个行业内目前仍然存有历史的顽疾,仍然存在着臭虫,但我已经讲过无论什么行业都有操蛋的吧,这是个人问题,不是群体问题。相比之下,拍拍良心看,现在的整个警察队伍比照曾经确实过分的年代是不是已经是天地之别,问问曾经真正挨过曾经年代老警察欺负的中老年人就知道了。
为何会说起羡慕,因为警察每天面对的人群,大多是三教九流之辈,没有武力加身,很多事情在处理上警察显得软弱无能,说白了,好人谁没事儿上派出所转悠啊都忙着自己的生活呢,警察打人这句话,我们常常听到,但是但凡有点脑袋的人都能想明白,警察会闲着没事儿干把那在家里消停待着的遵纪守法的人抓起来暴揍一顿吗?
以上言辞不免更会有人质疑,请允许我解释,武力,当然不可滥用,我所说的羡慕不是羡慕美国警察的随意滥用武力,而是在合法范围内准许在对方不听从警察指令时动用武力,现在确实有人民警察法赋予了相关权力,但实践中现在的中国警察并不能或者说不敢执行人民警察法里的所有权力。拿防疫工作举例,卡口的工作人员在让出入的人员扫码登记时,就会有不愿意配合的人,然而这些不愿意配合的人可会知道工作人员的所做所为是为了整个社区的稳定安全,因为这整个社区包括了这名不愿意配合的人啊,在这个时候是否应当对其进行武力控制来保障其他居民的安全呢。同理,警察盘查也好,调查也好,总会有那些不愿意配合的人,自我感觉良好认为自己没问题所以警察不必要对其进行盘查所以就不配合,而警察当看到对方不配合时会以什么视角审视,难道要说谢谢您的不配合吗,万一这不愿配合的人真背着案子呢,那便是对更多的人民群众的不负责任。因此,我要说,民众的素质如果真正达到了人人互相敬重路不拾遗夜不闭户的文明程度,要求警察绝对文明不要有暴力举动,一点问题没有,一味强调了警察不该暴力执法而分毫不过问被执法对象自身是否存在问题,是不是看问题的角度些微的片面了些。
请注意,我说羡慕里的那句话尾巴实际已经表明了,羡慕的是暴力手段对不法分子的镇压,可不是对遵纪守法的百姓也要肆意妄为。例如像给群众办个身份证居住证之类的业务,警察当然应该热心服务。但当面对泼皮无赖时,还要笑脸相迎,得来的只有蹬鼻子上脸,警察都不怕了,您们认为这些无赖还有谁管得了。
列位存有异议的同志们,谢谢您们的教诲。言辞中犀利的同志们,谢谢您们的敦促。
让我知道当警察,需要吾日三省吾身。
还想要质疑甚或是骂的您们,若是能让您舒服,骂两句无妨。我不算您辱骂警察。不过是,道不同不相为谋罢了吧。
_____分割线
2020年6月5日22:53 出警在路上