編譯器是怎麼實現引用類型的呢?本篇文章複習了const常量和指針,在此基礎上推測了引用類型的本質。旨在加深對語言的理解,希望對你有所幫助。 ...
編譯器是怎麼實現引用類型的呢?
預備知識1:使用const定義常量
使用const可以採用類似定義變數的方法來定義常量,在定義的時候必須初始化以指明常量值。比如 const int a = 1; 。
那麼編譯器會給const定義的常量分配記憶體空間嗎?如果分配了記憶體空間,那麼每次使用這個常量都要訪問這個地址,空間效率暫且不論,時間效率不也被大大浪費?在這裡可能一開始大家都會有這樣的疑問。特別是單片機編程出身的我,在那個一無所知的時候覺得浪費效率簡直渾身難受(單片機的計算能力非常有限)。
先說是否給const常量分配空間,答案是會。可以對一個const常量取地址,因此無疑const常量在記憶體中是有一席之地的。
再說時間效率,實際上編譯器都會採用常量摺疊技術來優化代碼。具體說就是像巨集替換一樣把常量替換成立即數。但與巨集替換不同的是,這個是在編譯階段完成的。這樣凡是用到const常量的時候,都不需要訪問記憶體去取出常量值,而是直接用立即數(這個數是直接寫在機器指令里的)。這樣的時間效率和巨集替換相當。儘可能多的使用const關鍵字吧,這樣可以大大減少bug數量。
//C++語句與對應的反彙編,可以看到給a分配了空間並初始化為1 //但是在之後用到a的地方使用了立即數1
//如果看不懂反彙編也沒關係,代碼中要表達的在上文中已經全陳述過了
const int a = 1; 00C516EE mov dword ptr [a],1 int b; b = fun(a); 00C516F5 push 1
預備知識2:指針
一般說的“指針”是指“指針變數”。但是指針有時候也被理解成地址的同義詞,所以指針變數不過是指“一個變數,裡面存儲的是一個指針/地址”。這裡想說的是常量指針和指針常量。
常量指針是說“一個指向常量的指針”(也不一樣指向一個常量,但*ptr被視為一個常量),本質上是一個指針變數。定義的方法是 const int * ptr ; 。
指針常量是說“一個保存了指針/地址的常量”,所以指針常量本質上是一個常量,定義的方法是 int * const ptr = &a;。就像一個const常量在定義時必須被初始化一樣,常量指針也必須在定義時初始化,也就是說明自己是哪一個地址。
上一小節說明瞭什麼叫常量摺疊,和const常量是如何被替換成立即數的。那麼指針常量當然也會被這樣替換成立即數,只不過這個立即數表示一個地址,比如等於1480091972什麼的。
預備知識3:變數的三個屬性
變數的最基本的屬性基本上有三種:
(1)名字(必須顯示說明)
(2)類型 (必須顯示說明)
(3)存儲類別 (預設方式或顯示說明(使用:auto、register、static、extern))
作用域和生存期(作用域和生存期被認為是另兩種屬性,但這兩個屬性不像前三種基本)實際上由存儲類別和定義變數的位置決定,所以變數最基本的屬性就是上面三種。當看到一段C++源代碼的某一個變數的時候,合格的程式員應該能說明這個變數的三種屬性是什麼,比如一個全局變數是int類型,名字是a。
可是這些屬性只對程式員和編譯器有意義,機器只懂機器碼不懂C++。也就是說編譯器在編譯源代碼的時候需要這些信息,這些信息也在源代碼中有體現,所以編譯器也可以得到這些信息。編譯器根據這些信息進行編譯,但是編譯之後這些信息就都丟失了,不保證再能從機器碼里得到這些信息。所以名字對編譯器是重要的信息,但編譯後一塊記憶體不再需要有名字,因為記憶體只需要編號就夠了。名字只對程式員和編譯器重要。
引用
經典的解釋什麼是引用的說法是:引用是變數的別名。確實是的,這句話完美到無懈可擊,如果說有什麼瑕疵那就是這句話不太好理解。什麼叫別名?那麼為什麼要給變數多起一個名字呢?特別是在形參類型是引用時,如何能把一個“名字”作為參數呢。前面說了名字只是變數的屬性之一,如果只是一個名字的話那連變數都不是啊,什麼叫引用呢?
我先接觸的是C語言,對於指針並沒有太多困惑,但是C++的引用著實讓我困惑了一陣。看看引用的使用場景:
1 int a = 1; 2 int &b = a; 3 b = 2; 4 cout << a << endl; //輸出2
上面的代碼仿佛可以解釋什麼叫“引用是變數的別名”。b是a的別名,就是對b的操作和對a的操作一樣, b = 2; 就可以理解成 a = 2; 。仿佛沒什麼難以理解。但是引用的應用價值在於作為形參的引用類型和返回引用類型。
在C語言中參數傳遞是傳值的,想要實現對實參的修改需要藉助指針,一個典型的swap函數的實現如下:
1 void swap(int*a,int *b){ 2 int temp = *a; 3 *a = *b; 4 *b = temp; 5 }
交換兩個變數a和b,需要傳入a和b的地址 swap(&a,&b); 。想要交換兩個盒子里的蘋果,必須先找到這兩個盒子,所以變數的地址在這個演算法中是必不可少的。只懂C語言的我認為這自然而然:想要修改實參的值就必須傳入地址。可是到接觸了c++就發現實現同樣的功能可以藉助引用,而且要簡單得多。既然“變數的地址在這個演算法中是必不可少的”,那麼引用到底是怎麼實現傳入地址的?
實際上引用可以看成指針常量。對const常量有定義時必須初始化的要求,同樣對引用也有這樣的要求:定義引用時必須指明是誰的引用(作為形參的引用類型是在傳入實參的時候創建並初始化的)。把引用看成指針常量就可以解釋傳入引用為什麼和傳入指針一樣的作用了。
什麼是別名呢,就是地址或者叫指針常量。