第四章原創庫(實踐)
C++比C#更适合做類庫。至少如下幾點:
- c++的const&(常引用)對象(變量),調用方無論如何都不能修改。C#不但可以修改值,還可以重新new 成員。C++寫得庫,調用方更容易排錯。
- C++模闆比C#泛型更靈活。尋找中位數,C++模闆可以支援任意類型。C#用模闆編譯不過,因為泛型不支援運算符<。
- 防破解。
- 性能優勢。
- 可移植性更強。
- Java、C#、Python都友善調用C++。
士農庫
下載下傳位址
士農庫1.1 頭檔案、lib、dll 兩個測試項目下載下傳:
CSDN下載下傳:
https://download.csdn.net/download/he_zhidan/10951802
說明
整個庫全部是動态連結,沒有靜态連結。全部是動态連結的方式使用mfc。
全部是寬字元,沒有多字元。
win32 的隻支援VC8
x64 隻支援VC9 VC10 VC11 VC14
Test是測試用的項目,基于對話框。支援的開發環境:VS2105 支援的mfc庫:VC8 VC9 VC10 VC11 VC14
UintTest是本機單元測試項目。支援的開發環境:VS2105 支援的mfc庫:VC14
由于沒準備debug的庫,是以Test和UnitTest隻能編譯Release版。
曆史
- 2010年末到2013年中,萌芽階段。簡單的将服務端、用戶端、單機版、輔助工具共用的内容封裝成一個庫。
- 2013中到2013年末,将一個庫拆分成多個庫。
- 2013年末到2017年六月,利用業餘時間不斷完善這些庫。
- 2017年6到10月,利用找工作的空隙,基本完成此庫。
- 2017年10到2018年11月,利用此庫完成職位作品《CAD圖紙大師》,此庫得到了驗證。
- 2018年11月到現在,增加了新庫三維算法庫(後取消),完成了整個庫重要功能的單元測試的自動化。
- 2019年3月到2021年9月,我在廣州智造家上海研發中心,在此庫的基礎上開發了列印大師。業餘時間和魏家瑜在此庫的基礎上完成了智勇三國二第一版。
主要功能
SN
封裝了許多基礎的功能。
一接口
Ø 讀寫鎖。
二避免依賴其它類庫
有些類經常用于庫間接口,是以需要避免依賴其它類庫。
Ø 字元串類、函數,比如:寬字元、多字元間的轉換。
Ø 時間類。
Ø 數組的封裝。
三其它
Ø 将錯誤資訊記錄到全局變量中,應用場景:構造函數和析構函數中throw會引起不可預料的問題。
Ø 安全緩存,額外開辟若幹個位元組的空間,并初始化為一個特定值,如果不越界,這些值不會改變。
Ø 智能指針,為了将關聯降為依賴。CAutoPtr<C> m_pC代替C m_c,頭檔案中不需要引用C類的頭檔案。隻需要聲明C類,在源檔案中引用C類的頭檔案。
Ø MD5。
Ø RSA。
Ø SHA。
Ø 考慮溢出的加減法。比如:int型的10億加20億,-10億減20億。
Ø 通過表名、列名、某些列的值生成sql語句。
Ø 安全指針和防野指針類。防野指針類:在構造函數中将狀态初始為已初始化,在析構函數中将狀态設定為已釋放。安全指針在使用時之前判斷 防野指針類釋放是“已初始化”,否則抛出異常。
Ø 将有參數的函數統一成沒參數傳回值類型void的仿函數。
Ø 周遊檔案夾的檔案和子檔案夾。
Ø 随機數和排列組合。
Ø 系列化和反系列化。将對象和變量轉化成二進制,并将二進制轉回變量和對象。
Ø 拆分,如字元串。
SNMFC
一網絡功能
Ø 網絡基本功能:如擷取本機IP,通過域名擷取IP,IE版本。
Ø HTML對話框的封裝類。
Ø 用于服務端的,帶“回調類”的綁定監聽類,利用IO完成端口。
Ø 用于用戶端的,帶“回調類”連接配接類,利用select模式完成,可以指定是否開啟新線程。連接配接時,可以指定逾時時間,預設5秒。如果直接調用系統的connect,逾時時間是75秒。
Ø 能夠自動處理“粘包”、“拆包”的二進制解析類。
Ø 安全套接字的輔助類,如:設定發送、連接配接逾時。
Ø 比較服務端的某個檔案夾和用戶端的某個檔案夾,并更新那些md5不同的檔案。
二多線程
Ø 用臨界區實作的線程鎖,和線程讀寫鎖。
Ø 視窗輔助類。
Ø 開啟一個線程并調用一個函數。
Ø 開啟一個線程并循環調用一個函數。
Ø 支援多線程的日志。
Ø 啟動一個線程,等待若幹秒後,Post或Send一個消息後,結束線程。
三界面
Ø 三态樹控件。
Ø 清單框擴充類和函數。
Ø 樹控件的擴充。
Ø 組合框的擴充。
Ø 關于視窗功能的封裝。比如:從左到右依次排列子視窗,排不下則下一行。可以指定行間距。頁眉和頁腳是行間距的一半。
Ø 位圖的加載和顯示。
四其它
Ø Ini檔案。
Ø 數組封裝類。
Ø 擷取硬體資訊,如網卡。
Ø 檔案或檔案夾的常用功能。
Ø 系統資料庫的擴充。
SNSTL
Ø 數組(向量)擴充。
Ø 用于多線程的向量。
Ø JSON解析。
Ø 集合的擴充。
Ø 映射的擴充。
Ø 指針向量,可以存派生類。
Ø 指針映射,可以存派生類。
其它庫
Ø UnitTest,本機單元測試項目,對整個庫的重要功能進行單元測試。
Ø SNBCG,著名界面庫的擴充,幾乎沒使用。
Ø SNPicture,圖形圖像的處理(如轉換bmp格式),幾乎沒使用。
Ø SNMath,數學及資料結構庫,幾乎沒使用。
何丹庫
整個庫全部是動态連結,沒有靜态連結。全部是動态連結的方式使用mfc。
全部是寬字元,沒有多字元。
暫時隻支援VS2015的Win32。
和士農庫的差別
- 類少而精。
- 支援VS2015和gcc。
- 部分功能有托管C++,友善C#調用。
- 大力推進自動化測設,VS自帶自動化測試工具、CPPUNIT。
第五章智勇三國二(實踐)
第六章:論文集
姜爾西論文
作者的話
(一)遊戲介紹
大家好,我是爾西。我是從業十三年的遊戲開發者,做過頁遊,手遊,現在正在做買斷制單機遊戲《三千神界:星火》。
《三千神界:星火》是一個開放世界動作RPG遊戲。視訊版如下:
《三千神界:星火》(原神風)戰鬥實機示範-我們家也有空連哦!【國産單機買斷制獨立遊戲】_單機遊戲熱門視訊 (bilibili.com)
動作方面是對标《新戰神》進行制作的,希望給大家帶來比較爽快的戰鬥體驗。我本人同時也是《鬼泣》《黑魂》系列的粉絲。是以在戰鬥系統和遊戲系統中也會看到《鬼泣》和《黑魂》系列的影子。彈反/盾反,處決,子彈時間這些都會有的,放心。
雖然我本人能打過《新戰神》的戰神難度,《隻狼》我也是二周目雙難過了的,但是我會盡量控制戰鬥體驗不像類魂遊戲那樣硬核。
RPG方面對标的是《上古卷軸5》/《原神》。主要展現在角色成長與角色冒險故事。比如通過技能樹決定角色的成長,擁有主線劇情,我的遊戲擁有九大主城,每個城市擁有自己的組織群組織線。當然我的遊戲是以二次元為主打的開放世界。在冒險的過程中獲得同伴,與同伴一起經曆故事是遊戲的醍醐味。我的遊戲是買斷制單機,是以角色不需要抽卡,角色不需要抽卡!重要的事情說兩遍。
開放世界對标的是《原神》/《曠野之息》。不同于其它的開放世界,這兩個遊戲的開放世界是以“探索”為關鍵詞,他不需要劇情來驅動,本身整個開放世界就是一個大型的遊樂場。我的開放世界最像《原神》的地方就是也是用箱庭來差別世界,差別在于《原神》使用文明來差別,而我使用神話體系來差別。在解迷方面我會在《原神》和《曠野之息》之間做一個平衡,略有花樣,但是略可以無腦的解迷就是我的遊戲的風格。
這裡我特别強調一點:對标是遊戲界通用的一個術語,意思是對照整個體系進行制作,而不隻是單獨借鑒或者學習某個要素。當然也不可能說個人獨立作品在成色或者品質上去對标大作。
如果你們有機會自己做獨立遊戲,做介紹或是拉投資什麼的,投資者第一個問你的問題就會是“你的作品對标的是哪個作品?”因為這是一個最直接的讓玩家了解你的遊戲是個什麼樣子的術語。
某些人惡意利用我說的“對标”帶我的節奏,這個鍋我不背。
還人說我的“對标”是“碰瓷”的,大家别聽這種忽悠。
每個遊戲開發者都是為喜歡的遊戲類型開發遊戲的,比如說我,喜歡《新戰神》《鬼泣》《黑魂》,喜歡《上古卷軸》《曠野之息》《原神》。因為這些遊戲帶給了我快樂,是以我也繼承了這些遊戲的理念,而我最希望的就是把我的作品呈現給這些同樣喜愛這些作品的人。這是每一個開發者最真誠的期望,把喜歡的東西送給喜歡的人,這不叫碰瓷。
再說了,你不說你對标借鑒,别人還說你抄襲,還說你借鑒了不說。反正橫豎都是他赢,欲加之罪,何患無辭?
(二)開發進度
目前的團隊隻有我一個人。我擔任所有的工作。我的開發計劃是獨立做出EA版,上架Steam。EA版上架之後,根據情況決定是否組建團隊,拉投資等等。
雖然說是開放世界,但是我的遊戲更像“新戰神”的架構。首先擁有一個必須通關的“強主線”,相當于《鬼泣》裡面的章節,裡面的關卡都經過精心設計,然後這個主線打過之後,會解開第一個城市的開放世界供大家探索。整個遊戲構架将是“主線關卡”+“箱庭”式開放世界。我的EA版将會擁有鬼泣3-4章的“強主線”,以及一個蒙德大小的開放世界。
如果EA的核心開發為60%,内容填充為40%,那麼我現在的開發進度是核心完成了一半左右。是以整個進度應該在30%。任重而道遠。
(三)閉關計劃
我是一個比較自閉的開發者,很多人覺得開發者就應該埋頭苦幹,我以我十幾年的經驗告訴大家,千萬别這樣。在這個自媒體時代,尤其是做獨立遊戲的開發者,應該有一個自媒體與大家交流。
以前關注我的小夥伴們知道我因為太過自閉甚至變成了抑郁症。對于我來說,做B站的UP是一次自我的救贖。但是也要注意平衡,我的情況是自媒體做上瘾了,甚至影響了我的開發進度。
是以我這裡宣布我的遊戲第一個DEMO出來之前,我不再參與各種亂七八糟的的節奏,也不再到各種地方亂竄發評論了,希望大家監督我。我的視訊也隻會發和我自己的遊戲相關的内容,盡量每個月給大家彙報一次。
大家不知道看到我最新改的個人簡介沒有?為《原神》正名這場戰争我們已經勝利了,真正理性的玩家早就從節奏中離開。《原神》在國外已經是不需要争議的神作,在國内恢複評價也不會遠。而剩下還在攻擊《原神》的人,大家通過最近的節奏也知道了,就隻剩下一群沒有底線的人了。他們被掃進時代的墳墓隻是時間問題。
但是為中國遊戲争奪話語權還将是一個長期的鬥争,遊戲行業光産品做得好不夠,還得有人出來說才行,是以我很喜歡亞食人。我作為一個自媒體,也會把這個做為我輩的時代責任,為了這個貢獻自己的綿薄之力。
我是爾西。我是從業十三年的遊戲開發者,我會持續出遊戲時評,遊戲開發的視訊,也會更新一些遊戲實況。喜歡的小夥伴們求關注一下。
謝謝大家一直以來的支援!作者:爾西大帝 https://www.bilibili.com/read/cv12563603?spm_id_from=333.999.0.0
實踐出真理:“交易收費”模式靠不靠譜
姜爾西
今天看到了《敢問路在何方-網遊商業模式考辯》。自從投身于遊戲行業以來,像這樣犀利的文章看到了不少,現在與以前唯一不同的地方,就是自己有實力來新身試驗了。
我是一個Webgame的開發者,獨立開發了一款名叫《古今東西》的遊戲,已經封測了半年,現在還在不斷地完善中。為了避免廣告的嫌疑,我就不放網站的連結了。
轉入正題。《敢》這篇文章分析了網遊收費模式的變化,最終提出了“交易收費”的概念,而我的遊戲中采用的正是這個模式。從結果來說,這個模式是失敗的——雖然不能算完全失敗。在這個遊戲的營運過程中,我總結了許多經驗,希望能夠與大家分享在實踐這個模式中遇到的問題以及這個模式中的沖突。如果能夠給正在設計這樣的模式,或是想設計這樣的模式的開發者以幫助,就是再榮幸不過的事情了。
《古今東西》是我做的第二款Webgame。當初在設計這個遊戲時,我對于《征途》這類道具收費模式如同所有的玩家一樣深惡痛絕。我希望在遊戲中設計一種新的交易模式,解決文中所說的RMB玩家與非RMB玩家的沖突。
如何解決?我的思想最終于《敢》中不謀而合。由商家出售道具轉為由玩家出售道具,充值得到的遊戲币作為貨币而流通。這樣做的好處是限制了RMB玩家的快速擴充,因為你要得到強化的東西,必須從其它玩家手裡面得到,而其它玩家要出售這個道具,表示這個道具對于這個玩家來說已經是“剩餘價值”。
這就是第四代“交易收費”模式的精髓。《敢》文中對于這個模式有段評價:
“沒有人去管你的貨币是充值換來的還是通過擺攤賺來的,同樣也沒人去管你的商品是自己打怪掉出來的還是之前從别的玩家那裡買來的。可以說,這是一個相當完美的池子,而我,顯然并不是第一個發現這個池子的人。”
最初我在設計時也是同樣的想法。然而,真正運用時,才發現這個模式離“完美”相去堪遠,讓我來介紹這個模式的沖突所在吧。
沖突之一:貨币回收
《敢》文中很簡單地說可以“通過流通過程對貨币進行損耗或回收”。這在實際的應用中會引發一系列的問題。“損耗或回收”的成立條件是“無償”,即玩家手中貨币消失了,玩家不會是以得到實際的利益。否則的話就是“消費”了。
比如說玩家A持有了100貨币,玩家B持有價值100貨币的道具。損耗/回收的過程隻能發生在交易的過程中,簡而言之就是“收稅”。在我的遊戲中,采取的就是這個辦法。根據價值的不同,交易時收取10%-50%的稅收(價格越高,稅率越高,跟個人所得稅一樣的原理)。
這個模式的沖突十分明了,玩家心理上的接受問題。賣出價值100貨币的道具,實際收入隻有80貨币的話。這20貨币就是被商家“黑”了。沒有玩家能夠,或者是“願意”了解貨币回收對于整個收費模式的意義。在玩家B心中會這樣想,這道具是我付出心血打出來的,玩家A付出了100貨币,這100貨币當然就是我“應得的”。
玩家不會去想,在以前的道具收費模式下,你如果得到了這個道具,要不自己用,要不就隻有丢棄。根本沒有擷取任何貨币的機會。玩家不會想,在現在的模式下,這原本是由商家全部賺去的100貨币,現在則拆分了80點給自己。
是以,每一次交易,都是對于商家道德的質疑。每一次交易玩家心中都會累積起不滿。在我的遊戲中,要求減免稅率和對這方面的抱怨就從來沒有停過。
在設計的時候,不能隻是關心“道理”是否正确,也必須考慮玩家的心理。就比如說有一個這樣的實驗:
某天,你期待已久的電影上映了,當你去看電影時,卻出了個不大不小的意外:
意外一:你剛到電影院門口準備買票時,發現你之前放在上衣口袋裡的70塊錢不見了,你還會繼續花70塊錢買票看電影嗎?
意外二:你剛到電影院門口,發現自己幾天前花70塊錢買的電影票不見了,你還會花70塊錢買票看電影嗎?
這兩種情況其實是一樣的:不管丢的是70塊現金還是價值70塊錢的電影票,我們損失的都是70塊錢的價值。按照傳統經濟學的觀點,人們在這兩種情況下的決策應該是一緻的。但是,實驗結果顯示:在意外一的情景下,大部分人選擇了買票看電影;而在意外二的情景下,大部分人選擇了打道回府。
這就是“心理賬戶”的其中一個例子,想詳細了解的可以去GOOGLE一下。
總之,回收隻要是“無償”的,玩家必然會不滿。而回收是“有償”的,則會打破這個收費模式,又回歸原來的道具收費。比如說如果玩家能用賺到的100貨币買一些東西,那麼一開始能夠充值100貨币的玩家顯然就能直接并且無限制地取得這個優勢。
這是一個無法平衡的沖突,但是,還并不能算是緻命。
沖突之二:生産過剩
生産過剩是這個模式下一個緻命的沖突。它在所有的遊戲中存在,隻不過在這個模式下對于商家的打擊比其它模式更為緻命。經濟危機的本質在“交易收費”模式下完整地展現出來。
充值與貨币之間關系是固定的,即充值多少,必然得到固定數量的貨币。這一點是貨币制度成立的根本,首先無可動搖。
而道具的産出則不一樣。遊戲中道具的取得公式很簡單:消耗=時間+道具。時間是指完成一系列過程需要的時間,道具是指要完成這個過程,擊倒小怪及BOSS需要吃的藍/紅等消耗品之類。
衆所周知,随着角色的不斷強大和玩家技術不斷精湛,完成早期的任務,擷取其中的道具所需要的消耗會相應減少。以前需要費許多時間,使用很多道具才能殺死的BOSS,也許現在隻要一刀就可以秒殺。
随着遊戲進度地增長,遊戲中同類道具的産出量會不斷地增加,是以價格會不斷地變低。這是所有遊戲共通的現象。
而在“交易收費”下不變的東西有三樣:一、充值得到的貨币。二、充值的玩家。三、充值的玩家的需求。
以裝備來說,每個玩家能夠持有的數量是有限的。在自由市場的體制下(即出售價格由玩家制定),同類道具産出量的增加會導緻價格不斷地下降。這跟現實世界的道理是一樣的。
在玩家手中持有的剩餘道具是10個,購買力是5個。那麼其中5個就是賣不出去的。而手中持有這5個人的,必然壓低價格出售,因為賣不出去等于0。
以我的遊戲為例,最初有件道具在市場上最高賣到60元RMB,到最後價格下降到1元也有人賣。
這個沖突在點卡、月卡模式下對于商家并沒有影響,因為是遊戲内部經濟崩潰,通貨膨脹而已,就算大家人手一把XX,商家收費照樣是一樣的。而在道具收費模式下,因為通過RMB購買的道具玩家不能産出,或者說整個伺服器的産出跟RMB玩家個人的需求無關,是以商家收費也不受影響。而在“交易收費”模式下,生産過剩導緻貨币價值暴增,會出現平時充值1000貨币能夠購買的東西,現在隻需要充值100貨币。
這個局面下,隻是商家收益減少,而其它什麼也沒有變化。因為非付費的玩家通過交易拿到手中100貨币,可以用這100貨币兌換相應的價值的東西,這跟拿到1000貨币,然後兌換相應價值的東西是一樣的,遊戲中的數字不同而已。然而,商家拿到手中的是100的RMB還是10的RMB,這其中的差別是決定性的。
簡而言之,當充值與遊戲内經濟挂鈎的時候,遊戲内經濟崩潰也将導緻商家收益的崩潰。商家收益的崩潰也是遊戲的崩潰——沒有人會營運賺不到錢的遊戲。
這個沖突極度緻命,而且沒有特效藥可以解決。因為這個沖突的根源在于遊戲中物資的生産,對于遊戲中每一點一滴地設計都會影響。然而,沒有特效藥也并不等于沒有治,仍然還是有一些處理方法:
1、最低限價
目前我在自己的遊戲中,采用的最低限價模式。即一件道具擁有底價,玩家間出售不得低于這個底價。結合沖突之一中提到的局面,這個解決方案稱不上優秀,其根本體驗跟道具收費模式是一樣的。因為買方購買一件道具需要固定的金錢,而賣方擁有大量剩餘無法出售。結合沖突之一中所說的,玩家仍然會累積怨念。
2、控制産出
控制産出在于對時間和空間的精打細算。比如說以取得X道具為例,在最初狀态下(就是說玩家剛剛達到能夠取得這個道具的水準時),取得這個道具需要1周。那麼,就限制這樣的任務或者是獎勵1周隻能進行一次,以後無論再強,1周也隻能産出一樣。但是,這個設計在實際中會有各種各樣的問題,比如說最初取得這個道具需要1周,之後需要1天,然後剩下6天去做其它N個任務,産出增值仍然沒有變。這跟防沉迷系統的破局是一個原理。
3、綁定
目前許多遊戲中使用的通用手段。隻要裝備了,用了,拿到手裡面了,有些東西就是不能被交易了,隻有自己用。這的确是有效的,目前我的遊戲中還沒有加綁定的功能,但是在之後的開發中會追加上。
4、消耗
裝備增加耐久、其它的各色道具改為消耗品。這樣,可以一定程度減少物品的積累。但是,本身成為消耗品的道具代價相應就會降低。比如說隻能用N次的東西,值得用多少貨币去購買嗎?這對于玩家的消費心理是一個考驗。
總之,之是以采用新的收費模式,是平衡付費和非付費玩家之間差距,減少玩家的怨念。其主要的目的不是減少願意付費的玩家的消費,這種本末倒置模式無疑是自掘墳墓。是以“交易收費”模式從這個角度來說是一個危險的賭局。
沖突之三:掩耳盜鈴
RMB玩家和非RMB玩家最主要的沖突,之是以引起極大抱怨的沖突,就在于RMB玩家能夠通過RMB獲得多大的優勢。在《敢》一文中所提到的“消費門檻”,比如說1000的、10000的之類,這其中就隐含了一個真理:玩家在遊戲中的某些東西,值得并需要花費10000去取得。
“交易收費”模式的基本概念就是東西從玩家手裡面買,而非付費的玩家通過出售道具給RMB玩家(第一手的交易必然是從RMB玩家到非RMB玩家,或是隻充值少量的RMB玩家)擷取貨币。這樣,RMB玩家消費的時候,将RMB流通向了非RMB玩家,進而起到了平衡作用。道理看上去是這樣,其實上則不然。
作為RMB玩家來說,RMB的供給是不受遊戲的限制的。像花十萬、幾十萬的人都有,這些人願意花這麼多錢,表示有這麼多錢的東西需要他來買。在“道具收費”模式下,RMB玩家所需求的東西是向商家購買,商家的“産出”當然是無限的。在“交易收費”模式下,RMB玩家所需求的東西是向玩家購買,那玩家的産出呢?
從伺服器級别上來說,玩家的産出是無限的。就以打造裝備、鑲嵌寶石來說吧,一個RMB玩家可以從伺服器中的其它任意玩家手中購得材料,寶石,然後為自己做用。
而非RMB玩家呢?想到得到100貨币,必須賣出相對等于100貨币價值的東西。獲得100貨币之後,這100貨币能夠購買的,難倒不是價值隻相對于100貨币的東西嗎?
非RMB玩家在“交易收費”模式和“道具收費”模式下所處的環境是一樣的,就是說自己能夠得到的東西無法超過自己“努力”的範圍。而RMB玩家隻是把從商家手中買東西,轉成了從玩家手中買東西。
是以道具收費模式下真正的沖突,通過RMB能夠取得極大的優勢,非RMB玩家取得不了優勢的局面在“交易收費”模式下也仍然沒有改變。
如果說唯一有什麼局面改變的,那就是在某些情況下(通常是遊戲初期),玩家沒有多少剩餘産出,RMB玩家有錢也買不到東西,是以無法無限制地增強。然而,随着時間推移,隻要有增強的空間,隻要能夠增強的上限沒有變化,RMB玩家還是能夠取得上限的優勢。
而通過限制RMB玩家的上限,比如說,最大隻能增強多少,這個方案在道具收費模式下也是通用的。根本不需要“交易收費”的模式。
是以大家明白為什麼這個沖突叫做“掩耳盜鈴”了吧。
結論就是,要限制RMB玩家和非RMB玩家之間的差距,其根本的核心在于限制RMB玩家能夠增強的空間。以Webgame為例,外國的許多 Webgame都是采取的包月制。每周或每月支付一定的費用,進而取得一定優勢。價格跟月卡差不多,然後你再有錢,也無法得到更多的增強了。這跟國内 Webgame中琳琅滿目的增強道具相比完全是兩個風格。
在這種局面下,RMB和非RMB玩家的差距才會縮小。然而,這種局面又回歸到點卡/月卡制了。為什麼國内的網遊無法采取這種模式?或者說,不願意采取這種模式,我在下面會分析。
沖突之四:二八原則
諷刺哀歎祈求那是憤青才做的事情,一個實行家應該做的是認清現實,并且在這個現實中決定自己的命運。下面要講的就是這個業界殘酷的,但是你又不得不知道的現實。我覺得任何制作網遊的人都應該要清楚地了解二八原則是如何作用到遊戲中的。
二八原則也叫巴萊多定律,是經濟學家巴萊多發明的。他認為,在任何一組東西中,最重要的隻占其中一小部分,約20%,其餘80%的盡管是多數,卻是次要的。
二八原則的意義是80%的銷售額是源自20%的顧客;80%的财富集中在20%的人手中。放在收費模式中來說,就是說一個伺服器中隻有20%的玩家願意付費。而在這付費的玩家中,80%的收益源自于20%的使用者。在中國的網遊中,這個比值是一九更确切一點。簡單點來說,有10個人付費的話,其中1個人付的費是另外9個人的總合的9倍。
這就是為什麼所有的網遊頁遊都使用相同的手腕進行斂财的根本所在。這取決于特定環境人群——在這個話題中就是中國的網遊玩家——的消費意識。
就像進賭場的有兩種人,一種人認為賭博就是用來赢錢的,要輸錢還賭什麼?另一種人認為賭博就是用來娛樂的,娛樂就要花錢,天經地義。
不願意付費的玩家,無論東西再便宜,他們都不會花錢。而願意付費的玩家,多少錢都肯花。在這種局面下,如果讓你設計收費的思路,結果會是怎樣?《征途》的成功就在于老史清楚地了解到了這個局面,現在所有網遊/頁遊都拷貝的收費模式也印證了同樣的真理。
這個局面對于做生意的人來說并不陌生,一般來說,如果你有10個客戶,而真正讓你賺到錢的,其實就是其中的1-2個客戶。你應該花精力的,也就在這1-2個客戶身上。現在網遊收費制度采取同樣的思路也是因為這樣原因。
這個破局的形成受到國情的影響。而之是以在國外沒有這樣的破局,是因為大家普遍的消費意識的不同。願意付費的玩家比例更大,是以與其深挖單個玩家,不如以量決勝負。以前聽說過猶太人和日本人的生意經,猶太人奉行擁有100個客戶,從每個客戶身上賺10000塊。日本人奉行擁有10000個客戶,從每個客戶身上賺100塊。
總結
如果說要對這篇文章做一個總結的話,那就是“交易收費”的模式比起目前“道具收費”的模式來說,并沒有什麼特别的先進性。當然,我也并不否定這個模式的未來,因為畢竟我自己的遊戲中就使用的這個模式。隻是要完成一個體系,不隻是單獨需要一個大的規模上的設想,而且需要許多的“發明”來完成。就像現在成熟的保險業,也是通過不斷地完善自己的體制和細節才走到今天這一步。
道具收費其實就是免費經濟的一種展現。免費擷取一種商品,通過對這種商品進行支援來擷取收益。就比如說電腦,外國人買電腦是很便宜的,但是這電腦上用的軟體卻要花費許多倍的錢,遊戲主機也是一樣,一個玩家開銷最大的不是主機,而是遊戲軟體,這種消費意識也是産業能夠形成的根本。而道具收費能夠吃得開,也是因為這個理由。
隻是有點倒錯的是,在中國,衆所周知,最大的開銷是電腦和遊戲主機,軟體都是盜版。而這個局面在網遊中卻逆反過來了。的确是一個比較有意思的現象。
遊戲是藝術品還是商品?從需要考慮使用者的消費心理,并且需要受限于使用者的消費心理來說,遊戲是商品。
我在初入行的時候認為遊戲是藝術品,我承認那時候自己的見識很淺薄,就如同《敢》的作者一樣,通過對于遊戲的概念和定義來決定它是不是藝術。而現在的我,真正從靈魂中了解到了什麼是藝術。
藝術就是你能夠貫注你的全心全靈,用自己所持有的一切去創造,去表現的東西!當你看到一件藝術品的時候,無論它是什麼形式,你能夠從靈魂中感到這一點!
我不敢定義遊戲是不是藝術,但是我十分地肯定,我所玩過的許多遊戲,我自己創造的遊戲就是藝術。如果誰敢否定這一點,我會毫不猶豫地對他豎起中指。
敢問路在何方?永遠不要放棄自己的夢想,路的确就在你的腳下。
QQ網友張進強評價
其實還有一種收費模式就是“平台模式”。錢充進來,交易不收費,但提現有要求,比如必須整數提現。比如提現收費,微信,支付寶都這樣做。
另外充進來的錢,沒有提現之前,都可以吃利息的。當然這需要非常大的體量才可以做到。
淺談RPG核心系統
我認為,一個RPG/ARPG的核心程式應該包含下列系統。我在說明的時候盡量說得細一些,雖然可能有些羅嗦,但是這樣有助于了解。
一、屬性系統
這是遊戲中最簡單的系統,包括主角、物品、NPC、技能等各自的屬性。屬性系統的詳細部分遊戲策劃應該給出,程式隻需要給其儲存的結構就行了。這裡的屬性,自然不隻包括事物的基本性狀,也包括它應該引發的腳本代号。
二、人物狀态機系統
這個系統十分複雜,也是所有系統得以運作的基礎。這個系統标志着人物目前處于一個什麼樣的情況中。
相對于動作而言,人物是站立中?還是跑動中?還是對人對持中?還是在施放魔法中?還是在施展技能中?當然,有些狀态是可以同時擁有的,比如說站立和施法,有些是不能同時擁有的,比如說站立和走動,這在程式中需要用不同的變量來區分。
相對于劇情而言,人物現在是接受了這個任務嗎?沒有接受任務嗎?或是完成了嗎?如果任務不止一步,人物進行到哪一步了?每個任務都要用不同的狀态機來儲存。
相對于屬性而言,人物現在是中毒嗎?沒有中毒嗎?人物現在可以移動嗎?可以以正常速度移動嗎?人物現在可以施展技能嗎?可以施展哪些技能?
主角目前身上有什麼物品(任務,或是非任務物品),身上有什麼裝備,曾經去過哪些地方,殺死過哪些怪物,和哪些人對過話,這些東西都是需要狀态機來表示的,因為腳本系統需要這些狀态機來進行判斷。當然,這其中有些狀态機不需要事先設定,而是等需要的時候才計算。比如說物品狀态機,在需要的時候給定一個物品的代号,然後狀态機系統就可以給出角色是否擁有這個物品,擁有多少個之類。
在這裡有一點關鍵性的東西,遊戲程式中的狀态機和遊戲設計中的狀态不是一一對應的,比如說,遊戲設計中有“麻痹”這一狀态,但是狀态機中卻不一定有(當然,狀态機是人設計的,你要設計一個這樣的狀态也是可以的),而是用“移動:否”、“動作:否”這兩個狀态機來表示。
當然還有許多許多的狀态機情況,遊戲的規則越複雜,所需要的狀态機就越多,狀态機系統也越複雜。因為各個狀态之間也是互相關聯的。狀态機系統之是以重要,是因為周圍世界中所發生的一切行為都是以人物處于何種狀态而決定的。
三、行為系統
這個系統決定了遊戲的表演方式,也與圖形引擎和聲音引擎挂鈎。當人物(或是怪物)發出一個動作之後,這個動作要怎麼表示?比如說攻擊,雖然在角色發動攻擊的一瞬間,攻擊是否命中,目标會受到多少傷害,這個已經被計算好了,但是不可能角色的刀子一舉起來,怪物就作出受傷的動作并且慘叫一聲,而是角色的攻擊動作到達某一個階段(程式上來說就是哪一幀)的時候,怪物才作出受傷的動作。
而且,還有許多不是在動作啟動時就可以做出計算的,而是在碰撞檢測之後才決定的(可以是實體,或是非實體的)。比如說弓箭、飛彈類型的魔法、攻擊範圍會漸漸變化的魔法(例如火環),追尾型的飛彈之類,這些東西的進行方式都是行為系統來控制,而非腳本系統(以下會介紹)來控制。以火環為例,它從發動到結束共有多少幀?每幀會産生什麼樣的變化?它影響的區域會怎麼樣變化?
不同的事物可以用不同的行為系統,但也可以共用同一個行為系統,比如說冰環和火環,雖然他們的渲染和傷害計算是不同的,但是他們的行為是一樣的。
又比如說死亡的動作。如果死亡就隻有一種表現形式,那麼關于死亡的行為系統也不複雜,但是有多種死亡動作呢?比如說向前倒下、向後倒下、軟倒(直立着跪下那種),如果是從正面受到攻擊,那麼向後倒下才符合情況,而用先前倒就不真實了。或者是向後倒之後,再加上一定程度的移動,就可以造成“吹飛”(被人打出去很遠)的效果。那麼判斷何時使用哪種動作,就是行為系統的部份。
當然,人物行為所造成的影響的計算也是這個系統的功能。分析人物目前的狀态,決定使用哪一個公式來計算影響,動作是否成功(例如命中之類),人物/怪物受到影響之後會産生什麼樣的動作(當然不是指怪物的決策行為的這種關于AI的部份,是指如果生命降到0就要執行死亡動作這一類)也是行為系統的部份。這雖然是一個非常重要的部份,但因為大家對計算都了解,是以就不多說了。
四、互動/作業系統
作業系統相對而言是簡單的,接受使用者的輸入,并判斷這個輸入将要産生什麼樣的影響,執行何種操作,也需要決策是否能夠執行操作。判斷的準則需要同時分析角色和目标的狀态。比如說,使用者把一個NPC設為目标時,這個目标是友好的嗎?是敵對的嗎?如果是友好的,則執行對話的行為,如果是敵對的,則執行攻擊行為。或者,不管目标怎麼樣,現在主角處于“麻痹”狀态,是以不管攻擊還是對話的行為都無法執行,是以使用者的這一操作便被忽略,不予執行。
遊戲的各種菜單也是這個系統的一部份。包括劇情對話框、物品購買界面以及各色各樣的特有對話系統(比如說《仙劍3》的煉劍系統界面,《軒轅劍3》的煉妖系統界面)都屬于這一類。因為他們本質上就是一種對話框,它提供給使用者一些資訊,而使用者執行的操作将對角色産生影響,或是觸發某個事件/腳本。比如說購買界面,在上面執行的操作就能夠給使用者增加一項或多項物品(從地上撿拾物品也是一樣)。煉劍系統則是改變主角的劍的性質。
互動系統的邏輯層面并不複雜,它的難點主要在于煩瑣的細節,是以這也是遊戲中BUG多出的地方。(例如暗黑中著名的物品複制BUG,諸如此類,就是因為作業系統有缺陷才造成的。)
五、腳本系統
這是構成精彩世界的重要組成部份。一個好的腳本系統幾乎可以操作遊戲世界中的一切(什麼?你說腳本不能控制使用者的菜單?那如果你的遊戲中要做一個給初學者的教學示範呢?)。在以劇情為重的RPG中,腳本系統将是十分複雜的。
但是,不管腳本如何複雜,它都由三個基本的部份組成。第一、腳本執行的先決條件。這個腳本執行需要什麼樣的條件呢?是領到某個任務,是殺死某個機關,或是到過某個地方?第二,腳本執行的動作。腳本執行的動作是多種多樣的,這取決于腳本系統的能做到什麼樣的事情。最基本最常見的展開一段劇情對話,或者是由一系列人物表演一系列動作,當然,隻要遊戲能做到的一切,都可以作為腳本的動作。第三,腳本産生的影響。腳本執行之後,給人物,給這個世界造成什麼影響呢?是增加人物的力量?或是直接導緻角色死亡?接受到一個新任務?或者完成一個任務?當然,也有可能觸發另一個腳本。
腳本分為遊戲腳本和地圖腳本,遊戲腳本是在任何情況下,隻要觸發并且條件滿足就可以執行的腳本,而地圖腳本則是在指定的地圖上才可以執行。通常它需要指定的機關和指定的環境,遊戲中的大部份表演都是基于地圖的腳本。
六、地圖/環境系統
如果腳本是遊戲世界的靈魂的話,那麼地圖系統則是遊戲的血肉。地圖系統包含了地形狀況、怪物、NPC、建築等一切與世界相關的機關。它是玩家活動的空間,一切元素的容器,也是腳本系統的容器,它決定什麼地方擁有什麼樣的事件。地圖上擁有什麼樣的人物、什麼樣的腳本,決定了地圖上會發生什麼樣有趣的事情。可以說,地圖決定了整個遊戲的面貌。
但是,地圖分為地圖和世界,世界包含整個遊戲部份,在上面腳本系統中所說的遊戲腳本也是屬于地圖/環境系統的實作部份。這一個系統沒有什麼可說的,想必大家都十分了解。
七、AI系統
這個系統也是很關鍵的一個系統。不過關于它并不需要太多的介紹,因為這也是大家都熟悉的系統。雖然這也是一個影響行為的系統,但是它的大部份功能在于怪物如何與角色戰鬥。複雜的行為模式是腳本系統要實作的,這個系統隻實作簡單的決策:戰鬥還是不戰鬥、行進路線、用什麼樣的技能/魔法戰鬥等。
八、主要制系統
這個系統也不用多作說明了,它由一系列菜單和調用組成。遊戲的存儲、讀取功能也是由這一個系統負責。它掌管整個遊戲的進行過程。
以上就是我對于RPG/APRG系統的了解了,其實,這樣分析我也還是第一次,需要注意的是,雖然這是分析RPG的系統,但是其中的許多概念對于所有遊戲都是通用的。因為本人并沒有真正開發過RPG遊戲,是以這些概念都是設想,僅供各位作為參考。有不足的地方,還希望大家一起研究讨論。
服務水準與遊戲技術一樣是硬實力
一、争議的起源
自從某某遊的兔女郎風波之後,無論是業界還是玩家之間,都展開對于營運能力的激烈讨論,我們選擇兩方比較有代表性的觀點來說明這個沖突的根源:
一方表示,遊戲是内容行業,是以官方如果提供了優質的内容,緻力于技術的累積,那麼這就是一個好公司。值得去支援。
另一方表示,遊戲是服務行業,而如果官方有很多使玩家利益受損,體驗受損的“營運事故”,那麼這個公司就不是一個好公司,不值得玩家去支援。
關于第一點,大家的争議并不大,遊戲隻要有好的内容,緻力于技術的累積,當然就是一個好公司,某某遊,鷹角,庫洛等都是堅持這一類以内容産出為公司發展核心的公司,當然在這一點上沒有問題。
然而,是不是因為營運能力,營運事故就能夠把這些公司定性為不好的公司呢?而以上這些公司,或多或少都有營運事故。這一點争議最大,而這篇文章就是要解決這個争議。
二、服務也是硬實力
在解決這個争端之前,首先得明确一個概念,服務也是硬實力!
而我覺得這個概念正是現在的遊戲公司沒有重視的。比如大偉哥在采訪中曾經說過,遊戲是内容行業,要回歸到内容的創作上來才是正道。這相比起那些做換皮氪金手遊的公司已經是很進步的理念了,但是這還不夠先進,還不能滿足于這個時代消費者的需求。
除了遊戲内容,遊戲服務也是遊戲公司需要重視的地方。不僅包括對于遊戲内玩家的服務,還包括對二創群體,對玩家社群的服務。并且這需要成戰略,成體系,而不是僅僅設立一個部門,配置設定點預算就好了。換句話說,這應該是與遊戲技術同等需要研究的課題。
而遊戲中之是以出現各種各樣的營運事故,就是與遊戲服務能力不足相關的。遊戲出現BUG,這是遊戲開發人員的水準不足,遊戲營運出現事故,是遊戲營運策劃的水準不足。
遊戲的服務和餐館的服務并沒有本質的差別,餐飲的服務員對客戶不好,對客戶的響應不及時,這就是服務員素質不足的表現,而如何提高服務員的素質?那就是對服務員進行專業的訓練,專業的訓練怎麼來的?當然是曆代以來累積的經驗與行業中形成準則的知識啊。
三、營運事故多的公司就是差公司嗎?
遊戲行業作為一個新興的産業,沒有那麼多行業累積的準則,也沒有那麼多累積的人才。尤其是公司之間的競争關系,使人才之間的流動更加困難,幾乎每一個公司都是重頭做起。靠自己吃的虧來交學費。
我這裡并不為任何遊戲公司洗地,一個遊戲公司的營運能力就是和他的技術力同等的硬實力。硬實力不行的公司不被人支援就是天經地義的事情。但是,我想要說的是,硬實力的多少是玩家自身決定的。遊戲好不好玩,你是否接受這個遊戲公司這個水準的服務,都是做為玩家擁有的權力。這就和你是否對一個餐廳的菜肴滿意,是否對服務滿意一樣。
而我要反駁的就是兩個極端思想,這兩種極端思想對于遊戲行業的發展,對于遊戲玩家社群的生态沒有任何的益處。
第一個極端思想就是把公司拟人化,拟人化之後,就可以把公司的營運事故當成某個人對某個人的傷害,因為個人對個人的傷害就是一種“恩怨”,而這種“恩怨”就可以被定格化。就好比說,我一年前有個服務生上菜慢一小時,半年前又有個服務生上菜慢30分鐘。我覺得這對我個人造成了極差的體驗,而且是對我的不尊重。這種想法對不對?完全合理,而且你也完全有理由讓餐廳道歉,賠償你的時間或精神損失。你也完全有理由因為這種待遇再也不去這個餐廳,完全沒有問題。
但是,餐廳的服務能力是不能被定格化的。相反他的評價是實時的。我是不是玩這個遊戲,就和我今天要不要進這個餐廳吃東西一樣,大部分人關心的,或者說真正核心的是,他今天能不能5分鐘之内給我把菜端上來。遊戲就是一種服務,我是不是支援這個遊戲,完全取決于我是否對他的服務有信心,是否能夠接受。
這是現實的話題:比如我是一個二創UP,如果我要選擇一個遊戲做為我二創的根據地。那麼這個遊戲之前對于二創環境的種種營運事故,會影響我的判斷嗎?當然有一定程度影響,但是我更看重的是目前他對于二創的支援。如果他目前的二創政策對于我來說更有利,我當然選擇它。
第二個極端思想就是上綱上線。因為這裡涉及很多敏感話題,是以我不能多談,不然文章都發不出來。了解節奏的玩家可以在評論區補全。遊戲本來就是很單純的内容服務,比如向世界各國的人提供差異化的服務是商業的常識。比如遊戲中出現的各種文化也隻不過是從萬裡山河中吸取靈感而已。而這些人利用國家與民族來制造極端思想與對立。這是另外一種極端思想。
而那些極端主義者,他們就是利用了“翻舊帳”和“上綱上線”這兩種的确具有一定邏輯,一定合理性的觀點極端化,并以此為理由攻擊遊戲公司,攻擊遊戲玩家,攻擊遊戲二創,攻擊遊戲UP,攻擊關聯的公司。
這些人的所作所為,甚至引起了遊戲史上最奇葩的情況,一群遊戲玩家站出來維護遊戲公司。為什麼會出現這種奇葩情況,你仔細想想,當你進一個餐廳吃飯的時候,旁邊有人拉住你,跟你細說這個餐廳以前對他多麼多麼不好,讓你别進去。等你用餐結束走出來時,他指着你破口大罵,說這餐廳以前對我多麼多麼不好,你怎麼還進這個餐廳吃飯。
為什麼一般的玩家都會站出來與這些人劃清界線,給他們安排了一個魔怔人的标簽,甚至站出來反對這些人,無他,就隻是為了享受一頓甯靜的用餐而已。
四:總結
綜上所述。
我覺得是否支援一個公司,是每個玩家自己根據自己的判斷所做出來的決定。支援還是反對,都是玩家自己理所當的權力,絕對不存在當一個人宣揚自己對某個餐廳現在很滿意的時候,另外一群人要對他群起而攻之的道理。
同理,我覺得應對這種極端的風潮,也是當今遊戲公司在營運上需要研究的課題。前車之鑒已經有了。遊戲公司、遊戲媒體也應該團結起來,抵制這些極端思想對于業界的侵蝕。
朱留超論文
驅動開發過程中如何避免不可挽回的損失
我們開發出來的驅動程式,大多數情況下是需要開機自啟動的。如果我們的驅動程式一旦部署到線上客戶機環境,而暴漏出了藍屏bug。此時會導緻如下情況:
開機啟動時藍屏 -> 電腦自動重新開機 -> 開機啟動時藍屏 ...
一旦出現這種情況,會導緻使用者電腦徹底不能開機。對于懂技術的使用者來說,需要進PE或安全模式,移除我們的出問題驅動。而對于不懂技術的使用者來說,就是把電腦交給維修店重裝電腦。不論哪種情況,都是不可挽回的損失(除非有其它守護程序另說)。
為了避免以上嚴重問題的發生,有必要設計一套穩健的方法,在出現開機過程藍屏的情況下,不至于循環藍屏導緻不能啟動電腦。思路如下:
驅動從功能上分為以下兩部分:
核心功能 >> 檢測異常關機次數 + 上報異常資訊 + 自動更新(出現嚴重錯誤時自救)
主體功能 >> 驅動的主體邏輯功能
被動監控驅動異常:
使用者電腦正常關機時,會觸發IRP_MJ_SHUTDOWN,而我們隻要在驅動中調用函數IoRegisterShutdownNotification 就可以捕獲到使用者正常關機而觸發的IRP_MJ_SHUTDOWN。當捕獲到此IRP後,我們可以在系統資料庫中設定一個值NormalShutdown=1。當電腦重新開機過程中,此時有兩種情況出現:
第1種(NormalShutdown為1):
驅動讀取到NormalShutdown值為1時,則認為上次是正常關閉電腦,此時開始執行驅動的【主體功能】,同時開啟一個計時器,比如10分鐘。十分鐘時間到了,再重置NormalShutdown為0
第2種(NormalShutdown為0):
驅動讀取到 NormalShutdown值為0時,則認為上次是藍屏導緻關機(也有可能是斷電或其它程式導緻的異常關機)。為了減少誤報,當讀取到NormalShutdown為0時,再設定一個累加計數的值,UnknowShutdown,每當讀取到NormalShutdown為0時,UnknowShutdown自增1,當連續3次(次數可以自定義)讀取到NormalShutdown為0時,則主動關閉驅動【主體功能】,隻保留必要的【核心功能】。此時如果是我們自身驅動引發的藍屏,則關閉【主體功能】後,應該可以正常啟動電腦。正常啟動後,上報異常資訊以及dump檔案(如果存在)給遠端伺服器。當背景捕獲到大量上傳的異常資訊後,認真排查引發故障的原因,及時退出新版本修複bug。當推出新版本後,驅動的【核心功能】中的自更功能将會下載下傳新版驅動到客戶機。此時重置NormalShutdown、UnknowShutdown,等待使用者下次重新開機電腦後生效。
主動式監控驅動異常(可選):
此方法可以更全面的捕獲到異常驅動,缺點是會加大網絡帶寬的消耗。方法如下:
驅動正常加載後,每個一段時間,發送一個心跳包給服務端,當關機的時候再通過注冊的關機回調函數發送一個【關機心跳包】。
服務端循環檢測客戶機的心跳包,當在規定時間内沒有收到心跳包,則判斷是否有收到【關機心跳包】,如果沒有收到關機心跳包,則認定此客戶機出現異常關機,進行統計。當越來越多的異常關機資訊出現時,應該引起足夠重視,重新審查代碼,及時推出新版本。
魏家瑜論文
本人當初于網上學習程式設計相識何志丹老師,從事軟體開發11年,2017年初開始進入公司上班,之前都是在家接單做私活,各種自動化輔助\外挂\病毒木馬\軟體破解\網站開發等。期間換過三家公司上班,一家創業公司主程、其中兩家公司任職網絡安全部門主管。主攻于驅動開發,與各種競争對手的病毒木馬對抗,保護公司線上産品穩定運作。除了職責内工作,平時還會給其它部門如用戶端、服務端排查BUG與代碼審計工作。
淺談軟體安全
一、代碼編寫嚴謹
開發必需得看懂自己寫的代碼,什麼叫看懂自己寫的代碼?每個API都應該進行判斷,我從到公司上班開始,發現公司大部分程式寫程式似乎都不嚴謹(尤其是),比如該判斷的時候不判斷,主觀性的認為程式不會在這行代碼上出問題,而往往BUG都是這樣積累的。
如果每行代碼都能看懂,該做判斷就要判斷,那當BUG産生的時候,會很容易進行排查定位。(api提供傳回值或者Out參數就是讓你得到結果,程式想寫穩就應該處理好每一種結果,讓結果隻能根據你設定的邏輯去跑)
代碼要求能看懂才是第一位,不是越精簡的代碼品質也好,現在的計算機運算速度已經很高,如果不是特别要求在程式性能上優化,沒必要去進行代碼的精簡。程式穩定比速度更重要,後期可以持續疊代優化。
- 資訊洩露如FTP、MYSQL、不應該直接在軟體裡使用,别人隻需要逆向就可以分析到;可開發服務端進行相關處理、如PHP服務端、Sock服務端等,這樣可以在服務端程式中進行一些有效性驗證。
- 從開發到上線開發環境、測試環境、線上環境。程式開發完畢後,可能會因為環境的不同而出現各種BUG,是以程式在上線前,哪怕是測試穩定,也不應該一次進行更新所有使用者。應該進行(灰階測試)小批量更新,并且程式得有挽救機制(避免使用者無法在自動更新,隻能從官網下載下傳的事情發生)
- 良好的習慣
開發之前,先做好程式流程圖,嚴格根據流程圖去開發,友善後續排BUG,也友善後續疊代需求的修改。
何志丹論文
編碼與實作方式
人類記憶字母、數字、漢字、符号需要記住讀音或筆順;計算機制隻需要記錄編号。
ASCII碼
文本編碼方式的基礎是ASCII碼,它是一個7位的編碼标準,包括26個小寫字母、26個大寫字母、10個數字、32個符号、33個控制代碼和一個空格,共128個代碼。其中ASCII碼是大多數常用編碼的基礎。
ANSI
ANSI(美國國家标準協會)通常使用 0x00~0x7f 範圍的1 個位元組來表示 1 個英文字元。超出此範圍的使用0x80~0xFFFF來編碼,即擴充的ASCII編碼。在簡體中文Windows作業系統中,ANSI 編碼代表 GB2312編碼(國标碼),GBK是GB2312的擴充;在繁體中文Windows作業系統中,ANSI編碼代表Big5(大五碼);在日文Windows作業系統中,ANSI 編碼代表 JIS 編碼。
一個漢字可能同時存在于GB2312、Big5、JIS中,他們的編碼不一定相同。“就”在GB2312中的編碼是{ 0xbe, 0xcd },在Big5中是{ 0xb4, 0x4e }。“x”在GB2312的編碼是{ 0xc1, 0x78 },在Big5中是{ 0xb8, 0x71 }。由于是先有繁體字後有簡體字,是以GB2312中有許多繁體字,Big5中我暫沒發現簡體字。
GB2312和Big5除ASCLL碼一個位元組外,其它都是兩個位元組。GB2312,範圍:0xA1A1(41377) - 0xFEFE(65278),漢字範圍:0xB0A1(45217) - 0xF7FE(63486)。Big5碼,“高位位元組”使用了0x81-0xFE,“低位位元組”使用了0x40-0x7E,及0xA1-0xFE。
Unicode
Unicode,萬國碼、統一碼,可以同時支援多國語言。
實作方式
目前(2021年8月)有99089個字元,其中包括71226個漢字。2個位元組最多隻能表示6萬多個字是以需要4位。
UTF32,所有字元都是4個位元組。
UTF16大部分字元(往往是常用字元)2位元組,少量4位元組。Unicode标準規定U+D800..U+DFFF不對應于任何字元,是以以D8到DF開頭的是4位元組,否則是2位元組。
UTF8理論上最多需要6個位元組,目前需要1到4個位元組。除首位元組外,其它位元組前兩個二進制位一定是10。隻需要1個位元組時,首位元組第一個二進制位一定是0;需要兩個位元組時,首位元組前3個二進制位一定是110;需要三個位元組時,首位元組前4個二進制位一定是1110;需要四個位元組時,首位元組前5個二進制位一定是11110。1個位元組有7個有效二進制位,2個位元組有11個二進制位,3個位元組有16個二進制位,4個位元組有21個二進制位。
轉換
C#利用System.Text.Encoding類轉換。string、char是小端utf16編碼。C++用MultiByteToWideChar和WideCharToMultiByte。gb2312和buf8是多位元組(char),utf16是寬位元組。VC中多位元組是gb2312編碼,寬位元組是小端UTF16編碼。測試環境:Win7+VS2012。
gcc的簡單使用
CentOS
CentOS是自帶界面的linux,自帶低版本gcc。Centos 6停止維護更新日期:2020年11月30日,CentOS7停止維護更新日期:2024年6月,CentOs8停止維護更新日期:2029年5月。停止更新後,如果常用更新都無法使用,比如更新gcc,安裝g++。安裝更新前,使用su 指令擷取root權限。
gcc編譯、連結簡單的檔案
gcc的頭檔案擴充名.h;c式源檔案擴充名.c,預設用gcc指令;C++式源檔案擴充名.cpp,預設用g++指令。
生成一個源檔案的可執行檔案指令
gcc main.c -o main
運作成功的話,生成可執行檔案main。不能執行指令“gcc main.c -o main.exe”,main.exe不能執行,改成main或main.b就可以執行了。可能原因:系統将main.exe當成壓縮檔案。
make指令
make指令執行目前檔案的makefile 檔案,此檔案的檔案名沒有擴充名。
最簡單的makefile
app:main.c
gcc main.c -o app
較實用的makefile
将main.c(main.cpp)和fun.c(fun.cpp)編譯連結成可執行程式app。
obj=main.o fun.o
target=app
#obj=>app
$(target):$(obj)
gcc $(obj) -o $(target)
#cpp(c)=>obj
%.o:%.c
gcc -c $< -o $@
帶宏的makefile
CFLAGS += -D _HeZhiDan
app:main.c
gcc $(CFLAGS) main.c -o main
生成動态庫并使用
生動态庫
gcc -shared -fPIC -o libtest.so test.c
将libtest.so複制到main.c 所在檔案夾
通過cd指令将目前目錄切換到main.c所在檔案夾。
通過下面的指令生成可執行檔案libtest.so前的./不能省略,否則運作main時加載libtest.so失敗。
gcc main.c -o main ./libtest.so
檢視動态庫導出那些函數
nm -D 7z.so
常見提示
錯誤資訊:g++: command not found
自帶的gcc不帶g++,通過下面的指令更新。
yum -y update gcc
yum -y install gcc+ gcc-c++
`app' is up to date
make指令傳回這個,表示檔案沒被修改過,無需重新編譯、連結。
makefile:2: *** missing separator. Stop.
第二行一定要以Tab開頭,不能用空格代替。
C#調用C++的類
非托管C++,本文的傳統C++也是指的非托管C++。
開發環境:Win10+VS2015
建立Win32 DLL
一、確定c:\code檔案夾存在。
二、建立Win32項目HD。
三、向導的應用程式類型選擇:DLL,勾選“導出符号”。
C#調用extern "C" 函數
一、打開hd.h和hd.cpp,發現向導已經導出一個變量、一個函數、一個類。
HD_API int fnHD(void);
前面加上extern "C" 變成
extern "C" HD_API int fnHD(void);
二、直接編譯
三、解決方案上滑鼠右鍵,在菜單中選擇“增加新項目”。
四、在主界面上增加一個按鈕,輕按兩下此按鈕增加響應函數。
private void button1_Click(object sender, EventArgs e)
五、導入函數并調用。
[System.Runtime.InteropServices.DllImport("hd.dll")]
private extern static int fnHD();
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(fnHD().ToString());
}
六、編譯。
七、将HD.dll複制到 WindowsFormsApplication1.exe的同目錄。
八、運作WindowsFormsApplication1.exe并點選“按鈕一”,結果:
如果有參數則函數聲明和定義必須加_stdcall。
C#通過函數擷取C++的連續資料如int[]
直接擷取指針
C++函數聲明
extern "C" __declspec(dllexport) int _stdcall GetDataLen();
extern "C" __declspec(dllexport) int* _stdcall GetData();
C++函數定義:
int a[3] = { 3, 5, 1 };
int _stdcall GetDataLen()
{
return 2;
}
int* _stdcall GetData()
{
return a;
}
C#調用
[System.Runtime.InteropServices.DllImport("HD.dll")]
private extern static int GetDataLen();
[System.Runtime.InteropServices.DllImport("HD.dll")]
private extern static System.IntPtr GetData();
private void button1_Click(object sender, EventArgs e)
{
int iLen = GetDataLen();
int a1 = 0, a2 = 0;
System.IntPtr p = GetData();
unsafe
{
int* p1 = (int*)p.ToPointer();
a1 = *p1++;
a2 = *p1++;
}
}
總結
兩個函數,分别擷取長度,擷取首資料位址。C#項目需要勾選“容許不安全代碼”。
C#配置設定釋放記憶體
C++函數聲明:
extern "C" __declspec(dllexport) void _stdcall SetDataToBuf(int* buf,int iBufLen);
C++函數實作:
void _stdcall SetDataToBuf(int* buf, int iBufLen)
{
int iLen = min(iBufLen, GetDataLen());
memcpy(buf, a, sizeof(int)*iLen);
}
C#調用:
[System.Runtime.InteropServices.DllImport("HD.dll")]
private extern static void SetDataToBuf(int[] buf,int iBufLen);
private void button2_Click(object sender, EventArgs e)
{
int iLen = GetDataLen();
int[] a = new int[iLen];
SetDataToBuf(a, iLen);
}
C#調用托管C++的類,托管C++調用非托管C++
一、HD項目的CHD類增加Add函數并實作。
二、目前解決方案新增托管C++項目HDCLR。
三、修改類Class1。
// HDCLR.h
#pragma once
#include "../HD/HD.h"
using namespace System;
public ref class Class1
{
public:
Class1();
~Class1();
!Class1();
int Add(int x, int y);
private:
CHD* m_hd;
};
//hdclr.cpp
#include "stdafx.h"
#include "HDCLR.h"
Class1::Class1()
{
m_hd = new CHD();
}
Class1::~Class1()
{
delete m_hd;
}
Class1::!Class1()
{
delete m_hd;
}
int Class1::Add(int x, int y)
{
return m_hd->Add(x, y);
}
四、增加附加依賴項..\Debug\hd.lib。
五、WindowsFormsApplication1項目增加引用。
六、WindowsFormsApplication1項目增加按鈕二。
private void button2_Click(object sender, EventArgs e)
{
Class1 class1 = new Class1();
MessageBox.Show(class1.Add(3, 5).ToString() );
}
七、編譯、運作。
托管C++和C#常用類型
托管C++的類:
static void Set4(int a, double b, float c, bool d, System::DateTime dt , array<int>^ arr,System::String^ str ,const int e)
C#導入的類
public static void Set4(int a, double b, float c, bool d, DateTime dt, int[] arr, string str, int e);
C#的string轉std::string:
char* cpType = (char*)(void*)Marshal::StringToHGlobalAnsi(strType);
std::string strRet = cpType;
Marshal::FreeHGlobal(IntPtr(cpType));
const char* 轉C#的string
gcnew System::String(str);
C#的IntPtr和C++的void轉換:
System::IntPtr^ ptr = gcnew System::IntPtr((void*)src.m_ptr);
void* ptr = (void*)src->m_ptr;
源碼壓縮包解壓
https://download.csdn.net/download/he_zhidan/21762080
一、解決方案平台改成X86。
二、先編譯HD項目,再編譯HDCLR項目,最後編譯WindowsFormsApplication1項目。
三、HD.dll要手動複制到WindowsFormsApplication1.exe所在目錄。
附:托管C++調用C#
C#的庫定義接口,托管C++實作此接口,C#的exe和托管C++的exe都可以調用兩者。
接口定義:
public interface ICall
{
int OnDo(int x );
}
接口實作:
public ref class Class1 : public CSharpLib::ICall
{
public :
virtual int OnDo(int x)
{
return x + 1;
}
};
C#調用:
CSharpLib.ICall call = new CLRLib.Class1();
int y = call.OnDo(4);
托管C++調用:
CSharpLib.ICall call = new CLRLib.Class1();
int y = call.OnDo(4);
托管C++定義接口,C#實作,C#調用,似乎不可行。
附:C#調用C函數,參數是函數指針
假定讀者已經會C#調用winapi,也會寫winapi。
C代碼:
頭檔案:
typedef void(__stdcall *CallFun)(int a);
extern "C" __declspec(dllexport) void __stdcall CallCallFun(CallFun callFun, int param);
源檔案:
void __stdcall CallCallFun(CallFun callFun, int param)
{
(*callFun)(param);
}
C#:
public partial class Form1 : Form
{
private delegate void LoopCallbackHandler(int a);
[DllImport("CPlusLib2.dll")]
private static extern void CallCallFun(LoopCallbackHandler callback, int param);
static void SetText(int param)
{
s_This.Text = param.ToString();
}
static Form s_This;
public Form1()
{
InitializeComponent();
s_This = this;
CallCallFun(SetText, 3);
}
}
運作結果:
将C#視窗标題改變成字元串3。
關于調試
C#的exe調用托管C++,托管C++調用非托管C++。直接調試C++dll所在項目,斷點可以進入托管C++和非托管C++,無法進入C#;直接調試C#的exe,斷點可以進入C#和托管C++,無法進入非托管C++;C#“啟動本機代碼調試”後,斷點可以進入C#和非托管C++,無法進入托管C++;托管C++改成“混合”後,調試托管C++,三者都可以進入斷點。
附簡單的C#類轉C++類
源碼下載下傳:簡單的C#類生成對應的C#類-C#文檔類資源-CSDN文庫
開發工具:
功能:
針對簡單的C#類,生成對應的非托管C++類,并生成托管C++的轉換函數。
應用場景:
界面層、資料層C#,邏輯層C++。
簡單的C#類:類型隻包括 double string List
關于接口
非托管C++和C#分别定義類似接口,托管C++派生與非托管C++的接口,和C#的接口是整體部分關系。
C#接口:
非托管C++:
托管代碼C++聲明:
托管C++實作:
托管C++ 派生于C#接口,和C++接口是整體、部分關系,更簡單。
halcon的Hobject轉HObject
非托管C++代碼:
class CPLUSALOG_API_20211029 CCTest
{
public:
CCTest();
int* Get();
~CCTest();
protected:
Hobject* m_pRegion;
}
CCTest::CCTest()
{
m_pRegion = new Hobject();
Halcon::gen_circle(m_pRegion, 10.0, 10, 10);
}
int* CCTest::Get()
{
return (int*)m_pRegion->Id();
}
CCTest::~CCTest()
{
delete m_pRegion;
}
C#代碼:
public class CTest
{
public CTest(System.IntPtr ptr)
{
m_region = new HObject(ptr);
HTuple tArea, tRow, tCol;
HOperatorSet.AreaCenter(m_region, out tArea, out tRow, out tCol);
}
HObject m_region;
}
m_region = new HObject(ptr,false); 表示不複制資料,預設複制資料
調用方,托管C++代碼:
CCTest ctest;
ProjectBaseLib::NGClass::CTest^ test = gcnew ProjectBaseLib::NGClass::CTest((System::IntPtr)ctest.Get());
調用結果:
面積:314
關于單元測試
建立托管的單元測試,這樣可以測試非托管C++。“公共語言運作時支援”需要修改。
傳統C++調用托管C++
托管C++調用C#
總結
托管C++的類庫, 可以被非托管C++ 調用非托管類,同時可以被C#調用托管類;也可以調用C#和C++的類。
VS2013建立的托管項目的FrameworkVersion是4.5,和C#一樣。非托管項目改成托管項目,FrameworkVersion為4.0,無法在界面上修改。隻能用文本編輯器修改項目檔案。增加“<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>”
調用方的FrameworkVersion必須大于等于被調用方的FrameworkVersion,否則加載失敗。
非托管exe改成clr就是托管c++。非托管dll改成clr,就支援托管c++文法,非托管exe一調用就崩。可能是winmain函數引起的。
C++取目前時間的微秒有意義麼?
程式有性能問題,想增加日志。C#可以很友善的取目前時間的毫秒,發現C++可以取微秒(C#也可以),考慮目前的電腦時鐘的精度達到微秒麼?
開發環境:Win10+VS2013
建立一個控制台程式,源碼如下:
#include <iostream>
#include <chrono>
int _tmain(int argc, _TCHAR* argv[])
{
int iNum = 0;
uint64_t uAdd = 0;
std::chrono::system_clock::time_point now1 = std::chrono::steady_clock::now();
uint64_t totalMicSe11 = std::chrono::duration_cast<std::chrono::microseconds>(now1.time_since_epoch()).count();
while (true)
{
iNum++;
std::chrono::system_clock::time_point now2 = std::chrono::steady_clock::now();
uint64_t totalMicSe12 = std::chrono::duration_cast<std::chrono::microseconds>(now2.time_since_epoch()).count();
uAdd = totalMicSe12 - totalMicSe11;
if (uAdd > 0)
{
break;
}
}
std::cout<< "運作次數:" << iNum << "增量:" << uAdd << "微秒 " ;
char ch;
std::cin >> ch;
return 0;
}
兩台電腦運作多次:
其中一台電腦最典型的情況:運作次數:71644增量:1249微秒
另一台電腦最典型的情況:運作次數:110614增量:663微秒
system_clock:從系統擷取的時鐘;steady_clock:不能被修改的時鐘。
結論:
2台電腦上增加微秒都是有意義的,至少可以知道電腦的大緻時鐘周期。
增加MFC支援後。
iNum++;之後增加Sleep(1);
第一台電腦典型運作結果:運作次數:1增量:10934微秒
代碼睡眠1毫秒,實際11毫秒,可能隻能至少一個時鐘周期。
Sleep(1)改成Sleep(0)
第一台電腦典型運作結果:運作次數:6219增量:1324微秒
改成Sleep(10000)
第一台電腦典型運作結果:運作次數:1增量:10010966微秒
誤差千分之一。
注意:
暫時不知道std::this_thread::sleep_for如何sleep(0)。
第七章項目及外包
監聽FTP服務端
功能簡述:監聽ftp服務端,當上傳檔案(新增或修改)時,通過(Post)通知web 辦公産品。
運作環境:Win7及以上版本。
支援ftp産品:由于是項目,和客戶溝通,隻需要協商少量ftp。大部分功能支援所有ftp,部分功能暫時隻支援Ser-U。
實作思路:監聽網絡通信,保留hook函數的技術儲備。
實作人:魏家瑜、何志丹。
技術示範視訊:https://www.bilibili.com/video/BV15Y4y167gW/
過程
軟體雖小,但在使用者使用過程中不斷發現需求,基本上一次才發現一兩條需要,偶爾還會細化、調整之前的需求。每次溝通、編碼、測試都需要牽扯精力。
- 如果使用者上傳了臨時檔案應該忽略。:擴充名為 tmp的檔案忽略,擴充名為docx 以 ~ 開頭 的檔案也忽略;擴充名為dwl1、dwl2的檔案也忽略。
- 檔案路徑應該用正斜杠“/”分割開。目前用的是雙反斜杠“\\”,且FTP檔案夾前也需要有“/”。
- 圖紙下達接口新增了一個字段類型。
- 調用建立物料接口建立物料時,增加傳輸物料編碼。
- “零件号結束字元”之前隻支援單個字元截取,現在改成要支援多個字元截取。在“零件号結束字元”文本框裡配置的字元可以為多個,用英文的“;”隔開,在截取時隻要找到文本框中出現的任意一個字元就開始截取操作。
- 新增配置項“過濾檔案夾名稱”,該配置項的值為FTP監聽路徑下的某一層級的某一檔案夾的名稱。
第八章視訊教程
C#入職教育訓練
https://www.bilibili.com/video/BV1LP4y1T7gM/
第一課三個結論:
一,winform和WPF做界面類似。
二,winform和WPF界面實作幾乎相同。
三,WPF的界面可以由非程式員做。
第二課
核心觀點:問題發現得越早,解決的成本越低。設計無法100%避免問題,但可以大幅減少問題。
使用者描述的需求
讀取源檔案夾各檔案,讀取各項目的“IO控制卡”、“光源控制器”、“相機”。
将“IO控制卡”按項目号,寫到目标檔案。目前檔案隻有一個sheet,第一列是項目号,“IO控制卡”寫到第二列,忽略第一行,遇到第一行第一列為空結束。
指定檔案夾有若幹檔案,檔案名一定有擴充名,且擴充一定為xls或xlsx。檔案名規則隻有以下兩種:
一,檔案名除掉擴充名就是項目号。
二,檔案名分三部分,依次是:項目号、分隔符-、其它資訊。
項目号可能有點号。
處理順序:先sheet,再行,最後列。不同的sheet(行、列)按升序處理。隻處理sheet名稱包括視覺的sheet,忽略其它sheet。處理所有行。處理最後一列外的列。
如果一個單元格包括“IO控制卡”,則讀取同行下一列的内容。如果有多個單元格符合要求,則讀取的内容串起來。讀取的值就是則項目的IO控制卡。
需求分析階段:數量流圖(DFD)
架構
直接使用第三方的元件讀寫excel。
概要設計-程式結構圖
原始viso檔案丢失,這個是事後補的。
詳細設計-流程圖
讀項目号
讀項目資訊
寫檔案
第三課快速疊代
1 , 一個項目可能有多個 xlsx 檔案,全部要讀取。
2,必須在界面上顯示讀取的項目數,目前處理的項目,是第幾個項目,此項目
有幾個檔案要讀取,正在處理此項目的第幾個檔案。目前顯示在界面上,以後可
能記錄在檔案,也可能同時記錄日志檔案并顯示界面。
3,光源控制器、IO 卡、相機也要讀取。
4,如果是“四軸解碼計數 IO 卡”則忽略,如果是"IO 卡", "雷塞卡", "IO 運動
控制卡" 才是 IO 卡。
- 寫檔案時,項目号可能有多個,用,、,隔開。
第四課測試先行
第五課版本控制
對新人而已,SVN簡單得多。本課包括如下内容:
- 關聯SVN并上傳
- SVN修改檔案
- SVN删除檔案
- SVN檢視某行的最後修改者
- SVN解決沖突
- 更新版本與回退版本
- SVN回退操作
- SVN回退版本
SVN增加鈎子以檢測送出的時候必須有日志
在C:\Repositories\TestCode\hooks(TestCode是庫名稱)增加pre-commit.bat,内容如下:
@echo off
set svnlook="C:\Program Files\VisualSVN Server\bin/svnlook.exe"
%svnlook% log %1 -t %2 | findstr . > nul
if %errorlevel% gtr 0 (goto err) else exit 0
:err
echo must log 1>&2
exit 1
增加後,無需重新開機SVN服務端。
第七課第三方庫
- 第二部分講解了第三方的json解析庫。
Log4net
C++版是log4。
- 建立一個WinForm項目,添加log4net的引用。
- app.config中增加如下内容:
-
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
日期的格式,每天換一個檔案記錄,如不設定則永遠隻記錄一天的日志,需設定 日志檔案名-->
<file value="Logs/" />
<datePattern value="yyyy-MM-dd-HH'.log'"/>
輸出的日志不會覆寫以前的資訊-->
<appendToFile value="true" />
<!--<rollingStyle value="Date" />-->
<staticLogFileName value="false"/>
變換的形式為日志大小 這種情況下MaxSizeRollBackups和maximumFileSize的節點設定才有意義-->
<rollingStyle value="Composite" />
備份檔案的個數-->
<maxSizeRollBackups value="10"/>
每個日志檔案的最大大小 可用的機關:KB|MB|GB 不要使用小數,否則會一直寫入目前日志-->
<maximumFileSize value="20MB"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%L] [%c] %m%n"/>
</layout>
</appender>
<root>
<level value="All"/>
<appender-ref ref="RollingLogFileAppender"/>
</root>
</log4net>
- Main函數的開始處增加 log4net.Config.XmlConfigurator.Configure();
- 輸出日志的方式如下:
log4net.LogManager.GetLogger("logName").Info("logInfo");
err級别用另外的日志檔案
一,複制appender name="RollingLogFileAppender" ,name改成RollingLogFileAppenderError。
二,<file value="Logs/" />改成<file value="Logs2/" />
三,RollingLogFileAppenderError最後增加
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="ERROR" />
<levelMax value="FATAL" />
</filter>
四,root增加appender,如圖所示:
不同的日志器
- 複制appender name="RollingLogFileAppender" ,name改成 RollingLogFileAppenderNewLog。
- <file value="Logs/" />改成<file value="Logs3/" />。
- root增加兄弟節點,内容如下:
<logger name="NewLogger">
<appender-ref ref="RollingLogFileAppenderNewLog"/>
</logger>。
運作結果:
logs檔案夾顯示所有日志器、所有級别的日志。
logs2檔案夾顯示所有Err日志。
logs3檔案夾顯示NewLogger日志器的日志。
第八課正規表達式
正規表達式解析字元串十分友善,比如解析各類日志。
本課講解了如下内容:
- 判斷一個字元串是否含有字元串ab。
- 判斷一個字元串是否是以字元串ab開頭。
- 判斷一個字元串是否以字元串ab結尾。
- 判斷一個字元串是否是ab。
- 任意數字。
- 任意字母。
- 任意字元。
- 比對|及二選一。
- ?*+
- 從右向左尋找。
- 忽略大小寫。
- 多行、單行、無屬性。
- 元組。
- 前占位符(?<=)和後占位符(?= )。
- 正整數。
第九課生成dump檔案
C++崩潰時,可以生成dump檔案,這是毫無疑問的。C#崩潰時也可以生成dump檔案,限于篇幅,請自行利用搜尋引擎搜尋。
修改系統資料庫生成dump檔案,系統資料庫位置:
SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\
值名稱 | 值 | 類型 | 說明 |
DumpCount | 1 | DWord | dump檔案個數,如果超出此數量,删除舊dump檔案。 |
DumpFolder | C:\crash | ExpandString | dump檔案存放路徑 |
DumpType | 1 | DWord | Dump檔案類型 |
本課在主線程和新開線程示範了三種異常:
//電腦隻有C槽,沒有zzz盤,必定崩潰
System.IO.Directory.CreateDirectory(@"zzz:\a");
throw (new System.Exception("dd"));
堆棧溢出,調試狀态可以發現,運作狀态無法UnhandledException和try。
第十課代碼靜态分析
如果使用綠盾,PVS-Studio.exe 和PVS-Studio_Cmd.exe需要加到白名單,如果不加前者,許可總是試用版;如果不加後者“Congratulations! PVS-Studio has not found any issues in your source code!”。如果不是自動更新政策,還需要手動更新政策。
本課講解了如下錯誤:
常見C#錯誤
常見C++錯誤
其它
int型成員變量,未初始化。VS2013Release版是随機值,VS2019Release版是0。
附加課
最小接口原則
算法課
線上視訊:https://www.bilibili.com/video/BV1x94y127co/
平時用C++寫算法,示範的時候用C#而已。
第一課中位數
起因
有時,平均數會嚴重失真。日志記錄了相機送圖時間,根據日志可以算出平均送圖間隔。極端情況:間隔0.1送圖五幀,停機1000秒,間隔0.1秒送圖六幀。計算的平均間隔是100秒,實際是0.1秒。通過中位數輔助可以解決問題。
内容簡介
最簡單的辦法,先排序,再取中間的元素。時間複雜度是O(N2),本課講解的方法是O(N)。優化的核心:[iBegin,iLeftEnd)的元素比标兵小,[iLeftEnd,iRightBegin)的元素未處理,[iRightBegin,iEnd)的元素大于标兵。
第二課最大子串
問題提出
如果每幀圖檔的處理時間都低于50ms,則不會有延遲。偶爾可以有延遲,但總延遲不能超過1000ms。
解決思路
計算總延時的方法:各幀用時減去50ms的最大子串。将各幀用時減出50,存放到清單a中。設0 <= i <= j < n,求索引為[i,j)的元素的和的最大值。如果i==j,則最大延遲為0。
内容簡介
O(n3),三輪循環:子串開始、子串結束、子串之和。
O(n2),記錄0到k子串之和,不以0開始的子串可以用兩個以0開始的子串相減表示。
O(nlog2n),三種情況:最大串在左邊,最大串在右邊,最大串橫跨左右兩邊,那不一定包括左邊最右的元素和右邊最左的元素。
O(n),兩個以0開始的子串相減的值最大,則作為減數的子串最小。
力扣2289(leetcode.cn)
給你一個下标從 0 開始的整數數組 nums 。在一步操作中,移除所有滿足 nums[i - 1] > nums[i] 的 nums[i] ,其中 0 < i < nums.length 。
重複執行步驟,直到 nums 變為 非遞減 數組,傳回所需執行的操作數。
樹狀數組
樹狀數組,求和、+=的時間複雜度是O(logN),普通數組是O(n)和O(1)。
本課包括的内容:
- 長度為1、2、4、8、16的樹狀數組。
- 任意長度的樹狀數組。
- 利用x&-x優化樹狀數組。
括号比對(雙周68)
單調棧
最長不降子序列272場
從兩個數組中各選擇一個數,使得它們的和最接近某一個給定的數
PYTHON調用C++
視訊:https://www.bilibili.com/video/BV1Z34y1a7Eh/
PYTHON調用C++函數
下載下傳并解壓pybind11,後面include pybind11.h會用到。
安裝python-3.9.8-amd64.exe,附加包含目錄和附加庫目錄會用到。
,VS2017以上,我用的VS2019。隻支援64位,是以無需編譯32位。
- 建立一個動态庫dll1。
-
dllmain之前增加如下代碼。#include "C:\pybind11-master\pybind11-master\include\pybind11\pybind11.h"
namespace py = pybind11;
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(dll1, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("add", &add, "A function which adds two numbers");
}
-
增加附加包含目錄:C:\Users\Administrator\AppData\Local\Programs\Python\Python39\include
附加庫目錄:
C:\Users\Administrator\AppData\Local\Programs\Python\Python39\libs\
- 確定子產品名和生成名相同。
- 修改擴充名為.pyd,生成工具體系結構選擇64位。
- 進入生成目錄。按住Shift鍵,滑鼠右鍵,選擇“打開Shell視窗”。
- 輸入指令“ipython”。
- 輸入指令“import dll1”。
- 輸入指令“print(dll1.add(4,5))”。
PYTHON調用C類
class CMan
{
public:
CMan(const std::string& strName)
{
m_strName = strName;
}
void SetName(const std::string& strName)
{
m_strName = strName;
}
const std::string& GetName()const
{
return m_strName;
}
private:
std::string m_strName;
};
PYBIND11_MODULE(dll1, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
//導出CMan類
py::class_<CMan>(m, "Man")
.def(py::init<const std::string&>())
.def("__repr__", [](const CMan& man) {
return "name:" + man.GetName();})
.def_property("name", &CMan::GetName, &CMan::SetName);
;
}
如果m_strName是公有,還可以關聯成員變量:
.def_readwrite("name1", &CMan::m_strName)
.def_readonly("name1", &CMan::m_strName)
指針做參數
void Swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
PYBIND11_MODULE(dll1, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("swap", [](py::buffer a, py::buffer b)
{
py::buffer_info ainfo = a.request();
py::buffer_info binfo = b.request();
Swap(static_cast<int*>(ainfo.ptr), static_cast<int*>(binfo.ptr));
});
}
中文字元
python預設編碼是UTF8,Visual C++預設編碼是gb2312,是以要轉碼。否則C++顯示亂碼,python直接退出。python提示如下:
源碼分兩部分:支援中文,不支援中文。
編譯pybind
先安裝CMake,我安裝的是cmake-3.21.2-windows-x86_64.msi。
安裝PythonInterp3.6或以上,我安裝的是python-3.9.8-amd64.exe。
安裝pytest,可以Windws cmd運作“pip install pytest”,線上安裝。
安裝Boost1.56或更高版本。
注意
cmake錯誤:
Python config failure: Python is 64-bit, chosen compiler is 32-bit。