C/C++語法之若干個為什么


一、語法
語法是一個語言的基礎,每個語言都會形式化的定義自己的語法規則,因為現在大部分時候還是“屬性文法”,也就是基於語法的語義識別,所以嚴格的語法對於任何一門語言都是必須的,即使所謂的第四代編程語言SQL語言看起來非常高端,但是事實上它的語法也是有嚴格規定的,也可以通過語法文件(好像應該叫巴克斯-諾爾范式吧)來描述。
C語言是我們接觸的比較多的語言,它的語法事實上是相對比較寬松自由的,比方說,可以在沒有看到聲明的時候調用一個函數,如果函數聲明中沒有通過void或者其它方法指明參數類型,那么調用的時候參數是任意多個的等。這些規則在習慣了教科書教育的同學看來來比較不可思議的,因為容易出錯。但是對於C語言的發明者來說,這些都是為了簡潔方便的。因為早期的程序員都是一些水平比較高的開發者,語法並不負有保證或者避免程序員犯低級錯誤的責任,只有讓程序員盡量少的敲擊鍵盤,盡量的freestyle。但是后來隨着計算機的普及,計算機編程的門檻越來越低,計算機語言的設計者們就考慮到使用語言本身來避免可能發生的低級錯誤。
二、早期C語言函數參數后置聲明
在早期的C語言函數定義中,函數的參數並不是在函數體內聲明的,而是在表示參數的右括號')'和表示函數體實現的左大括號'{'之間聲明函數類型的。舉個例子來說,對於我們現在常用的
int foo(int bar, int baz)
{
return bar + baz;
}
最為正宗的聲明方式是
int foo(bar,baz)  int bar, baz;
{
return bar + baz;
}
也就是函數參數的聲明是在參數之外的。這個語法大家現在看起來是比較的詭異(weird),但是換一個角度來看,這個實現在某些情況下是有好處的,比方說,你聲明的兩個參數都是double類型,或者是一個結構類型,那么這種寫法是可以節省輸入量的。我們換個例子
int foo(double x,double y)
如果換成是古典的定義方法,可以是
int foo(x,y) double x,y;
大家可以計算一下輸入量,是不是第二中方法的輸入量更少一些。但是現在作為工程中的碼農來說,這種參數聲明為x和y的做法是不被允許的,所以可能參數輸入可能會大於輸入量。另一方面,古典的定義方法的另一個好處就是可以一目連然的看到所有同種類型的函數參數。當然這一點在很多公司的編碼規范里也是比較忌諱的,因為一方面現在的軟件協作開發對於可讀性和可維護性要求更高,另一方面是現在公司假設大部分碼農的理解能力要比以前低一些。
有人就說了,你在這里比比吃吃的說這么多,是不是吃飽了撐的慌,恩,吃是吃飽了,但是一直沒休息好。
三、gcc的__attribute__不能放在函數定義括弧后面
我也是偶爾用到了gcc的這個擴展語法,是真正用到,不是在這里鑽牛角尖或者曬智商下限。當時就發現一個問題,就是修飾函數屬性的__attribute__只能寫在函數定義的開始,而不能寫在函數體之前,例如
int foo () __attribute__((weak)) 
{
return ;2
}
這個是在gcc中編譯不過的。但是在聲明中是可以的,也就是
int foo () __attribute__((weak));
可以的。而且
int __attribute__((weak)) foo () 
{
return ;2
}
也是可以的。
大家現在可能會明白我為什么會說第二點的原因。大家可以看到,為了兼容早期的C語言定義,在參數和函數體之間是留給函數參數類型聲明的,所以不能用來放函數屬性。和現實一樣,很多你過眼即望的歷史可能正在影響着你的現在。
順便說一下,結構中聲明的屬性就可以在語句的最后。
四、C++ 類中靜態成員初始化
對於C++的靜態成員的初始化,可能剛開始大家都覺得有些別扭,所以我在這里再描述一下。比方說在類i里演示一下它的初始化方法
struct FOO
{
static int myvar;
};
int FOO::myvar = 0x1234;
為什么不能把這個初始化直接放在類的聲明中呢?
因為在C++中,變量的初始化可能是要執行初始化函數的,而初始化函數中可能會完成各種操作。如果把靜態成員的初始化放入函數申請,那么這個初始化函數由誰來執行?因為一個類的聲明會被任意多的編譯單元(通俗的說就是源文件)編譯,你如何在編譯階段判斷這個初始化函數是由哪一個編譯單元承擔。所以只能讓用戶來手動決定在哪里執行這個靜態成員的唯一一次初始化動作。我這里的例子里的靜態成員比較簡單,所以沒有體現展示效果,如果大家連這個都理解不了,那可能也不會來看這篇文章了。
五、C++類中常量成員初始化
常量成員雖然是常量,但是它是以對象為單位的,不同的實例對於這個類來說可能是不相同的。聲明為const,只是說在類的執行過程中不應該修改這個成員的值,但是執行函數構造函數的時候是可以初始化的,而事實上這個也是唯一的一次初始化機會。這個初始化只能在構造函數的參數聲明和函數體之間定義,也就是所謂的initializer。
例如
struct FOO
{
const int myconst;
FOO():myconst(something) {
myconst = something;//注意,這種方法同樣有編譯錯誤,只能在上面的冒號之后,函數體定義之前初始化。事實上,編譯並不會理解初始化函數中執行的語法對於函數初始化有什么語義和影響,它只能理解那些初始化子,因為那里的定義更為嚴格,可控性強。
};
}
六、C++中函數默認參數
這一點對於剛從C語言轉到C++的同學來說可能比較神奇,所以我們現在想象一下它的實現方法。
事實上函數默認參數對被調用者(callee)來說是透明的,因為它並不知道調用者到底自備了幾個參數,它只能一視同仁,假設所有調用者傳遞了所有的參數,包括默認參數。而對於調用者(caller)來說,它只是由編譯器代勞完成了一些形式化的東西,也就是如果你調用的地方沒有給出所有參數,那么編譯器將默認參數作為參數,依然是湊夠了所有參數,這一點對於程序員和被調用函數來說都是透明的。這里就有一個前提,那就是調用者必須看到包含有默認參數的函數聲明,這一點並不是問題,因為C++中的確是必須知道所有調用函數的原型(應為名字粉碎的原因“name mangling”,可以參考我之前一篇博客中關於為什么C++函數必須聲明的文章)。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com