精華區beta C_and_CPP 關於我們 聯絡資訊
本篇提到的「語義」,指的就是這些語言機制實際的「作用」或「功能」。 主要討論的四種語義:「pass by value」、「refer to object」、「value」 以及「variable」。 分析一下這四種語義的涵義。 ◎四種語義 一、「variable 語義」: 與它對照的是 constant 語義。前者指該語言機制「本身」預設是可變的, 後者則否,例如: int var = 5; // var 是一個 varaible(變量) int const c = 3; // c 是一個 constant(常量) 要注意的一點,一個物件(instance)究竟是變量或常量,在 C/C++ 中, 是可以藉由 const 關鍵字來修飾的。 二、「value 語義」 這是一種屬於高級(高階)的功能,指的是物件本身或其所參照的物件, 具有「直接指派(賦值)」的能力。 C 語言,除了 array 之外,都具有 value 語義,在 C++ 中,用戶自訂型別 的 value 語義是透過編譯器預設(或用戶自己實作)的 operator= 來實現。 此外,要特別注意的一點是,指標的 value 語義,是對其本身而言, 但對它所參照(指向)的物件,必須透過 dereference 的動作,才可 以間接 access 該物件的 value 語義(如果有的話)。因此必須特別強 調:「指標對其所參照的物件,並不直接帶有 value 語義」。 由於 value 語義的高級(高階)特性,現代的編程觀念,是儘可能鼓勵 多使用 value 語義,而避免使用隱晦的指標。 三、「refer to object 語義」 指的是指標和 reference 這兩種特殊的機制。事實上只有在 C/C++ 中, 必須特別強調這點(但 C 沒有 reference),因為大部份的新語言是不 支援 pointer 的功能,而傾向把 refer to object 的語義當成內建的 功能。 四、「pass by value 語義」 指的是物件在函式間傳遞時(透過參數或返回值的方式),具有自動被 複製的能力。這一點,C 及 C++ 都相同,例如: T foo(T var) { ... // 對 var 進行操作 return var; } struct T obj; T ret = foo(obj); 這個介面,在 foo 函式被喚起時,var 就自動成為外部模組中,obj 的 一個複本(也就是 var 複製了 obj),但它的生存範圍只限於函式 foo 之內,也就是它是一個區域物件。 而,當函式返回時,var 物件又被複製成 ret 物件。obj, var, ret 這 三個物件是三個各自獨立的實體(instance)。 由於 pass by value 語義的特性,對內建型別而言,最有效率,又符合 「模組區域化」的觀念,因此為 C 語言所採用。至於物件之間的溝通, 則以傳遞指標「間接 access」的方式達成。 C++ 保留了 C 語言 pass by value 的特性,並提供 reference 的機制, 顯示的支援 refer to object 語義。一般新的語言則是直接內建 refer to object 語義,而不(完整)提供 pass by value 的語義(不完整的 意思是,通常只對內建型別支援)。 ◎ C++ 各種語言機制,與各種語義的對照 pass by value refer to object value variable -------------------------------------------------------------------------- object Yes No user-defined Yes pointer Yes Yes/Inderict self only Yes reference self only Yes depends on object No array No No No No/elements only -------------------------------------------------------------------------- 筆者想,應該不會有其他語言(的物件相關機制),比 C++ 更複雜了吧? 在此,逐項檢視 C++ 語言機制的語義特性: ① object: 除了承襲 C 的 pass by value 特性之外,一個 C++ object(或說 C++ class) 是否帶有 value 語義,端視用戶的決定/實現。 C++ 編譯器預設了 memberwise-copy 的方式(而不是 bitwise-copy),可以說 對 value 語義具有相當良好的支援效果,因為只要保證每個成員都帶有 value 語義,則該 class 就自然具備 value 語義。這算是一種相當高級的特性。 ② pointer: 檢視上表,不能不佩服 C 語言的偉大,發明了「指標」這種東西!四種重要的 語義,它全都具備(其中 refer to object 是以 dereference 的間接方式來達 成)。也可以說,學不好指標,就學不好 C/C++,不過水能載舟,亦能覆舟,利 弊得失是一體的兩面,自由、不受限制的代價往往就是錯誤、煩惱的根源。 ③ reference 可以說是 C++ 對用戶的恩賜,一個簡單,卻是非常重要的機制。新的語言幾乎 都是以內建支援的方式,提供 refer to object 的語義。C++ 保留了 C 的預設 pass by value 語義的優點(彈性),再另外增加 reference 的機制,提供對 refer to object 語義的「直接」支援(指標也支援,但是間接的,容易造成 語法晦澀、複雜、不直觀,及閱讀理解、維護的困難)。 ④ array 慘慘慘!看到上表,對 array 來說,真是「怎一個『慘』字了得?」(所以要 三個「慘」字才夠 :) )。 筆者認為,這個表也很明白地反映出,為什麼在 C++ 中,很少鼓勵直接使用 array,而大都會建議新手,以其他具有高級(階)特性的機制來取代 array (通常是 std::vector)。 首先,C 語言主要的 pass by value 特性,array 沒有,嘗試直接返回一個 陣列,或以參數傳遞,實際作用到的是,array 中第零個元素的位置。返回 區域變量的位址,是一種錯誤的設計,這是新手常犯的錯誤。 其次,現代語言高級的 value 語義,array 也沒有,要複製一個 array,必 須用 memcpy 的方式手動實行 bitwise copy 的動作。 至於 refer to object 及 variable 的特性,就不用多提了,因為這不是使 用 array 時所關心的問題。 array 作為一種最基本的線性、連續(所以支援 random access)的資料結構, 其重要性自不待言,但 C 語言(C++ 為了兼容於 C,所以幾乎延襲其所有特 性,除了 C99 的動態長度之外)所提供的 array,主要是為了效率考量,不 但沒有前面所述的 pass by value 及 value 兩項重要的語義特性,連帶的, 和 C 語言其他變量的規則相同,array 並不會自動初始化。 而實際上,使用 C/C++ array 最危險的一點是,它沒有邊界檢查的功能(也 是為了效率考量),這常常造成不可預期的錯誤,甚至災難。 ◎ Object-based(ADT)設計思維的風格 對 C++ 用戶而言,充分理解各種語言機制的特性,與其說是消極地避免犯錯, 不如積極地看成它是 C++ 為用戶提供了「選擇最適當的機制」的自由。能夠良 好掌握並且善用,它就是 advantage;反之,它就變成 trouble。 從比較高階的觀點(也就是 C++ 當初發明的主要目的,支援抽象化的 ADT 機 制)來看,對於本篇所提到的各種機制,在使用上有如下的建議: ①儘可能 prefer 「value 語義」。 簡單的說,只要讓你所有的 class 都帶有 value 語義,那麼,以此組合出來 的 class,就自然帶有 value 語義。value 語義的好處是它排除了直接使用 原始指標所帶來的晦澀和各種風險。至於,當物件複製成本成為效率瓶頸時, 可以利用類似 reference counting 的技巧來實現底層的動作,讓用戶的介面 保持一致,這也是一種優良的設計風格。(較新的語言如 Java, C# 等的好處 就是它們語言本身內建支援這種風格) ②如果 reference 就足夠,儘可能少用 pointer 殺雞焉用牛刀。指標當然是偉大的,但是請多多愛用 reference,它會讓你的 「C++ 程式人生」更愉快。 ③如果必須用到 pointer,也不要刻意迴避。 有些語言根本就沒有 pointer 這種東西,不僅簡化了語法,也避免了許多錯 誤的可能。這些語言之所以可以不要 pointer,是因為它們會提供其他方便 的機制來支援大部份常用的功能,例如內建以 pass by reference 的方式 傳遞物件等。 但 C++ 並沒有內建這些機制,假如你就是必須要用到 pointer,也不用刻意 扭曲,讓設計變得不自然。還是一句話,當用則用,指標搞不定,C++ 永遠學 不好。 ④儘量謹慎小心 array 的使用 和 pointer 一樣,C/C++ array 搞不定,麻煩也很大。若說要禁用 array, 也太過頭了,還是只能說,沒事(極度效率需求)時,請多愛用 vector,而 當 array 不可避免時,也請用最嚴謹的態度面對它。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 59.120.214.120 ※ 編輯: cppOrz 來自: 59.120.214.120 (09/02 20:48)