发新帖本帖赏金 5.00元(功能说明)我要提问
返回列表
打印
[C语言]

我多年锤炼的小伙伴入职C语言培训案例,长篇连载中

[复制链接]
楼主: 小马儿
手机看帖
扫描二维码
随时随地手机跟帖
21
小马儿|  楼主 | 2016-8-17 16:36 | 只看该作者 回帖奖励 |倒序浏览
入职C语言例子一(5)

在上一节中,我们已约定了程序基本框架,并且也简单的介绍了编程思维的理念,好似,新人很快就可以写出合乎要求的程序了。

可惜,每个人的成长之路都不是一帆风顺的, 记得当初自己学习计算机编程时,哪个技能不是从大量的模仿中才能体悟一点点的,进而融入到自己的知识体系中的,一点一滴的成长起来的。

依据上一节的整体框架要求,我曾经带过的很多新人,甚至包含一些粉丝给我的发来的邮件,写出来的程序基本上都是新瓶装旧酒,仅是以前实现例子的翻版而已。

我给大家示意一下,大家体会体会,自己是否也有这样实现的冲动。

/* 循环输出菱形 */
for (……)
{
    /* 输出前导空格 */
    if (上半菱形)
       以三角型方式输出;
    else
       以倒三角形方式输出;

    /* 输出左边星号 */
    if (上三角形)
       ...
    else if (中心空心)
       ...
    else
       ...

    /* 输出中间空格 */
    if (上半空心)
       以三角型方式输出;
    else if (下半空心)
       以倒三角形方式输出;

    /* 输出右边星号 */
    if (上三角形)
       ...
    else if (中心空心)
       ...
    else
       ...

    /* 输出换行 */
    printf("\n");
}

注:该处的示例是结合第二个实现版本的,将一个菱形分为上三角形,下三角形,中间的空心三部分分别输出,详情请查看《入职C语言例子一(2)》。

应该如何实现呢?在上一节中,我们提到了重点在于寻找四个函数关系。文字的表现力经常是苍白的,给大家展现一幅我指导新人时的随意手绘图,或许,大家立即就明白了。



理解了上图,额外强调几点:
1. 空心菱形的每一行都必须等同看待,忘记上三角、下三角、中间空心等这样的分割;
2. 四个函数关系内部不允许出现if语句,但允许提炼公共函数,如abs类的;
3. 成功没有捷径,技能的学习也没有捷径,将浮躁的心放下来;

走到这儿,很多人都写了七八遍了,拜目前的大学教育模式所赐,很多人又出现了浮躁情绪,即时的安抚还是很有必要的,经常打趣的一句话就是瞧瞧你的师兄xxx,别看现在负责好几款产品得心应手,当初还写了十多遍呢,哈哈。

实际上很多人也意识到了我这样折腾的目的。大学的学习都是浅尝辄止的,很多东西都急于求成,但将这种心态带入企业,带入产品研发中却是致命的,我仅是想通过这样的形式,让新人少走弯路。用心良苦,却常引来误解无数,内向者口是心非肚子里骂几句,张狂者会直接诘问我这样折腾他们有何意义,呵呵。

随意牢骚了几句人生坎坷路啊,经这样一指点,大部分人都可以按照要求顺利的完成该例子,虽然每一部分都是一个复杂的表达式。

此时,我会给大家讲解一个很关键的计算概念:迭代。

我们所从事的嵌入式产品是强实时工业产品,不仅经常涉及傅里叶等各种复杂计算,而且还要求在指定时间内完成,因此对计算效率等要求会比较高,最经常采取的策略就是通过迭代,保留有价值的中间计算结果,减少整体计算量。

而通过迭代,不仅各部分的表达式不至于那么的复杂,而且会减少计算量。该例子比较简单,没什么技术含量,大部分人很快的就完成了迭代的调整,但前后对比的效果却印象深刻,算是额外的收获吧。

至此,空心菱形程序的所有技术点都描述完毕了,按着这样的要求,大多数人可以写出满足要求的程序了。还是非常的建议正在阅读的你能亲自写一写,调一调,下一节我会贴出标准答案,大家可以在比对一下,而差异部分正好是我们后续内容的起点。

上周被人骗了199元钱,骗子的方法很老套,自己也听过不止一次,可惜还是被骗了,或许,只有经历一次,各种骗人的招数才能烙进脑袋里去。

不恰当的例子,不过想表达一个基本的道理,成长需要经历和体悟,再次建议正在阅读的你要多动手。

我是小马儿,一个渴望良知与灵魂的工程师,欢迎您的陪伴与同行。

使用特权

评论回复
22
小马儿|  楼主 | 2016-8-17 16:38 | 只看该作者
每篇**都比较长, 是多年演化锤炼的结果, 干货还是比较多的, 也希望大家多提意见.

使用特权

评论回复
23
小马儿|  楼主 | 2016-8-17 16:39 | 只看该作者
上一篇**中也少了一幅图片, 大家到我的图集中找吧, 看起来是手绘的一幅图片就是了, 是一个概念图.

使用特权

评论回复
24
小马儿|  楼主 | 2016-8-19 18:42 | 只看该作者
入职C语言例子一(6)

前面第(3)节中,列举了一个很优雅的实现,一些朋友提醒我该程序输出不正常,自己测试了一下,确实如此。

当时写程序时,是直接以文档的方式写的,一些例程也是小伙写的程序中拷贝出来的,重在意图表现,所有的程序代码都没有测试过,缺乏了严谨性,优化如下:
for (x = -m+1; x < m; x++)
{
    for (y = -m+1; y < m; y++)
    {
        t = abs(x) + abs(y);
        if (t >= n && t < m)
            printf("*");
        else
            printf(" ");
    }
    printf("\n");
}


咱们书接上节,言归正传,在上一节中,新人磕磕碰碰的,终于写出了符合技术要求的程序,皆大欢喜,以为要完工了,可惜,路依旧漫漫。

此时,我会给大家分享该题目的标准答案,让大家同自己写的程序进行比对,以前的程序都是以片段方式提供的,标准答案以完整的格式提供,示意如下:

/********************************************************************
*
*   Copyright (C), 1999-2004, xxxxxx. Co., Ltd.
*
*   文件名称:diamond.c
*   软件模块:空心菱形输出
*   版 本 号:1.0
*   生成日期:2003/3/23
*   作    者:xiaomaer
*   功    能:空心菱形输出,该程序占用内存小,但计算稍大,可修改为"内存换资源"算法
*
*********************************************************************/

#include <stdio.h>

/* 菱形最大高度 */
#define MAX_DIAMOND_HEIGHT 16

/* 提前申明 */
int myGreater(int n);

/* 主程序 */
int main()
{
    int n, nRow;
    int nIn, nOut;
    int nCount1, nCount2, nCount3;

    /* 输入内外菱形高度,并进行合法判断 */
    for (;;)
    {
        printf("输入内外菱形高度(最大%d行):外菱形高度,内菱形高度:\n", MAX_DIAMOND_HEIGHT - 1);
        scanf("%d,%d", &nOut, &nIn);
        if (nIn < nOut && nOut < MAX_DIAMOND_HEIGHT && nIn >= 0)
            break;
        printf("输入不合法,请重新输入:\n\n");
    }

    /* 循环输出菱形 */
    nIn = nOut - nIn;        /* 调整为菱形内外差值 */
    for (nRow = -nOut+1; nRow < nOut; nRow++)
    {
        //行号
        printf("%-010d", nRow);

        /* 输出前导空格 */
        nCount1 = nRow >= 0 ? nRow : -nRow;        /* 取绝对值 */
        for (n = 0; n < nCount1; n++)
            printf(" ");

        /* 输出左边星号 */
        nCount1 = nOut - nCount1;            /* 外三角部分,后续迭代使用 */
        nCount2 = myGreater(nCount1 - nIn);  /* 内三角部分,后续迭代使用 */
        nCount3 = nCount1 - nCount2;         /* 内外之差为实际需要输出 */
        for (n = 0; n < nCount3; n++)
            printf("*");

        /* 输出中间空格 */
        nCount3 = 2 * nCount2 - 1;           /* 由三角形拓展为菱形 */
        for (n = 0; n < nCount3; n++)
            printf(" ");

        /* 输出右边星号 */
        nCount1--;                           /* 外三角部分 */
        nCount2 = myGreater(nCount1 - nIn);  /* 内三角部分 */
        nCount3 = nCount1 - nCount2;         /* 内外之差为实际需要输出 */
        for (n = 0; n < nCount3; n++)
            printf("*");

        /* 输出换行 */
        printf("\n");
    }
    return 0;
}

/* 取大于0的数 */
int myGreater(int n)
{
    if (n < 0)
        return 0;
    return n;
}

不知正在阅读的你看到这个标准答案的感觉,能否寻找出和自己程序的差异的地方,下一节我们以此程序为起点,给大家介绍一些真实产品中的代码特点。

我是小马儿,一个渴望良知与灵魂的工程师,欢迎您的陪伴与同行。

使用特权

评论回复
25
小马儿|  楼主 | 2016-8-20 10:26 | 只看该作者
入职C语言例子一(7)

在上一节中,我们给出了标准答案,是以真实产品代码风格写的,期望小伙伴们能同自己的实现比较一下。

现在的90后是有个性的一代,很多人都非常反感直接的大道理灌输,鉴于此,我期望我们的小伙伴们能自己需寻找答案,然后大家在交流碰撞中成长,效果或许会更好一些。

这篇**就让我们一起来找出标准答案中有价值的地方吧。

1. 有意义的变量命名

大学老师教编程的时候,重点精力都放在了语法方面,侧重于将所有的语法给大家展现一下(这种学习方法我相当不赞同,后续会展现自己的方法,项目组内俗称大树法则的方法),因此经常使用短小的程序展示语法,但因为程序短小,因此变量命名也就随意了一些,因此i,j,k,m,n就成了常客,然后不小心带入了产品中,然后……。

但在产品研发的时候,即使比较简单的设备,代码量也会比较大,为了代码阅读维护方便,有意义的名字就变得非常的重要了。在标准例子中,使用了nIn和nOut就是想用简单的英语单词表示内外菱形的高度。

一些朋友可能读过《可读代码的艺术》或《代码整洁之道》等书籍,作者强调使用准确的英文单词来表达特定含义。

但我们是中国人,能想起一些简单的单词词汇已经颇为不易,想准确表达更是天方夜谭,因此,项目组内经过了无数次的迭代和探索后,形成了一种变量定义习惯:尽可能使用简单的相近英文词汇,全局变量必须加准确含义的中文注释,函数内的一些自动变量,因其作用范围很小,有时候注释可省略。

关于变量命名的故事还有好多,这个刚刚是给大家起个头,我们后面会有专门的系列**介绍,记住在真实产品的代码中,需要有意义的变量命名,忘记m和n吧。

2. 代码分节

是什么是节(section),第一次知道这个概念的时候,正式全球跨千年的时候,我还在大四,我在北京一家企业打零工,当时公司承接了一个日本银行的项目,日方对代码质量要求很严格,专门派了一个专家过来给我们讲解各种要求,以及其背后的道理。

当时还很年轻,狂傲不羁,因此大部分的苦口婆心都被当做了耳旁风,但唯独对节的概念**比较深刻(可能是一开始就讲的是这个了,呵呵,后续的就没耐心听了,这个系列**阅读比例逐次下降,估计是同样的道理)。

我们在读代码的时候,人的思维一段时间内仅停留在一个较窄范围的点上,如果面对的是看不到尾的代码,会潜移默化的将其看做灭绝师太的裹脚布——又臭又长,逆反情绪悠然而生。

因此,我们需要将代码按逻辑分成一块一块的,以空格作为区分,然后每块代码前增加适当的注释,解释这一块代码的功能,是所谓节的概念。

经过这样的改造,读代码的时候,感觉会完全不一样。关于节的价值,远不值这些,后续会在编程规范系列**中和大家慢慢道来,此时,我们仅要求小伙伴知道垒又臭又长的代码是不对的。

实际上在第5节中,我约定程序整体结构时,已经有这样的意图了,大家不放回忆并体味一下。
/* 循环输出菱形 */
for (……)
{
    /* 输出前导空格 */

    /* 输出左边星号 */

    /* 输出中间空格 */

    /* 输出右边星号 */

    /* 输出换行 */
}

3. 细节化标注

某些代码存在着一定的深度,一段时间就会忘记,通过右侧简单的注释加以标注,不仅便于后续代码的阅读理解,而且标注点一般是关键代码段,给后续的代码审查也带来的方便。

在示例代码中,会看到迭代表达式右侧(微信公众号排版问题,经常到了下面一行)有简单的标注,主要就是起这样的作用的。

但万事过犹不及,很多刚入职的小伙伴喜欢在右侧加好多的注释,仅挑出有价值的进行标注,需要长期的锻炼和慢慢的体悟,或许,那一天回头审视自己的代码,会将许多无用的注释删除的时候,就修炼到家了。

4. 资源

嵌入式系统中,资源是一个需要时时刻刻关注的问题。

何为资源,在我们的概念中,不仅内存和flash空间大小是资源,cpu计算能力也是资源,甚至代码可读性(可维护性),代码实现复杂度(耗去的人力成本),复用率等等诸多方面,都被我们称之为资源。

好钢要用在刀刃上,但首先要明白刀刃在哪儿。缺乏了明确边界,空谈提高资源利用率是无意义的。如可读性第一位,内存和cpu资源比较宽裕,我们那个最优雅实现版本最佳了。如果cpu计算能力紧张,上一节的标准实现更好一些。如果内存稍微宽裕,为了增加代码可读性,我们还有更好的方法。

前面已经有人给我留言提到过这种方法了,不知大家有没有感受到,锻炼到现在这个时候,单纯的菱形输出是多么easy的事情啊,非要搞个空心菱形,将程序搞的混乱不堪。

但加入我们用一个数组来表示整个菱形输出,第一次以*输出一个菱形,第二次以空格在输出一个菱形,然后将整个数组输出出来,是否会非常的简单呢,代码可读性瞬间爆棚,执行效率也高,仅仅多占了一些内存而已。

原想将这个版本的代码也贴出来的,但担心这篇**又成为了裹脚布,大家自己尝试着实现吧,非常简单了。



或许大家还有各种各样的体悟,为了限制篇幅,就此打住,大家可以将自己的观点通过留言的方式表达出来。

下一节,我们终于要给该例子的收尾了,是一个非常有价值的总结,欢迎大家关注。如果正在阅读的你有所思,有所悟,不妨将其转发给自己的职场朋友,独乐了不如众乐乐。

我是小马儿,一个渴望良知与灵魂的工程师,欢迎您的陪伴与同行。

使用特权

评论回复
26
wzh8158| | 2016-8-22 07:13 | 只看该作者
灰常精彩,马儿辛苦了!

使用特权

评论回复
27
小马儿|  楼主 | 2016-8-22 09:10 | 只看该作者
wzh8158 发表于 2016-8-22 07:13
灰常精彩,马儿辛苦了!

这是我们的小伙伴入职时痛苦例程, 文字稍微多了一些, 估计十之**的人都没耐心细细读取一遍, 但我们的小伙伴是要经历一番的.
俗话说一分耕耘一分收获, 不同的态度, 不同的付出, 收获差异是很大的.
而这仅仅是漫漫雄关从头越, 大家或许可以体会到培养一个合格的嵌入式工程师是多么的辛苦, 依据过往经验, 三年的艰辛能上路,就已经很不错了, 不过五六年后, 回头总览群山的感觉应该很爽.
很多人没耐心读, 而你能体味到其中精彩之处, 已殊为不易了, 值得鼓励.

使用特权

评论回复
28
xgmmss| | 2016-8-22 14:20 | 只看该作者
顶一个,楼主的分享让我受益匪浅,非常感谢!做一个合格的嵌入式工程师不容易。

使用特权

评论回复
29
雪走天涯| | 2016-8-23 08:45 | 只看该作者
我花了三小时才写出来达到这个效果的代码

使用特权

评论回复
30
小马儿|  楼主 | 2016-8-23 09:35 | 只看该作者
雪走天涯 发表于 2016-8-23 08:45
我花了三小时才写出来达到这个效果的代码

已经相当不错了, 我们是想通过这种方式, 让刚入职的小伙伴去体验, 每个人都会写出几个版本的程序, 对比后, 才能感受到自己的提高, 以这种方式带人入门而已.

使用特权

评论回复
31
小马儿|  楼主 | 2016-8-23 09:36 | 只看该作者
xgmmss 发表于 2016-8-22 14:20
顶一个,楼主的分享让我受益匪浅,非常感谢!做一个合格的嵌入式工程师不容易。 ...

同感

使用特权

评论回复
32
小马儿|  楼主 | 2016-8-23 09:40 | 只看该作者
入职C语言例子一(8,end, 总结与反思)

----------------------------------------------------------------------

经过马拉松的历程,第一个例子终于到了最后的篇章,好多小伙伴都被磨的没脾气了,终于看到了一丝曙光,呵呵,在熬一小段路,黎明就在眼前。

这一节不会增加新的知识点,但更为重要,因为我们要进行归纳、总结、思考、锤炼、提高……

走到这儿,好多小伙伴都写了十几个版本的程序了,我习惯于问一个问题,哪几个版本最有感触。时间已经过去很久,重新翻看一下自己写的早期程序,互相比对着,很多小伙伴都能感觉到自己成长了一小截,也能感受到一点点我们提倡的工匠精神。

和很多小伙伴交流过,大家比较有感的,主要有三个版本:
1. 第一个满足需求版本;
2. 在满足结构约定后版本;
3. 标准答案。
而正是这几个关键节点,记录着自己成长的点点滴滴,而我们的要求很简单:将自己的感悟记录下来。

                                         ◆

我们来归纳汇总一下第一个空心菱形输出例程中提到的知识点:
1. 需求清晰理解,最使用的策略是:将需求用自己的语言表达出来,和对方确认后再实施。
2. 边界判断,要让小伙伴意识到:在嵌入式产品中,最终产品的鲁棒性,很多时候就是表现在这样一点一滴的简单判据上。
3. 基本调试手段的锻炼,工欲善其事必先利其器,无须多言。
4. 编程思维的引入,需要慢慢的体会抽象的价值,这是一个难点,但想走得远必须扛过去。
5. 数值计算过程中,很重要的一个概念:巧用迭代。
6. 基础编程规范的引入,体会节的概念,要意识到产品代码可读可维护的重要性。
7. 在嵌入式编程领域,资源是受限的,而我们要学会针尖上的舞蹈。
8. 撰写工作笔记,善于总结,习惯去体悟成长的脚步。

                                 ◆

记得刚开始从事嵌入式编程的时候,我的职业导师给我欣赏了他的记事本,密密麻麻的各种调试记录,感悟想法,技术资料,知识归纳,我终于明白了他为何获得了全公司上上下下的认可。

因此,我们的团队形成了一条不成文的规矩,必须做工作笔记,不管方式,不管格式,只要开始记录就好。当然,还有整理、思考和提高,不急,后续我们慢慢来。

转眼工作15年多了,已经记录下了上千页的各种随笔、文摘、工作纪要和头脑风暴,随意翻阅,好似摩挲着破碎在自己身后的时光,有一种充实的感觉,当然,也让我经营该公众号多了一丝底气。

C语言中最有价值但又最容易出错的莫过于指针了,第二个例子就是加强这方面锻炼的,但不同于第一个例子,篇幅较短。

总是专业**,大家会比较腻,下一篇换换风格。刚开始做公众号时,谈到想用十多篇**交代一下为啥要做公众号和想做什么的话题,位于"异维设计—来龙去脉"篇章中,当时着急进入主题,临时中断了,想补充一下。还有"基础知识"和"IT篇章"两大类,竟然一篇**也没有,搞的太畸形了,也需要补充两篇。

我是小马儿,一个渴望良知与灵魂的工程师,欢迎您的陪伴与同行。

微信公众号最大的缺陷就是**很碎片化, 我将这个系列**整理了一下, 弄成了一个pdf合集, 想要的话, 需要关注我的微信公众号了, 也算是对乐于分享的朋友一点的小小的奖励.

使用特权

评论回复
33
小马儿|  楼主 | 2016-8-23 09:40 | 只看该作者
第一个例子终于弄完了, 第二个例子要过一段时间才会整理, 忙于写其他**了, 大家慢慢等待吧.

使用特权

评论回复
34
小马儿|  楼主 | 2016-8-25 14:44 | 只看该作者
我想整理成一个pdf文档, 有没有人想要, 想要的留言啊。

使用特权

评论回复
35
王三土| | 2016-8-28 11:00 | 只看该作者
我要我要   4658031492@qq.com   庆幸自己能在似懂非懂的时候看见这篇**    收益良多

使用特权

评论回复
36
qin552011373| | 2016-8-29 10:32 | 只看该作者
mak

使用特权

评论回复
37
qin552011373| | 2016-8-29 10:33 | 只看该作者
mark

使用特权

评论回复
38
luvemcu| | 2016-8-29 21:32 | 只看该作者
好**,mark慢慢看

使用特权

评论回复
39
wsnsyy| | 2016-8-30 10:34 | 只看该作者
小马儿 发表于 2016-8-25 14:44
我想整理成一个pdf文档, 有没有人想要, 想要的留言啊。

PDF咋样了啊,终于有人肯分享正在提高能力的东西了

使用特权

评论回复
40
小马儿|  楼主 | 2016-8-30 18:00 | 只看该作者
例子1侧重于交代做产品时需要注意的一些C语言规则, 例子二用于加深对指针的理解, 例子三用于加深对函数指针的理解, 是框架程序的基础, 不然看真实产品的程序困惑会比较多.三个例子合起来, 正好对新人是一个整体促进和提高.
这三个例子针对的是刚招聘入公司, 有过一点C语言基础, 写过一些程序的小伙伴.
要注意这个大背景, 不要将其简单的理解为就是为了输出一个菱形而已, 仅是拿着一个例子在说事, 一些朋友留言说一个简简单单的菱形输出有必要分成这么多篇**吗, 估计就是回错了意, 大家要注意了.
如果刚开始学习C语言, 可以到网上找一些视频, 讲解语法的, 会更合适一些.

使用特权

评论回复
发新帖 本帖赏金 5.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则