C++指針詳解

什麼是指針

​ 指針也是一種數據類型,具備指針類型的變量叫指針變量。指針變量的做用是存放單元地址c++

//語法:數據類型 * 變量名 例:
int * ptr = 1;

指針爲何要指明其類型編程

  • 類型包括了指針所指向的元素所須要的內存空間大小數組

  • 指針參與運算是跟指針對象類型是密切相關的。數據結構

​ 指針值得是內存單元的地址。指針變量指的是存儲地址的變量,也就是存儲指針的變量。二者的區別須要注意。函數

* / &

​ C++提供兩個與地址有關的運算符*&。在不一樣的運用場景中他們有着不一樣的做用。下面咱們來討論他們在各類不一樣場景中的應用。測試

*稱爲指針運算符,也稱爲解析(dereference)this

  • * 用來定義指針變量spa

  • 加在指針變量以前表示解析當前指針變量,取其所指向的對象指針

    cout << * ptr << endl;
    // 1

&稱爲取地址運算符code

  • & point能夠取到變量point所指向的元素的地址

  • &出如今變量聲明中表示聲明引用

  • 出如今賦值語句中,表示取右邊對象的地址。

    int * p = &i;

指針的賦值

​ 就像普通變量同樣,指針在定義以後必須賦值才能引用。如定義的那樣,指針變量存儲的就是地址。因此指針變量賦值的語法以下數據類型 * 指針名 = 初始地址;,但賦值的地址必須是已經定義過的常量(eg:數組名稱實際上就是一個指針常量),對象或變量,結構體等,不然會引發空指針異常

int a[10];  //定義int型數組,同時用a指針常量記錄數組的首地址
int * ptr = a;  //定義並初始化int型指針
int * const p2 = &a;    //固然咱們也能夠本身定義指針常量

指針運算

​ 指針是一種數據結構,和其餘數據結構同樣,指針也是數據+操做。可是指針參加的運算又與其餘的操做不太同樣。

​ 指針能夠參加整數的加減運算,可是又與普通的加減運算又一點區別,指針參加的加減運算與指針的類型密切相關。

ptr + nl;   //表示當前ptr指針後方第nl個數據的地址。
ptr++;ptr--;
*(ptr+nl);
ptr[nl];    //表示ptr以後第nl個元素的內容。也能夠寫做ptr[nl],因此如今對數組的訪問是否是點想法了呢?

PS:通常來說,指針的運算是和數組聯合使用,由於只有數組會開闢一段連續的地址空間,同時若是使用這種方式必定要注意下標越界檢查。

​ 前面介紹了指針的算術運算,接下來介紹指針的關係運算。

  • 指針能夠和0進行比較,0用來表示空指針,也就是不指向任何一個有效地址。

    PS:空指針也經常用NULL(NULL是一個經常使用的宏定義,其定義其實也是0)來定義。在定義一個指針以後不知道指向什麼地方的時候,必定要將轉爲空指針,避免錯誤調用形成一些不可碰見的錯誤。

  • 指針數組:若是一個數組的每個元素都是指針變量,那麼這個數組就是指針數組。指針數組的每一個元素都是統一類型的指針。

    int * ptrArray[capacity];
    //讀者能夠自行猜測根據前面講的指針加法運算考慮下指針的二維數組。

指針在函數中應用

​ 想象一下一個在沒有指針的狀況下若是你要傳遞一大堆數據到一個函數中。這是個噩夢,就算你真的把全部參數都寫了一遍,這個函數也不具備廣泛性可適用性。同時再想像一下當你須要返回的參數有一堆的時候,若是沒有指針,根本無法實現對吧。

​ 使用指針做爲函數參數有幾點好處:

  • 實參和形參共享內存空間。也就是參數的雙向傳遞

  • 減小數據傳輸時的開銷。

  • 經過指針也能夠指向函數代碼的首地址。

    PS:若是參數對象並不須要作任何修改,咱們應該在參數列表中將它定義爲常量指針。

    雖然在傳入參數的時候傳入指針和對象的引用是一回事。可是應用會使程序的可讀性更好些。

指針型函數

​ 當一個函數的返回值是指針類型的時候這個函數就是一個指針性函數。而指針型函數的主要目的就是在函數結束的時候把大量的數據返回。

語法格式以下:

數據類型 * 函數名(參數列表){}

​ 前面提到咱們在須要返回一堆數據的問題,當然咱們能夠傳入這堆數據的指針,實現前面所說的雙向傳遞。可是這樣可讀性較差,同時會破壞本來傳入數據的本來,若是你後面還要用原來的數據的話,那麼這樣幹基本就GG了。

函數指針

​ 函數指針的正解是指向函數的指針

​ 在咱們日常調用函數的時候每一個函數都有一個函數名。是的你沒猜錯,指針又來了。調用函數的時候咱們一般的形式是函數名(參數列表)其實質上就是函數代碼的地址。那麼函數指針就具備和函數名相同的做用。

函數範圍值類型 (* 函數指針名)(參數列表)

​ 那麼很多讀者會疑問void返回類型的函數怎麼辦?在仔細思考一下,void類型的函數指針實際上是存在的。可是void類型的指針不管是使用仍是理解都有必定的難度。

  • void類型的指針沒有類型,或則說不知道所指的對象長度。

    PS:感興趣的讀者能夠想象如何構建一個能夠存儲任何類型的數組。之因此是數組只是爲了方便查找快。

  • 任何類型的指針均可以賦值給它,而且不須要轉換。但相應的問題是它只能得到對象的地址,而不能得到對象的大小。

  • 因爲不能明確長度,因此你能猜到咱們沒法使用它參與指針運算

  • 沒法復引用,也就是說沒法解析。由於沒有void類型的解析規則。

    假如 int * p,那麼在你解析的時候,程序知道將該指針後的四位做爲對象的內容。可是咱們前面說過void類型不知道所指向類型的長度。也就沒有對應的解析規則,應用程序就沒法解析了。

->.的理解

​ 咱們前面知道了指針能夠指向一個對象,那麼來想象一下若是我指向一個結構體(類通常成員私有,很差作例子)當我用指針指向他的時候我須要訪問它的成員我須要這樣寫。

MyStruct * ptr = new MyStruct();
(* ptr).member;
//巨麻煩有木有

請饒恕我懶癌晚期,可是每次能少些就少些,因此定義對於指針變量,須要訪問其指向的指針對象時。咱們能夠直接使用.來訪問。因而上面一段就變成了ptr->member。嗯,順眼多了。

this--一顆神奇的指針

​ 不論哪一種語言,你們都沒少寫this這顆指針吧,別否定,呵呵,我不信。

this指針是一個隱含於每個類的非靜態成員函數中的特殊指針。它用來指向正在被成員函數操做的對象

PS:this實際上類成員函數的一個隱含參數,在調用類成員函數時,目的對象的地址會自動做爲該參數的值。這樣被調用的函數就能訪問當前目的對象的數據成員了。

*

SO:結合前面講的->.的區別你知道爲何this以後你只能使用->了吧。

 

引用和指針的區別

​ 對通常應用而言,把引用理解爲指針,不會犯嚴重語義錯誤。引用是操做受限了的指針(僅允許取內容操做)。想象一下,指針能夠對你的對象或者變量進行增刪改查,他的權限對於有些應用來講是否是太大了。

​ 指針可以毫無約束地操做內存中的如何東西,儘管指針功能強大,可是很是危險。

​ 就象一把刀,它能夠用來砍樹、裁紙、修指甲、理髮等等,誰敢這樣用?

​ 若是的確只須要借用一下某個對象的「別名」,那麼就用「引用」,而不要用「指針」,以避免發生意外。

​ 好比說,某人須要一份證實,原本在文件上蓋上公章的印子就好了,若是把取公章的鑰匙交給他,那麼他就得到了不應有的權利。

​ ----「高質量c++編程」

​ 指針指向一塊內存,它的內容是所指內存的地址;引用是某塊內存的別名。

  • 指針是一個實體,而引用僅是個別名;

  • 引用使用時無需解引用(*),指針須要解引用;

  • 引用只能在定義時被初始化一次(PS:必須初始化,且不爲空,甚至指向空指針都不行。),一旦引用被初始化,就不能改變引用的關係(指針則能夠隨時改變所指的對象)。引用「從一而終」 ❤️

  • 引用沒有 const,指針有 const,const 的指針不可變;

  • 引用不能爲空,指針能夠爲空;

  • 「sizeof 引用」獲得的是所指向的變量(對象)的大小,而「sizeof 指針」獲得的是指針自己(所指向的變量或對象的地址)的大小;

  • 指針和引用的自增(++)運算意義不同;

引用與指針的詳細區別

​ 在c++中參數傳遞返回值的傳遞有三種方式:

  • 值傳遞

    void function1(int x){
        x = x + 10;
    }
    ​
    int n = 0;
    //實際上傳遞的是n的副本,並非n真實地址所指向的那個對象。
    function1(n);
    cout << "n = " << n << endl;// n = 0
  • 指針傳遞

    //指針傳遞的是變量的地址,咱們直接修改地址,等同於修改了原變量
    void function2(int *x)
    {
        (* x) = (* x) + 10;
    }
    int n = 0;
    function2(&n);
    cout << "n = "  << n << endl; // n = 10
  • 引用傳遞

    //傳遞的引用,這裏的X只是n的另一個名字,其指向的地址仍是x的地址。
    void funcion3(int &x)
    {
        x = x + 10;
    }
    int n = 0;
    function2(n);
    cout << "n = "  << n << endl; // n = 10

​ 由於引用確定會指向一個對象,引用應被初始化。

PS: 不存在指向空值的引用這個事實意味着使用引用的代碼效率比使用指針的要高。由於在使用引用以前不須要測試它的合法性。

​ 指針與引用的另外一個重要的不一樣是指針能夠被從新賦值以指向另外一個不一樣的對象。可是引用則老是指向在初始化時被指定的對象,之後不能改變。

string s1("Nancy");
string s2("Clancy");
string & rs = s1;   // rs 引用 s1
string * ps = &s2;  // ps 指向 s2
rs = s2;    //引用不用解析能夠直接賦值,如今rs仍舊引用s1,可是s1的 rs 指向的是值如今是 "Clancy"
ps = &s1; // ps 如今指向 s1;
cout <<" rs 指向的內容是" << rs << endl;
cout <<" ps 指向的內容是" << *ps << endl;
相關文章
相關標籤/搜索