第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流

     公园2003年,秋。
     西安。
     晴,黄历上写——易出行,交友。
     交大东二楼。
     笑容,邪邪的笑容浮现在他英俊自信骄傲的脸上……
     
     (以下转正常)
     今天是新硕士报道后的第一天,小P一早就高高兴兴的向教研室走去。“嗯,应该可以见到有趣的家伙吧。”他想。他很是期待见到新同学和结交新朋友。到Z老板办公室和老板打过招呼后,他向实验室走去。“听说一个工作了3年的家伙已经到了,不知道是不是邪恶大叔啊。”小正太一边走一边邪恶的想着。推门进去发现一个高大沉稳老练的男人正在收拾电脑桌,呵呵,他的美人尖还透露出他的智慧。
     “你好,我叫小P。”
     男人回过头:“呵呵,你好,我叫陈老C,你长得还真是年轻啊。”
     “呵呵,幼稚,幼稚而已。”
     “你是本科后直接上研的吧?”
     “是啊是啊,听说你工作了3年?”
     “没错,我是96级的,工作3年以后又回学校来啦……”
     ……
     “……我做过一些项目,有用C做的,有用C++做的……”老C和小P聊的不错,感觉还挺投缘的。
     “哦,我也用C写过一些小的程序,C++课程也学习过,成绩还挺不错的。”小P开始吹牛了,“C++是C语言在面向对象方面的发展……”
     “噢,这么说也没有什么错,开始的时候C++的确是C在面向对象方面的发展,但是……”老C开始给小P上课了,“现在C++其实是一个语言的联邦,在它的内部存在4种不同的语言……”
     “是吗?”
     “是的,这4种语言包括C——这是理所当然的——同时还包括面向对象部分,template部分和STL部分,每个部分都有其独特的考虑问题的方式,有着自己独到的习语和风格……”
     “习语?风格?”
     “是的。”老C解释道,“我们学习编程语言就像学习一般的语言一样——比如英语——如果你只是掌握了英语的基本语法你可以和英国人或者美国人自由的交流吗?”
     “我想应该不行吧,我想我可能不理解他们所说的……比如词汇……”小P回答道。
     “对,你有没有发现当我们即使掌握了某种语言的语法也无法和其他人进行有效的交流的时候,就是说你对某段语言所表达的意思不了解的时候,一般都是如何不了解的?”
     “哦?这个我还没有仔细考虑过。让我想想……”小P以手撑头,陷入沉思状……“我试着总结一下,应当是对以下几方面不了解。”小P开始喷了。
  1. 对语言的基本单元无法理解,在英语里面应当是词汇。
  2. 对句子无法理解,尤其是一些句型不了解,比如英语里面的too...to句型。
  3. 如果单词理解了,句子也理解了,但是还是无法理解一段语言,那么应当是思维方式与说这段话的人有距离。
     “没错!”老C称赞道,“你还真是聪明啊,这么短时间就可以总结个子丑寅卯出来。”
     “呵呵,哪里哪里,我就是比较油菜而已……”
     “哈哈,那我们就拿你的总结与C++对比一下吧。”
  1. 单词就像C++里面的惯用习语,比如virtual constructor或者PIMPL等
  2. 句型就像设计模式。
  3. 思维方法就是语言风格。
     “不懂,什么是virtual constructor和PIMPL?什么又是设计模式?语言风格又是什么东东?”小P开始怀疑老C是火星人,他说的是汉语吗?
     “没有关系,以后我们还有机会来讨论那些C++常用的习语,现在我们就几个常见的思维方式,也就是语言风格的问题举几个简单的例子吧。”老C从实验室的角落拉过来一块白板,又找出一支彩笔开始在白板上画了起来。
     “我这里现在有一个简单的问题,”老C在白板的上面写下几行文字。

设计一段代码,这段代码需要根据不同的输入在屏幕上打印对应的水果名称

     “能具体一些吗?”小P问。
     “可以啊,我写开始的一段代码,你补全如何?”
     “想考我吗?没有问题……呵呵。”
     老C开始在白板上写代码,“我们意思意思就可以了,在这里我们使用C语言,所以是C语言的风格”。
     “C?这个我还比较擅长。”小P现在有些想露一手。
     “好吧,看看我们如何完成。”老C刷刷几笔写下了如下代码。
typedef enum tagFRUIT{ORANGE, APPLE, BANANA} FRUIT;
void PrintFruitName(FRUIT fruit)
{
}
     “嗯,容易……”小P开始完成PrintFruitName()函数。
void PrintFruitName(FRUIT fruit)
{
    switch (
fruit)
    {
    case ORANGE:
        pirntf("orange\n");
    break;
   
    case APPLE:
        printf("apple\n");
    break;
   
    case BANANA:
        printf("banana\n");
    break;
   
    default:
        return;  
    }
}

      “好了,我们的算法是这样的,一个函数从某处——无论什么地方,键盘输入也好,某个配置文件也好——读到了需要输出的蔬果信息,然后调用我们的打印函数将水果名称打印出来。”老C说完写下了完整的main()函数。

int main()
{
    FRUIT fruit;
   
    /* Get the information of fruit. */
    fruit = GetFruitInfo();
    /* Print the name of the fruit. */
    PrintFruitName(fruit);

    return 0; 
}

     “Perfect!”小P高兴的说道。
     “嗯,只能说不错而已,完美还谈不上。”老C点点头,“如果我们希望添加一种新的水果到我们的系统里面,比如梨,pear,怎么办?”
     “好办啊,我只要在FRUIT的枚举类型中添加PEAR,然后在PrintFruitName()函数中添加一个case分支就可以了,有什么复杂的?”
     “呵呵,注意你刚才用了两个添加的词语,表示你有两次添加的动作……”
     “这又有什么不可以的呢?”
     “没有什么不可以,只和问题的规模有关,但是,”老C停顿一下,“这里我想强调的是语言风格的问题……”
     “?”小P开始集中注意力。
     “因为我们无法摆脱语言对思维方式的影响,反过来思维方式表现在代码上就是风格,”老C指指白板,“比如这个PrintFruitName()函数,就是一个很典型的C风格函数。”
     “为什么呢?”
     “因为它……”老C叹了口气,“我不知道怎么形容,只好说的陈词滥调一些——因为它面向过程。”老C挠挠头,“不,确切的说它掌握了太多的细节……”
     “听不懂……”小P有些囧。
     “好吧,那么让我们来换个角度看问题。假如你现在是个测试工程师……什么叫测试工程师?就是保证代码质量的……”老C面对一个技术幼齿有些郁闷,“他们按照一定的步骤对代码进行测试,包括检视代码、使用特殊输入数据检验代码质量等等……”老C开始结巴了,“你就认为是专门检查代码的算了。”
     “好吧,然后呢?”
     “假如你刚才已经检查过我们写下的所有代码,并认为它们都是正确的,这个时候有个家伙说因为需求变动需要增加PEAR到系统中,并且需要改动水果名称的输出格式,他刚刚更改了PrintFruitName()函数,需要你去做检查,你会怎么办?”
     “凉拌!让他把他改过的部分圈出来,我看他改的对不对。”
     “哦,这样会有问题的……”
     “什么问题?”
     “假如这个家伙是500/2,他在增加case PEAR:分支到BANANA下面的时候不小心删除了BANANA分支的break……”
     “饿滴神啊,这样在函数输入形参如果是BANANA,那么结果真是出人意料啊!”
     “是啊是啊,所以……”
     “所以如果是由我检查代码,我可不能听信那个家伙的话,只看他自以为修改过的部分,是吧。”小P开始有些理解了。
     “是啊,你必须把今天早上做过的所有检查再做一遍,来确保那个家伙没有捣乱……”
     “啊,那么重复的工作量岂不是很大?”小P开始觉得测试工程师个个都是双眼通红,走路带晃的ggmm,“那么我们有什么好办法呢?”
     “在C语言范围内还是有一些方法的……”老C说道,“只是都比较麻烦而已。”
     “是么?让我想想……”小P开始开动脑筋,几分钟后,他有些失望,“我想不出什么好方法,老C,你有什么办法?”
     “我们可以使用回调函数,或者使用指向函数的指针……”
     “?”小P眼睛成星星状,“怎么做?”
     老C开始挽袖子,并在白板上涂涂抹抹。

typedef enum tagFRUIT{ORANGE, APPLE, BANANA} FRUIT;
typedef void (*PRINT_PROC)(char*);
typedef struct tagFRUIT_INFO
{
    FRUIT               fruit_;   
   
const char* const   name_;
}FRUIT_INFO;

void DoPrintFruitName(char* name)
{
    printf("%s\n", name);
}

void DoPrintFruitNameCompany(char* name)
{
    printf("XJTU's %s\n", name);
}

const FRUIT_INFO g_fruitInfo[] =
{
    ORANGE,    "orange"
    ,APPLE,    "apple"
    ,BANANA,   "banana"
};

void
PrintFruitName(FRUIT fruit, PRINT_PROC printProc)
{
    int i;
   
    for (
         i = 0;
         i < (sizeof(g_fruitInfo)/sizeof(g_fruitInfo[0]));
         ++i
        )
    {
        if (g_fruitInfo[i].fruit_ == fruit)
        {
            (*printProc)(g_fruitInfo[i].name_);
        }
    }
}

int main()
{
    FRUIT fruit;
   
    /* Get the information of fruit. */
    fruit = GetFruitInfo();
    /* Print the name of the fruit. */
    if (/* Print fruit's company. */)
    {
        PrintFruitName(fruit,
DoPrintFruitNameCompany);
    }
    else
    {
        PrintFruitName(
fruit, DoPrintFruitName)
    }

    return 0;
}


     “这样,”陈老C解释道,“如果我需要增加PEAR到系统中,只需要在g_fruitInfo的表格中增加一行就可以了。而且程序可以根据外部的需求决定是否输输出产水果的公司,当然这些都是说明性的,用伪代码代替了。”
     “*^*”小P看的有些眩晕,“我有些头晕,像晕车……”
     “没有关系,只是你不熟悉罢了。”老C摸着下巴,“这就是我说的风格问题,因为思考问题的方式不同导致代码的风格看起一时难以接受而已。”
     “哦?看来只是习惯问题?那么我再看看……”克服了晕车般的头痛,小P又看了几遍代码,感觉依照自己的C基础,看懂是没有问题的,毕竟自己还是很油菜的。“的确是习惯问题,但是为什么老C会这样思考问题呢?”小P想。
     “因为信息隐藏……”
     “你怎么知道我在想什么?”
     “因为你在自言自语!”老C很是囧,“我们来看看现在的代码吧,它与我们第一版的代码区别在于信息的隐藏和信息的外部存储。原来的PrintFruitName()信息都存储在函数内部,即函数需要知道具体的FRUIT枚举才可以做出相应的输出,而且输出数据的格式信息也存储在函数中。这样这个函数就和我们的需求紧密结合,就是说如果我们的需求——水果类型和输出名称的信息及格式——任何一个发生变化,那么这个函数体本身也必须发生变化,代价就是——写代码的人和测试代码的人都强烈拒绝新的需求变化。而如果我们把具体信息对PrintFruitName()隐藏,使得函数只知道在一个表格中寻找对应的类型,然后根据自己接口处的函数指针调用格式化输出函数,那么在需求发生变化时,我们只需要修改具体保存信息的数据格式——比如g_fruitInfo表格,而无需修改函数体本身;更炫一点儿的是,我们还可以使用宏把对g_fruitInfo表格的操作封装起来,防止维护人员对g_fruitInfo表格进行误操作……但是考虑到你的承受能力,我们以后再谈论这个话题。这样,就产生了相对稳定的结构——PrintFruitName()函数体,但是此设计又没有拒绝维护人员根据需求进行变更……
     “哦,让我再想想……”小P又回头去看代码,“这次感觉好多了,但是,”小P开始恶狠狠的对老C说,“你一早就这么写出来得了,为什么还要我浪费时间啊!”
     “我又没有说你写得不好,如果是我第一次写,我也会写得和你一样!”
     “?”
     “一切都由需求而定!我们一开始时并不知道哪些需求会发生变化,因此只要满足现有需求就可以了,所以你一开始写的代码并没有什么不妥……问题发生在需求变化之后,”老C加重了语气,“现在我们已经知道需求可能在那些地方变化,而且就现实情况而言,一旦需求在某处发生变化,那么它就会经常在此处发生变化。这个时候我们就需要对程序结构进行调整,以应对这种变化,而不只是在原来的基础上缝缝补补……那样会带来很多重复性的劳动!”
     “……槑”
     “算了,这些都是随着编码规模的增大而得来的经验,相信你以后会逐步明白的,我看好你哦!”
     “谢谢啊!”
     “总之这就是思维的差异带来的代码风格的差异,我也是在用C++进行了几次项目之后才慢慢明白的,如果只是单纯的游弋在C语言的世界,我觉得我要明白这些东西还需要很长时间啊。”
     “这个和C++有什么关系?”小P有些不解。
     “当然有关系了,因为使用了C++后,我思考问题的方式与只使用C的时候发生了很大的不同,因为思维方式的不同,导致我的C代码风格发生了很大变化……但是话说回来,每种风格没有好坏的区别,只有合适不合适的区别。等我们打扫完实验室,我们再试试怎么用C++改写这些代码,至于现在,时候也不早了,我们去吃午饭吧!”制止住又想发问的小P,老C拉起他就向门外走。
     “哎哎,我还有问题要问呢,要不吃饭的时候你再喷喷……”


     (未完待续)
     (请勿转载,谢谢!)

posted on 2009-01-18 02:08 Anderson 阅读(2629) 评论(12)  编辑 收藏 引用

评论

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流 2009-01-18 19:51 虫牙

支持
狂顶  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流 2009-01-19 13:15 likenk

^_^,道理和MFC中的消息映射一样。  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流[未登录] 2009-01-19 15:09 Anderson

@likenk
没错!就是DECLAR_DYNAMIC和IMPLEMENT_DYNAMIC做的事情,而后面的具体实现函数就像On_XXX一样。^_^  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流 2009-01-20 13:50 李现民

这种风格, 挺好  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流 2009-01-20 15:18 Auguste

这样写,挺好的,希望能继续下去!  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流[未登录] 2009-01-22 16:56 Len

不错阿,从叙述到代码风格,都是非常认真
讲得东西也非常有吸引力  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流 2009-02-19 15:28 霜之哀伤

nice  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流 2009-03-28 21:15 funcoding

支持,请继续  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流 2009-04-18 21:38 wqf

太赞了!咱交大有才的人咋就这么多!  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流[未登录] 2009-09-01 19:25 wstonep

支持!!!!  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流 2009-10-02 18:38 ddlau

我这是第二次看你的文章了,第一次因为一些缘故没能看完~
写的对我来说很好,很有帮助,谢谢  回复  更多评论   

# re: 第一桶 从C到C++ 第一碗 潘小P初登教研室 陈老C笑谈显风流 2010-09-04 15:47 JasonRen

初看adapter代码的时候没有发现,仔细想想里面从架构到模块实现都采用了这种思想!帅帅说代码写得好,但当时我没有发现好在什么地方,现在有体会了!用c++的语言风格在C中实现!!回头再仔细分析一下!
  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2009年1月>
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567

导航

统计

常用链接

留言簿(6)

随笔档案(21)

文章档案(1)

搜索

最新评论

阅读排行榜

评论排行榜