近來在論壇中機場經常看到有關數組指針和指針數組的討論。這個是學習c++等語言中不可少的步驟, 不過向來指針的東西就是很有用但是也是很難用的東西,所以學習起來也不是很容易了。近來本人也 沒有什麼項目可以做的,所以就隨便寫的自己關於這些方面的理解,供同行參考,同時也可以把自己 的錯誤理解暴露在陽光下,接受大家評判的洗禮。
################################ # # # 基本知識 # # # ################################
當然我們一切都是從最簡單的內建類型開始,最後我會做一些推廣。 先看一下基本的形式,我們從這裏起步!
--------------指針---------------- int a=10; int *p=&a;
-------------指針的指針----------- int b=20; int *p=&b; int **p2p=&p;
-------------簡單數組----------------- int c[10];//整數數組,含有10個整數元素 file://也就是說每一個元素都是整數 --------------指針數組-------------------- int *p[10];//指針數組,含有10個指針元素 file://也就是說每一個元素都是指針 --------------數組指針-------------------- int (*p)[10];//數組指針,這個指針可以用來指向 file://含有10個元素的整數數組
上面這些簡單的形式是我們必須要首先理解,這個是基本的知識。 同時我們從上面也要得出一個很重要的知識提示:c++語言層面上 關於變量聲明的部分,後綴結合變量的優先級比前綴要高的。 看我們上面的例子的最後兩個就明白了,我們爲了實現數組指針的 聲明我們不得不變通一下。我們採用()來實現優先級的改變,實現了 數組指針的聲明。
################################ # # # 進一步提高知識 # # # ################################
數組,數組的指針,指針的數組,概念太多了。我接受概念一多的 時候,我就想把這些複雜的東西簡單一下。因爲我太懶了,概念簡化 一下,記住更容易一點。所以我們這裏要認識一下上面這些概念本質。 這樣可以簡化概念,減少記憶的難度。
先看一段程序。 #include <iostream> #include <typeinfo> using namespace std; int main() { int vInt=10; int arr[2]={10,20}; int *p=&vInt; int **p2p=&p; int *parr[2]={&vInt,&vInt}; int (*p2arr)[2]=&arr;
cout<<"Declaration [int vInt=10] type=="<<typeid(vInt).name()<<endl; cout<<"Declaration [arr[2]={10,20}] type=="<<typeid(arr).name()<<endl; cout<<"Declaration [int *p=&vInt] type=="<<typeid(p).name()<<endl; cout<<"Declaration [int **p2p=&p] type=="<<typeid(p2p).name()<<endl; cout<<"Declaration [int *parr[2]={&vInt,&vInt}] type=="<<typeid(parr).name()<<endl; cout<<"Declaration [int (*p2arr)[2]=&arr] type=="<<typeid(p2arr).name()<<endl;
return 0; }
運行的結果如下:(我在前面加了行號#XX) #01 Declaration [int vInt=10] type==int #02 Declaration [arr[2]={10,20}] type==int * #03 Declaration [int *p=&vInt] type==int * #04 Declaration [int **p2p=&p] type==int * * #05 Declaration [int *parr[2]={&vInt,&vInt}] type==int ** #06 Declaration [int (*p2arr)[2]=&arr] type==int (*)[2]
現在我們來分析一下結果。因爲我們已經具有了第一部分的基本知識,我們現在 可以很明確區別出來我們聲明的類型。這裏主要有兩個很重要的部分,我們不過 是就事講事情,編譯器是如何實現的原理不在這裏討論之列。
--------#02:數組------------
現在看#02,想到了什麼沒有呀?在編譯器看來數組只是相對應類型的指針類型。 當我們把數組傳遞給函數作爲參數的時候,傳遞的是指針,所以我們可以利用 參數來修改數組元素。這個轉化是編譯器自動完成的。
void f(int[]); int a[2]={10,20}; f(a);//這行等價於編譯器完成的函數轉化f(int *p)
也就是說這裏編譯器自動完成了int[]類型到int *的轉化, 注意是編譯器完成的,也可以說是語言本身實現的,我們 對此只有接受的份了。
-------#05:指針數組---------------
指針數組的編譯器內部表示也是對應類型的指針。
------#06:數組指針---------------- 數組指針的編譯器內部表示就是有一點特別了。 編譯器(或者說是語言本身)有數組指針這個內部表示。 由於c++語言的類型嚴格檢查的語言(當然還有一些是存在隱式類型轉化的)
所以我們下面的寫法是不能編譯通過的。 { file://---------編譯不能通過-------------- int arr[3]={10,20};//注意是3個元素數組 int (*p2arr)[2]=&arr;//注意是指向2個元素數組的指針 file://---------編譯不能通過-------------- }
################################ # # # 初步小結 # # # ################################
通過上面兩個小節的內容,大家應該基本明白了, 數組,指針,指針數組,數組指針到底是怎麼一回事情了吧。
-----------補充開始----------------------- 關於數組和指針的轉化,以及我們使用指針(++,--)等來操作數組, 是基於數組在內存中是連續分佈的。
但是我們使用“迭代器”的時候,情況是不一樣的。 這個問題本文不討論。
-----------補充結束---------------------
不過c++語言本身有很多詭異的地方(因爲c++要考慮到跟c語言以及舊的c++版本兼容)。 內建類型的這些性質特徵到了函數部分會有一點小的變化,不過如果你瞭解了編譯器做了 什麼以後的話,你也就不會太奇怪了。不過關於函數部分的內容我下次再說了。
現在回到上面的內容。我們這裏還是講一下內建類型。顯然一樣類型的變量是可以互相賦值。 不過當然還有一些其他情況也是可以的,比如類型的寬化,關於類的繼承體系問題等等。
當然了,不一樣的類型一般來說是不能互相賦值,當然這裏的例外就是強制轉化, 類的繼承體系等情況了。
看到這裏就會明白下面的程序爲什麼會運行的了。 我這裏也把下面的程序作爲今天內容的總結:
#include <iostream> using namespace std; int main() { int a[2]={10,20}; int *p=a;//根據上面說明,由於編譯器的參與,兩者類型轉化後一致 int vInt=10; int *parr[2]={&vInt,&vInt}; int **p2p=parr;//上面分析,類型一致 return 0; }
|