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