C語言為什麼要有「->」運算符,為何不使用點運算符代替它呢?

2020-12-11 IT劉小虎

基本上,每一個C語言程式設計師都明白點運算符「.」和箭頭運算符「->」可以用於訪問結構體的成員,只不過箭頭運算符「->」需要與結構體指針結合使用。事實上按照現在流行的C語言語法,通過結構體指針直接訪問成員,也只能通過箭頭運算符。

struct test *x

;x.member = 1; // 非法

x->member = 1; // 合法

C語言為何要有「->」運算符?

C語言為何要有「->」運算符?

拋開結構體不談,C語言中的指針本身並無需要用到點運算符「.」的地方,因此結構體指針與點運算符「.」結合時,編譯器把這種結合解釋為訪問結構體成員,按理說並不會產生歧義,C語言以語法簡潔聞名,那為什麼還要提供「多餘」的「->」運算符呢?或者說,C語言中的箭頭運算符「->」有什麼歷史淵源嗎?

上述問題其實可以簡化成兩個子問題,一是為什麼C語言要有「->」運算符,再就是為什麼C語言中的「.」運算符不能與結構體指針結合訪問成員。

C語言「->」運算符的歷史

其實,在C語言的第一個版本(相關C參考手冊(C Reference Manual,CRM)在1975年5月隨第6版Unix一起發布)中,「->」運算符並不像今天一樣與「.」運算符同義,而是另有一種特有的含義。

CRM 所描述的C語言在許多方面都與現代C

CRM 所描述的C語言在許多方面都與現代C語言有很大的不同,例如 CRM 的結構體成員實現了全局字節偏移的概念,沒有類型限制,可以訪問任意地址。也就是說,當時的C語言中,所有的結構體成員的名字都具有獨立的全局含義,因此所有結構體的成員名都不能一樣。

struct S { int a; int b;};上面這幾行C語言代碼定義了結構體 S,成員 a 代表 0 偏移,而成員 b 則代表 2 字節偏移(這裡假設 int 變量佔用 2 字節內存,也不考慮內存對齊)。

當時C語言做了這樣的限制:所有結構體的所有成員,要麼有唯一的名字,要麼代表唯一的字節偏移量,例如:

struct X {

int a;

int x;

};

上述代碼定義了結構體 X,它也包含成員 a,它的名字與結構體 S 中的成員 a 重複了,但是沒有問題,因為它們都代表 0 偏移。下面這種定義就屬於非法了:

struct Y {

int b;

int a;

};

因為結構體 Y 中的成員 a 與結構體 S 中的成員 a 重名,並且代表的字節偏移量也不相等。

早期箭頭運算符「->」是用於確定偏移量

在當時的C語言語法中,箭頭運算符「->」就是用於確定偏移量的。既然每個結構體的成員代表的字節偏移量都是全局的,那麼下面這樣的語句也是合法的:

int i = 5;i->b = 42;100->a = 0;上述幾行C語言代碼的意義很明確:i->b 表示以 5 為基準的 2 字節偏移處,因此 i->b=42; 的意思是將地址 7 處的 int 值設置為 42。同樣的道理,100->a=0; 則表示將地址 100 處的 int 值設置為 0。

讀者應注意,在當時版本的C語言中,箭頭運算符「->」並不關心它的左表達式,因此哪怕 100->a 也是合法的。

箭頭運算符「->」並不關心它的左表達式

這樣利用結構體成員偏移量的做法對於「* 」和「.」運算符的組合是不可用的,例如

int i = 5;

(*i).b = 42;

*i 本身就是一個無效的表達式,「* 」是一個獨立的運算符,因此對其操作數施加了更加嚴格的類型要求。當時 CRM 引入箭頭運算符「->」就是用於解決這種限制帶來的不便的。

後來,在 K&R 設計的C語言中,許多 CRM 中的功能被重新設計,「結構體成員作為全局偏移標識符」的設計被完全推翻,此後箭頭運算符「->」的功能與「* 」和「.」運算符結合的功能完全相同。

為什麼C語言不支持「.」運算符與結構體指針結合訪問成員?

同樣,在 CRM 描述的C語言中,「.」運算符的左操作數被要求必須是一個左值,這也是它與「->」運算符不同的原因,如上所述。請注意,CRM 不需要「.」運算符的左操作數是結構體類型的,它只要求左操作數是左值。

這裡讀者應該區分「左操作數」和「左值」。

應該區分「左操作數」和「左值」

這意味著在 CRM 版本的C語言中,程式設計師可以編寫下面這樣的代碼:

struct S {

int a, b;

};

struct T {

float x, y, z;

};

struct T c;

c.b = 55;

讀者應該注意到結構體 T 並沒有成員 b,但是 c.b=55; 卻仍然是合法的,這是因為編譯器不關心變量 c 的類型,它只關心 c 是否一個左值:某種可寫的內存塊。因此 c.b=55; 的意義是將 55 寫入名為 c 的連續內存塊中字節偏移量 2 處的 int 值中。

因此,如果我們寫了下面這樣的C語言代碼:

S *s;

...

s.b = 42;

編譯器將認為這樣是有效的,因為 s 也是一個左值。最終得到的C語言程序將嘗試將 42 寫到指針變量 s 本身(而不是它指向的結構體)所在連續內存字節偏移量 2 處。不用說,這樣的結果必定會產生預料之外的結果,很可能帶來內存溢出,但是程式語言本身並不關心這些事情。

程式語言本身並不關心這些事情

也就是說,在那個版本的C語言中,對「.」運算符重載(使其支持通過結構體指針訪問成員)根本就行不通,因為「.」運算符與指針結合時,已經具備自己的含義了(與左值結合,訪問指定偏移量的內存)。雖然以今天的眼光來看,這樣的設計很古怪,但是當時的確就是這樣設計的。

當然了,這樣的奇怪設計並不是「.」運算符不能與結構體指針結合使用訪問成員的充足理由,但是後來 K&R 在重新設計C語言時沒有考慮重載「.」運算符,應該是需要兼容之前版本的C語言,畢竟歷史遺留下來的C語言代碼也是需要得到支持的。

最後

可能也有讀者認為,即使是今天的C語言,似乎「->」運算符也不是必須的,因為「* 」和「.」運算符結合就能輕易的代替它:

struct S *p;

p->b = 3;// 完全可以使用下面這樣的語句替換

(*p).b = 3;

既然簡潔是C語言的特點,就應該做到極致,何必提供「多餘的」箭頭運算符「->」呢?的確如此,就功能性而言,「->」完全可以不要,但是在C語言程序開發中,我們還需要考慮程式設計師的感受,請看下面這兩種寫法:

(*(*(*a).b).c).d

a->b->c->d

它們的功能是一致的,但是第二種寫法無論是書寫,還是閱讀,都要簡潔的多。

歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。

未經許可,禁止轉載。

相關焦點

  • C語言中的運算符
    二、C語言運算符有哪些C語言有大量的運算符,可大致分為以下幾類:括號與結構體符號、單目、算術、移位、關係、位、邏輯、三目、賦值、逗號。括號與結構體符號:括號包括()、[],結構體符號指的是結構體成員引用時的符號「->」、「.」
  • C語言入門教程運算符
    乘號、除號因為ASCII符號中沒有與數學中相對應的符號,所以使用星號表示乘號,使用斜線表示除號。C語言中增加了一個求模運算符,是用來取餘的。需要注意的是,求模運算符的兩個操作數必須是整型。C語言中的除法運算,不同類型的除數和被除數會導致不同類型的運算結果。一種情況是,當除數和被除數都是整數時,運算結果也是整數。當不能整除時,就直接捨棄小數部分,只保留整數部分。另一種情況是,一旦除數和被除數中有一個是小數,那麼運算結果也是小數,並且是double類型的小數。C語言中的取餘運算,就是求相除後的餘數。取餘運算中%的兩邊都必須是整數,不能出現小數,否則會報錯。
  • 單片機c語言教程:C51運算符和表達式
    這個語句就是 typedef,這是個很好用的語句,但我卻不常用它,通常我定義變量的數據類型時都是使 用標準的關鍵字,這樣別人能很方便的研讀你的程序。typedef 的語法是typedef 已有的數據類型 新的數據類型名 運算符就是完成某種特定運算的符號。運算符按其表達式中與運算符的關係可分為單目運算符,雙目運算符和三目運算符。單目就是指需要有一個運算對象,雙目就要求有兩個運 算對象,三目則要三個運算對象。
  • C語言中的運算符和表達式
    4、符號運算符「+」(正號)不改變操作數的值及符號, 「-「(負號)可用於得到一個數的相反數。8、算術表達式使用算術運算符將運算對象連接起來、符合C語言語法規則的式子。五、條件運算符和條件表達式條件運算符是C語言中唯一的一個三目運算符,它需要三個操作數,條件表達式為:表達式1?表達式2:表達式3。?
  • 有意思的C語言運算符
    比如:5 - 2,它的操作數是 5 和 2,而運算符則是 「-」。常見的運算符可大致分為 4 種類型:算術運算符、關係運算符、賦值運算符和邏輯運算符;它優先級從低到高的順序為:賦值運算符 < 邏輯運算符 < 關係運算符 < 算術運算符;下面對這4種類型一一講解。1、賦值運算符賦值運算符可分為簡單賦值、複合算術賦值和複合位運算賦值。
  • C語言 | 算術運算符
    「要成為絕世高手,並非一朝一夕,除非是天生武學奇才,但是這種人…萬中無一」
  • C語言有大約40個運算符,最常用的有這些
    圖5.1 語句i = i + 1;在C語言中,類似這樣的語句沒有意義(實際上是無效的):2002 = bmw;因為在這種情況下,2002被稱為右值(rvalue),只能是字面常量,不能給常量賦值,常量本身就是它的值。因此,在編寫代碼時要記住,=號左側的項必須是一個變量名。實際上,賦值運算符左側必須引用一個存儲位置。
  • c語言基礎語法五:運算符與表達式
    >一:運算符根據操作數的數量可以分為:根據用途可以分為:1.賦值運算符使用形式:含義:把右邊的值存儲到左邊變量名所標識的內存空間中。C語言提供的取地址符&去獲取變量的地址。4.逗號運算符比如:int a, b, c;定義了三個變量。[,]運算符,它沒有具體的運算規則,一般起一個分隔的作用。
  • C語言中的三目運算符是啥?有何用處?
    一般來說,C語言中的三目運算符為a?b:c即有三個參與運算的量。由條件運算符組成條件表達式的一般形式為:表達式1?在我們使用條件表達式時,還應注意以下幾點:(1)條件運算符的運算優先級低於關係運算符和算術運算符,但高於賦值符。因此 max=(a>b)?a:b可以去掉括號而寫為 max=a>b?a:b(2)條件運算符?和:是一對運算符,不能分開單獨使用。
  • c程序的運算符和表達式(一)
    一、計算a/b和a%b的值「/」是運算符中的除,在使用該運算符時除了分母不能為0外,還要特別注意在c語言中如果兩個操作數a、b的值都是整型,結果也是整型;「%」是取餘運算,要求兩個操作數必須是整型。例如9%2的值為1,而9.5%2是非法的。
  • Python 運算符,什麼是Python 運算符
    Python語言支持以下類型的運算符:算術運算符比較(關係)賦值運算符、邏輯運算符、位運算符、成員運算符、身份運算符、運算符優先級,接下來讓我們一個個來學習Python的運算符。= 不等於 - 比較兩個對象是否不相等 (a != b) 返回 true.> 不等於 - 比較兩個對象是否不相等 (a > b) 返回 true。這個運算符類似 != 。> 大於 - 返回x是否大於y (a > b) 返回 False。
  • 單片機c語言教程:運算符和表達式(關係運算符)
    單片機C語言中有六種關係運算符,這些東西同樣是在我們小時候學算術時就已經學習過了的:本文引用地址:http://www.eepw.com.cn/article/170887.htm> 大於< 小於>= 大於等於<= 小於等於== 等於!
  • 單片機c語言教程第七章--運算符和表達式(關係運算符)
    單片機c語言教程第七章--運算符和表達式(關係運算符) 佚名 發表於 2009-04-15 09:37:37 關係運算符,同樣我們也並不陌生。
  • 教你輕鬆學會C語言系列之——五花八門的運算符
    回到程序語言中,比如:「1+1」就是一個最簡單的表達式,其中的兩個數字(1)就是操作數,加號(+)就是運算符,共同構成了一個表達式。在C語言中,單個常量或變量可以看作最簡單的表達式,使用除賦值運算符(=)之外的其他任意運算符連接的式子均屬於表達式。C語言中的運算符種類繁多,只要掌握了基本用法。
  • python語言基本數據類型-運算符
    運算符用於執行程序代碼運算,會針對一個以上操作數項目來進行運算。例如:5+8,其操作數是5和8,而運算符則是"+"。運算符大致可以分為算術運算符、比較運算符、賦值運算符、邏輯運算符、成員運算符。算術運算符:+,-,*,/,%,**,//比較運算符:==,!
  • C語言——選擇結構設計(switch語句和關係運算符及關係表達式)
    在此之前我們介紹了if選擇結構,當然了if並不能解決所有的選擇問題,例如,將學生的成績分等,按優,,良,一般這樣分的話,顯然if語句就顯得力不從心了,那麼在這時候我們就應該使用switch語句去進行操作。switch語句是多分支選擇語句,用來實現分等等問題。
  • 「Go 語言教程」Go 語言常量變量和運算符
    Go 語言教程每一門語言都會有常量的定義,變量的定義,以及基於這些定義的運算,那麼本次將進行Go語言的常量,變量和運算符進行介紹和講解。>常見的運算符類型有,算數運算符,關係運算符,邏輯運算符,位運算符,賦值運算符,其他的運算符。
  • 單片機c語言教程:C51運算符和表達式(指針和地址運算符)
    指針是單片機C語言中一個十分重要的概念,也是學習單片機C語言中的一個難點。對於指針將會在第九課中做詳細的講解。在這裡我們先來了解一下單片機C語言中供給的兩個專門用於指針和地址的運算符:本文引用地址:http://www.eepw.com.cn/article/170885.htm* 取內容 取地址取內容和地址的一般形式分別為:變量 = * 指針變量 指針變量 = 目標變量
  • C/C++中的三目運算符
    三目運算符作用:通過三目運算符實現簡單的判斷語法:表達式1 ?示例:int main() {int a = 10; int b = 20; int c = 0; c = a > b ?a : b; cout << "c = " << c << endl; //C++中三目運算符返回的是變量,可以繼續賦值 (a > b ?
  • LabVIEW編程技巧:如何實現C語言中條件運算符「?:」的功能
    目的在C語言中,條件運算符「?: 」是唯一的一個三目運算符,它對第一個表達式作真/假檢測,然後根據其結果返回另外兩個表達式中的一個,其通用表達式形式如下圖所示:上式中y的值取決於表達式1的值,當其值為真時,y的值為表達式2的值,否則,y的值為表達式3的值。