c和c++的區別先總結4點
1. 函數的默認值
C++可以給函數定義或聲明時給參數賦初值
(c語言後來制定的c99標準也能給默認值,但由於現在普遍學習的是c89標準,因此沒有此功能)
使用默認值的好處是什麼?
使用默認值的原理又是什麼?
使用默認值有何規則限定?請接着看。
比如main函數先定義倆變量
int a;
int b;
然後調用sum(a,b)的時候
壓入參數的時候的時候執行的彙編就是
Move eax,dword ptr[ebp-8]
Push eax
Move ecx,dword ptr[ebp-4]
Push ecx
可以看出壓入參數的順序先b再a的。是從右往左的
如果在定義或者聲明的時候給函數的參數了默認值,再或者調用時直接參數給了立即數
比如 Int sum (int a,int b=10);這樣
到時候執行main的調用sum這行的彙編就會變成
Push 0Ah
Move ecx,dword ptr[ebp-4]
Push ecx
可以看到少了一行彙編指令,直接把默認值作爲立即數壓入。
無疑是提高了效率
那麼使用默認值的規則是什麼。
剛纔從原理上看到了,參數壓棧的順序在底層的彙編體現是從右往左壓的。
因此默認值的的給定也必須從右往左給,可以少給,但不可以跳着給。
比如sum2(int a,int b,int c);這個函數可以
sum2(int a,int b,int c=3);或
sum2(int a,int b=2,int c=3);或
sum2(int a=1,int b=2,int c=3);或
其中任意一種給發。
但絕不能這樣給
sum2(int a=1,int b,int c);或
sum2(int a=1,int b,int c=3);或
sum2(int a,int b=2,int c);或
爲什麼不能以上這些給發
比如sum2(int a,int b=2,int c);這樣給。是不是以爲着調用的時候
必須這樣sum2(1,,3);這顯然違反語法的。
剛只是用聲明舉例。
然而實際上是可以聲明和定義都可以給,但不能給重複,一個參數的默認值只能給一次
例如
Int sum(int a,int b=20);
Int sum(int a=10,int b);
兩次聲明是可以的。
單獨來看並不符合Int sum(int a=10,int b);剛說的從右至左的給默認值規則。
但由於它的上一行存在Int sum(int a,int b=20);已對b給過默認值了。因此再只給a默認值並不違法。
如果
Int sum(int a,int b=20);
Int sum(int a,int b=20);
這樣是不可以的
最後值得注意的是,剛纔說的都是聲明與定義在main函數上方,因爲編譯時的順序是一行一行從上往下讀的,如果這樣給定默認值,顯然也是不對的。
例如
Int sum2(int a,int b,int c=3);
Main()
{
....
Sum2(1)
...
}
Sum2(int a,int b=2,int c);
2. 內斂函數inline
內聯函數是在編譯時 在調用點把內聯函數的代碼展開在調用點處,並不會生成函數的符號。
例如
Inline Int sum (int a,int b)
{return a+b;}
Main()
{
Int x=10;
Int y=20;
Sum(x,y);
}
編譯時就是
Main()
{
Int x=10;
Int y=20;
x+y;
}
並不會給sum函數開闢個新棧,而是直接的代碼替換。
這麼看來內聯函數和宏非常的相似。那麼既然內斂叫內斂,宏叫宏,那麼肯定有區別所在。
區別就是 宏是單純的字符替換,在預編譯的階段,不會做任何的詞法解析,類型檢查,也就是說宏出錯的可能性非常高,不安全。而inline內斂函數是在編譯時期,會進行詞法解析,類型檢查,詞法、類型有誤就會編譯失敗。
所以說,inline相當於安全版的宏。
那麼既然內斂函數名字中有個函數,那麼它和函數有是否有本質區別呢?
答案是肯定的。
首先一個是在調用方的代碼替換,一個是另開闢新棧的相對獨立執行。
其次一般函數會生成符號,而內斂函數本質來講只是個安全宏,所以不會生成符號。
那麼既然內斂函數這麼安全,也不用開闢新棧效率高,爲什麼不定義所有的函數爲內斂函數呢?
首先內斂函數只對本文件可見,且編譯時不生成符號,這對於跨文件的工程來說,顯然是不好的。
其次,如果多次使用內聯函數,將進行大量的代碼替換,造成.txt段過於冗餘。
最後我們說的效率高,是在函數的執行開銷小於棧幀的開闢開銷的情況下成立的。
也就是說假如代碼只有簡單的短短几行,用內斂的確能提高效率。
但如果函數代碼過於複雜,內斂的效率還是沒有一般函數高的。
最後值得注意的是內斂函數只算一個給編譯器的建議,也就是說編譯器可能不會使用內斂,因爲某些函數使用代碼替換會導致錯誤,比如遞歸函數,遞歸的核心就是開闢棧幀遞歸數據然後層層計算最後收尾,簡單的代碼替換怎麼能確定遞歸的尾巴?
並且inline只在release版本生效,debug版本是不啓作用的。
3.函數的重載
c語言中函數的定義不能重名,如果重名就會出現重定義,因爲函數生成的符號只由函數名決定。
但是c++中的函數定義可以重名,但是重名的前提是函數的參數不能完全相同,因爲c++的函數符號是由函數名和參數類型決定的。
我們都知道在不同作用域可以定義相同名字的變量。比如main中的局部變量和本文件的全局變量就可以同名。並且main中優先使用的是離自己作用域最近局部變量。
int sum(int a,int b);
int main()
{
int sum(char *a,char *p);
sum(1,2);
return 0;
}
例如這樣就會出錯,sum(1,2)只會看到int sum(char *a,char *p);
而無法看到int sum(int a,int b);
所以說函數重載必須在同一個作用域內。
還有一個點就是實參和形參的轉化問題。
我們先回一下c語言的類型轉換
箭頭所指的線路都是編譯器默認會選擇的優先轉向。
比如float類型變量參與運算時,都會默認轉換成double型,然後參與運算,目的是爲了提高精度。
舉例
比如short a=-1;和 usigned int b=1;比較 。
看似b比較大吧,但其實結果我們知道的,a和b比較時結果卻是a比較大,這是爲什麼?我們看a>b 這個式子,a是short,b是u_int ,按道理a要先轉化成int型,這時是仍爲-1,然後發現b是usigned,繼續轉化爲usigned,然而usigned中不可能有-1,我們知道-1應該是很大的數,即2^32-1 。所以a>b這個式子其實最後值是1,即a比b大。
可是如果不是箭頭所指呢,即我定義的函數形參是float,然後重載一個形參爲int的。
最後我傳入實參爲double型,編譯器就不知道double是該優先轉成float還是int,因此就會有問題。
4. c和c++函數互相調用
之前也說了,c和c++的函數生成符號不一樣,那麼c++和c在互相調用對方函數的時候就會出錯,因爲根本無法找到對應的符號。
而由於c++是基於c語言而來,肯定會想到兼容性的問題,因此如果c++想要使用c的函數,c++會有特殊處理,可以把函數符號轉化成c的不帶參數的形式,只用在c++文件裏這樣
Extern ”C”
{
Int sum(int ,int )
}
這樣編譯時同時也會生成c語言版本的函數符號,到時候c++想要使用c中定義的函數也可以找到符號。
但是反過來呢,如果c想要使用c++文件裏定義的函數呢。c可是不會兼容c++的,c可不能在自己文件裏使用extern”c++”這種東西。
那麼該怎麼做呢?
其實可以創建一個接口文件。
比如這是cpp文件
Sum.cpp
Int sum(int a,int b)
{
Return a+b;
}
這是.c文件
Main()
{
Int a=10;
Int b=20;
sum(a,b);
}
目前來看,.c文件是絕對使用不了.cpp裏的sum定義的。
那麼這時就要新建一個mid.cpp接口文件了
這個文件內容如下
Mid.cpp
Int sum(int a,int b)
extern”C”
{
Mysum(int a,int b)
{
return sum(a,b);
}
}
首先mid文件中先聲明瞭sum.cpp中sum函數,由於都是cpp文件,因此sum對於mid是可見的,那麼mid就可以看到sum的定義體,那麼接下來mid文件只要把這個sum函數封裝一下,並且用extern”C”生成C符號的形式封裝出一個函數,這個函數的功能和sum完全一樣,參數一樣,返回值也一樣,這不就能讓c使用c++的函數了嗎