http://blog.csdn.net/fyyy4030/article/details/8467043

卓越的教练是如何训练高手的?

   编者按:我们知道,优秀的运动员除了自身的天赋和努力之外,出色的教练必不可少。一个成功的企业除了拥有出类拔萃的员工之外,同样需要一位出色的教练,那就是企业的CEO。由此可见,如果我们要想成为一位优秀的程序员,毫无疑问卓越的教练是致关重要的。无数事实告诉我们,成功者之所以成功,是因为成功地选择导师的缘故。

周立功公司之所以在一定程度上取得了一些成功,绝对不是运气所为,除了不断努力选拔优秀人才之外,且不惜代价投入人力和资本培训员工,更重要的是有效的方法,从而保证了初学者的投入与产出的最佳效果,让具备一定潜质的应届大学生脱颖而出。

优秀与平庸全在一念之间,关键在于你的首次择业而非就业,良好的习惯与平和的心态跟人们的第一份工作和第一位导师有很大的关系。当下的利益固然重要,但从某种程度上来看,机会、环境、远景比眼前利益更重要,因为机会、环境和远景等因素会最大限度地将你锻造成为一个在未来有绝对竞争力的人才。同样是赚钱,你是否参与了公司由默默无闻迈向卓越的过程?你是否收获了更多的快乐?你是否找到了前所未有的成就感?而很多人仅仅赚到了一些钱,幸福而不快乐。

你将希望成为哪一类人,何去何从?最初的选择尤其重要。

   本文是我们训练程序员的系列文章之一,仅供参考!

这几年在全国巡回招聘应届毕业生的过程中,经常会遇到这样的现象:有些同学对自己的笔试比较满意,可是最后却得不到面试的机会,心里大为不解,颇有“死不瞑目”的味道。那么问题到底出现在哪里呢?让我们来看一个例子,这是我们招聘过程中一道常见的题目。

  写一个函数,完成内存移动,并为其写一个简单的测试用例来进行测试。

够简单的吧?有的同学很快就写出了答案,详见程序清单1与程序清单2。

程序清单 1   V0.1版程序

void MyMemMove(char *dst,char *src,int count)

{

while(count--)

{

*dst++ = *src++;

}

}

程序清单 2   测试用例

void Test()

{

charp1[256] = ”hello,world!”;

charp2[256] = {0};

MyMemMove(p2,p1,strlen(p1));

printf(“%s”,p2);

}

客观地讲,相比那些交白卷或者函数声明都不会写的同学来说,能够写出这段代码的同学已经非常不错了,至少在C语言这门课程上已经达到了现行高校的教育目标,但是离企业的用人要求还有一定的距离。我们不妨将上面的程序称为V0.1版本,看看还有没有什么地方可以改进。

首先我们看看函数声明是否合理,V0.1版的程序将源地址和目的地址都用char*来表示,这样当然也没有什么问题,但是让其他人使用起来却很不方便,假如现在要将count个连续的结构体对象移动到另外一个地方去,如果要使用v0.1的程序的话,正确的写法如下:

MyMemMove((char *)dst,(char *)src,sizeof(TheStruct)*count)

也就是说我们需要将结构体指针强制转换成char *才能够正常工作,这样除了字符串以外其它的类型都不可避免地要进行指针强制转换,否则编译器就会呱呱叫,比如在VC++2008下就会出现这样的错误:

errorC2664: 'MyMemMove' : cannot convert parameter 1 from 'TheStruct *'to 'char *'

那么如何解决这个问题呢?其实很简单,我们知道有一种特别的指针,任何类型的指针都可以对它赋值,那就是void*,所以应该将源地址和目的地址都用void*来表示。当然函数体的内容也要作相应的改变,这样我们就得到了V0.2版的程序。

程序清单 3   V0.2版程序

void MyMemMove(void *dst,void *src,int count)

{

while(count--)

{

*(char*)dst = *(char *)src;

dst= (char *)dst + 1;

src= (char *)src + 1;

}

}

有的同学可能会问,这里面不是还有指针强制转换吗?只不过是换了地方。没错,强制指针转换确实是从使用者的代码转移到了库的代码里,但我们可以将MyMemMove理解为库,而将Test理解为使用者,事实上通过调整之后的效果却有天壤之别,V0.1是一逸永劳,而V0.2是一劳永逸!

还有几个细节需要注意,为了实现链式表达式,我们应该将返回值也改为void*。此外,如果我们不小心将“*(char *)dst = *(char *)src;”写反了,写成“*(char *)src =*(char*)dst;”编译照样通过,而为了找出这个错误又得花费不少时间。注意到src所指向的内容在这个函数内不应该被改变,所有对src所指的内容赋值都应该被禁止,所以这个参数应该用const修饰,如果有类似的错误在编译时就能够被发现:

errorC3892: 'src' : you cannot assign to a variable that is const

作为程序员犯错误在所难免,但是我们可以利用相对难犯错误的机器,也就是编译器来降低犯错误的概率,这样我们就得到了V0.3版的程序。

程序清单 4  V0.3版程序

void * MyMemMove(void *dst,const void *src,int count)

{

void*ret=dst;

while(count--)

{

*(char *)dst = *(char *)src;

dst = (char *)dst + 1;

src = (char *)src + 1;

}

returnret;

}

现在再来考虑这样一种情况,有使用者这样调用库:MyMemMove(NULL,src,count),这是完全可能的,因为一般来说这些地址都是程序计算出来的,那就难免会算错,出现零地址或者其它的非法地址也不足为奇。可以预料的是,如果出现这种情况的话,则程序马上就会down掉,更糟糕的是你不知道错误出在哪里,于是不得不投入大量的精力在浩瀚的代码中寻找bug。解决这类问题的通用办法是对输入参数作合法性检查,也就是V0.4版程序。

程序清单 5  V0.4版程序

void * MyMemMove(void *dst,const void *src,int count)

{

void*ret=dst;

if(NULL==dst||NULL ==src)

{

returndst;

}

while(count--)

{

*(char*)dst = *(char *)src;

dst = (char *)dst + 1;

src= (char *)src + 1;

}

returnret;

}

上面之所以写成“if(NULL==dst||NULL ==src)”而不是写成“if (dst == NULL || src ==NULL)”,也是为了降低犯错误的概率。我们知道,在C语言里面“==”和“=”都是合法的运算符,如果我们不小心写成了“if (dst= NULL || src = NULL)”还是可以编译通过,而意思却完全不一样了,但是如果写成“if (NULL=dst||NULL=src)”,则编译的时候就通不过了,所以我们要养成良好的程序设计习惯:常量与变量作条件判断时应该把常量写在前面

V0.4版的代码首先对参数进行合法性检查,如果不合法就直接返回,这样虽然程序dwon掉的可能性降低了,但是性能却大打折扣了,因为每次调用都会进行一次判断,特别是频繁的调用和性能要求比较高的场合,它在性能上的损失就不可小觑。

如果通过长期的严格测试,能够保证使用者不会使用零地址作为参数调用MyMemMove函数,则希望有简单的方法关掉参数合法性检查。我们知道宏就有这种开关的作用,所以V0.5版程序也就出来了。

程序清单 6  V0.5版程序

void * MyMemMove(void *dst,const void *src,int count)

{

void*ret=dst;

#ifdef DEBUG

if(NULL==dst||NULL ==src)

{

returndst;

}

#endif

while(count--)

{

*(char*)dst = *(char *)src;

dst= (char *)dst + 1;

src= (char *)src + 1;

}

return ret;

}

如果在调试时我们加入“#define DEBUG”语句,增强程序的健壮性,那么在调试通过后我们再改为“#undefDEBUG”语句,提高程序的性能。事实上在标准库里已经存在类似功能的宏:assert,而且更加好用,它还可以在定义DEBUG时指出代码在那一行检查失败,而在没有定义DEBUG时完全可以把它当作不存在。assert(_expression_r_r)的使用非常简单,当_expression_r_r为0时,调试器就可以出现一个调试错误,有了这个好东西代码就容易多了。

程序清单 7  V0.6版程序

void * MyMemMove(void *dst,const void *src,int count)

{

assert(dst);

assert(src);

void*ret=dst;

while(count--)

{

*(char*)dst = *(char *)src;

dst= (char *)dst + 1;

src= (char *)src + 1;

}

returnret;

}

一旦调用者的两个指针参数其中一个为零,就会出现如图1所示的错误,而且指示了哪一行非常容易查错。

图 1  assert(NULL)时,显示错误

到目前为止,在语言层面上,我们的程序基本上没有什么问题了,那么是否真的就没有问题了呢?这就要求程序员从逻辑上考虑了,这也是优秀程序员必须具备的素质,那就是思维的严谨性,否则程序就会有非常隐藏的bug,就这个例子来说,如果用户用下面的代码来调用你的程序。

程序清单 8  重叠的内存测试

void Test()

{

charp [256]= "hello,world!";

MyMemMove(p+1,p,strlen(p)+1);

printf("%s\n",p);

}

如果你身边有电脑,你可以试一下,你会发现输出并不是我们期待的“hhello,world!”(在“helloworld!”前加个h),而是“hhhhhhhhhhhhhh”,这是什么原因呢?原因出在源地址区间和目的地址区间有重叠的地方,V0.6版的程序无意之中将源地址区间的内容修改了!有些反映快的同学马上会说我从高地址开始拷贝。粗略地看,似乎能解决这个问题,虽然区间是重叠了,但是在修改以前已经拷贝了,所以不影响结果。但是仔细一想,这其实是犯了和上面一样的思维不严谨的错误,因为用户这样调用还是会出错:

MyMemMove( p, p+1, strlen(p)+1);

所以最完美的解决方案还是判断源地址和目的地址的大小,才决定到底是从高地址开始拷贝还是低地址开始拷贝,所以V0.7顺利成章地出来了。

程序清单 9  V0.7版程序

void * MyMemMove(void *dst,const void *src,int count)

{

assert(dst);

assert(src);

void* ret = dst;

if(dst <= src || (char *)dst >= ((char*)src + count)) {

while (count--) {

*(char *)dst = *(char *)src;

dst = (char *)dst + 1;

src = (char *)src + 1;

}

}

else{

dst = (char *)dst + count - 1;

src = (char *)src + count - 1;

while (count--) {

*(char *)dst = *(char *)src;

dst = (char *)dst - 1;

src = (char *)src - 1;

}

}

return(ret);

}

经过以上7个版本的修改,我们的程序终于可以算是“工业级”了。回头再来看看前面的测试用例,就会发现那根本就算不上是测试用例,因为它只调用了最正常的一种情况,根本达不到测试的目的。有了上面的经历,测试用例也就相应地出现了,我们不妨用字符数组来模拟内存。

程序清单 10  相对全面的测试用例

void Test()

{

charp1[256] = "hello,world!";

charp2[256] = {0};

MyMemMove(p2,p1,strlen(p1)+1);

printf("%s\n",p2);

MyMemMove(NULL,p1,strlen(p1)+1);

MyMemMove(p2,NULL,strlen(p1)+1);

MyMemMove(p1+1,p1,strlen(p1)+1);

printf("%s\n",p1);

MyMemMove(p1,p1+1,strlen(p1)+1);

printf("%s\n",p1);

}

初写代码的时候,往往考虑的是程序正常工作的情况该怎么处理。当你有了几年经验,写了几万行代码后就会发现,处理异常部分的分支代码有时比正常的主干线代码还要多,而这也正是高质量程序和一般程序拉开差距的地方。如果把软件产品当作一台机器,那么这样一个个细小的函数和类就是零部件,只有当这些零部件质量都很高时,整个软件产品的质量才会高,不然就会像前几年的国产轿车一样,今天这个零件罢工明天那个零件休息。而作为检验这些零部件的测试用例,一定要模拟各种恶劣的环境,将零部件隐藏的缺陷暴露出来,从这意义上说,编写测试用例的程序员要比软件设计的程序员思维要更严谨才行。

卓越的教练是如何训练高手的相关推荐

  1. “做教练”之好声音训练

    "做教练"之好声音训练 人的音质大部分是天生的,这就是为什么音乐学院.戏剧学院.电影学院等艺术院校在选材时会挑嗓音好的学生的原因.我们一般人也是可以通过后天习得获得自己最好的声音的 ...

  2. PCoE卓越诊断学中心——NASA共享训练数据的资源站

      艾姆斯研究中心的卓越诊断学中心(PCoE)为预测技术的发展提供了保护伞,专门解决了航空和太空探索应用领域内的预测技术空白. 主站内容   各种预后技术开发途径中的共同点是在组件级别研究故障物理.在 ...

  3. 计算机科学精彩帖子收集

    linux源码 LXR 源自"the Linux Cross Referencer",中间的"X"形象地代表了"Cross".与 Sourc ...

  4. strcpy函数的实现

    大家一般觉得名不见经传strcpy函数实现不是非常难,流行的strcpy函数写法是: char *my_strcpy(char *dst,const char *src) {assert(dst != ...

  5. 为什么有人工作10年仍然平庸,有人2年就足够卓越?

    为什么有人工作10年仍然平庸,有人2年就足够卓越? 我们身边不乏这样的人,他们工作勤奋,也经常学习,有的甚至在一个岗位上有超过10年的工作经验,但是为什么成就依然平平? 我们也时常看到这样的人,他们年 ...

  6. 卷积神经网络 训练的结果是什么_射击训练:卷积神经网络识别解剖结构标志位点...

    本文2100字,阅读大约需要7分钟 在医学图像分析中,我们需要识别解剖结构的标志位点,识别出的位点可用于临床分析以及解剖结构分割等用途.深度学习的发展,给这样的方法带来了一些新的可能,已有不少的研究. ...

  7. facenet训练自己的数据_①如何帮助自己简易分析体测数据②没有私教一个人无法开始训练?...

    我们进健身房后,办卡以后,会有教练联系免费帮你做检测,检测后会拿出一张纸,聊完天,你就拿出一张卡,抱着一两个月我就会有巨大改变的决心,你辛苦赚的万八千就消失了. 这张纸真的有这么神奇吗? 只有健身教练 ...

  8. 高绩效教练收获_高绩效呼叫中心怎样诞生的?

    高绩效呼叫中心 所具有的共同点是什么?它们都拥有专注于企业使命和愿景,知道如何使员工尽情投入.创造最佳绩效的强有力的领导者. 呼叫中心管理者的技能要求近些年有哪些改变?要想在他们的岗位上取得成功,他们 ...

  9. 【qscoj】伊苏比的梦幻之旅之教练(直接+特殊+字符串)

    描述 梅西和C罗需要聘请一位教练对他们进行二人训练.梅西的能力值为a,C罗的能力值为b.教练1的训练效果值为二者能力值相加,即a+b.教练2的训练效果值为二者能力值相乘,即a*b.他们希望聘请一位训练 ...

最新文章

  1. vs2019 MFC 中 cannot open include file 'afxres.h' 问题解决方法
  2. 4e4 Coursework decomposition
  3. C++面向对象设计原则详解
  4. mysql 5.5 重新编译_源码编译mysql5.5过程记录
  5. 这次使用一个最舒服的姿势插入HttpClient拦截器技能点
  6. 算不算被虚度的这几年
  7. SpringBoot + Vue 开发前后端分离的旅游管理系统
  8. Jsrender初体验
  9. 软件测试典型缺陷分析,几种典型的软件缺陷分析方法
  10. matlab shapley函数,合作博弈shapley值讲解.ppt
  11. android4.4 计算器,卡西欧仿真计算器
  12. mac 安装ffmpeg以及各种编码器
  13. 定时刷新页面 html,js实现定时刷新页面的代码
  14. 孙氏太极拳--无极桩
  15. 【VUE】前端搜索引擎优化seo
  16. CSDN创作中心Markdown编辑器基本使用方法
  17. 重新启动oracle 服务,在linux重新启动下如何设置oracle服务自动开启
  18. 除了X站,程序员还喜欢上这些网站...
  19. 初学者指南:如何毫不费力地优化Jupyter笔记本?
  20. Hdu 4193 Non-negative Partial Sums (数据结构_单调队列)

热门文章

  1. 汽车音响AMP功放-扬声器诊断驱动开发
  2. Python爬虫之百度/360搜索关键字自动提交
  3. 一零零九、Docker搭建 全集群 环境配置
  4. 访问阿里云存储的图片URL实现在网页直接预览略缩图而不直接下载
  5. AXI总线信号含义说明
  6. 计算前缀表达式与后缀表达式
  7. 使用steamcommunity302加速github访问
  8. 基于微信小程序的宠物寄养平台小程序
  9. matlab读取串口数据并显示曲线
  10. windows7 防火墙关于文件共享的设置