為什麼C語言用int *a 來聲明指針變數,而不是int &a聲明?
C語言的初學者,個人覺得int *a = b聲明有點異類,聲明完成後*a不是b的地址,反而是b的值,很容易讓新手(可能只有我一個)產生誤區,這裡面是不是有故事?
C 語言發明的時間很早,當時人們對"類型系統" 還很陌生,很多人還習慣將二進位位元組作為唯一的數據類型。設計 C 語言的語法時,也不知是誰想出來的,有種設計哲學:讓聲明的形式與它的使用形式儘可能相似。在這種設計哲學下,是先想如何使用,再來逆推如何聲明,這樣 C 語言的聲明語法顯得有點古怪,
比如:
- 聲明 int a; 使用時,a 的類型就是 int。
- 聲明指針 int *b; 使用時,*b 的類型就是 int。
- 聲明數組 int b[10]; 使用時用 b[0];
- 定義函數指針,int (*ptr)(int a, int b); 使用時,就是 (*ptr)(a, b); 可以簡寫成 ptr(a, b);
C 這種語法設計,在今天看來是不好。使用這種語法,程序員不可以直接從左往右讀,有時甚至需要從中間往兩邊讀。只是 C 語言的影響太大,人們常見類 C 的語法,C 語言有些不好的地方也就忍了。C++ 就繼承了這種語法,甚至這種語法的設計哲學,反正我總記不住成員函數指針是怎麼聲明的。
到今天,人們對「類型系統" 很熟悉了,也積累了幾十年的語言使用經驗。現在設計的語言,語法上傾向將類型放到後面,而並非放到前面。這樣比較統一,也可以從左往右讀。比如
var a : Int;
var b : [Int];
var ptr : (Int, Int) -&> Int;
var signal : (Int, Int -&> Void) -&> (Int -&> Void);
對比 C 語法
int a;
int b[10];
int (*ptr)(int, int);
void (*signal(int, void(*)(int)))(int);
特別最後一個類型,沒有什麼人可以一眼可以看出它的含義(我懷疑已經寫錯了)。這種情況,在 C 中會用 typedef 來簡化。
typedef void (*handler_t)(int);
handler_t signal(int, handler_t);
想了解更多,請閱讀 C 專家編程 第 3 章。
-----------------
簡單討論前置類型(類型放在變數前面)和後置類型(類型放在變數後面)。跟原題目無關,是對評論的回復。這只是個人看法。
語言有類型推導,很多時候不用顯式寫類型。類型放在前面或者後面,對基本的數據類型影響還不算大。只是對複合類型,特別是函數(或者類似概念),語法上還是有影響的。怎麼從語法上表示一個函數呢?使用前置類型,從語法上,大致可以寫成:
Func&
你也可以去掉括弧,寫成,
Func&
規定前面是參數類型,最後的是返回類型。但這種方式,從形式上是看不出那些是返回值,那些是參數。
你很難去掉這個 Func 的記號,去掉會引起歧義。更進一步,怎麼去表示一個函數,它傳入一個函數作為參數,而返回一個函數呢?大致為:
Func&
這種語法,就會引起 Func 的嵌套,而 Func 純粹多餘。
更進一步,我增強了語言,可以支持多返回值。按照 C 的語法方式,大致定義成:
(int, int) fun(int a, int b);
那怎麼表示這種函數類型呢。你可以寫成:
Func&<(int, int), (int, int)&> fun;
那再繼續,怎麼表示這種,定義一個函數,傳入一個多返回值的函數作為參數,而再次返回一個多返回值的函數作為參數。這樣,不單單是 Func 會引起嵌套,連那些括弧也會引起嵌套,而這些是語法噪音。為避免嵌套,語言中就會傾向於先定義某個類型,比如
typedef Func&
Func&
類型寫在後面,可以輕易地去掉 Func 噪音。比如
var fun: (int -&> void) -&> (int -&> void)
並且它的形式並非是嵌套的,而是一個線狀,並且可以統一地連鎖下去。
另外還有更具價值觀的解釋,編程時更應該關注變數用途,而不是變數的類型。變數的名字反應了用途,既然用途比類型更重要,名字就應該放在最開始。當然這種解釋見仁見智,有點站不住腳。
是取地址運算符的意思
int *a = b;
這一行代碼的含義是,定義一個指向整型的指針a,然後在初始化的時候直接取b的地址,賦值給a
哎,這怎麼解釋……
按規定這個語句其實是
typedef int *INT_PTR;
INT_PTR a = b;
這就看暈了那要是遇到
void (*pfunc)(int, void(*)(int)) = func;
這可怎麼辦……
定義的同時賦值這個語句在C當中只是把定義和賦值兩個語句合起來了而已,不管定義的是什麼類型,定義的類型寫成了什麼鬼樣子,都是一樣。題主你肯定是還沒有區分聲明和解指針的關係。int * a = b;這裡從編譯器來看是這樣的:(int *) a = b;注意(int *)是一個類型,是一個整體,它的叫法是「指向整形變數的指針類型」。這裡的int和*是不能分開的。而你在定義了上述的a指針後,如果你再寫:
int c = *a;
這裡的*就不是一個類型了,而是一個「操作符」的概念,它的作用是,從a所指的地址讀出sizeof(int)那麼長的位元組(當然也就是b的值),然後賦值給c。所以,int *和*操作符完全是兩個東西,明白了這個就好理解了這種簡單類型可以直接看前面部分……顯而易見地,*a 的類型是 int,很明顯它是個值而不是地址嘛。a 的類型是 int*,它才是地址。
更複雜的類型的話推薦一些學習資料:
C gibberish ? English 一個把 C 變數定義轉成英文的東西
Clockwise/Spiral Rule 比較詳細,通俗易懂的語法解釋
這個叫取地址符。int a = 0; (1)
int *pa = a; (2)
(1)是聲明一個int類型的變數a,為其分配一塊地址,並把地址內容初始化為0。(2)是聲明一個變數pa,類型是int *,表示指向int類型,是指針變數。系統為pa這個指針變數分配一塊地址,取a的地址作為pa的值。*運算符 是取內存中的值。所以*pa = a = 0,pa = a的地址。聲明和運算時表示的含義不同。我覺得就不要給C語言洗地了,這塊就是沒設計好。
誠然我們可以這麼理解:int* a = b;但是遇到多變數定義怎麼破?int* a = b, x = 0; 這個x怎麼解釋?int啊還是int*?在上例*和a實際關係更緊密,和int反而遠了,這和一般人的直覺是不同的。設計者的本意就是:*a是前面是類型int,故而*a是int,a自然就是int*,而附帶的賦值表達式是賦值給a而非*a - 這當然感覺有些操蛋(廢話,a還沒初始化,*a怎麼可能賦值),讓人費解。
雖然這讓初學者有些困惑,不過C語言本來就該讓所有人使用,這個特性可以當做傳銷過濾器,凡是不能迅速理解的就別學C語言算了。
ps:題主提的int a也是不可取的,這個更反直覺。如果int不能定義多變數的話,目前的定義方式是非常符合邏輯的。有故事,而且非常有故事,相當的騷!
如果我是C語言的發明者,在定義指針時,我一定會規定用「!、@、#、¥」等等來修飾指針變數,就是不準用「*」來修飾指針變數。
你說定義指針時用int *p = a;完了使用指針時又用*p,誰搞得清。
所以題主也不用太較真兒了,只要記住在定義指針時,「*」只是說明p是指針就行了。
這東西是最開始定義不同而已. 如果把定義反過來, 樓主是不是還會有同樣的疑問?
題主問題的答案就在這裡,KR C 2nd Edition, Section 5.1, p94:
The declaration of the pointer ip ,
int *ip;
is intended as a mnemonic; it says that the expression *ip is an int . The syntax of the
declaration for a variable mimics the syntax of expressions in which the variable might
appear. This reasoning applies to function declarations as well. For example,
double *dp, atof(char *);
says that in an expression *dp and atof(s) have values of double , and that the argument of
atof is a pointer to char .
解釋一下,KR說,這種聲明形式是為了助記,*ip的類型就是前面的int,這樣的聲明模仿了變數在出現時表達式的樣子。
====================================================================(以下可以參考KR C Section A8)C語言中聲明語句(這是簡化了的)是如下格式的:type declarator1, declarator2, ... ;
type可以是基本數據類型、結構/聯合、枚舉或typedef類型之一,declarator是聲明符,比如(KR C Section A8.5給出了相當嚴謹的定義,當然如果看得懂的話):
*p
f()
a[n]
(*pf[n])()
(*(*x[3])())[5]
等等,那麼這些declarator就有著type的類型,然後你反推回去,就知道聲明符中的變數是什麼類型的了。
這也就解釋了諸如int *a, b;
這樣的經常有人不明白的形式,很容易明白為什麼只有a是指針而b只是整型。
=================================================================
所以,學習C語言的話,經常翻翻KR C,有很多平時的疑惑一下子就解開了=================================================================補充:這種事情還是要看我們最權威的「C語言聖經」,因為一個編程語言為什麼是這個樣子,它的創造者們說的是最清楚、最準確的。列舉一下KR C中和聲明格式有關的一部分章節:Section 2.4, Section 5.1, Section 5.12, Section 6.7, Section A8int * a = b ;
拆分開的話,這行語句執行起來有以下幾個步驟
變數b取變數b的地址取變數b的地址,並將它賦值到a定義a的類型為int*,取變數b的地址,並將它賦值到a也就是說
a = b;於是*a = b;(記住對於任何值a, *a = *a = a,假如每一步都不會報錯的話)至於為什麼是類型在前,因為過去的編程語言講究的是精確,而不是容易寫。講究精確的原因是因為過去的計算機系統資源非常有限,用戶寫程序時候是需要同時計劃內存分配的,所以強調使用合適的數據類型可以保證讓用戶不要浪費內存。現代系統基本上沒有人要求程序員去管內存怎麼用了(除非你是大型系統),所以就是這個結果。
================
尼瑪鬧笑話了,之前說引用時候想的是dereference,但其實解引用指的是*操作不是……太久不動C/C++了,還好複習了一下 T_T這在Cpp里叫引用……int* a中int*是一個整體,和int是完全不同的兩種類型(我當然知道你能強制Cast)。符號的意思為取址運算符,其結果為一個內存地址。將一個內存地址賦予一個指針對象不是再正常不過的事情了嗎?
首先你得明白,計算機地址的概念。然後看一個例子,你就明白。
int number = 10;int* p = number;(這裡說明一下, int* 是指針類型變數的聲明,指針類型用來存儲地址。就像int來存儲整數, float來存儲浮點數)然後咱們來列印printf("%p", p); //這裡就是一個地址,我電腦上是0x7fff6eb9ce84
printf("%d", *p); //輸出值是10; 這裡的*是個操作符,就想+、-號一樣。說的簡單點 int* 你可以換個名字就叫zhizhen, 來保存地址。 上面的代碼就可以換成zhizhen p = number;而*完全就是一個操作符了, 就是取地址里內容的意思。再附一張圖我猜*比長的更像指針(逃
可是int a是引用啊??
搞不懂為什麼很多人喜歡寫成 int *a,明明應該是int * a
int*叫int型指針類型
*a中的*是運算符號,和+-符號一樣,不過是一元的不是你不理解,是你概念沒搞明白int *a?=b。
等式左邊int *a 是定義一個指針,該指針指向整數,即a對應存放的是一個整型變數的地址。在定義指針變數時,*只表示類型說明,不具有運算意義。 等式右邊b,其中的為取地址運算符,實現的操作是:取得一個可定址數據b的內存地址。 總結如下: 1.在定義指針變數時,如int *p;此時*表示類型說明,說明p是一個存放地址的指針變數,不是運算符,沒有計算意義。而在非定義的語句中,*具有指向的運算意義,如cout&<&<*p;表示輸出p所指向的內存數據。 2.變數的指針表示變數的地址,如a,表示變數a在內存空間的首地址,是一個常量。以p=a;為例,其中p是一個變數,用於存放int型數據a的地址。 鄙人拙見,歡迎各位討論和指正~~int *a = b 等同於:
int *a;a=b;也就是說:*a類型是int,反推出a是指針(地址),a的值為b的地址。*省像素啊。又好看
推薦閱讀:
※如何寫C++而不是C with Class?
※編程人常說「精通 C,你想幹啥就幹啥 」這個 C 是指 C++ 還是最原始的沒有類的 C ?
※作為非計算機專業的學生,覺得 C 語言遠比其他語言易於上手,正常嗎?
※怎樣理解C語言是才是代碼的精髓,可以讓你領略不一樣的世界這句話?(其實就是怎麼翻譯成人話-_-#
※C語言C11為什麼選擇`thrd_create`這麼奇怪的命名?