碼管字符畫(huà)模擬:段碼表與坐標(biāo)映射的C語(yǔ)言實(shí)現(xiàn))
這道題拿到的第一反應(yīng)很多人會(huì)以為是要接真實(shí)硬件、寫(xiě)驅(qū)動(dòng)、控制引腳之類(lèi)的事情。實(shí)際上在絕大多數(shù)程序設(shè)計(jì)實(shí)訓(xùn)里數(shù)碼管指的是純軟件模擬讓你把0到9這十個(gè)數(shù)字用字符畫(huà)的形式在終端里畫(huà)成七段數(shù)碼管的樣式。題目基礎(chǔ)但考察點(diǎn)一點(diǎn)不少——字符處理、二維數(shù)組、坐標(biāo)映射、邊界條件甚至評(píng)測(cè)系統(tǒng)里常見(jiàn)的輸出格式坑全都藏在里面。我當(dāng)初做某高校的實(shí)訓(xùn)作業(yè)三時(shí)這題排在最后一題難度不算高卻讓不少同學(xué)在評(píng)測(cè)平臺(tái)上反復(fù)吃癟。這篇就把我從題目拆解到代碼落地、再到調(diào)試翻車(chē)的完整過(guò)程寫(xiě)出來(lái)給正在被這類(lèi)題目折磨的同學(xué)做個(gè)參考。先說(shuō)明一點(diǎn)本文所有實(shí)現(xiàn)思路都以輸出字符畫(huà)數(shù)碼管為例這也是多數(shù)程序設(shè)計(jì)作業(yè)的考察形式。核心思想是一套段碼表 坐標(biāo)投影的映射邏輯如果能把這套邏輯吃透后面無(wú)論是做時(shí)鐘顯示、LED點(diǎn)陣屏模擬還是真正去驅(qū)動(dòng)單片機(jī)上的數(shù)碼管都能很快遷移過(guò)去。1. 題目本質(zhì)把點(diǎn)亮的段翻譯成屏幕上的行列坐標(biāo)1.1 七段碼與十進(jìn)制的映射關(guān)系真實(shí)世界里的七段數(shù)碼管長(zhǎng)得就是一個(gè)8字被拆成七段最上面一橫叫a段右上方豎線(xiàn)叫b段右下方豎線(xiàn)叫c段最下面一橫叫d段左下方豎線(xiàn)叫e段左上方豎線(xiàn)叫f段正中間那一橫叫g(shù)段。亮的段組合不同就拼出了不同的數(shù)字。比如數(shù)字0需要點(diǎn)亮a、b、c、d、e、f這六段唯獨(dú)g段不亮數(shù)字1只點(diǎn)亮b、c兩段數(shù)字8則七段全亮。把這層關(guān)系整理成表格一眼就能看明白數(shù)字點(diǎn)亮的段熄滅的段0a b c d e fg1b ca d e f g2a b d e gc f3a b c d ge f4b c f ga d e5a c d f gb e6a c d e f gb7a b cd e f g8a b c d e f g無(wú)9a b c d f ge這張表就是整道題的核心資產(chǎn)我習(xí)慣叫它段碼表。很多同學(xué)拿到題目后第一反應(yīng)是直接寫(xiě)十個(gè)if分支或者把每個(gè)數(shù)字的圖案硬編碼成字符串?dāng)?shù)組然后printf出來(lái)。這兩種做法不是不行但代碼冗余、易錯(cuò)而且當(dāng)題目要求擴(kuò)展成放大版、橫線(xiàn)豎線(xiàn)長(zhǎng)度可變時(shí)基本就要推翻重寫(xiě)。正確做法是先抽象出每個(gè)數(shù)字點(diǎn)亮哪些段再根據(jù)段去畫(huà)圖這樣數(shù)據(jù)和邏輯徹底分離。1.2 為什么選擇段碼表而不是直接存成品圖案直接存成品圖案的思路是把0~9每個(gè)數(shù)字的三行或五行圖案預(yù)先寫(xiě)好比如數(shù)字1就存成 、 | 、 | 然后逐個(gè)輸出。聽(tīng)起來(lái)很直接但它有一個(gè)致命的局限性——圖案是死的沒(méi)法響應(yīng)尺寸變化。我見(jiàn)過(guò)一道加強(qiáng)版作業(yè)要求把數(shù)碼管放大成兩倍、三倍橫線(xiàn)變長(zhǎng)、豎線(xiàn)變高。如果你存的是成品圖案放大意味著每個(gè)數(shù)字的每一行圖案都要重新手寫(xiě)十個(gè)數(shù)字、五種尺寸那就是五十張表誰(shuí)寫(xiě)誰(shuí)崩潰。但如果你用的是段碼表放大只需要在繪圖階段多一個(gè)size參數(shù)畫(huà)橫線(xiàn)時(shí)連續(xù)輸出size個(gè)-畫(huà)豎線(xiàn)時(shí)連續(xù)輸出size行|。同一個(gè)段碼表從默認(rèn)尺寸到任意放大尺寸代碼改動(dòng)不到十行。另外段碼表這個(gè)思路更貼近真實(shí)硬件。單片機(jī)驅(qū)動(dòng)數(shù)碼管時(shí)本質(zhì)上就是告訴七段中哪幾段通電段碼表可以直接映射到IO口的電平狀態(tài)。也就是說(shuō)你現(xiàn)在寫(xiě)的這個(gè)純軟件模擬題和后面學(xué)嵌入式時(shí)的數(shù)碼管驅(qū)動(dòng)代碼邏輯幾乎是同一個(gè)。這也是為什么我強(qiáng)烈建議一開(kāi)始就建立段碼表這個(gè)抽象而不是貪圖方便去硬編碼圖案。2. 繪制引擎從段碼到字符畫(huà)的兩層映射2.1 五層?xùn)鸥褡鴺?biāo)的定義有了段碼表下一步要想清楚一個(gè)七段數(shù)碼管在字符畫(huà)坐標(biāo)系里到底占據(jù)多大的空間。標(biāo)準(zhǔn)尺寸下我習(xí)慣用5行3列的網(wǎng)格來(lái)表示一個(gè)數(shù)字row 0: a a a row 1: f b row 2: g g g row 3: e c row 4: d d d也就是說(shuō)第一行畫(huà)a段橫線(xiàn)第二行在左列畫(huà)f段豎線(xiàn)、右列畫(huà)b段豎線(xiàn)第三行畫(huà)g段橫線(xiàn)第四行在左列畫(huà)e段豎線(xiàn)、右列畫(huà)c段豎線(xiàn)第五行畫(huà)d段橫線(xiàn)。這里用到的字符很簡(jiǎn)單橫線(xiàn)用-豎線(xiàn)用|空位用空格。把每個(gè)數(shù)字的點(diǎn)亮狀態(tài)和這個(gè)坐標(biāo)網(wǎng)格結(jié)合起來(lái)就形成了一個(gè)完整的繪制映射。比如數(shù)字0點(diǎn)亮了a、b、c、d、e、f那么第一行就有橫線(xiàn)第二行左右都有豎線(xiàn)第三行空白第四行左右都有豎線(xiàn)第五行有橫線(xiàn)。最終畫(huà)出來(lái)是_ | | |_|注意第三行其實(shí)是空的但為了讓上下豎線(xiàn)看起來(lái)連續(xù)實(shí)際輸出時(shí)這一行輸出兩個(gè)空格。如果完全按空位用空格來(lái)畫(huà)0的中間一行就是兩個(gè)空格組合起來(lái)剛好形成 | | 的視覺(jué)效果。這里有一個(gè)關(guān)鍵細(xì)節(jié)段碼表里記錄的是段名而坐標(biāo)網(wǎng)格記錄的是位置兩者之間需要一個(gè)翻譯過(guò)程。我把這個(gè)過(guò)程拆成兩層映射第一層數(shù)字 → 段碼表得到該數(shù)字點(diǎn)亮哪些段。第二層段名 → 網(wǎng)格坐標(biāo)把點(diǎn)亮翻譯成在5行3列數(shù)組的哪個(gè)位置填入字符。第二層映射其實(shí)就是一張固定坐標(biāo)表每種段名對(duì)應(yīng)一個(gè)坐標(biāo)或坐標(biāo)集合段名位置a(0, 0) (0, 1) (0, 2)b(1, 2) (2, 2) (3, 2)c(1, 0) (2, 0) (3, 0)d(4, 0) (4, 1) (4, 2)e(1, 0) (2, 0) (3, 0)f(1, 0) (2, 0) (3, 0)g(2, 0) (2, 1) (2, 2)細(xì)心的讀者會(huì)發(fā)現(xiàn)e段和c段、f段和b段的坐標(biāo)其實(shí)是對(duì)稱(chēng)的關(guān)鍵看你怎么約定左右。這里要特別強(qiáng)調(diào)一點(diǎn)一旦確定了坐標(biāo)表整道題都必須保持一致否則就會(huì)出現(xiàn)6和9鏡像、2和5顛倒這類(lèi)經(jīng)典錯(cuò)誤。2.2 按行拼接多位數(shù)時(shí)的下標(biāo)計(jì)算單個(gè)數(shù)能畫(huà)出來(lái)了題目真正的難點(diǎn)才剛開(kāi)始如果輸入的不是單個(gè)數(shù)字而是一串?dāng)?shù)字比如1234567890該怎么辦很多人的第一反應(yīng)是逐個(gè)數(shù)字畫(huà)完換行繼續(xù)畫(huà)下一個(gè)。這在視覺(jué)上是豎著排列的不符合數(shù)碼管橫著并排顯示的直覺(jué)。正確的做法是整串?dāng)?shù)字的所有個(gè)位共享同一個(gè)行坐標(biāo)系先畫(huà)所有數(shù)字的第0行再畫(huà)所有數(shù)字的第1行以此類(lèi)推。假設(shè)每個(gè)數(shù)字畫(huà)布寬3列數(shù)字與數(shù)字之間空1列那么第i個(gè)數(shù)字的最左列是i * 4它的三列分別是i * 4 0、i * 4 1、i * 4 2。整個(gè)輸出區(qū)域的總列數(shù)是n * 4 - 1n是數(shù)字個(gè)數(shù)最后一列后面沒(méi)有多余空格。繪制流程是這樣的外層循環(huán)遍歷行號(hào)0到4內(nèi)層循環(huán)遍歷每個(gè)數(shù)字把每個(gè)數(shù)字在其局部網(wǎng)格中的字符放到全局矩陣的對(duì)應(yīng)列上。如果用數(shù)組實(shí)現(xiàn)就是先把整個(gè)輸出矩陣全部填成空格然后按數(shù)字→矩陣列的規(guī)則填入-或|最后一次性按行輸出。這樣既不會(huì)出現(xiàn)順序錯(cuò)亂也好控制行尾空格。用一個(gè)最簡(jiǎn)單的情況驗(yàn)證輸入12畫(huà)出的圖案是_ | _| |_|左邊是數(shù)字1右邊是數(shù)字2中間隔一列。第一行數(shù)字1沒(méi)有a段所以只有兩個(gè)空格數(shù)字2有a段所以在自己的第2、3列位置畫(huà)_。最終拼接出來(lái)就是上面這個(gè)效果。保證每個(gè)數(shù)字的局部坐標(biāo)正確拼接就只是簡(jiǎn)單的下標(biāo)平移邏輯非常干凈。3. 代碼落地C語(yǔ)言實(shí)現(xiàn)的完整骨架與邊界處理3.1 輸入解析如何穩(wěn)妥讀取一串?dāng)?shù)字實(shí)訓(xùn)作業(yè)里的輸入格式通常是第一行一個(gè)整數(shù)n表示測(cè)試數(shù)據(jù)組數(shù)接下來(lái)n行每行一個(gè)非負(fù)整數(shù)或字符串可能有前導(dǎo)零。我踩過(guò)的第一個(gè)坑就是天真地用了scanf(%d, num)來(lái)讀輸入。一旦測(cè)試數(shù)據(jù)里出現(xiàn)前導(dǎo)零比如007%d會(huì)把前面的零全部吃掉最終讀到的是7輸出的數(shù)碼管少了一位。更麻煩的是如果數(shù)字特別長(zhǎng)超出了int甚至long long的范圍直接讀整數(shù)會(huì)導(dǎo)致越界或溢出的未定義行為。穩(wěn)妥的做法是把每個(gè)輸入都當(dāng)作字符串來(lái)讀而不是當(dāng)作整數(shù)。逐位處理時(shí)對(duì)每個(gè)字符ch只要它在0到9范圍內(nèi)就把它轉(zhuǎn)換為對(duì)應(yīng)的數(shù)字值然后用這個(gè)值去查段碼表。C語(yǔ)言里我推薦用fgets讀入一行char line[1000]; fgets(line, sizeof(line), stdin);之后用strlen拿到長(zhǎng)度再把末尾的\n去掉。這里有個(gè)容易忽略的地方Windows環(huán)境下文件末尾可能是\r\n所以要去掉\n之后還要判斷一下是否剩一個(gè)\r。雖然在線(xiàn)評(píng)測(cè)系統(tǒng)大多是Linux環(huán)境但本地測(cè)試時(shí)Windows的換行就可能讓你多輸出一個(gè)空行白白吃一個(gè)WA。所以在解析換行符這件事上多一點(diǎn)防御性處理沒(méi)壞處。如果題目明確說(shuō)明每行只有一個(gè)數(shù)字也可以直接用scanf(%s, buf)它會(huì)自動(dòng)跳過(guò)空白字符并讀入連續(xù)的非空字符天然避開(kāi)了換行符問(wèn)題。但要注意%s不會(huì)檢查緩沖區(qū)上限如果數(shù)字串很長(zhǎng)可能造成棧溢出。用fgets加長(zhǎng)度限制或者自己手寫(xiě)一個(gè)按字符讀取的邏輯會(huì)更安全。3.2 核心繪圖函數(shù)與主流程整個(gè)程序我拆成了三個(gè)模塊段碼表定義、單數(shù)字繪制、整串輸出。段碼表用字符串?dāng)?shù)組來(lái)存每個(gè)數(shù)字存一個(gè)字符串字符串里出現(xiàn)的每個(gè)字符就是該數(shù)字要點(diǎn)亮的段const char *seg_code[10] { abcdef, // 0 bc, // 1 abdeg, // 2 abcdg, // 3 bcfg, // 4 acdfg, // 5 acdefg, // 6 abc, // 7 abcdefg, // 8 abcdfg // 9 };注意這里我用的段名順序是a、b、c、d、e、f、g順序本身不重要但必須和后面坐標(biāo)表保持一致。如果你在寫(xiě)坐標(biāo)表時(shí)把c段放在右邊f(xié)段放在左邊而段碼表里數(shù)字2寫(xiě)成了abcdeg那畫(huà)出來(lái)就是鏡像效果瞬間翻車(chē)。單數(shù)字繪制函數(shù)我推薦在一個(gè)5行3列的局部網(wǎng)格里操作void draw_digit(char grid[5][3], int digit) { for (int i 0; i 5; i) for (int j 0; j 3; j) grid[i][j] ; const char *seg seg_code[digit]; while (*seg) { switch (*seg) { case a: grid[0][0] grid[0][1] grid[0][2] -; break; case b: grid[1][2] grid[2][2] grid[3][2] |; break; case c: grid[1][0] grid[2][0] grid[3][0] |; break; case d: grid[4][0] grid[4][1] grid[4][2] -; break; case e: grid[1][0] grid[2][0] grid[3][0] |; break; case f: grid[1][0] grid[2][0] grid[3][0] |; break; case g: grid[2][0] grid[2][1] grid[2][2] -; break; } seg; } }這里有一個(gè)我后來(lái)才意識(shí)到的簡(jiǎn)化空間b段和f段在局部網(wǎng)格中位置完全不同但在全局網(wǎng)格中如果某個(gè)數(shù)字同時(shí)點(diǎn)亮b和f第二行左右兩側(cè)就都有豎線(xiàn)這正好對(duì)應(yīng)數(shù)字0、4、8、9等??墒窃谏厦娴拇a里c、e、f三個(gè)段都填了相同的位置看起來(lái)像是重復(fù)代碼。實(shí)際上這是因?yàn)槲野炎罅胸Q線(xiàn)統(tǒng)一由e、f、c負(fù)責(zé)右列由b負(fù)責(zé)。這個(gè)設(shè)計(jì)本身沒(méi)有問(wèn)題但如果你在段碼表里把數(shù)字4寫(xiě)成bcfg而不是bcgf繪制結(jié)果依然正確因?yàn)樽鴺?biāo)是統(tǒng)一的。更推薦的做法是定義一個(gè)二維數(shù)組把每個(gè)段的坐標(biāo)預(yù)置好后續(xù)畫(huà)圖時(shí)只需要一次遍歷int seg_pos[7][3][2] { // a: (0,0) (0,1) (0,2) // b: (1,2) (2,2) (3,2) // ... };這樣代碼更短也不容易出現(xiàn)switch漏掉case的問(wèn)題。整串輸出時(shí)我用一個(gè)大的二維數(shù)組作為全局畫(huà)布。列數(shù)由數(shù)字串長(zhǎng)度決定行數(shù)固定是5char canvas[5][1000]; for (int i 0; i 5; i) for (int j 0; j total_cols; j) canvas[i][j] ;然后對(duì)每一個(gè)數(shù)字digit拿到它自己的5x3局部網(wǎng)格再按列偏移寫(xiě)入畫(huà)布。最后逐行輸出輸出時(shí)注意控制行尾空格for (int i 0; i 5; i) { for (int j 0; j total_cols; j) putchar(canvas[i][j]); putchar(\n); }如果你的評(píng)測(cè)系統(tǒng)對(duì)行尾空格不敏感這樣可以省事但若嚴(yán)格判重就需要在每一行從右向左找到最后一個(gè)非空格字符只輸出到那里為止。3.3 空格對(duì)齊的細(xì)節(jié)空格對(duì)齊是我在這道題里栽過(guò)最多跟頭的地方。默認(rèn)尺寸下單個(gè)數(shù)字占據(jù)3列但如果兩個(gè)數(shù)字之間不留空格11會(huì)變成六個(gè)豎線(xiàn)擠在一起完全看不出是兩個(gè)數(shù)字。所以必須約定數(shù)字之間的間隔至少1列。用固定4列寬度來(lái)排版是最省心的每個(gè)數(shù)字占3列額外加1列作為右側(cè)間隔最后一列特殊處理不加間隔。還有一種對(duì)齊陷阱藏在垂直方向。數(shù)字1的圖案只有兩列豎線(xiàn)沒(méi)有橫線(xiàn)。第一行它對(duì)應(yīng)的區(qū)域是兩個(gè)空格因?yàn)閿?shù)字1不點(diǎn)亮a段。如果繪圖時(shí)沒(méi)有把局部網(wǎng)格先全部填成空格這個(gè)位置就會(huì)殘留上一輪的數(shù)據(jù)造成殘影。所以每次繪制單個(gè)數(shù)字之前無(wú)論如何都要先把5x3的網(wǎng)格清空。這個(gè)問(wèn)題在畫(huà)多位數(shù)時(shí)尤其隱蔽因?yàn)榍耙粋€(gè)數(shù)字留下的字符會(huì)出現(xiàn)在后一個(gè)數(shù)字的空白區(qū)域?qū)е螺敵鲭s亂無(wú)章。我見(jiàn)過(guò)不止一個(gè)同學(xué)排查半天最后發(fā)現(xiàn)只是初始化漏了。4. 測(cè)試與翻車(chē)記錄那些讓人想砸鍵盤(pán)的細(xì)節(jié)4.1 常見(jiàn)錯(cuò)誤的復(fù)現(xiàn)與排查想靠自己寫(xiě)出正確代碼最好的辦法不是盯著屏幕干瞪眼而是構(gòu)造一組系統(tǒng)的測(cè)試用例。我最常用的一組是單個(gè)數(shù)字0到9各測(cè)一遍然后再測(cè)0123456789整串最后測(cè)111、888、909這種容易暴露段位錯(cuò)誤的數(shù)據(jù)。我把常見(jiàn)的翻車(chē)情況整理成一張表方便對(duì)照排查現(xiàn)象可能原因排查思路輸出整體左右顛倒段碼表與坐標(biāo)表左右鏡反單獨(dú)畫(huà)數(shù)字2看b段是否出現(xiàn)在右側(cè)豎線(xiàn)上數(shù)字6和9圖案互換把e段和f段的坐標(biāo)搞反了對(duì)照段碼表6點(diǎn)亮e9點(diǎn)亮f檢查坐標(biāo)表出現(xiàn)殘影空白處有上一次的字符局部網(wǎng)格沒(méi)有重置在draw_digit開(kāi)頭強(qiáng)制把5x3網(wǎng)格填空格多位數(shù)時(shí)數(shù)字?jǐn)D在一起數(shù)字之間沒(méi)有留間隔列檢查列偏移是否為 i * 4輸出下方莫名多一空行讀取輸入時(shí)沒(méi)去掉換行符打印讀取到的字符串長(zhǎng)度確認(rèn)\n被正確截?cái)嗳繑?shù)字變成一團(tuán)橫線(xiàn)豎線(xiàn)段的坐標(biāo)全部錯(cuò)放成橫線(xiàn)位置單獨(dú)測(cè)試數(shù)字1應(yīng)該只有兩列豎線(xiàn)數(shù)字2和5是最容易暴露坐標(biāo)問(wèn)題的兩個(gè)數(shù)字。一個(gè)正常顯示的2頂部和中間和底部都有橫線(xiàn)右側(cè)右上豎線(xiàn)、左側(cè)左下豎線(xiàn)。如果你畫(huà)出來(lái)的2看起來(lái)像鏡像的5說(shuō)明b段和e段、c段和f段的關(guān)系搞錯(cuò)了。我的自查習(xí)慣是先只畫(huà)數(shù)字2再畫(huà)數(shù)字5把它們左右并排放在一起看。如果2的右上是亮的左下是亮的說(shuō)明坐標(biāo)正確如果反了趕緊去改坐標(biāo)表而不是改段碼表。4.2 評(píng)測(cè)系統(tǒng)特有的輸出比較陷阱本地測(cè)試全對(duì)提交上去卻顯示W(wǎng)rong Answer這種事在字符畫(huà)題目里太常見(jiàn)了大概率不是邏輯問(wèn)題而是輸出格式問(wèn)題。第一個(gè)坑是行尾空格。多數(shù)在線(xiàn)評(píng)測(cè)系統(tǒng)會(huì)忽略行尾多余的空格但有的評(píng)測(cè)程序不會(huì)它會(huì)把你輸出的每一行和標(biāo)準(zhǔn)答案逐字節(jié)比較。如果我的代碼輸出固定3列寬度的數(shù)字而標(biāo)準(zhǔn)答案在輸出時(shí)做了行尾空格裁剪那么評(píng)測(cè)就會(huì)判你不通過(guò)。保守做法是每一行輸出前從行尾往前掃描去掉所有連續(xù)空格再換行。這樣無(wú)論評(píng)測(cè)系統(tǒng)是否敏感都能通過(guò)。第二個(gè)坑是多余的空行。很多同學(xué)在讀入數(shù)字串后忘記處理?yè)Q行符導(dǎo)致程序先輸出一個(gè)空行再開(kāi)始畫(huà)數(shù)碼管。如果評(píng)測(cè)系統(tǒng)忽略空行還好嚴(yán)格模式下這就是一個(gè)致命WA。建議在正式輸出前先調(diào)試打印出整個(gè)畫(huà)布的內(nèi)容用十六進(jìn)制形式查看每一行的結(jié)尾確認(rèn)有沒(méi)有多余的\r或\n。第三個(gè)坑是題目要求輸出多組測(cè)試數(shù)據(jù)的間隔。有些題目要求每組輸出之間用一個(gè)空行分隔有些則不要求。這個(gè)完全取決于原題描述如果描述不清晰可以對(duì)比樣例輸出數(shù)一數(shù)兩個(gè)數(shù)碼管之間到底空了幾行。我自己的經(jīng)驗(yàn)是如果是每組輸出后帶一個(gè)空行的格式我會(huì)在繪圖函數(shù)外部統(tǒng)一控制而不是在畫(huà)布內(nèi)部加空行這樣邏輯更清晰。5. 進(jìn)階玩法尺寸放大、小數(shù)點(diǎn)與動(dòng)態(tài)刷新5.1 把數(shù)碼管做成可縮放控件默認(rèn)的3列寬5行高只是最基礎(chǔ)的樣子。如果題目要求數(shù)碼管放大兩倍、三倍那么橫線(xiàn)的寬度、豎線(xiàn)的高度都要跟著變。這時(shí)候段碼表加坐標(biāo)投影的方案優(yōu)勢(shì)就體現(xiàn)出來(lái)了。我把繪制函數(shù)的坐標(biāo)定義改成了參數(shù)化橫線(xiàn)長(zhǎng)度是size豎線(xiàn)高度也是size。比如放大兩倍時(shí)數(shù)字的寬度是2 * size 1兩側(cè)豎線(xiàn)各占1列中間橫線(xiàn)占size列高度是2 * size 3上下橫線(xiàn)各1行、中橫線(xiàn)1行豎線(xiàn)每側(cè)占size行。數(shù)字之間的間隔列保持1列。核心的段碼表完全不用改只需要改坐標(biāo)映射的計(jì)算方式。void draw_digit_to_canvas(char *canvas, int canvas_width, int digit, int start_col, int size) { // 段碼表遍歷邏輯不變 // 橫線(xiàn): 從 start_col1 到 start_colsize, 連續(xù)輸出 - // 豎線(xiàn): 連續(xù)輸出 size 行 |, 行偏移由 size 決定 }我之前做擴(kuò)展作業(yè)時(shí)把這個(gè)程序改造成支持任意size順便做了個(gè)簡(jiǎn)單參數(shù)校驗(yàn)比如size不能為0否則一條橫線(xiàn)都沒(méi)有。放大后的驗(yàn)證方法也很簡(jiǎn)單畫(huà)一個(gè)放大三倍的8數(shù)一數(shù)橫線(xiàn)是不是真的有3個(gè)-豎線(xiàn)是不是真的有3行。這個(gè)可視化驗(yàn)證比任何單測(cè)都好使。5.2 時(shí)鐘場(chǎng)景與動(dòng)態(tài)刷新思路畫(huà)完單純的一串?dāng)?shù)字很多人會(huì)覺(jué)得這題也就這樣了但其實(shí)還可以往兩個(gè)方向擴(kuò)展。一個(gè)是時(shí)鐘場(chǎng)景。數(shù)碼管時(shí)鐘需要顯示HH:MM:SS的格式中間的冒號(hào)是兩排小點(diǎn)。這時(shí)段碼表依然不變只是把數(shù)字?jǐn)U展成數(shù)字、冒號(hào)、小數(shù)點(diǎn)等元素。冒號(hào)可以單獨(dú)處理成兩行各一個(gè)*或.也可以抽象成一個(gè)獨(dú)立元素。整套代碼的架構(gòu)依然保留把所有元素先畫(huà)到畫(huà)布上再統(tǒng)一刷新輸出。如果做動(dòng)態(tài)時(shí)鐘就需要考慮上一幀的殘影問(wèn)題刷新前先把畫(huà)布清空即可。另一個(gè)是真正嵌入式場(chǎng)景下的掃描刷新。多位數(shù)碼管在實(shí)際硬件里很少把所有筆段一次性并行點(diǎn)亮通常采用動(dòng)態(tài)掃描快速輪詢(xún)每一位某一位電平拉高時(shí)輸出它的段碼停留幾毫秒再切到下一位。由于視覺(jué)暫留效應(yīng)人眼看起來(lái)所有數(shù)字是同時(shí)亮的。這個(gè)思路和字符畫(huà)里的按行拼接其實(shí)是同一件事——你的代碼從逐位輸出變成逐行掃描本質(zhì)上就是一個(gè)軟件掃描器。很多上過(guò)單片機(jī)課程的同學(xué)回頭看這道題目才會(huì)發(fā)現(xiàn)當(dāng)初學(xué)到的映射思想居然這么貼近底層硬件?;氐綄?shí)訓(xùn)本身這道數(shù)碼管題目最值得學(xué)習(xí)的不是背熟十個(gè)數(shù)字的段碼而是建立用數(shù)據(jù)表驅(qū)動(dòng)程序的意識(shí)。一開(kāi)始就把段碼和坐標(biāo)映射拆開(kāi)后面無(wú)論尺寸怎么改、輸入怎么刁鉆都只是小修小補(bǔ)。我現(xiàn)在寫(xiě)代碼時(shí)仍然保持著這個(gè)習(xí)慣凡是和規(guī)則有關(guān)的東西先整理成表再寫(xiě)邏輯。這道題最好的一點(diǎn)是它簡(jiǎn)單到不會(huì)讓你被復(fù)雜業(yè)務(wù)干擾又能讓你完整體驗(yàn)一次從數(shù)據(jù)抽象到工程實(shí)現(xiàn)的完整流程認(rèn)真做完收獲不止AC一個(gè)結(jié)果那么簡(jiǎn)單。