register關鍵字請求“編譯器”將局部變量存儲于寄存器中
C語言中無法取得register變量地址
在C++中依然支持register關鍵字
1、C++編譯器有自己的優化方式,不使用register也可能做優化
2、C++中可以取得register變量的地址
C++編譯器發現程序中需要取register變量的地址時,register對變量的聲明變得無效。
早期C語言編譯器不會對代碼進行優化,因此register變量是一個很好的補充。
C語言的struct定義了一組變量的集合,C編譯器并不認為這是一種新的類型
C++中的struct是一個新類型的定義聲明
理論上bool只占用一個字節,
如果多個bool變量定義在一起,可能會各占一個bit,這取決于編譯器的實現
1)C語言返回變量的值 C++語言是返回變量本身
C語言中的三目運算符返回的是變量值,不能作為左值使用
C++中的三目運算符可直接返回變量本身,因此可以出現在程序的任何地方
2)注意:三目運算符可能返回的值中如果有一個是常量值,則不能作為左值使用
(a < b ? 1 : b )= 30;
3)C語言如何支持類似C++的特性呢?
當左值的條件:要有內存空間;C++編譯器幫助程序員取了一個地址而已
// C語言中 const修飾的變量是一個 常變量,本質還是變量,有自己的地址空間
// C++中 const 變量聲明的是一個真正的常量,不是變量,所以編譯器不會為該常量分配空間
// const 修飾的常量會被放到 符號表 中
const int a = 10;
// 這里對 const 常量取地址,這一步操作會讓編譯器為該變量分配空間,分配的空間并不會被 a 使用
int p = (int )&a;
// 通過指針改變指向的空間的值,這個空間是編譯器為常量分配的空間,但是常量的值并不在這個空間內
// 所以即使通過指針修改了這個空間的值,也不會影響到 a 本身
*p = 5;
// a 的值不變,因為它的值在符號表中,不在程序運行的空間內
printf (“%d, %p\n”, a, *p);
C++中的const常量類似于宏定義
const int c = 5; ≈ #define c 5
C++中的const常量與宏定義不同
const常量是由編譯器處理的,提供類型檢查和作用域檢查
宏定義由預處理器處理,單純的文本替換
在程序運行過程中const變量只有一個拷貝,而#define 所定義的宏變量卻有多個拷貝,所以宏定義在程序運行過程中所消耗的內存要比const變量的大得多;
引用:
void swap1 (int &a, int &b)
{int tmp = a;a = b;b = tmp;
}void swap2 (int *a, int *b)
{int tmp = *a;*a = *b;*b = tmp;
}int main()
{int a = 10;int b = 20;swap2 (&a, &b);swap1 (a, b);printf ("%d, %d\n", a, b);return 0;
}
引用在C++中的內部實現是一個常指針
Type& name ?è Type* const name
C++編譯器在編譯過程中使用常指針作為引用的內部實現,因此引用所占用的空間大小與指針相同。
從使用的角度,引用會讓人誤會其只是一個別名,沒有自己的存儲空間。這是C++為了實用性而做出的細節隱藏
當我們使用引用語法的時,我們不去關心編譯器引用是怎么做的
當我們分析奇怪的語法現象的時,我們才去考慮c++編譯器是怎么做的
若返回棧變量
// 用非引用類型接收函數返回的引用,就等于將函數返回的引用的數據值,復制給了該接收對象
// 效果和返回非引用數據是一樣的
若返回棧變量
不能成為其它引用的初始值
不能作為左值使用
若返回靜態變量或全局變量
可以成為其他引用的初始值
即可作為右值使用,也可作為左值使用
const & int e 相當于 const int * const e
普通引用 相當于 int *const e1
當使用常量(字面量)對const引用進行初始化時,C++編譯器會為常量值分配空間,并將引用名作為這段空間的別名
使用字面量對const引用初始化后,將生成一個只讀變量
普通引用在定義必須要初始化,引用是一塊空間的別名,如果空間不存在,引用 就沒有意義
內聯函數
// 內聯函數聲明時inline關鍵字必須和函數定義結合在一起,否則編譯器會直接忽略內聯請求。
inline int func(int a, int b)
{return a < b ? a : b;
}
/*C++編譯器可以將一個函數進行內聯編譯
被C++編譯器內聯編譯的函數叫做內聯函數
內聯函數在最終生成的代碼中是沒有定義的
C++編譯器直接將函數體插入在函數調用的地方
內聯函數沒有普通函數調用時的額外開銷(壓棧,跳轉,返回)
*/
//**C++編譯器不一定準許函數的內聯請求!
/*內聯函數是一種特殊的函數,具有普通函數的特征(參數檢查,返回類型等)
內聯函數是對編譯器的一種請求,因此編譯器可能拒絕這種請求
內聯函數由 編譯器處理,直接將編譯后的函數體插入調用的地方
宏代碼片段 由預處理器處理, 進行簡單的文本替換,沒有任何編譯過程
*/
/*
現代C++編譯器能夠進行編譯優化,因此一些函數即使沒有inline聲明,也可能被編譯器內聯編譯
另外,一些現代C++編譯器提供了擴展語法,能夠對函數進行強制內聯
如:g++中的__attribute__((always_inline))屬性
*/
/*
C++中內聯編譯的限制:
不能存在任何形式的循環語句
不能存在過多的條件判斷語句
函數體不能過于龐大
不能對函數進行取址操作
函數內聯聲明必須在調用語句之前
*/
/*
編譯器對于內聯函數的限制并不是絕對的,內聯函數相對于普通函數的優勢只是省去了函數調用時壓棧,跳轉和返回的開銷。因此,當函數體的執行開銷遠大于壓棧,跳轉和返回所用的開銷時,那么內聯將無意義
*/
只有參數列表后面部分的參數才可以提供默認參數值
一旦在一個函數調用中開始使用默認參數值,那么這個參數后的所有參數都必須使用默
將所有同名函數作為候選者
嘗試尋找可行的候選函數:
1 )精確匹配實參
2) 通過默認參數能夠匹配實參
3) 通過默認類型轉換匹配實參
匹配失敗:
1) 最終尋找到的可行候選函數不唯一,則出現二義性,編譯失敗。
2) 無法匹配所有候選者,函數未定義,編譯失敗。
當使用重載函數名對函數指針進行賦值時:
根據重載規則挑選與函數指針參數列表一致的候選者
嚴格匹配候選者的函數類型與函數指針的函數類型
構造
// 一個函數返回一個對象的時候會創建一個匿名對象,拿返回的那個對象
// 對匿名對象進行初始化,會調用拷貝構造
// 如果沒有去接收函數的返回值的話,匿名對象會立馬被銷毀
// test6_1();
// 如果用一個對象去接收函數的返回值,先用函數返回的對象去初始化
// 匿名對象,調用一次拷貝構造,然后拿新的對象的名字去命名這個匿名對象
// 匿名對象從無名轉成有名
// Test6_1 t1 = test6_1();
當對象作為函數參數傳遞的時候會調用拷貝構造
析構的順序和構造的順序相反,先構造的后析構
// 對象初始化列表,在構造函數后面加:,后面加上要初始化的對象
// 對象初始化列表要比當前類的構造函數先執行
// 對象的初始化先后順序和 在對象初始化列表 的順序無關,和在類中的聲明先后順序有關
Test9_2():m_a(10), m_c(30), m_b(20), m_ca(100)
{printf ("9_2 222222222222構造函數....\n");
}
// 構造函數中調用構造函數 不會達到預期的效果的
Test10_1(int a, int b)
{m_a = a;m_b = b;Test10_1(a, b, 30); // 匿名對象、臨時對象
}
// 靜態成員函數只能使用靜態成員變量
// 靜態成員變量,屬于類,不屬于某個對象
// 是所有對象共享的,靜態是在數據區分配,只有一個備份
// 靜態變量不能由某個對象進行初始化
// 靜態變量必須在類的外部重新定義并且初始化// 重新定義類的靜態變量并且初始化
int Test11::sm_a = 100; // 類的靜態變量的使用
// 1、通過某一個對象進行引用
t2.sm_a = 30;// 2、通過類名來引用
Test11::sm_a = 60;
// malloc 和 free:它們本身不是C語言的語法的一部分,是庫函數提供的 函數
// new 和 delete: 它們本身是C++語言的一部分,是 運算符 不是 函數
// 創建普通類型變量
int main12_1()
{int *p1 = (int *)malloc(sizeof(int));free(p1);// new + 數據類型int *p2 = new int;*p2 = 10;printf ("*p2 = %d\n", *p2);// 釋放 new 出來的空間delete p2;// new 可以在申請空間進行初始化int *p3 = new int(90);printf ("*p3 = %d\n", *p3);delete p3;return 0;
}// 申請數組
int main12_2()
{int *p = (int *)malloc(sizeof(int) * 10);free(p);// 用 new 申請數組 new + 數據類型[size]int *p1 = new int[10];// deleta釋放數組 必需要加 [] delete [] p1;return 0;
}class Test12
{
public:Test12(int a, int b){m_a = a;m_b = b;printf ("構造函數\n");}~Test12(){printf ("析構函數\n");}
private:int m_a;int m_b;
};// 動態創建對象
int main12_3()
{Test12 *p = (Test12 *)malloc(sizeof(Test12));free(p);// new 在創建對象的時候會自動構造函數進行對象的構建Test12 *p1 = new Test12(10,20);// delete在進行釋放對象的時候會自動調用析構函數進行對象資源的回收delete p1;return 0;
}