整合自網(wǎng)絡(luò)信息,作者:小馬兒,編輯:付斌
不記得從何時開始,自己就經(jīng)常承擔(dān)企業(yè)招聘的工作,側(cè)重于工業(yè)嵌入式產(chǎn)品研發(fā)人員的招聘。
出于對企業(yè)和新人的負責(zé)態(tài)度,我傾向于尋覓那種基礎(chǔ)知識扎實,且對嵌入式編程有興趣的人員,這樣對企業(yè)發(fā)展、對個人成長都有好處,可惜很多次都如同大海撈針,希望而來失望而歸。
這是我很糾結(jié)的一個問題,不由得想起了曾經(jīng)聽過的一個故事:一個外國人來國內(nèi)某公司考察,首先看到了職員的工作態(tài)度,狂吐槽,這樣的員工企業(yè)應(yīng)該全部裁掉;然后中午吃飯的時候,品嘗了企業(yè)的午餐,又開始罵企業(yè)了。
回頭看一看目前的大學(xué)教育模式,很多學(xué)校還是沿襲類似于初高中的填鴨式教育方式,大多數(shù)學(xué)生也是在吃喝玩樂混日子,即使大一新生還有些激情,很快就被大環(huán)境給污掉了。然后就是學(xué)生、學(xué)校和企業(yè)的互罵,搞得一片狼藉,戾氣沖天。
一個要好的朋友在國內(nèi)某985大學(xué)教計算機,一次和他聊天埋怨到,現(xiàn)在的學(xué)生入職后好長時間都沒法上手,培訓(xùn)周期很長,然而朋友的觀點是,這應(yīng)該是理所當(dāng)然的啊,大學(xué)能學(xué)點啥,都需要到企業(yè)才能鍛煉的。無語……
真應(yīng)該是這樣嗎?我很茫然,但看到每個學(xué)生背后為學(xué)費辛勤勞作的父母,看到他們對子女期望的眼神,我認為,我們可以做的更好,也應(yīng)該做的更好。
為了讓大家體會我內(nèi)心糾結(jié)的心情,先簡單的聊一聊招聘試題的事情。
以前我參加過中興的招聘,招聘老師給我出了一道題目,int ?n = ?3,4;我說我不知道,這是變態(tài)的用法,我們不應(yīng)該這樣用,您也不應(yīng)該出這樣的題目。可能當(dāng)時年輕氣盛,將招聘老師氣了個半死,我當(dāng)然也無緣中興了。吸取以前的教訓(xùn),后來我在組織招聘時,有一個基本原則,只有在工作中經(jīng)常會用到的知識,才會作為考察的范圍。
為了挖掘一些好的苗子,我們也一直在思考怎樣的試題可以更好的考察出求職者的能力和興趣。最開始的方式是先問一些基礎(chǔ)課和專業(yè)基礎(chǔ)課的基本概念,濾除基礎(chǔ)知識不扎實的人;然后期望求職者能講解一個自己曾經(jīng)憑興趣寫過的程序,然后借著求職者的描述,然后不斷的深入,以判斷他的能力具體到了什么程度。
可惜,99%的人根本說不出自己憑興趣寫的程序,碰上幾個胡謅的人,幾句話就露餡了。后來只好雪藏該問題,然后降低難度,出一些單層循環(huán),且每層循環(huán)適度判斷的編程例子(如判斷一個整數(shù)中0的個數(shù)等)。如果每次招聘,能碰上一兩個將這類程序快速寫出來的同學(xué),大家都可以興奮半天了。
這就是我們能招聘到的學(xué)生的普遍能力了,在我看來,要達到這個能力,只要隨意的學(xué)習(xí)幾天編程就可以了,還需要大學(xué)四年干嘛,讓人郁悶啊。
但一個更加現(xiàn)實的問題是,這樣的人招聘進來企業(yè)如何使用。我們不敢直接拿著產(chǎn)品讓菜鳥人員練手,大家都知道,產(chǎn)品是要賣給用戶的,初期的小問題到了用戶那兒就會放大成大問題的。
企業(yè)培養(yǎng)不同于學(xué)校教育,不會有人給你填鴨式,更別指望有人手把手的教你,很多時候都是散養(yǎng),頂多指點幾句然后自己看書去。這種情況也導(dǎo)致了很多人初期很難上手,一些人甚至被迫轉(zhuǎn)行,荒廢了自己多年的專業(yè)方向。
如何快速過渡,結(jié)合自己多年的工作和新人培養(yǎng)經(jīng)驗,我總結(jié)了一套比較有效的方式,將產(chǎn)品研發(fā)需要的初級C語言知識巧妙的融入到三個例子中,讓新人通過這三個例子去學(xué)習(xí),去碰壁,去思考,持續(xù)的提高自己的C語言能力,盡快具備可參與產(chǎn)品研發(fā)的能力。
如何學(xué)習(xí)C語言?
很多剛?cè)肼毜男氯?,都喜歡問一個類似的問題:“如何學(xué)習(xí)……?”,然后一些朋友就會給拷貝一大堆書籍資料,更熱心的還會指導(dǎo)先看哪本在看哪本。
但不幸的事,很多時候就截至于此了,一大堆資料依然靜靜的躺在電腦硬盤里,只是在偶然的情況下才會打開翻看兩眼目錄。
走過一些企業(yè),培訓(xùn)體系一般是這樣的:
1、新人入職后,師傅會給一堆資料讓看,然后新人硬著頭皮看一些;
2、哪天師傅不忙了,惦記起這個新人,然后交給其一個產(chǎn)品,讓其折騰;
3、可惜具體產(chǎn)品一般都涉及多個學(xué)科,面對一大堆疑問,新人會感覺騰云駕霧般難以前行;4.一段時間后部分人邁過了入職時的絕望懸崖,有了自己的積累,開始慢慢的深入接觸產(chǎn)品,但因各種文檔資料奇缺,只能一邊學(xué)習(xí)一邊調(diào)整;
5、數(shù)年后,新人成為了老手,同時新的產(chǎn)品體系也誕生了;
6、然后重復(fù)以上死循環(huán)。
長此以往,公司的產(chǎn)品體系變得非常的雜亂,技術(shù)難以復(fù)用,無法進行有效的積累,那種如臂使指的團隊構(gòu)建更是空談。
如何才能跳出上述死循環(huán)呢?我在自己的職業(yè)生涯中進行了大量的探索嘗試,有了一點感悟:不僅需要將新人的培訓(xùn)工作盡可能前移,盡可能體系化,而且要盡早的融入我們的設(shè)計及團隊理念。因此在后續(xù)的三個C語言例子中大家會體會到很多東西不僅僅是C語法層次的內(nèi)容。
前面我談到,在公司,不可能在重復(fù)學(xué)校的填鴨式的教育模式,也不會有人手把手的教你。那么在這種情況下,如何組織培訓(xùn)工作,很多時候反而成了一件頗具藝術(shù)感的選擇難題。
隨著移動互聯(lián)網(wǎng)的盛行,在線教育也流行了起來,網(wǎng)絡(luò)上出現(xiàn)了大量的視頻教程。緊隨時代的脈絡(luò),我嘗試過將入職需要學(xué)習(xí)的內(nèi)容做成書籍和視頻,但效果一般。后來細細研究這個領(lǐng)域,發(fā)現(xiàn)大多數(shù)視頻僅僅是將大學(xué)課堂的內(nèi)容移到了網(wǎng)絡(luò)上,很多人也僅僅是耐著性子看個幾集,然后就不了了之了,因此導(dǎo)致的效果不佳。關(guān)于在線教育我也有一些自己的想法和思考,后面會專門撰文描述,這兒就不深入介紹了,總之,初期的嘗試是不太成功的。
C語言第一個例子:需求不明確
第一個例子:“編寫一個控制臺程序,已知內(nèi)層和外層菱形的高度,輸出一個空心菱形”。
是否看上去很簡單啊,寫到這兒,很是期望正在閱讀的你能先停下來,思考思考,然后付諸行動,編寫幾行代碼小試一番,然后在繼續(xù)讀下去,收獲會更大。
這些年,我?guī)н^很多人,大部分人看到這個題目后,然后立即就開始寫程序去了,自己內(nèi)心多少有點小小的失望,為何?
一般通過公司層層把關(guān)招聘進來的軟件人員,這個例子總是可以弄出來的,不過可惜的是,近一半以上的人不能準確的實現(xiàn)出來,僅一個菱形高度的理解,就是五花八門,亂七八糟的,執(zhí)行如這樣的:
或者這樣的:
或者干脆是這樣的:
為何會出現(xiàn)這種情況,這恰恰是該例子的第一個大坑:需求不明確。一開始就碰到挫折,對很多新人來說,這無異于當(dāng)頭一棒,不過記憶也最為深刻。
一個團隊協(xié)作時,會存在大量的交流,而交流過程中,總是會產(chǎn)生或多或少的歧義,而恰恰是這些歧義會導(dǎo)致需求的不明確,會導(dǎo)致大量的返工,甚至?xí)?dǎo)致項目的失敗。我給新人的第一份建議:將需求用自己的語言表達出來,和對方確認后再實施。這一點在以后的團隊協(xié)作中非常的重要,因此我早早的將這一點嵌入到了入職培訓(xùn)中。
經(jīng)過這么一折騰,第一個例子重新描述如下:“寫一個控制臺程序,用戶輸入內(nèi)層和外層菱形的高度,輸出一個空心菱形,菱形的高度定義為菱形的上三角形的高度,如輸入5和3,輸出如下:
??
????? *
??? ***
? **? **
** ? ?? **
** ? ?? **
?** ? **
? ** **
? ?***
? ? *
這兒稍微補充兩點:
1、菱形高度定義好似有違常理,但在編程的世界中,簡潔就是常理,如果定義為整個菱形高度,那么不僅需要進行正整數(shù)判斷,而且還需要奇數(shù)判斷,增加了程序的復(fù)雜度。
2.、用一個例子描述,比一大堆文字管用多了,是所謂一圖頂千言。
明確知道要做什么了,然后很多人又回去,然而很多情況下執(zhí)行結(jié)果如下所示:
這兒引出了第二個很重要的概念:邊界判斷。給出兩個數(shù),如何簡潔且完備的判斷其是合理的輸入,這是編程的基本功,這些內(nèi)容在大學(xué)教學(xué)過程中一般不太重視,但想做出合格的嵌入式產(chǎn)品,這一點必須引起足夠的重視。如何做出優(yōu)雅的邊界判斷,這個例子比較簡單,留給正在閱讀的你吧,順便回頭想一想我前面菱形高度的定義吧。
第二次被打道回府后,很多人會有急躁情緒,菱形還沒輸出呢,就給灌輸了一堆規(guī)則。碰到這種情況,我會給他們講解嵌入式程序的特點(這一點留待后文慢慢描述),以及我以前的經(jīng)歷。我剛開始工作時,因CPU速度受限,程序還主要是匯編語言,領(lǐng)導(dǎo)讓寫一小段匯編程序,但每次提交后,都被劈頭蓋臉的罵一通,讓回去整改,直到后來一行匯編語句都省不下去了,才算通過了?;仡^看,為了一個小小的例子,竟然將大多數(shù)匯編指令熟悉了,方才明白領(lǐng)導(dǎo)的良苦用心。現(xiàn)在的新人臉皮薄了一些,不敢亂罵了,只好慢慢講道理了。
經(jīng)過第二次的折騰,大部分新人態(tài)度都能稍微端正起來,而且這次寫出來的例子,和需求基本吻合了,但當(dāng)興致沖沖的跑過來交差時,我反而不看程序,開始問起在實現(xiàn)這個程序過程中用到的調(diào)試技巧。
這個例子對新人來說有一點點的難度,不可能一次就寫好的,肯定會經(jīng)歷一些痛苦的調(diào)試過程。但是在國內(nèi)的大學(xué)教育體系下,卻又不太重視基本調(diào)試技能的鍛煉,期望新人一開始意識到調(diào)試是一項基本功夫,需要引起足夠的重視,需要去持續(xù)的加強。
雄關(guān)漫道真如鐵,而今邁步從頭越,一個小小的例子還未起步,對很多人已是一場痛苦的經(jīng)歷。要想成為一個合格的嵌入式工程師,需要方法,需要才智,更需要背后的汗水和堅持,本系列文章會帶領(lǐng)大家體味我的成長路,但無法代替你自己的付出和汗水。
C語言第二個例子:邊界判斷
上文中,我提到由該例子引出了三個重要概念,其中第二個概念是:邊界判斷。給出兩個數(shù),如何完備且簡潔的判斷其是合理的輸入,這是編程的基本功。很可惜的是,這幾個例子都沒有做到。結(jié)合自己多年帶人的經(jīng)驗,這兒補充闡述一下。
輸入兩個數(shù),一個是外菱形的高度(m表示),一個是內(nèi)菱形的高度(n表示),有如下幾個判據(jù):
1、兩個高度都應(yīng)該是正整數(shù)(內(nèi)菱形高度可以為0);
2、受限于屏幕的大小,菱形高度應(yīng)該受限;
3、外菱形高度應(yīng)該大于內(nèi)菱形的高度。
以前帶人的時候,拿到例程后,我喜歡先輸入(1000,800)這樣的值,因為這是容易被忽略的地方,很多人郁悶的鎩羽而歸。
大部分人都是缺判據(jù),也有一些人性格比較謹慎,習(xí)慣寫一大堆的判斷條件,如下面這種的:
if ?(m > 0 && n > 0 && m > n && m < 30 ?&& n < 30) { ? ……}
總之,都沒有抓住判斷條件應(yīng)該完備且簡潔的基本準則,實際上該例子很簡單,只要簡單的分析,判斷條件也就三個,如下:
1、內(nèi)菱形高度大于等于0;
2、外菱形高度小于約定之(假設(shè)30);
3、外菱形高度大于內(nèi)菱形高度;
寫成程序示意如下:
if (n >= 0 && m > n && m < 30) { ? ……}
還記得第(1)節(jié)中我們約定菱形的高度是上三角形高度嗎,帶來的好處就是判斷的簡潔化,概念是為目標而服務(wù)的,不然該處的判斷還需要額外的增加兩條奇數(shù)判據(jù)了,簡潔性也會打折扣了,呵呵,可以再回味一番。
在嵌入式產(chǎn)品中,最終產(chǎn)品的魯棒性,很多時候就是表現(xiàn)在這樣一點一滴的簡單判據(jù)上,該處的不厭其煩,也是期望新人慢慢的融入研發(fā)團隊時,能夠充分意識到這一點。
終于要開始輸出空心菱形了,但對于剛畢業(yè)的大學(xué)生,這個例子剛上手還是有一些繞的,其思維邏輯是如何的呢。
一般人都會發(fā)現(xiàn),輸出空心菱形要稍微復(fù)雜一些,那么我們修改為輸出菱形呢,是否簡單很多,如果還嫌復(fù)雜,修改為輸出三角形呢。呵呵,說白了,就是將復(fù)雜的問題去其枝葉,先簡后繁的慢慢處理。
一開始的問題就簡單多了,已知三角形高度m,輸出三角形,如m=5,輸出如下:
? ? *
? ?***
? *****
?*******
*********
為了直觀,將空格也表示出來,示例如下:
----*
---***
--*****
-*******
*********
此時,結(jié)論已經(jīng)很形象了,每行輸出的空格從m-1遞減,每行輸出的*從1開始遞增,循環(huán)子為m,程序示例如下:
for (i = 1; i <= m; i++)
{?
?j = m - i;?while (j--)
? ?printf(" ");
?j = i * 2 - 1;
??while (j--)
? ?printf("*");
?printf("n");
}
問題復(fù)雜化,考慮空心三角形,相當(dāng)于里面又多了一個三角形,我們輸出的時候,將其簡單扣去即可,假設(shè)n=3,如下圖示例:
----*
---***
--**-**
-**---**
**-----**
程序示例如下:
for (i = 1; i <= m; i++)
{
?j = m - i;
? while (j--)
? ? ? printf(" ");
?j = i * 2 - 1;
? for (k = 0; k < j; k++)
?{ ? ?
? if (k < m-n || k >= j-m+n)
? ? ?printf("*");
? ? else
? ? ?printf(" ");
? }
? printf("n");
}
進一步復(fù)雜化,輸出完整的空心菱形,僅僅需要將上面的程序重復(fù)一下,僅僅是將其顛倒一下,且高度調(diào)整一下(減1處理,表達式更加復(fù)雜了)而已,我就不展示示例程序了,大家有興趣的可以自己嘗試一下。
有些人在實現(xiàn)該程序的時候,一開始就是對空心菱形進行分析,最直觀的分析策略就是將菱形分層了三段,上三角形,下三角形,中間的空心部分,如下圖示意:
? ? *
? ?***
? ** **
?** ? **
** ? ? **
?** ? **
? ** **
? ?***
? ? *
按照這種思路,程序示意如下:
for(i=1;i<2*m;i++)
{
? ?
? ?if(i<=m-n)
? ?{? ? ? ?
? ? ? ?star = 2*i-1;
? ? ? ?empty= m-i;
? ? ? ?while(empty--)
? ? ? ? ? ?printf(" ");? ? ? ?while(star--)
? ? ? ? ? ?printf("*");? ?}
? ?else if(m-n ? ?{? ? ? ?
? ? ? ?if(j <= n && i <= m)
? ? ? ?{? ? ? ? ? ?
? ? ? ? ? ?num_empty = 2*j-1;? ? ? ? ? ?empty = m -i;
? ? ? ?}? ? ? ?
? ? ? ?else{? ? ? ? ? ?num_empty = 2*(2*n-1-(j-1))-1;
? ? ? ? ? ?empty = i-m;
? ? ? ?}
? ? ? ?
? ? ? ?num_star = star = m-n;
? ? ? ?while(empty--)
? ? ? ? ? ?printf(" ");? ? ? ?while(star--)
? ? ? ? ? ?printf("*");
? ? ? ?while(num_empty--)? ? ? ? ? ?printf(" ");
? ? ? ?while(num_star--)
? ? ? ? ? ?printf("*");? ? ? ?j++;
? ?}
? ? else
?{? ? ? star = 2*(2*m-1-(i-1))-1;
? ? ? empty = (2*m-1-star)/2;
? ? ? while(empty--)? ? ? ? ? ?printf(" ");
? ? ? ?while(star--)
? ? ? ? ? ?printf("*");? ?}
? ?printf("n");
}
不管如何,至此,這個例程就算完成了,但大家有沒有發(fā)現(xiàn)上面這些程序都談不上優(yōu)雅啊,其中各種m和n的表達式,一段時間以后看,基本同亂麻差不多了,試想,如果這是產(chǎn)品的程序,讓后來人如何閱讀并維護。
C語言第三個例子:簡單粗暴的數(shù)字之美
優(yōu)美,總是讓人心醉,一提到優(yōu)美,最容易想到的是悅目的圖畫,動聽的樂章、精妙的詩文……。然而,數(shù)學(xué),自然科學(xué)的皇后,卻蘊含著比詩畫更多的優(yōu)美。
優(yōu)雅的程序,或許其背后都蘊藏著數(shù)學(xué)的優(yōu)美。
在上一節(jié)中描述的一些例子中,我們總是在努力的拼湊各種m和n的表達式,與其這樣苦苦尋找,為何不直接將這個空心菱形放入坐標軸中呢。
在電腦屏幕上,人們習(xí)慣將靠右稱之為x軸,靠下稱之為y軸,將空心菱形畫在屏幕上,示意如下:
然后通過解析幾何知識勾勒空心菱形,程序示意如下:
for (x = 0; x < m * 2 - 1; x++)
{
? ?for (y = 0; y < m * 2 - 1; y++) ? ?
? ?{ ? ? ? ?
? ? ?if (abs(m - 1 - y) <= m - 1 - abs(m - 1 - x) && ? ? ? ? ? ? ?
? ? ? abs(m - 1 - y) > n - 1 - abs(m - 1 - x)) ? ? ? ? ? ?
? ? ? printf("*");
? ? ?else
? ? ? printf(" ");
? ? ? }
? ? ? printf("n");
}
我們將所有的判斷都集中在了一起,閱讀程序,很容易明白這個大大的判斷語句是干嘛的了,是否比以前的實現(xiàn)都優(yōu)雅了很多呢。
不過這個判據(jù)好像還是挺復(fù)雜的,有沒有更好的辦法呢,估計很多朋友在看到我上面的那幅圖時已經(jīng)想到了,那就是將坐標軸移到菱形的中間去,示意如下:
外菱形的四條邊我們用表達式描述出來,如下:
(+x) + (+y) < m
(-x) + (+y) < m
(+x) + (-y) < m
(-x) + (-y) < m
合并后的表達式為:abs(x)+abs(y)
for (x = -m; x <= m; x++)
{ ? ?
for (y = -m; y <= m; y++) ? ?
{ ? ? ? ?
? t = abs(x) + abs(y); ? ? ? ?
? if (t >= n && t <= m) ? ? ? ? ? ?
? ? printf("*"); ? ? ? ?
? else ? ? ? ? ? ?
? ? printf(" "); ? ?
?} ? ?
?printf("n");
}
不知大家看到這段代碼是怎樣的感覺,我僅記得當(dāng)初自己發(fā)現(xiàn)這個實現(xiàn)后,第一次被這種簡單的數(shù)學(xué)美給震撼了。如果大家也有相同的感覺,我堅信,你可以在編程的這條荊棘路上走很高很遠…
C語言第五個例子:嵌入式C語言和桌面C語言差異巨大
在大學(xué)階段,我學(xué)的是計算機專業(yè),對編程的愛好聚焦在各種巧妙的算法和實現(xiàn)上。工作后,開始從事嵌入式產(chǎn)品開發(fā),碰了N多的壁,吃了N多的苦,才明白了嵌入式C語言和桌面C語言是有差別的。
為了讓新人盡快邁入嵌入式門檻,因此,今天,注定是一場批斗大會,經(jīng)歷波折,方能成長。
我們首先拿最優(yōu)秀的實現(xiàn)開刀,在上一節(jié)最后,分享了一個很優(yōu)美的實現(xiàn),示例如下:
for (x = -m; x <= m; x++)
{ ? ?
?for (y = -m; y <= m; y++) ? ?
?{ ? ? ? ?
? ?if (abs(x) + abs(y) >= n && abs(x) + abs(y) <= m)
? ? printf("*"); ? ? ? ?
? ?else
? ? printf(" ");
? } ? ?
? printf("n");
}
該實現(xiàn)將最復(fù)雜的條件判斷置于兩層循環(huán)之中,因此程序效率很低,而在嵌入式編程中,尤其是一些強實時模塊,性能經(jīng)常是緊繃著的弦。
工業(yè)嵌入式產(chǎn)品研發(fā),追求團隊作戰(zhàn),追求產(chǎn)品可維護性,追求性能和資源的均衡??勺x性、可維護性、cpu計算能力、內(nèi)存、可靠性等等東東都成為了資源,需要我們的庖丁解牛,經(jīng)常,我們喜歡將工業(yè)嵌入式產(chǎn)品編程戲稱為針尖上的舞蹈。
除了這一點,前面的程序還有很多的不足,舉例如下:
1、整個程序經(jīng)?;祀s一談,增加了他人閱讀程序的復(fù)雜性;
2、m,n類似的名字,典型的學(xué)校風(fēng)格;
3、各種復(fù)雜的m和n的表達式,一段時候后,估計自己看起來都頭大了;
……
因此,我們需要繼續(xù)上路,為了后續(xù)程序?qū)崿F(xiàn)的方便,將整個程序結(jié)構(gòu)約定如下:
int main()
{
? ?
/* 輸入內(nèi)外菱形高度,并進行合法判斷 */
? ?/* 循環(huán)輸出菱形 */
? ?
for (……)? ?
? ?{
? ?? ? ? ?/* 輸出前導(dǎo)空格 */
? ? ? ?/* 輸出左邊星號 */
? ? ? ?/* 輸出中間空格 */
? ? ? ?/* 輸出右邊星號 */
? ? ? ?/* 輸出換行 */
? ? ? ?printf("n");
? ?}
? ?return 0;}
看到這個程序框架,容易明白一點,我們要求以單層循環(huán)的方式輸出菱形。
細細觀察空心菱形的結(jié)構(gòu),每一行從左到右可以抽象為前導(dǎo)空格,左邊星號,中間空格和右邊星號四部分,只需要找出這四個值和行號的函數(shù)關(guān)系,整個程序也就迎刃而解了,如下圖示意:
----*
---***
--**-**
-**---**
**-----**
-**---**
--**-**
---***
----*
我的職業(yè)導(dǎo)師經(jīng)常會和我探討計算機編程思維的概念,時至今日,我們也沒有辦法給其下一個準確的定義,但在反復(fù)的迭代鍛煉中,慢慢的體會到了哪些實現(xiàn)可以稱之為計算機編程思維。
舉一個例子,在計算機世界中,我們經(jīng)常僅鼠標、鍵盤、磁盤、磁盤上的數(shù)據(jù)等等都稱之為文件,可以進行簡單的遍歷。為何風(fēng)牛馬不相及的東西,我們非要將其抽象成統(tǒng)一的概念呢,這非一句話能描述清楚,后續(xù)會帶著大家慢慢體悟,但這種設(shè)計思想在產(chǎn)品研發(fā)過程中,卻比比皆是。
插入這兩段廢話后,回頭再來看我們的菱形輸出例子,明明每一行的結(jié)構(gòu)不盡相同(回憶一下第二個實現(xiàn)版本,就是分類型分別輸出的),但我們非要求同存異,或許,我期望從一開始,就在新人的心中播下架構(gòu)設(shè)計的種子,期待著后續(xù)的萌芽。
雖然這兒的話題是以新人培訓(xùn)切入的,但未嘗不是自己的成長之路,所不同的是我是在一次次的跌打滾爬中成長起來的。將自己曾經(jīng)摔過的跟頭融入到入職C語言訓(xùn)練的例子中,甚至將自己的整個成長史寫出來,但愿別人能以我為鏡,成長的更踏實更快速一些。
C語言第六個例子:嵌入式C語言和桌面C語言差異巨大
在上一節(jié)中,我們已約定了程序基本框架,并且也簡單的介紹了編程思維的理念,好似,新人很快就可以寫出合乎要求的程序了。
可惜,每個人的成長之路都不是一帆風(fēng)順的, 記得當(dāng)初自己學(xué)習(xí)計算機編程時,哪個技能不是從大量的模仿中才能體悟一點點的,進而融入到自己的知識體系中的,一點一滴的成長起來的。
依據(jù)上一節(jié)的整體框架要求,我曾經(jīng)帶過的很多新人,甚至包含一些粉絲給我的發(fā)來的郵件,寫出來的程序基本上都是新瓶裝舊酒,僅是以前實現(xiàn)例子的翻版而已。
我給大家示意一下,大家體會體會,自己是否也有這樣實現(xiàn)的沖動。
/* 循環(huán)輸出菱形 */
for (……)
{? ?/* 輸出前導(dǎo)空格 */
? ?if (上半菱形)
? ? ? 以三角型方式輸出;
? ?else
? ? ? 以倒三角形方式輸出;
? ?/* 輸出左邊星號 */
? ?if (上三角形)
? ? ? ...
? ?else if (中心空心)
? ? ? ...
? ?else
? ? ? ...
? ?/* 輸出中間空格 */
? ?if (上半空心)
? ? ? 以三角型方式輸出;
? ?else if (下半空心)
? ? ? 以倒三角形方式輸出;
? ?/* 輸出右邊星號 */
? ?if (上三角形)
? ? ? ...
? ?else if (中心空心)
? ? ? ...
? ?else
? ? ? ...
? ?/* 輸出換行 */
? ?printf("n");
}
注:該處的示例是結(jié)合第二個實現(xiàn)版本的,將一個菱形分為上三角形,下三角形,中間的空心三部分分別輸出,詳情請查看《入職C語言例子一(2)》。
應(yīng)該如何實現(xiàn)呢?在上一節(jié)中,我們提到了重點在于尋找四個函數(shù)關(guān)系。文字的表現(xiàn)力經(jīng)常是蒼白的,給大家展現(xiàn)一幅我指導(dǎo)新人時的隨意手繪圖,或許,大家立即就明白了。
理解了上圖,額外強調(diào)幾點:
1、空心菱形的每一行都必須等同看待,忘記上三角、下三角、中間空心等這樣的分割;
2、四個函數(shù)關(guān)系內(nèi)部不允許出現(xiàn)if語句,但允許提煉公共函數(shù),如abs類的;
3、成功沒有捷徑,技能的學(xué)習(xí)也沒有捷徑,將浮躁的心放下來;
走到這兒,很多人都寫了七八遍了,拜目前的大學(xué)教育模式所賜,很多人又出現(xiàn)了浮躁情緒,即時的安撫還是很有必要的,經(jīng)常打趣的一句話就是瞧瞧你的師兄xxx,別看現(xiàn)在負責(zé)好幾款產(chǎn)品得心應(yīng)手,當(dāng)初還寫了十多遍呢,哈哈。
實際上很多人也意識到了我這樣折騰的目的。大學(xué)的學(xué)習(xí)都是淺嘗輒止的,很多東西都急于求成,但將這種心態(tài)帶入企業(yè),帶入產(chǎn)品研發(fā)中卻是致命的,我僅是想通過這樣的形式,讓新人少走彎路。用心良苦,卻常引來誤解無數(shù),內(nèi)向者口是心非肚子里罵幾句,張狂者會直接詰問我這樣折騰他們有何意義,呵呵。
隨意牢騷了幾句人生坎坷路啊,經(jīng)這樣一指點,大部分人都可以按照要求順利的完成該例子,雖然每一部分都是一個復(fù)雜的表達式。
此時,我會給大家講解一個很關(guān)鍵的計算概念:迭代。
我們所從事的嵌入式產(chǎn)品是強實時工業(yè)產(chǎn)品,不僅經(jīng)常涉及傅里葉等各種復(fù)雜計算,而且還要求在指定時間內(nèi)完成,因此對計算效率等要求會比較高,最經(jīng)常采取的策略就是通過迭代,保留有價值的中間計算結(jié)果,減少整體計算量。
而通過迭代,不僅各部分的表達式不至于那么的復(fù)雜,而且會減少計算量。該例子比較簡單,沒什么技術(shù)含量,大部分人很快的就完成了迭代的調(diào)整,但前后對比的效果卻印象深刻,算是額外的收獲吧。
至此,空心菱形程序的所有技術(shù)點都描述完畢了,按著這樣的要求,大多數(shù)人可以寫出滿足要求的程序了。還是非常的建議正在閱讀的你能親自寫一寫,調(diào)一調(diào),下一節(jié)我會貼出標準答案,大家可以在比對一下,而差異部分正好是我們后續(xù)內(nèi)容的起點。
C語言第七個例子:不斷完善心中的答案
前面幾節(jié)中,列舉了一個很優(yōu)雅的實現(xiàn),一些朋友提醒我該程序輸出不正常,自己測試了一下,確實如此。
當(dāng)時寫程序時,是直接以文檔的方式寫的,一些例程也是小伙寫的程序中拷貝出來的,重在意圖表現(xiàn),所有的程序代碼都沒有測試過,缺乏了嚴謹性,優(yōu)化如下:
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");
}
咱們書接上節(jié),言歸正傳,在上一節(jié)中,新人磕磕碰碰的,終于寫出了符合技術(shù)要求的程序,皆大歡喜,以為要完工了,可惜,路依舊漫漫。
此時,我會給大家分享該題目的標準答案,讓大家同自己寫的程序進行比對,以前的程序都是以片段方式提供的,標準答案以完整的格式提供,示意如下:
/********************************************************************
*
* ? Copyright (C), 1999-2004, xxxxxx. Co., Ltd.
*
* ? 文件名稱:diamond.c
* ? 軟件模塊:空心菱形輸出
* ? 版 本 號:1.0
* ? 生成日期:2003/3/23
* ? 作 ? ?者:xiaomaer
* ? 功 ? ?能:空心菱形輸出,該程序占用內(nèi)存小,但計算稍大,可修改為"內(nèi)存換資源"算法
*
*********************************************************************/
#include
/* 菱形最大高度 */
#define MAX_DIAMOND_HEIGHT 16
/* 提前申明 */
int myGreater(int n);
/* 主程序 */
int main()
{
? ?
? ?int n, nRow;? ?int nIn, nOut;
? ?int nCount1, nCount2, nCount3;
? ?/* 輸入內(nèi)外菱形高度,并進行合法判斷 */
? ?for (;;)
? ?{? ? ? ?printf("輸入內(nèi)外菱形高度(最大%d行):外菱形高度,內(nèi)菱形高度:n", MAX_DIAMOND_HEIGHT - 1);
? ? ? ?scanf("%d,%d", &nOut, &nIn);? ? ? ?if (nIn < nOut && nOut < MAX_DIAMOND_HEIGHT && nIn >= 0)
? ? ? ? ? ?break;
? ? ? ?printf("輸入不合法,請重新輸入:nn");? ?}
? ?/* 循環(huán)輸出菱形 */
? ?nIn = nOut - nIn;
? ? ? ? ? ?/* 調(diào)整為菱形內(nèi)外差值 */? ?for (nRow = -nOut+1; nRow < nOut; nRow++)
? ?{? ? ? ?//行號
? ? ? ?printf("%-010d", nRow);
? ? ? ? ? ? ? ? ? ?/* 輸出前導(dǎo)空格 */
? ? ? ?nCount1 = nRow >= 0 ? nRow : -nRow; ? ? ? ?/* 取絕對值 */
? ? ? ?
? ? ? ?for (n = 0; n < nCount1; n++)? ? ? ? ? ?
? ? ? ? ? ?printf(" ");? ? ? ?/* 輸出左邊星號 */
? ? ? ?nCount1 = nOut - nCount1; ? ? ? ? ? ?/* 外三角部分,后續(xù)迭代使用 */
? ? ? ?nCount2 = myGreater(nCount1 - nIn); ?/* 內(nèi)三角部分,后續(xù)迭代使用 */
? ? ? ?nCount3 = nCount1 - nCount2; ? ? ? ? /* 內(nèi)外之差為實際需要輸出 */
? ? ? ?for (n = 0; n < nCount3; n++)? ? ? ? ? ?printf("*");
? ? ? ?/* 輸出中間空格 */
? ? ? ?nCount3 = 2 * nCount2 - 1; ? ? ? ? ? /* 由三角形拓展為菱形 */
? ? ? ?for (n = 0; n < nCount3; n++)
? ? ? ? ? ?printf(" ");? ? ? /* 輸出右邊星號 */
? ? ? ?nCount1--; ? ? ? ? ? ? ? ? ? ? ? ? ? /* 外三角部分 */
? ? ? ?nCount2 = myGreater(nCount1 - nIn); ?/* 內(nèi)三角部分 */
? ? ? ?nCount3 = nCount1 - nCount2; ? ? ? ? /* 內(nèi)外之差為實際需要輸出 */
? ? ? ?for (n = 0; n < nCount3; n++)? ? ? ? ? ?printf("*");
? ? ?/* 輸出換行 */
? ? ? ?printf("n");? ?}
? ?return 0;
}
/* 取大于0的數(shù) */
int myGreater(int n){
? ?if (n < 0)
? ? ? ?return 0;
? ?return n;
}
不知正在閱讀的你看到這個標準答案的感覺,能否尋找出和自己程序的差異的地方,下一節(jié)我們以此程序為起點,給大家介紹一些真實產(chǎn)品中的代碼特點。
撇開大道理講嵌入式代碼
在上一節(jié)中,我們給出了標準答案,是以真實產(chǎn)品代碼風(fēng)格寫的,期望小伙伴們能同自己的實現(xiàn)比較一下。
現(xiàn)在的90后是有個性的一代,很多人都非常反感直接的大道理灌輸,鑒于此,我期望我們的小伙伴們能自己需尋找答案,然后大家在交流碰撞中成長,效果或許會更好一些。
這篇文章就讓我們一起來找出標準答案中有價值的地方吧。
1. 有意義的變量命名
大學(xué)老師教編程的時候,重點精力都放在了語法方面,側(cè)重于將所有的語法給大家展現(xiàn)一下(這種學(xué)習(xí)方法我相當(dāng)不贊同,后續(xù)會展現(xiàn)自己的方法,項目組內(nèi)俗稱大樹法則的方法),因此經(jīng)常使用短小的程序展示語法,但因為程序短小,因此變量命名也就隨意了一些,因此i,j,k,m,n就成了???,然后不小心帶入了產(chǎn)品中,然后……。
但在產(chǎn)品研發(fā)的時候,即使比較簡單的設(shè)備,代碼量也會比較大,為了代碼閱讀維護方便,有意義的名字就變得非常的重要了。在標準例子中,使用了nIn和nOut就是想用簡單的英語單詞表示內(nèi)外菱形的高度。
一些朋友可能讀過《可讀代碼的藝術(shù)》或《代碼整潔之道》等書籍,作者強調(diào)使用準確的英文單詞來表達特定含義。
但我們是中國人,能想起一些簡單的單詞詞匯已經(jīng)頗為不易,想準確表達更是天方夜譚,因此,項目組內(nèi)經(jīng)過了無數(shù)次的迭代和探索后,形成了一種變量定義習(xí)慣:盡可能使用簡單的相近英文詞匯,全局變量必須加準確含義的中文注釋,函數(shù)內(nèi)的一些自動變量,因其作用范圍很小,有時候注釋可省略。
關(guān)于變量命名的故事還有好多,這個剛剛是給大家起個頭,我們后面會有專門的系列文章介紹,記住在真實產(chǎn)品的代碼中,需要有意義的變量命名,忘記m和n吧。
2. 代碼分節(jié)
是什么是節(jié)(section),第一次知道這個概念的時候,正式全球跨千年的時候,我還在大四,我在北京一家企業(yè)打零工,當(dāng)時公司承接了一個日本銀行的項目,日方對代碼質(zhì)量要求很嚴格,專門派了一個專家過來給我們講解各種要求,以及其背后的道理。
當(dāng)時還很年輕,狂傲不羈,因此大部分的苦口婆心都被當(dāng)做了耳旁風(fēng),但唯獨對節(jié)的概念記憶比較深刻(可能是一開始就講的是這個了,呵呵,后續(xù)的就沒耐心聽了,這個系列文章閱讀比例逐次下降,估計是同樣的道理)。
我們在讀代碼的時候,人的思維一段時間內(nèi)僅停留在一個較窄范圍的點上,如果面對的是看不到尾的代碼,會潛移默化的將其看做滅絕師太的裹腳布——又臭又長,逆反情緒悠然而生。
因此,我們需要將代碼按邏輯分成一塊一塊的,以空格作為區(qū)分,然后每塊代碼前增加適當(dāng)?shù)淖⑨?,解釋這一塊代碼的功能,是所謂節(jié)的概念。
經(jīng)過這樣的改造,讀代碼的時候,感覺會完全不一樣。關(guān)于節(jié)的價值,遠不值這些,后續(xù)會在編程規(guī)范系列文章中和大家慢慢道來,此時,我們僅要求小伙伴知道壘又臭又長的代碼是不對的。
實際上在第5節(jié)中,我約定程序整體結(jié)構(gòu)時,已經(jīng)有這樣的意圖了,大家不放回憶并體味一下。
/* 循環(huán)輸出菱形 */
for (……)
{
? ?/* 輸出前導(dǎo)空格 */
? ?/* 輸出左邊星號 */
? ?/* 輸出中間空格 */
? ?/* 輸出右邊星號 */
? ?/* 輸出換行 */
}
3. 細節(jié)化標注
某些代碼存在著一定的深度,一段時間就會忘記,通過右側(cè)簡單的注釋加以標注,不僅便于后續(xù)代碼的閱讀理解,而且標注點一般是關(guān)鍵代碼段,給后續(xù)的代碼審查也帶來的方便。
在示例代碼中,會看到迭代表達式右側(cè)(微信公眾號排版問題,經(jīng)常到了下面一行)有簡單的標注,主要就是起這樣的作用的。
但萬事過猶不及,很多剛?cè)肼毜男』锇橄矚g在右側(cè)加好多的注釋,僅挑出有價值的進行標注,需要長期的鍛煉和慢慢的體悟,或許,那一天回頭審視自己的代碼,會將許多無用的注釋刪除的時候,就修煉到家了。
4. 資源
嵌入式系統(tǒng)中,資源是一個需要時時刻刻關(guān)注的問題。
何為資源,在我們的概念中,不僅內(nèi)存和flash空間大小是資源,cpu計算能力也是資源,甚至代碼可讀性(可維護性),代碼實現(xiàn)復(fù)雜度(耗去的人力成本),復(fù)用率等等諸多方面,都被我們稱之為資源。
好鋼要用在刀刃上,但首先要明白刀刃在哪兒。缺乏了明確邊界,空談提高資源利用率是無意義的。如可讀性第一位,內(nèi)存和cpu資源比較寬裕,我們那個最優(yōu)雅實現(xiàn)版本最佳了。如果cpu計算能力緊張,上一節(jié)的標準實現(xiàn)更好一些。如果內(nèi)存稍微寬裕,為了增加代碼可讀性,我們還有更好的方法。
前面已經(jīng)有人給我留言提到過這種方法了,不知大家有沒有感受到,鍛煉到現(xiàn)在這個時候,單純的菱形輸出是多么easy的事情啊,非要搞個空心菱形,將程序搞的混亂不堪。
但加入我們用一個數(shù)組來表示整個菱形輸出,第一次以*輸出一個菱形,第二次以空格在輸出一個菱形,然后將整個數(shù)組輸出出來,是否會非常的簡單呢,代碼可讀性瞬間爆棚,執(zhí)行效率也高,僅僅多占了一些內(nèi)存而已。
總結(jié)
經(jīng)過馬拉松的歷程,終于到了最后的篇章,我們來歸納匯總一下第一個空心菱形輸出例程中提到的知識點:
1、需求清晰理解,最使用的策略是:將需求用自己的語言表達出來,和對方確認后再實施。
2、邊界判斷,要讓小伙伴意識到:在嵌入式產(chǎn)品中,最終產(chǎn)品的魯棒性,很多時候就是表現(xiàn)在這樣一點一滴的簡單判據(jù)上。
3、基本調(diào)試手段的鍛煉,工欲善其事必先利其器,無須多言。
4、編程思維的引入,需要慢慢的體會抽象的價值,這是一個難點,但想走得遠必須扛過去。
5、數(shù)值計算過程中,很重要的一個概念:巧用迭代。
6、基礎(chǔ)編程規(guī)范的引入,體會節(jié)的概念,要意識到產(chǎn)品代碼可讀可維護的重要性。
7、在嵌入式編程領(lǐng)域,資源是受限的,而我們要學(xué)會針尖上的舞蹈。
8、撰寫工作筆記,善于總結(jié),習(xí)慣去體悟成長的腳步。
記得剛開始從事嵌入式編程的時候,我的職業(yè)導(dǎo)師給我欣賞了他的記事本,密密麻麻的各種調(diào)試記錄,感悟想法,技術(shù)資料,知識歸納,我終于明白了他為何獲得了全公司上上下下的認可。
因此,我們的團隊形成了一條不成文的規(guī)矩,必須做工作筆記,不管方式,不管格式,只要開始記錄就好。