2018年12月5日 星期三

c++ 中的別名 &, 與無名 &&, 和指標 * 等變數的複雜關係

c++ 語法中, 正常變數, 別名 & 變數和無名 &&變數的重要觀念:
1. 等號右邊(來源物件)必定是一個實體物件, 當它沒有具體名稱時稱為無名物件, 際上編譯器視需要會產生唯一識別碼當作名稱, 可以理解成用 && 來代表無名變數
2. 一旦實體化後賦予一個名稱, 就是具名物件, 也就是正常變數.
3. 如果宣告變數前置 & 符號, 代表的是別名, 它並不會產生新物件, 只是賦予一個名稱去共用等號右邊的資源, 可以理解成用 & 來代表別名變數.
4. 當用等於 = 符號指定物件時, 如果等號左邊(目標物件)的變數名稱未曾實體化過, 就會先呼叫建構式去產生物件, 再指定給該變數, 該變數就成為具名物件. 也就是正常變數
5. 如果傳進建構式的來源物件是無名物件(T  &&), 則呼叫移動型建構式, 如果是具名物件(T &)則呼叫複製型建構式
6. 如果用等於 = 符號指定物件時, 如果等號左邊的變數(目標物件)曾經實體化過, 意味著要根據來源物件去更新目標物件的內容
7.如果實體化的目標物件, 用等於 = 符號指定新物件, 則會呼叫等號 = 運算式, 根據來源物件, 如果是無名物件(T &&)則呼叫移動型運算式:  operator =(T &&), 如果是具名物件(T &)則呼叫複製型運算式: operator (T &), 去更新目標物件的內容
#include <stdio.h>
#include <iostream>
int main(){
    int a=3; // 3 是無名整數 &&,  建構新的物件 a = int(3), 之後無名變數 && 自動消滅
    int b=a;// 建構新的物件 b = int(a),  a, b 是正常變數, 但 b 與 a 不同實體
    int &c=b; // c 只是 b 的別名, b 與 c 共用一個實體
    int &&d=std::move(a); //  d 與 a 共用實體, 等同於 int &d = a; 但 d 不會消滅
    printf("\na=%d\n",a);  // 不同於無名的 3
    printf("b=%d\n",b);    //   不同於 a 的 3
    printf("c=%d\n",c);   //     等同於 b 3
    printf("d=%d\n",d);  //   等同於 a 的 3
    d=4;
    c=2;
    printf("\na=%d\n",a); // 4, a 內容跟著改變
    printf("b=%d\n",b); // 2, b 內容跟著改變
    printf("c=%d\n",c);  // 2, c 也變了
    printf("d=%d\n",d);   // 4, d 也變了

多變數指向相同位置時, 意味著該位置被多個變數參照(reference). 取出變數的參考位址要在變數前面加上  & 符號(注意與別命變數之差異: 別名變數放在目標物, 位址擷取則是在來源物), 取出指標的內容值則要在變數前慣上 * 符號.
    int    a;     // a 是一般變數, &a 就是該變數的參考位址
    int*  ptr; //  ptr 是指標變數, *ptr 就是指標的內容, 因此宣告成  int  *ptr; 也無妨
    ptr = &a;// 指標變數必須先初始化, 將 ptr 指向 a 的位置後, *ptr 與 a 的內容相同
如果覺得主角 * 太難理解, 用符號 & 可以宣告 a 的別名(alias) 變數  b(註: 必須用符號 = 同時去初始化)
    int   a;
    int&   b = a;// 也可以宣告成  int   &b=a; 也就是指定 b 是 a 的別名(名稱不同).
一旦理解了指標與別名, 那指標的別名就容易多了, 為了要取出指標物件的主角, 只要在指標前慣上星號 * 就是變數的內容:
#include "stdio.h"
int main() {
   int*   a;   //  a 是指標變數
   int*  &b = a; //  b 也是指標變數, 只不過初始化時為 a 的別名, 不管用 int*&   b = a; 或 int  *&b = a; 結果都相同
   int   c  = 3;      //  c 的內容等於 3
   b  = &c; //   既然 b 是 a 的別名且又是指標變數, 可以把 b 指向 c , 等同把 a 指向 c
   printf("*a = %d\n",  *a);// 因此取出 a 的內容等同取出  b 的內容, 也就是 c 的內容:  3
 }
宣告一個 * 的指標變數, 它就類似一維陣列, 而陣列就是一個固定連續空間, 但宣告陣列與宣告指標變數不同的是: 陣列會分配出記憶空間但指標不會, 而指標變數可以任意定位, 陣列卻不可以.因此兩者用法並不相同, 其實不可混淆.
    int   *ptr;          // 實體位置未指定, 初始化之前都不得使用
    int   dim[ ] = {1,2,3}; // 分配出 3 個整數的空間
    ptr = dim;        // 但 ptr 可以指向相同型態的陣列

至於連續兩個 ** 也就是連續的指標空間(類似儲存指標的陣列)
   int      a[ ]   = {1,2,3}; // a 內容陣列是整數, a 其實是指標, 只是不能重定位
   int      b[ ]   = {4,5,6}; // b 也是整數陣列, b 是指標
   int*  ab[ ]   = { a, b }; // ab 是儲存整數指標的陣列
   int** pab;       // 宣告整數型態的二維指標 pab
   pab = ab;       //  pab 可以指向 ab
上述 int** pab 也可宣告成  int*  *pab甚至 int   **pab, 運算結果都一樣, 當 pab 指向 ab 之後, 實際上 pab[0] 內容就是 a , pab[1] 為 b, 而 **pab 則為 pab[0][0], 因此全部內容展開就是:
   pab[0][0] = 1,  pab[0][1] =2 , pab[0][2] = 3
   pab[1][0] = 4,  pab[1][1] =5.  pab[1][2] = 6
函式的參數如果是以函數型式, 稱為無名函數(annonymous function vaule)傳進來, 就用符號 && 代表無名, 尤其在 move constructor 一定會用到. 最後要注意的是主角與別名 & 甚至無名 && 等變數代表了一個實體物件, 針對類型成員要是用句號 .  取用物件資源, 但 *,  *&, ** 等指標變數必須經過初始化後再用星號 * 轉回物件, 或用中括號取得陣列中的物件, 類型的指標變數要另外用箭號 ->  取用物件成員. 在類型中 *this 是物件主角,  但 this 是物件的指標.