c# out原理 ref_移植贪吃蛇——从C#到C++
前言
因為某些機緣巧合,引起了我對C++的重視。一時興起,決定將兩年前用Unity寫的Snake進行移植。經過兩周的抽空,總算是完成了。項目采用現代C++標準編寫,采用CMake構建,圖形庫為SDL。由于本次的重點不在于圖形這塊,所以沒有使用原版的素材,采用矩形代替。
在工程實現上除了基本的業務外,還實現了C#的event以及的Unity的GameObject與Component。
本文將從C#開發者的角度出發比較C++的不同點,最后總結其思想。由于本人在此之前從未有C++的工程經驗,對于許多特性在此之前也是一知半解,對于一些事物的理解若有誤還請指教。
低成本封裝
首先最引我矚目的便是C++的參數傳遞,形如這般的函數:
void Init(const string& title, int width, int height);由于C++的引用參數string&性質,將值傳入時不會發生拷貝,而是等于直接使用原變量。可以有效降低封裝抽象的成本,加上const字段是為使得形如"123"這樣的常量區對象也能傳入。
當然這在C#也并不是沒有,ref便是如此。但這在C#并不會下意識去用,畢竟在C++若是不用指針或引用作為參數的話可是會直接拷貝新對象的,而在C#直接使用也不會造成很大的負擔(值類型直接拷貝,引用類型用指針)。
其次便是C++的內聯函數了,作為函數宏的替代品之一。可以在編譯時將函數展開為具體的內容,節省了一次函數調用的消耗。但內聯函數需寫在頭文件中,若是關聯項多,修改后便會增加編譯時長。且展開量過大也會增大代碼量,增加編譯時長。但不失為一個降低封裝成本的手段。
明確的內存
其次與C#最大的不同便是對象的創建了,C++有著以下兩種形式:
A a = A(); A* a = new A();了解C++的自然曉得,前者在當前內存域下申請,后者在堆申請。而在C#則隱去了這個細節,而是設立固定的規則:
- 引用對象使用指針,原則上在堆申請,若對象的生命周期存在于申請的函數里,則在棧申請——是為逃逸分析。
- 值對象在當前內存域下申請,且由于不是指針,變量傳遞會產生拷貝。除非使用ref、in、out等參數關鍵字。
而C++的內存申請機制則帶來了明確感,如在函數里申請生命周期只存在函數里的對象,需要明確的使用A a = A();方式。且在構建類的時候,對于那些不使用A* a = new A();創建方式的成員變量,其內存占用是明確的,在類對象申請內存的時候會一并申請,即這些成員變量在內存布局上可能是連續的。從這點來說可比C#要牛逼多了。
相似的容器
在容器方面,C++與C#大體看起來是相似的,當然在API的爽度而言還是C#更勝一籌(C++17拉近了不少)。但實際上還是存在一些細節上的不同,就比如我們常用的Key-Value容器:C++的std::map與C#的Dictionary在實現乃至功能上就不一樣。實際上std::map對應C#的應該是SortedDictionary:它們都是基于紅黑樹實現,都是有序存儲的表。而Dictionary則是基于哈希實現的,即我們俗稱的哈希表,與之對應的是std::unordered_map。
通過命名能看出兩種語言在這方面的傾向性:紅黑樹占用的內存更小,但查找和刪除的時間復雜度都是O(logn),而哈希查找和刪除的時間復雜度都是O(1)。實際使用的時候感覺還是得權衡利弊,不能貪圖方便就一直用一套。std::set與HashSet這邊也是類似的對應,以此類推。
在序列容器方面的對應倒是工整:std::vector對應List,都是不斷擴容的數組容器。鏈表方面則是std::list對應LinkedList。但std::array卻無對應了,硬要說的話就是與C#的原生數組對應,畢竟這個容器出現的意義就是彌補與C語言兼容的原生數組。
順帶一提,在使用std::vector時由于會出現擴容復制的問題,需要考慮好成員對象的拷貝方案,乃至于內存泄漏的問題。
智能的指針
內存管理是所有編程語言都無法繞開的點,絕大多數編程語言對于堆內存的管理都是采用垃圾回收的方式。而在C++的鴻蒙時代則與C語言一樣,需要手動管理指向堆內存的指針。盡管也有std::auto_ptr這樣的東西,但在功能上還不夠全面。而手動管理內存將難以解決對象在多處被引用時將如何安全銷毀的問題,為了實現這種機制也得做出不少妥協。
所幸隨著時代的發展,現代C++迎來了智能指針,它基于引用計數的規則,將裸指針包裝起來,當符合銷毀條件后便可自動回收。智能指針有著幾種具體的類實現,而其中最常用的是std::share_ptr,當它持有指針時將增加計數,反之同理將減少計數,最終歸0銷毀。但其較之垃圾回收有個致命的缺陷:相互引用時將一直保持計數,無法銷毀。為此C++引入了std::weak_ptr:它不會增加計數,在計數歸0時持有指針也隨之銷毀。如此對于相互引用的情況下,分清主次,合理分配share_ptr與weak_ptr即可解決無法銷毀的問題。
智能指針在使用上總有一種外掛的感覺,需要成體系的去使用。不如內置的垃圾回收式語言來的方便,且寫起來還是有一定的心智負擔(相互引用),不過在性能而言較之垃圾回收更為優越(回收對象與時機都很明確,且是被動進行的)。
模板與泛型
C++的模板與C#的泛型表面上用起來很是相似,實則有所不同。以下對比兩者的差異:
template<class T, int x> // C++支持模板參數,可填寫整型或指針 GenericList<T> where T : Employee // C#使用System.Object不支持的方法時,需進行類型約束指定基類 // 這么騷的操作見過么? void f(int x);template <class ... Args> void Do(Args... args) {f(args ...); };從實際使用體驗與兩者的命名可以看出,「模板」的本質是參數化代碼生成,而「泛型」則是類型參數化。即泛型只是模板功能的一部分而已。模板能實現的其他功能,在C#則以其他方式代替了(如變長參數params)。
后記
從以上種種便能看出C++與C#在設計哲學上的不同,C#通過約束開發者行為從而達到更穩定健壯的結果,哪怕會失去一定的性能與靈活性,而C++則更依賴開發者自身的素質(如C++支持多重繼承而C#僅僅支持單類+多接口繼承)。
從個人的使用體驗來看,現代C++并非不能作為業務開發語言。只是對開發者的素質要求較之一般語言更高,從招聘成本與項目穩定性而言是個問題。如此來看,除非有必要的性能敏感且需要一定封裝的核心層(如游戲引擎),否則用C + 腳本語言或者C#/Java這類可上可下的語言是個更好的選擇。
總結
以上是生活随笔為你收集整理的c# out原理 ref_移植贪吃蛇——从C#到C++的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php导出csv_原生PHP实现导出cs
- 下一篇: js微信监听返回_微信小程序(2)- 框