看板 C_and_CPP 關於我們 聯絡資訊
※ 引述《IOP14759 (iop14759)》之銘言: : 開發平台(Platform): (Ex: Win10, Linux, ...) : WIN8 : 編譯器(Ex: GCC, clang, VC++...)+目標環境(跟開發平台不同的話需列出) : c++builder : 額外使用到的函數庫(Library Used): (Ex: OpenGL, ...) : 無 : 問題(Question): : 想請教此程式如果想寫成迴圈該怎麼寫? : 程式碼(Code):(請善用置底文網頁, 記得排版,禁止使用圖檔) : int pcs,ID,count; : AnsiString bit0,bit1,bit2,bit3,bit4,bit5,bit6,bit7,ID_display; : //將ID轉為2進制的字串 : bit1=(ID&0x02)>>1; : bit2=(ID&0x04)>>2; : bit3=(ID&0x08)>>3; : bit4=(ID&0x10)>>4; : bit5=(ID&0x20)>>5; : bit6=(ID&0x40)>>6; : bit7=(ID&0x80)>>7; : ////////////////////////////////////////////////////////////////// : if(pcs==1)ID_display=bit7; : if(pcs==2)ID_display=bit7+bit6; : if(pcs==3)ID_display=bit7+bit6+bit5; : if(pcs==4)ID_display=bit7+bit6+bit5+bit4; : if(pcs==5)ID_display=bit7+bit6+bit5+bit4+bit3; : if(pcs==6)ID_display=bit7+bit6+bit5+bit4+bit3+bit2; : if(pcs==7)ID_display=bit7+bit6+bit5+bit4+bit3+bit2+bit1; //最多7個 : ///////////////////////////////////////////////////////////////// : if(count==3){Form1->Label31->Caption=ID_display;} //第三欄id : if(count==2){Form1->Label19->Caption=ID_display;} //第二欄 id : if(count==1){Form1->Label1->Caption=ID_display;} //第一欄id : ////////////////////////////////////////////////////////////////// : 補充說明(Supplement): : 這是小弟的最近寫的,每次讀1~7個ID,每個ID隨機為1或0 : 我是用字串+字串的方式來顯示每個ID分別是1或0 : 因為暫時沒有需要太多次迴圈所以用笨方法一個個判斷 : 但是自己知道這方法很笨,如果以後要讀更多ID我就無解了 好讀(?)網頁(?)版:https://pastebin.com/raw/6CGgV6Cx - 因為不知道 AnsiString 是什麼,這裡我先使用 std::string 代替,還請見諒 - 示範部分,單純為了方面看到內容,存入了 1 跟 0 以外的數值,一切以原 PO 的規格為主 id 順序的部分啊,我就使用遞增去作了 首先,我們來看看這段邏輯 if(pcs==1)ID_display=bit7; if(pcs==2)ID_display=bit7+bit6; if(pcs==3)ID_display=bit7+bit6+bit5; if(pcs==4)ID_display=bit7+bit6+bit5+bit4; if(pcs==5)ID_display=bit7+bit6+bit5+bit4+bit3; if(pcs==6)ID_display=bit7+bit6+bit5+bit4+bit3+bit2; if(pcs==7)ID_display=bit7+bit6+bit5+bit4+bit3+bit2+bit1; //最多7個 讓我們先把他變成一個函數 std::string unkonwn_logic_a(int pcs, std::string bit0, std::string bit1, std::string bit2, std::string bit3, std::string bit4, std::string bit5, std::string bit6, std::string bit7) { if (pcs == 1) return bit7; if (pcs == 2) return bit7 + bit6; if (pcs == 3) return bit7 + bit6 + bit5; if (pcs == 4) return bit7 + bit6 + bit5 + bit4; if (pcs == 5) return bit7 + bit6 + bit5 + bit4 + bit3; if (pcs == 6) return bit7 + bit6 + bit5 + bit4 + bit3 + bit2; if (pcs == 7) return bit7 + bit6 + bit5 + bit4 + bit3 + bit2 + bit1; } 可以觀察到,事情似乎有些有趣。來改寫一下 std::string unkonwn_logic_a(int pcs, std::string bit0, std::string bit1, std::string bit2, std::string bit3, std::string bit4, std::string bit5, std::string bit6, std::string bit7) { using namespace std::placeholders; const auto &rec = std::bind(unkonwn_logic_a, _1, bit0, bit1, bit2, bit3, bit4, bit5, bit6, bit7); // TODO pcs 小於等於 0 該做些什麼? if (pcs == 1) return bit7; if (pcs == 2) return rec(1) + bit6; if (pcs == 3) return rec(2) + bit5; if (pcs == 4) return rec(3) + bit4; if (pcs == 5) return rec(4) + bit3; if (pcs == 6) return rec(5) + bit2; if (pcs == 7) return rec(6) + bit1; // TODO pcs 大於 7 該做些什麼? } 由於是遞迴結構,我們觀察前面兩個看看 // 當 pcs 為 1 時,我們取尾巴 1 個 if (pcs == 1) return bit7; // 當 pcs 為 2 時,我們取尾巴 2 個 if (pcs == 2) return rec(1) + bit6; 因此我們可以 "合理" 推斷,當 pcs 為 n 時,我們取尾巴 n 個,這樣就完成剛剛的 TODO 了 (當然,合理因人而異,這裡只是提供一種可能性而已,畢竟原 PO 並沒有提供其他的線索) std::string unkonwn_logic_a(int pcs, std::string bit0, std::string bit1, std::string bit2, std::string bit3, std::string bit4, std::string bit5, std::string bit6, std::string bit7) { using namespace std::placeholders; const auto &rec = std::bind(unkonwn_logic_a, _1, bit0, bit1, bit2, bit3, bit4, bit5, bit6, bit7); if (pcs <= 0) return ""; if (pcs == 1) return rec(0) + bit7; if (pcs == 2) return rec(1) + bit6; if (pcs == 3) return rec(2) + bit5; if (pcs == 4) return rec(3) + bit4; if (pcs == 5) return rec(4) + bit3; if (pcs == 6) return rec(5) + bit2; if (pcs == 7) return rec(6) + bit1; if (pcs == 8) return rec(7) + bit0; return rec(8); } Ok,現在我們來處理應該要是 ID 但是名字卻是 bit 的變數們,讓我們稍微改變一下寫法 std::string unkonwn_logic_a(int pcs, std::string ids) { using namespace std::placeholders; const auto &rec = std::bind(unkonwn_logic_a, _1, ids); if (pcs <= 0) return ""; if (pcs == 1) return rec(0) + ids.at(7); if (pcs == 2) return rec(1) + ids.at(6); if (pcs == 3) return rec(2) + ids.at(5); if (pcs == 4) return rec(3) + ids.at(4); if (pcs == 5) return rec(4) + ids.at(3); if (pcs == 6) return rec(5) + ids.at(2); if (pcs == 7) return rec(6) + ids.at(1); if (pcs == 8) return rec(7) + ids.at(0); return rec(8); } 這個時候,我們會發現幾個有趣的數字,0 以及 8。他們用在了遞迴的參數,以及 ids 的 index 使用。 我們可以很快的觀察到 8 這個數字其實就是 ids 的長度,讓我們基於這個觀察再修改一下 std::string unkonwn_logic_a(int pcs, std::string ids) { using namespace std::placeholders; const auto &rec = std::bind(unkonwn_logic_a, _1, ids); const auto &ids_len = ids.length(); if (pcs <= 0) return ""; if (pcs == 1) return rec(pcs - 1) + ids.at(ids_len - pcs); if (pcs == 2) return rec(pcs - 1) + ids.at(ids_len - pcs); if (pcs == 3) return rec(pcs - 1) + ids.at(ids_len - pcs); if (pcs == 4) return rec(pcs - 1) + ids.at(ids_len - pcs); if (pcs == 5) return rec(pcs - 1) + ids.at(ids_len - pcs); if (pcs == 6) return rec(pcs - 1) + ids.at(ids_len - pcs); if (pcs == 7) return rec(pcs - 1) + ids.at(ids_len - pcs); if (pcs == 8) return rec(pcs - 1) + ids.at(ids_len - pcs); return rec(8); } 哎呀,似乎發現了不得了的事情呢。這次,讓我們擺脫 8 的詛咒,使用 ids 的長度來處理他 std::string unkonwn_logic_a(int pcs, std::string ids) { using namespace std::placeholders; const auto &rec = std::bind(unkonwn_logic_a, _1, ids); const auto &ids_len = ids.length(); if (pcs <= 0) return ""; if (pcs > ids_len) return rec(ids_len); return rec(pcs - 1) + ids.at(ids_len - pcs); } 遞迴... 遞迴... 遞... 迴... fold... 由於字串的相加具有結合律,(sa + sb) + sc = sa + (sb + sc),我們可以將原本的 right fold 改以 left fold 實作,剛好 std::accumulate 就是 left fold // 這個是小幫手. std::vector<int> range(int from, int to) { std::vector<int> ret{}; for (int i = from; i <= to; i++) { ret.emplace_back(i); } return ret; } std::string unkonwn_logic_a(int pcs, std::string ids) { using namespace std::placeholders; const auto &rec = std::bind(unkonwn_logic_a, _1, ids); const auto &ids_len = ids.length(); if (pcs <= 0) return ""; if (pcs > ids_len) return rec(ids_len); const auto &pcs_range = range(1, pcs); return std::accumulate(pcs_range.cbegin(), pcs_range.cend(), std::string{}, [&](std::string acc, int cur_pcs) { acc += ids.at(ids_len - cur_pcs); return std::move(acc); }); } 最後,我們可以使用 for loop 去表達 left fold。不知道大家的 for loop 除了 left fold,還 用來表達了那些概念呢? 感覺會不小心搞不清楚是在 for 什麼呢 std::string unkonwn_logic_a(int pcs, std::string ids) { using namespace std::placeholders; const auto &rec = std::bind(unkonwn_logic_a, _1, ids); const auto &ids_len = ids.length(); if (pcs <= 0) return ""; if (pcs > ids_len) return rec(ids_len); auto acc = std::string{}; for (int cur_pcs = 1; cur_pcs <= pcs; cur_pcs++) { acc += ids.at(ids_len - cur_pcs); } return acc; } 可以針對 for loop 的特性做一些微調,讓程式碼更簡潔,以及修改一下型別,讓程式更明確 std::string unkonwn_logic_a(size_t pcs, std::string ids) { const auto &ids_len = ids.length(); pcs = std::min(pcs, ids_len); auto acc = std::string{}; for (int cur_pcs = 1; cur_pcs <= pcs; cur_pcs++) { acc += ids.at(ids_len - cur_pcs); } return acc; } 好了,現在可以回答原 PO 的問題:想請教此程式如果想寫成迴圈該怎麼寫? 嗯,大概就是這樣 誒,等等,你說那些 bit operation 怎麽不見了,好吧,那讓我們處理處理。 首先,我們修改一下 unkonwn_logic_a 取得長度跟取 index 的形式,新增幾個小幫手 std::string::size_type length(const std::string &s) { return s.length(); } std::string::value_type at(const std::string::size_type pos, const std::string &s) { return s.at(pos); } namespace std { std::string to_string(const std::string::value_type c) { return {c}; } } // namespace std std::string unkonwn_logic_a(size_t pcs, std::string ids) { const auto &ids_len = length(ids); pcs = std::min(pcs, ids_len); auto acc = std::string{}; for (int cur_pcs = 1; cur_pcs <= pcs; cur_pcs++) { acc += std::to_string(at(ids_len - cur_pcs, ids)); } return acc; } 接著把 unkonwn_logic_a 改為 template 的形式 (小祕密,函數實作根本一樣)。 這裡比較麻煩的是決定 pcs 的型別,詳細可以查查 decltype 跟 declval 的用法,這裡不贅述。 template <typename IDS> std::string unkonwn_logic_a(decltype(length(std::declval<IDS>())) pcs, IDS &&ids) { const auto &ids_len = length(ids); pcs = std::min(pcs, ids_len); auto acc = std::string{}; for (decltype(pcs) cur_pcs = 1; cur_pcs <= pcs; cur_pcs++) { acc += std::to_string(at(ids_len - cur_pcs, ids)); } return acc; } 那假設我們想要用 uint8_t 去存 ids,就幫 uint8_t 實作 length, at 跟 std::to_string 就好了 size_t length(const uint8_t &b) { return sizeof(b) * 8; } uint8_t at(const size_t pos, const uint8_t &b) { return (b & (1 << pos)) ? 1 : 0; } namespace std { std::string to_string(const uint8_t b) { return std::to_string((unsigned){b}); } } // namespace std 完成後,下面的程式碼就都可以運作了,在瀏覽器上跑跑看吧 https://godbolt.org/z/wrd7Rv 這樣就優雅解決原 PO 的煩惱了:如果以後要讀更多ID我就無解了 對於通用的需求,我們可以使用 template 版本的 unkonwn_logic_a,只要幫存 id 的結構實作 length, at 跟 std::to_string 就可以。而對於不喜歡 template 版本的人,也可以重新寫一份專 屬於特定型別的 unkonwn_logic_a,沒問題的。 最後一段那個 count 跟 label 的我實在看不懂,請讓我無視他。 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 111.185.85.36 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1585685214.A.57D.html
s4300026: 推薦這篇文章。 04/01 08:26
LPH66: 推一個 04/01 08:46
cuteSquirrel: 好強 04/01 19:59
NciscalA: 推。 04/02 11:33
loveme00835: xD 好多洞恐怖 04/02 20:28
loveme00835: 在做 lifetime extension 的時候確保你的函式是什麼 04/04 01:35
loveme00835: 行為會比較好喔,而且看來沒有好好利用 ADL 而選擇 04/04 01:35
loveme00835: 把東西直接丟進 std 命名空間這污染不一般,decltyp 04/04 01:35
loveme00835: e() 雖然說很方便,但你還是沒有避掉隱式轉型,所以 04/04 01:35
loveme00835: 大部分用 auto/decltype 的情境都很多餘 04/04 01:35
loveme00835: 通常我們在模板化的時候會儘量使用 STL 常見的介面 04/04 01:45
loveme00835: ,所以呼叫std::string::size() 會是比較好的選項, 04/04 01:45
loveme00835: 如此未來要換成 std::string_view / std::span 都是 04/04 01:45
loveme00835: 可行的選項,不然這個模板就是割雞用牛刀的範例,pr 04/04 01:45
loveme00835: iority 因此降低了。另外應該是為了避掉編譯錯誤才 04/04 01:45
loveme00835: 把參數宣告成 ref to const uint8_t 還有 const uin 04/04 01:45
loveme00835: t8_t 吧?整份扣看起來很糟糕 04/04 01:45
sarafciel: 推 04/06 01:03