溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

一個C#開發(fā)者重溫C++的心路歷程

發(fā)布時間:2020-09-02 01:35:03 來源:腳本之家 閱讀:217 作者:kiba518 欄目:編程語言

前言

這是一篇C#開發(fā)重新學習C++的體驗文章。

作為一個C#開發(fā)為什么要重新學習C++呢?因為在C#在很多業(yè)務(wù)場景需要調(diào)用一些C++編寫的COM組件,如果不了解C++,那么,很容易。。。注定是要被C++同事忽悠的。

我在和很多C++開發(fā)者溝通的時候,發(fā)現(xiàn)他們都有一個非常奇怪的特點,都很愛裝X,都覺得自己技術(shù)很好,還很愛瞧不起人;但如果多交流,會發(fā)現(xiàn)更奇怪的問題,他們幾乎都不懂代碼設(shè)計,面向?qū)ο蠛蜆I(yè)務(wù)邏輯的代碼寫的也都很爛。

所以,這次重溫C++也是想了解下這種奇異現(xiàn)象的原因。

C++重溫

首先打開VisualStudio,創(chuàng)建一個C++的Windows控制臺應(yīng)用程序,如下圖:

一個C#開發(fā)者重溫C++的心路歷程

圖中有四個文件,系統(tǒng)默認為我打開了頭文件和源文件的文件夾。

系統(tǒng)這么做是有意義的,因為剛學習時,外部依賴項,可以暫時不用看,而資源文件夾是空的,所以我們只專注這兩個文件夾就可以了。

作為一個C#開發(fā),我對C++就是一知半解,上學學過的知識也都忘記的差不多了,不過,我知道程序入口是main函數(shù),所以我在項目里先找擁有main函數(shù)的文件。

結(jié)果發(fā)現(xiàn)ConsoleTest.cpp文件里有main函數(shù),那么,我就在這個文件里開始學習C++了,而且它的命名和我項目名也一樣,所以很確定,它就是系統(tǒng)為我創(chuàng)建的項目入口文件。

然后我打開ConsoleTest.cpp文件,定義一個字符串helloworld,準備在控制臺輸出一下,結(jié)果發(fā)現(xiàn)編譯器報錯。。。只好調(diào)查一下了。

調(diào)查后得知,原來,c++里沒有string類型,想使用string類型,只能先引用string的頭文件,在引用命名空間std,如下:

頭文件

頭文件到底是什么呢?

頭文件,簡單來說就是一部分寫在main函數(shù)上面的代碼。

比如上面的代碼,我們將其中的引用頭文件和使用命名空間的代碼提取出來,寫進pch.h頭文件;然后,我們得到代碼如下圖:

pch.h頭文件:

一個C#開發(fā)者重溫C++的心路歷程

ConsoleTest.cpp文件:

一個C#開發(fā)者重溫C++的心路歷程

也就是說,頭文件是用來提取.cpp文件的代碼的。

呃。。。好像頭文件很雞肋啊,一個文件的代碼為什么要提取一部分公共的?寫一起不就好了!為什么要搞個文件來單獨做,多傻的行為?。?/p>

好吧,一開始我也的確是這么想的。

后來我發(fā)現(xiàn),頭文件,原來并不是單純的提取代碼,還是跨文件調(diào)用的基礎(chǔ)。

也就是說,ConsoleTest.cpp文件,想調(diào)用其他Cpp文件的變量,必須通過頭文件來調(diào)用。

比如,我新建一個test.cpp和一個test.h文件。

然后我在test.cpp中,定義變量test=100;如下:

#include "pch.h"#include "test.h"int test = 100;

接著我在test.h文件中再聲明下test變量,并標記該變量為外部變量,如下。

現(xiàn)在,我在回到ConsoleTest.cpp文件,引用test.h文件;然后我就可以在ConsoleTest.cpp文件中使用test.cpp中定義的test變量了,如下:

如上述代碼所示,我們成功的輸出了test變量,其值為100。

到此,我們應(yīng)該了解到了,頭文件的主要作用應(yīng)該是把被拆散的代碼,扭到一起的紐帶。

----------------------------------------------------------------------------------------------------

PS:我在上面引用字符串頭文件時,使用的引用方法是【#include <string>】;我發(fā)現(xiàn),引用該頭文件時,并沒有加后綴.h;我把后綴.h加上后【#include <string.h>】,發(fā)現(xiàn)編譯依然可以通過。

簡單的調(diào)查后得知,【#include <string>】是C++的語法,【#include <string.h>】是語法。因為C++要包含所有C的語法,所以,該寫法也支持。

Cin與Cout

Cin與Cout是控制臺的輸入和輸出函數(shù),我在測試時發(fā)現(xiàn),使用Cin與Cout需要引用iostream頭文件【#include <iostream>】,同時也要使用命名空間std。

在上面,我們提到過,使用字符串類型string時,需要引用頭文件string.h和使用命名空間std,那么現(xiàn)在使用Cout也要使用命名空間std。這是為什么呢?

只能推斷,兩個頭文件string.h和iostream.h在定義時,都定義在命名空間std下了。而且,通過我后期使用,發(fā)現(xiàn)還有好多類和類型也定義在std下了。

對此,我只能說,好麻煩。。。首先,缺失基礎(chǔ)類型這種事,就很奇怪,其次不是一個頭文件的東西,定義到一個命名空間下,也容易讓人混亂。

不過,對于C++,這么做好像已經(jīng)是最優(yōu)解了。

----------------------------------------------------------------------------------------------------

PS:Cin與Cout是控制臺的輸入和輸出函數(shù),開始時,我也不太明白,為什么使用這樣兩個不是單詞的東西來作為輸入輸出,后來,在調(diào)查資料時,才明白,原來這個倆名字要拆開來讀。

讀法應(yīng)該是這樣的C&in和C&out,這樣我們就清晰明白的理解了該函數(shù)了。

define,typedef,指針,引用類型,const

define

首先說define,define在C++里好像叫做宏。就定義一個全局的字符串,然后再任何地方都可以替換,如下:

也就是說,define定義的宏,在C++里就是個【行走的字符串】,在編譯時,該字符串會被替換回最初定義的值。這。。。這簡直就是編譯器允許的bug。。。

不過,它當然也有好處,就是字符串更容易記憶和理解。但是說實話,定義一個枚舉一樣好記憶,而且適用場景更加豐富,所以,個人感覺這個功能是有點雞肋,不過C++好多代碼都使用了宏,所以還是需要了解起來。

typedef

typedef是一個別名定義器,用來給復(fù)雜的聲明,定義成簡潔的聲明。

如上述代碼所示,我定義了一個結(jié)構(gòu)體kiba_Org,如果我要用kiba_Org聲明一個變量,我需要這樣寫【struct kiba_Org korg】,必須多寫一個struct。

但我如果用typedef給【struct kiba_Org korg】定義一個別名kiba,那么我就可以直接拿kiba聲明變量了。

呃。。。對此,我只能說,為什么會這么麻煩!?。?/p>

以為這就很麻煩了嗎?NO!?。∵€有更麻煩的。

比如,我想在我定義的結(jié)構(gòu)體里使用自身的類型,要怎么定義呢?

因為在C++里,變量定義必須按照先聲明后使用的【絕對順序】,那么,在定義時就使用自身類型,編譯器會提示錯誤。

如果想要讓編譯器通過,就必須在使用前,先給自身類型定義個別名,這樣就可以在定義時使用自身類型了。

呃。。。好像有點繞,我們直接看代碼。

如上述代碼所示,我們在定義結(jié)構(gòu)體之前,先給它定義了個別名。

那么,變量定義不是必須按照先聲明后使用的【絕對順序】嗎?為什么這里,又在定義前,可以定義別名了呢?這不是矛盾了嗎?

不知道,反正,C++就是這樣。。。就這么屌。。。

指針

指針在C++中,就是在變量前加個*號,下面我們定義個指針來看看。

如上述代碼所示,我們定義了倆指針,int *ipointer和int* ipointer2??梢钥吹剑疫@倆指針的*一個靠近變量一個靠近聲明符int,但兩種寫法都正確,編譯器可以編譯通過。

呃。。。就是這么屌,學起來就是這么優(yōu)雅。。。

接著,我們用取地址符號&,取出i變量的地址給指針,然后指針變量*ipointer中ipointer存儲的是i的地址,而*ipointer存儲的是518,如下圖:

一個C#開發(fā)者重溫C++的心路歷程

那么,我們明明是把i的地址給了變量*ipointer,為什么*ipointer存儲的是518呢?

因為。。。就是這么屌。。。

哈哈,不開玩笑了,我們先看這樣一段代碼,就可以理解了。

如上述代碼所示,我把聲明和賦值給分開了,這樣就形象和清晰了。

我們把i的地址給了指針(*ipointer)中的ipointer,所以ipointer存的就是i的地址,而*ipointer則是根據(jù)ipointer所存儲的地址找到對應(yīng)的值。

那么,int *ipointer = &i;這樣賦值是什么鬼?這應(yīng)該報錯啊,應(yīng)該不允許把i的地址給*ipointer啊。

呃。。。還是那句話,就是這么屌。。。

->

->這個符號大概是指針專用的。下面我們來看這樣一段代碼來了解->。

首先我們定義一個kiba結(jié)構(gòu)體的實例,定義定義一個kiba結(jié)構(gòu)體的指針,并把kinstance的地址給該指針。

此時,如果我想為結(jié)構(gòu)體kiba中的字段id賦值,就需要這樣寫【(*kpointer).id = 518】。

我必須把*kpointer擴起來,才能點出它對應(yīng)的字段id,如果不擴起來編譯器會報錯。

這樣很麻煩,沒錯,按說,微軟應(yīng)該在編譯器中解決這個問題,讓他*kpointer不用被擴起來就可以使用。

但很顯然,微軟沒這樣解決,編譯器給的答案是,我們省略寫*號,然后直接用存儲地址的kpointer來調(diào)用字段,但調(diào)用字段時,就不能再用點(.)了,而是改用->。

呃。。。解決的就是這么優(yōu)雅。。。沒毛病。。。

引用類型

我們先定義接受引用類型的函數(shù),如下。

如上述代碼所示,u經(jīng)過函數(shù)usage后,他的值被改變了。

如果我們刪除usage函數(shù)中變量i前面的&,那么u的值就不會改變。

好了,那么&符號不是我們剛才講的取地址嗎?怎么到這里又變成了引用符了呢?

還是那句話。。。就是這么屌。。。

呃。。。還有更屌的。。。我們來引用個指針。

如上述代碼所示,我定義了兩個結(jié)構(gòu)體變量kiunew,kiu,和一個指針*kiupointer,然后我把kiu的地址賦值給指針。

接著我把指針和kiunew一起發(fā)送給函數(shù)usagePointer,在函數(shù)里,我把指針的地址改成了kiunew的地址。

運行結(jié)果如下圖。

一個C#開發(fā)者重溫C++的心路歷程

可以看到,指針地址已經(jīng)改變了。

如果我刪除掉函數(shù)usagePointer中的【引用符&】(某些情況下也叫取地址符)。我們將得到如下結(jié)果。

一個C#開發(fā)者重溫C++的心路歷程

我們從圖中發(fā)現(xiàn),不僅地址沒改變,賦值也失敗了。

也就是說,如果我們不使用【引用符&】來傳遞指針,那么指針就是只讀的,無法修改。

另外,大家應(yīng)該也注意到了,指針的引用傳遞時,【引用符&】是在*和變量之間的,如果*&k。而普通變量的引用類型傳遞時,【引用符&】是在變量前的,如&i。

呃。。。指針,就是這么屌。。。

const

const是定義常量的,這里就不多說了。下面說一下,在函數(shù)中使用const符號。。。沒錯,你沒看錯,就是在函數(shù)中使用const符號。

如代碼所示,我們在入?yún)nti前面加上了const修飾,然后,我們得到這樣的效果。

i在函數(shù)constusage,無法被修改,一但賦值就報錯。

呃。。。基于C#,估計肯定不好理解這個const存在的意義了,因為如果不想改,就別改啊,標只讀這么費勁干什么。。。

不過我們換位思考一下,C++中這么多內(nèi)存控制,確實很亂,有些時候加上const修飾,標記只讀,還是很有必要的。

PCH

在項目創(chuàng)建的時候,系統(tǒng)為我們創(chuàng)建了一個pch.h頭文件,并且,每個.cpp文件都引用了這個頭文件【#include "pch.h"】。

打開.pch發(fā)現(xiàn),里面是空代碼,在等待我們填寫。

既然.pch沒有被使用,那么將【#include "pch.h"】刪掉來簡化代碼,刪除后,發(fā)現(xiàn)編譯器報錯了。

調(diào)查后發(fā)現(xiàn),原來項目在創(chuàng)建的時候,為我們設(shè)置了一個屬性,如下圖。

一個C#開發(fā)者重溫C++的心路歷程

如圖,系統(tǒng)我們創(chuàng)建的pch.h頭文件,被設(shè)置成了預(yù)編輯頭文件。

下面,我修改【預(yù)編譯頭】屬性,修改為不使用預(yù)編譯頭,然后我們再刪除【#include "pch.h"】引用,編譯器就不會報錯了。

那么,為什么創(chuàng)建文件時,會給我們設(shè)置一個預(yù)編譯頭呢?微軟這么做肯定是有目的。

我們通過名字,字面推測一下。

pch.h是預(yù)編譯頭,那么它的對應(yīng)英文,大概就是PrecompileHeader。即然叫做預(yù)編譯,那應(yīng)該在正式編譯前,執(zhí)行的編譯。

也就是,編譯時,文件被分批編譯了,pch.h預(yù)編譯頭會被提前編譯,我們可以推斷,預(yù)編譯頭是用于提高編譯速度的。

C++是一個同時面向過程和面向?qū)ο蟮木幊陶Z言,所以,C++里也有類和對象的存在。

類的基礎(chǔ)定義就不多說了,都一樣。

不過在C++中,因為,引用困難的原因(上面已經(jīng)描述了,只能引用其他.cpp文件對應(yīng)的頭文件,并且,.cpp實現(xiàn)的變量,還得在頭文件里外部聲明一下),所以類的定義寫法也發(fā)生了改變。

C++中創(chuàng)建類,需要在頭文件中聲明函數(shù),然后在.cpp文件中,做函數(shù)實現(xiàn)。

但是這樣做,明顯是跨文件聲明類了,但C++中又沒有類似partial關(guān)鍵字讓倆個文件合并編譯,那么怎么辦呢?

微軟給出的解決方案是,在.Cpp文件中提供一個類外部編寫函數(shù)的方法。

下面,我們簡單的創(chuàng)建一個類,在頭文件中聲明一些函數(shù)和一些外部變量,然后在.cpp文件中實現(xiàn)這些函數(shù)和變量。

右鍵頭文件文件夾—>添加——>類,在類名處輸入classtest,如下圖。

一個C#開發(fā)者重溫C++的心路歷程

然后我們會發(fā)現(xiàn),系統(tǒng)為我們創(chuàng)建了倆文件,一個.h頭文件和一個.cpp文件,如下圖。

一個C#開發(fā)者重溫C++的心路歷程

然后編寫代碼如下:

classtest.h頭文件:

calsstest.cpp文件:

調(diào)用測試代碼如下:

結(jié)語

通過重溫,我得出如下結(jié)論。

一,C++并不是一門優(yōu)雅的開發(fā)語言,他自身存在非常多的設(shè)定矛盾和混淆內(nèi)容,因此,C++的學習和應(yīng)用的難度遠大于C# ;其難學的原因是C++本身缺陷導(dǎo)致,而不是C++多么難學。

二,指針是C++開發(fā)學習設(shè)計模式的攔路虎,用C++學習那傳說中的26種設(shè)計模式,還勉強可以;但,如果想學習MVVM,AOP等等這些的設(shè)計模式的話,C++的指針會讓C++開發(fā)付出更多的代碼量,因此多數(shù)C++開發(fā)對設(shè)計模式理解水平很低也是可以理解的了。

三,通過學習和反思,發(fā)現(xiàn),我曾經(jīng)接觸的那些愛裝X的C++開發(fā),確實是坐井觀天、夜郎自大,他們的編寫代碼的思維邏輯,確確實實是被C++的缺陷給限制住了。

----------------------------------------------------------------------------------------------------

到此,我重溫C++的心路歷程就結(jié)束了。

代碼已經(jīng)傳到Github上了,歡迎大家下載。

Github地址:https://github.com/kiba518/C-ConsoleTest

----------------------------------------------------------------------------------------------------

總結(jié)

以上所述是小編給大家介紹的一個C#開發(fā)者重溫C++的心路歷程,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對億速云網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI