為什麼要有指針?

本人菜鳥一枚,現在還在不停的問為什麼的階段。看到變數我會問為什麼程序需要變數。看到結構體我會問為什麼需要結構體。看到類我會問為什麼需要類。這些雜七雜八的問題我大致能夠自己回答出來。比如:

  • 為什麼需要變數?

答:因為整個計算機程序最核心的就是數據,為了存儲及改變數據,就需要一個名詞來標記數據,變數就是用來標記數據的,因為他允許改變數據,所以叫「變」量。就像常量只是為了存儲數據的一樣,不能夠改變數據,常在。所以叫「常」量。

  • 為什麼需要結構體?

答:因為單個的變數不能夠完整的保存數據了,比如一個點,數據有x坐標和y坐標,所以需要一個整體的數據來表示這些數據,這些數據一般都有幾個結構,所以叫「結構」體。

  • 為什麼需要類?

答:因為對數據的表示不能夠容納真實數據了,比如一條狗,毛髮,性別等等可以使用單個的變數來表示,但是一些狗的動作怎麼表示?狗會有吃飯,咬人,狗拿耗子等等等動作。這些動作怎麼保存下來?於是有了「類」。

當然我的回答可能很淺顯,是指把最基本的需求說了出來。但是面對這個問題:

  • 為什麼需要指針?

我連最基本的需求都有點不明白。我會用指針,但很多時候不明白為什麼這個地方要用指針。

指針說白了就是存放的地址,可以用指針指向任何的地方,變數、結構體、類,說是「指」,其實存放這些數據的地址,方便查找,看不懂別人寫的指針程序,拿出大殺招,直接畫個內存分配圖也能搞的明明白白。但是為什麼要「指」?

我直接使用引用也能夠操作這些變數、結構體、類的數據。所以關鍵還是為什麼要另外開闢一塊內存存放這些數據的地址用來訪問,為什麼要「指「?

希望各位解惑,謝謝。


http://blog.youxu.info/2009/07/02/fortran/

我應該是從這篇blog學來的。原文簡單幹脆,比我這篇羅嗦的文字要好多了。

----------------------

你問題提得蠻好的,只是可惜沒人解釋得通透。我看不少人提到了彙編,就是沒進一步解釋下去。

我年紀太小,不知道歷史上這一切如何上演,所以或有出入,但並不影響解答此問。

0)史前

早期的CPU(也許並沒有真正的實現)並不如今天的強大,內存讀寫的指令可能只有「從*常數*0x1234地址處讀入1位元組到寄存器a」,或者「把寄存器b的值寫入*常數*地址0x5678這個地方」。

那個時候沒有變數這一說,所有的內存讀寫都得指定好常數,也就是得把具體的數字(也稱為字面量,literal)寫死在程序里。

換言之,你如果想清空100位元組的內存,而每條指令只能對內存中某一個位元組進行寫入,那就得寫100條指令。隨著要處理的數據的膨脹,程序也得跟著膨脹,而這是不可接受的。

1)流程跳轉

為了解決這個問題,人們發明了控制流程的指令,比如「如果寄存器c的值為0,則略過下一條指令」和「無條件跳轉到首地址為0x9012」的地方,從那個地方繼續運行」。注意,指令本身也是編碼成位元組存在內存里的,這就是馮諾伊曼機器最優雅的地方。這時候我們可以寫出循環了(這裡不展開,感覺沒必要)。

2)自修改程序

問題並沒有解決,因為就算有了循環,整個程序還是只能寫常量,所以如果想要對不同的內存塊清零,只能寫不同的指令。

但是人是很聰明的。

利用「指令本身也是編碼成位元組存在內存里」這個特徵,我們可以通過修改內存來修改指令,繼而修改行為。舉例來說,在0x3454處存了一條指令,這條指令叫做「把0x5678開始的一位元組清零」。比方說這條指令佔用了三個位元組,第一個位元組告訴CPU這是一條清零指令,後兩個位元組(0x3455和0x3456)存了一個表示地址的整數,告訴CPU到底要把哪個位元組清零。顯然,這個整數會和0x5678有關,而且大多情況就是0x5678。

現在,要是CPU在某處執行了一句「把起始地址為0x3455的那個2位元組數自增1」,那實際上0x3454處的指令就變成了「把0x5679開始的一位元組清零」!

看到希望了么,我們可以寫一個指令,然後不斷修改這個指令,再加上流程式控制制的指令產生一個循環,就能用一段固定長度(注意,代碼內容隨著執行並不固定)的代碼清零任意指定的一段內存塊!

這叫做「自修改程序」。

3)間接定址

後來的事情就很簡單了。編寫這種自修改程序極容易出錯,因為稍微改錯一個地方指令就全改亂了(比如在上例中如果把0x3454中的數字給改了,就完全變成了另一條指令),所以人們再次發明新的工具,叫間接定址。所以有了這樣的指令「把寄存器a存的數字當成地址,取出該地址處的位元組放到寄存器b里」和「把寄存器a存的數字當成地址,把寄存器b的位元組寫入到該地址處」。

回到自修改程序的那個例子里,我們可以把「把0x5678開始的一位元組清零」換成:

「把寄存器b設為0」

「把0x5678這個數字寫到0x1234處」

「讀入0x1234的數字到寄存器a」

「把寄存器a存的數字(此處即0x5678)當成地址,把寄存器b(此處為0)的位元組寫入到該地址處」

這樣實現雖然看起來費事,但是總算得到了等價的功能。另外,我們以後只需要讀寫0x1234這個內存就能達到改變行為的目的,而不要冒險去修改指令本身了。換言之,指令本身被固化,其行為更加穩定。今天,代碼雖然也在內存里,也編碼成了一個個位元組,但是一般不和數據放在一起,而且一般執行的時候是只讀的。

假設程序員無限聰明(當然這種好事從來就沒有發生過:),寫的代碼從來不出錯,那麼間接定址是沒有必要的,因為直接寫自修改程序就行了。間接定址沒有增加任何新的功能,這點不像跳轉指令。

4)指針

哦,現在解釋指針就很簡單了,指針就是間接定址例子裡面那個0x1234的內存塊。它存了一片地址,而指針解引用(比如*p)就對應的是間接定址讀寫的指令了。

所以說到最後,「為什麼要有指針的」就可以化成「為什麼要有間接定址」,問題基本等價於「為什麼不直接使用自修改程序」了。

希望我說得比較明白。


如果按題主已理解的知識來講,可以這樣說,通常指針也是一種變數,儲存可以改變的引用。

為甚麼要改變引用呢?最常見的情況是我們需要動態地在運行時創建數據,例如鏈表的節點,然後用指針把節點連接。另一個例子是C++的多態,可在運行時創建不同類型的派生類,把它們的地址儲存在基類的指針中。

以指針去表示引用,是比較原始、接近一般計算器架構的方式。有更高級形式的引用,如C#、Java等語言的引用,它們比指針較多使用限制但更易使用。


因為指令集裡面有一種間接定址的方式,抽象出來就是指針了


從C語言角度講,指針就是方便。以至於我剛開始學C#時不適應沒有指針的生活。不過現在好了,我開始用彙編了,這比指針還爽。【高級語言程序員不要打我】


懂的太少,想的太多

指針提供了一個(在某些情況下)更加方便的方式來訪問所需要的變數

你可以不用變數,枚舉用戶所有的輸入情況來進行輸出也不是不行

你可以不用結構體,每一個元素拿出來分別定義也可以,用int a0,a1,a2,a3,a4,a5,a6替代int a[7]也不是不行

你也可以不用類,C里就沒有類,照樣能寫大程序

你也可以不用指針

你也可以不用C語言,直接寫彙編或者機器碼


題主不小心踏進了程序員的大雷區——指針,所以才招來一堆牛逼和不牛逼的程序員過來抑或吐槽抑或找優越感,你不用管他們。

我覺得吧,你先從門電路學起,再學計算機體系結構,然後是彙編和指令集(這些要花你幾個月的時間)。那之後你這些問題都不用問了。你也可以打破你現在思維上的束縛。當然我相信到時候你再回來看你自己的問題的時候也會覺得naive。

如果你只想編程,就別研究這些什麼「目的」whatsoever,照別人的做,等有一天自己頓悟。不頓悟也沒關係,大多數人並不懂物理,一樣可以做個好木匠。

最後,為了激起你的興趣,你寫了一坨一坨的東西,不想知道計算機它在到底執行些什麼東西嗎?連人類都看不懂的 *p++,計算機怎麼理解的了?


指針方便人為的操作、管理內存。

另外引用就是通過指針實現的


1 首先確認一下你的問題:

為什麼要有指針的意思是為什麼要有指針的這個概念,而不是指的是C/C++中為什麼要暴露給用戶指針的語法是吧?

2 對於指針的語法,不是所有語言都有的。但是不代表這些語法後面沒有指針的概念。比如說Java,號稱說我們沒有指針用起來簡單,但是空指針異常是個什麼情況呀。

3 看到你的總結,感覺基本還是初學者。所以要搞明白指針的意圖最好先搞明白C/C++的內存分配方式。明白堆上的內存和棧上的內存的差別,然後把你的問題轉化為

我是不是可以不使用棧上的內存。

等你學習了構造函數,還可以問自己,我如何禁止用戶在堆上創建我的對象。

4 看到有人指出引用是一種特殊的指針,簡單的理解可以認為,基本上引用的實現也是用指針實現出來的。

但是按照標準,不能把引用看成指針。他們有很多的不一樣,比如指針一定占內存,引用可以不佔用內存。因此有指向指針的指針,但沒有指向引用的指針的。我對引用的理解(指傳統意義上的,不包括右指引用),他的出現是為了解決C++運算符重載後代碼美觀的問題。

這條有點深入了,新手可忽略。


你的學習方式是錯誤的。

你的這些為什麼要有XX的理解,大多數都是不那麼正確的。

為什麼要有指針,指針的內涵也就是地址是先於指針而存在的,對於C語言而言指針並不是在混沌沒有指針的世界中突然發明出來的,是先有地址,然後C語言發明了一系列的語法讓我們可以對地址進行操作,所以你這種學習方式是有問題的,至少不能用這種臆測的方式來學習C語言。

如果你的問題是為什麼有了引用還需要有指針,我建議你先從C語言學起,再學C++語言,你就會知道引用的出現比指針晚太多了,所以根本不存在什麼已經有了引用還需要指針幹什麼?

如果你的問題換成,如果我設計一種新的語言,是否需要設計指針,如果不設計指針,我能用什麼語法來代替,這樣的話,我覺得會是一個好問題。

當然,問題中最大的槽點是:

我連最基本的需求都有點不明白。我會用指針,但很多時候不明白為什麼這個地方要用指針。

指針說白了就是存放的地址,可以用指針指向任何的地方,變數、結構體、類,說是「指」,其實存放這些數據的地址,方便查找,看不懂別人寫的指針程序,拿出大殺招,直接畫個內存分配圖也能搞的明明白白。但是為什麼要「指」?

我直接使用引用也能夠操作這些變數、結構體、類的數據。所以關鍵還是為什麼要另外開闢一塊內存存放這些數據的地址用來訪問,為什麼要「指「?

按照這種說法的話,其實變數也是不需要的,變數的本質不也就是一個內存地址的別名么,你可以自己一開始把內存都規劃好然後直接直接讀寫指定的位置就好了。


因為在馮諾依曼體系里,我們需要間接定址,需要對地址進行操作,所以從程序語言的實現上看,總是需要指針的。

程序里的函數和數據怎樣存儲在內存上?大部分都是存儲在棧上,每個函數以及它的參數和內部變數都會存放在一個叫棧幀(Stack Frame)的東西里。當一個函數訪問了一個外部變數時,我們怎麼知道去哪裡找這個外部變數呢?通過一個特殊的指針,這個指針指向定義了這個變數的函數所在的棧幀,然後加上一個Offset來移動指針,就能找到這個外部變數的值。而如果要用到堆內存,那就更需要指針了,編譯的時候是不知道分配到堆內存的變數的地址的,你需要維護一個指向堆內存

上變數地址的指針,當要用到這個變數的時候,通過這個指針去訪問。

如果不看程序語言的實現,只看對程序員可見的部分的話又會如何?關於這個問題,我覺得題主除了問為什麼,還應該多問如果不這麼做,我可以怎樣做。

指針只是一個操作地址的工具,如果你允許程序員直接對內存操作,例如手動分配堆內存讓函數返回一個數組,就要用到指針,C/C++就是這樣;如果不允許程序員管理內存或自己對地址進行操作,確實可以不需要指針,例如Java和Python。

程序是否一定需要變數?Scheme這種函數式語言告訴你可以不需要,至少它沒有C語言意義上的變數,它的變數都是數學意義上的變數,大多數時候你不能也不需要去改變變數的值。

程序是否一定需要結構體?可以不需要。以點的x坐標和y坐標為例,一個由點構成的數組可以由一個由x坐標構成的數組和一個由y坐標構成的數組表示,這是完全等價的,結構體的意義是讓針對該結構的操作更方便了,例如對點進行排序,不使用結構體也能做到,但是比較麻煩。

程序是否一定需要類?可以不需要,早期的Pascal和Ada這樣的語言就沒有類,因為那時的開發方式主要還是面向過程,後來因為軟體工程上的需求,才提出了「對象的行為」並出現了面向對象的開發方式。

不過要是我的話,我會少問點為什麼,多讀點書,然後就會發現很多為什麼不需要問了


很簡單啦,你自己動手寫一個void swap() 函數,交換下兩個int變數的值,

比如這樣:

運行後發現,咦,好像之前和之後沒變哇。。。

再試試下面的:

什麼?不知道怎麼測試?

完了自己再寫一個交換兩個指針的函數,看看效果(需要指針的指針)

搞明白了趕緊去看書吧,不看書總亂想是不好的。。。


我喜歡樓主思考問題的方式!

但是考慮的方向需要稍微做一下修改。首先要明白語言的設計特性。

變數

一個東西在內存裡面,而你想用語言去表示那個東西,就必須找到一個表示它。於是我們用常量或變數去表示內存里的值。比如,

int a = 2;

就是把2這個值,放在了內存中。(但是你不知道它的位置,如果你有看到整個內存的能力,你有可能發現有一個2在No.300處)

並且你想要在程序中調用它,就必須有一個東西代表它,於是用變數a代表了這塊內存中的內容.

有了變數,你就可以用他表示一個值。從而你可以使用這個變數。

指針

如果你只有這一行程序的話,那指針就沒有太大的存在必要了。

但是如果你有好幾個函數需要讀寫這個值,那麼這時候問題就來了。

int myMoney = 1000;

如果你的賬上有1000元,有好幾個函數要操作這個值,這時候就會產生兩種需求。

在函數里修改這個值的時候是應該修改原值呢? 還是不修改原值?

如果使用foo(myMoney)這種形式的話,就會把myMoney代表的內存中的內容「複製」一份到函數棧里,這樣你在函數里修改這個值不會對外界有任何影響。

但是,如果你想在函數中對原值進行操作,這時候就不能只傳進來內容,而需要傳進來一個myMoney的地址,這樣,在函數裡面就能再程序找到那塊地址,把內容修改掉。

所以有了傳遞地址的需求。為了方便傳遞地址,所以有了指針,指針也是一個變數,只不過裡面存的內容是一個地址。

總結地來說, 變數為了表示數據而生, 指針為了傳遞數據為生。

-------------- 拋翔引玉 ---------------

還望各位大神多批評。


我認為最至少必要功能有兩個:

  1. 需要在函數里修改值, 因為函數里傳遞的都是副本, 所以需要靠指針來修改真實值.

  2. 當需要傳遞非常大的參數給函數時, 指針相對小非常多, 提高了效率.


還有一個原因是效率問題。

假設有 struct Foo 函數doFoo要對foo進行操作。沒有指針的話,你只能把函數返回Foo,然後在複製函數的返回值到源對象上。

要是傳入一個數組,這種現象會更加過分。同時C里數組是屬於不可拷貝的,你說現在你怎麼辦?

沒有指針C還是圖靈完備的。如果沒指針,C幾乎沒有任何優勢。


指針是C語言中表示引用的唯一的語法工具。

但題主最後表示不明白為什麼不能用引用,那關心的其實是C++吧!

在C++里,指針存在的原因是引用在語法上不能為空!這樣的好處是引用可以很安全的使用,不用擔心非法訪問這類系統錯誤。壞處則是你不能延遲初始化「引用」,也不能表達一個為空的「引用」,這些情景下你就不得不用C++的指針了。

至於「為什麼要另外開闢內存來存放地址」,引用在C++里是由指針實現的,其實也需要開闢內存來放地址。所有語言的引用都需要在內存里存地址。


題主問題還可以,想的也很多.但是題主掉進了一個思維的陷阱:你用已經存在的東西來臆測這些東西製造出來的原因,有點類似某些人用楷書來猜測漢字起源的構字法.

從計算機的發展來說,最早人類使用直接地址數值來訪問數據和指令,因為直接數值太過於魔法,人類發明了助記符,這就是彙編語言出現了.

之後人類又覺得助記符也不夠說人話,又折騰了稍微像人話一點的語言,就是以C為代表的"高級語言",但是C語言發明的時候為了滿足性能要求,做了一個折衷,對地址的訪問產生了兩種方式,一種是比較像人話的助記符,變數,交給編譯器去管理,另一種則是和古代一樣直接對地址訪問的助記符,那個就是指針,給習慣了不說人話的程序員去用——所以指針確實很難學,因為那根本就是不說人話嘛.

再之後C++覺得為了函數里能改變原參數值就非要用不說人話的指針太不人道了,於是搞了個比較像人話的引用.

再後來么,各路語言大師覺得硬體速度那麼快,沒必要為了一點運行效率非加一個不說人話的指針,之後指針也就越來越少出現在高級語言里了.


呦,在之前我也沒想過這個問題,為什麼要有指針,只是之前在C++裡面沒了指針似乎什麼也做不了,但是用其他語言,比如Java, C#, JS, PHP之後就沒再用過指針了,但是卻也沒什麼問題。

今天認真想一下,為什麼要使用指針的答案是——因為你自己要管理內存。C/C++

這類編程語言目標是更高的效率,所以在設計上更接近計算機本身而不是開發者這方面,很多時候他們更多的考慮到計算機有什麼而不是人需要什麼,指針是計算機儲存物理地址的變數,如果你想得到更高的效率,一定要通過指針來自己管理內存。假設C/C++沒有指針的概念,你會發現沒辦法分配和釋放內存上面的數據了。

但是其他沒有指針的編程語言,提供了自己的內存管理機制也就是所謂的GC,指針還在,不過是系統幫你維護了而已。這些高級語言的開發者可以忽略如何分配釋放管理內存的工作,把更多的精力放在如何涉及邏輯本身上面,這是一個很不錯的進步。但是記得,這樣做犧牲了相應的效率。

結論是,指針的存在啊是為了讓用戶可以管理內存。如果系統接管了對內存的管理,那麼語言層面就把指針封裝成引用提供給用戶。但是,事實上,不論你用還是不用,指針還在那裡默默的為你服務。有個傳說之前版本的C#錯誤提示裡面會告訴你「指針異常」,而不是「引用異常」。(或者這個故事是Java的?)。


說到底還是題主想得太多,見得太少。

Haskell沒有變數,早期的lisp沒有結構體,很多語言都沒有類(有人說過「面向對象是對人類智慧的侮辱」),c#沒有指針,APL以上這些好像都沒有……………


最可能的原因是你看的代碼真的就沒必要用指針。

你寫的業務也沒有非要用指針的必要

引用是編譯期才能提供的特性,有些業務你用不了。

比如實現一個運行時動態變數名和值綁定的表達式計算系統。

或一些偏門邏輯:你要對一個4位元組變數的第三位元組干點亂七八糟的事,比如只操縱某個顏色分量。


為什麼要有門牌號呢?因為房子太多,沒有門牌號找不到你要找的房子;那為什麼要有這麼多房子呢?因為人太多一間房子住不下。

指針同理。


推薦閱讀:

Android 會像 Windows 一樣,打敗 iOS 嗎?
如何提高寫代碼的水平?
Windows 下進行 C/C++ 開發,Eclipse 和 Visual Studio 哪個好?從編譯速度、UI、方便程度上如何比較?
這個開源的6千行UI框架,能打敗QT,MFC嗎?
數據結構和C語言有什麼聯繫?

TAG:編程語言 | 編程 | C編程語言 | CC | 指針 |