0.1 C++與C的對比
- C++有三種編程方式:過程性,面向對象,泛型編程。
- C++函數符號由 函數名+參數類型 組成,C只有函數名。所以,C沒有函數重載的概念。
- C++ 在 C的基礎上增加了封裝、繼承、多態的概念
- C++增加了泛型編程
- C++增加了異常處理,C沒有異常處理
- C++增加了bool型
- C++允許無名的函數形參(如果這個形參沒有被用到的話)
- C允許main函數調用自己
- C++支持默認參數,C不支持
- C語言中,局部變量必須在函數開頭定義,不允許類似for(int a = 0; ;;)這種定義方法。
- C++增加了引用
- C允許變長數組,C++不允許
- C中函數原型可選,C++中在調用之前必須聲明函數原型
- C++增加了STL標准模板庫來支持數據結構和算法
一、常用的關鍵字極其用法
1.1 const
主要用法
C++ 的const關鍵字的作用有很多,幾乎無處不在,面試中往往會問“說一說const有哪些用法”。下面是一些常見的const用法的總結:
除此以外,const的用法還有:
- const引用可以引用右值,如const int& a = 1;
注:
- const 成員方法本質上是使得this指針是指向const對象的指針,所以在const方法內,
- const 成員函數可以被非const和const對象調用,而const對象只能調用const 成員函數。原因得從C++底層找,C++方法調用時,會傳一個隱形的this參數(本質上是對象的地址,形參名為this)進去,所有成員方法的第一個參數是this隱形指針。const成員函數的this指針是指向const對象的const指針,當非const對象調用const方法時,實參this指針的類型是非const對象的const指針,賦給const對象的const指針沒有問題;但是如果const對象調用非const方法,此時實參this指針是指向const對象的const指針,無法賦給非const對象的const指針,所以無法調用。注意this實參是放在ecx寄存器中,而不是壓入棧中,這是this的特殊之處。在類的非成員函數中如果要用到類的成員變量,就可以通過訪問ecx寄存器來得到指向對象的this指針,然后再通過this指針加上成員變量的偏移量來找到相應的成員變量。文章來源http://blog.csdn.net/starlee/article/details/2062586/
- const 指針、指向const的指針和指向const的const指針,涉及到const的特性“const左效、最左右效”
- const 全局變量有內部鏈接性,即不同的文件可以定義不同的同名const全局變量,使用extern定義可以消除內部鏈接性,稱為類似全局變量,如extern const int a = 10.另一個文件使用extern const int a; 來引用。而且編譯器會在編譯時,將const變量替換為它的值,類似define那樣。
const 常量和define 的區別
- const常量有數據類型,而宏定義沒有數據類型。編譯器可以對前者進行類型安全檢查,而對后者只進行字符替換,沒有類型安全檢查,並且在字符替換中可能會產生意想不到的錯誤(邊際效應)。
- 有些集成化的調試工具可以對const常量進行調試,但是不能對宏定義進行調試。
- 在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
- 內存空間的分配上。define進行宏定義的時候,不會分配內存空間,編譯時會在main函數里進行替換,只是單純的替換,不會進行任何檢查,比如類型,語句結構等,即宏定義常量只是純粹的置放關系,如#define null 0;編譯器在遇到null時總是用0代替null它沒有數據類型.而const定義的常量具有數據類型,定義數據類型的常量便於編譯器進行數據檢查,使程序可能出現錯誤進行排查,所以const與define之間的區別在於const定義常量排除了程序之間的不安全性.
- const常量存在於程序的數據段,#define常量存在於程序的代碼段
- const常量存在“常量折疊”,在編譯器進行語法分析的時候,將常量表達式計算求值,並用求得的值來替換表達式,放入常量表,可以算作一種編譯優化。因為編譯器在優化的過程中,會把碰見的const全部以內容替換掉,類似宏。
1.2 sizeof
- sizeof關鍵字不會計算表達式的值,而只會根據類型推斷大小。
- sizeof() 的括號可以省略, 如 sizeof a ;
- 類A的大小是 所有非靜態成員變量大小之和+虛函數指針大小
1.3 static
static的用法有:
(1)聲明靜態全局變量,如static int a; 靜態全局變量的特點:
- 該變量在全局數據區分配內存;
- 未經初始化的靜態全局變量會被程序自動初始化為0(自動變量的值是隨機的,除非它被顯式初始化);
- 靜態全局變量在聲明它的整個文件都是可見的,而在文件之外是不可見的;
(2)聲明靜態局部變量,即在函數內部聲明的,靜態局部變量的特點:
- 該變量在全局數據區分配內存;
- 靜態局部變量在程序執行到該對象的聲明處時被首次初始化,即以后的函數調用不再進行初始化;
- 靜態局部變量一般在聲明處初始化,如果沒有顯式初始化,會被程序自動初始化為0;
- 它始終駐留在全局數據區,直到程序運行結束。但其作用域為局部作用域,當定義它的函數或語句塊結束時,其作用域隨之結束;
(3)聲明靜態函數,限定函數的局部訪問性,僅在文件內部可見
(4)類的靜態數據成員,與全局變量相比,靜態數據成員的好處有:
- 靜態數據成員沒有進入程序的全局名字空間,因此不存在與程序中其它全局名字沖突的可能性;
- 可以實現信息隱藏。靜態數據成員可以是private成員,而全局變量不能;
1.4 typedef
typedef 用來定義新的類型,類似的還有#define 和 using (C++11) (應該盡可能用using ,比如 using AAA = int64_t; )
與宏定義的對比
- #define 在預處理階段進行簡單替換,不做類型檢查; typedef在編譯階段處理,在作用域內給類型一個別名。
- typedef 是一個語句,結尾有分號;#define是一個宏指令,結尾沒有分號
- typedef int* pInt; 和 #define pInt int* 不等價,前者定義 pInt a, b;會定義兩個指針,后者是一個指針,一個int。
1.5 inline
inline用來向編譯器請求聲明為內聯函數,編譯器有權拒絕。
與宏函數的對比
- 內聯函數在運行時可調試,而宏定義不可以;
- 編譯器會對內聯函數的參數類型做安全檢查或自動類型轉換(同普通函數),而宏定義則不會;
- 內聯函數可以訪問類的成員變量,宏定義則不能;
- 在類中聲明同時定義的成員函數,自動轉化為內聯函數
- 宏只是預定義的函數,在編譯階段不進行類型安全性檢查,在編譯的時候將對應函數用宏命令替換。對程序性能無影響
不能聲明為inline的函數
- 包含了遞歸、循環等結構的函數一般不會被內聯。
- 虛擬函數一般不會內聯,但是如果編譯器能在編譯時確定具體的調用函數,那么仍然會就地展開該函數。
- 如果通過函數指針調用內聯函數,那么該函數將不會內聯而是通過call進行調用。
- 構造和析構函數一般會生成大量代碼,因此一般也不適合內聯。
- 如果內聯函數調用了其他函數也不會被內聯。
1.6 static const \ const \ static
1. static const
static const 數據成員可以在類內初始化 也可以在類外,不能在構造函數中初始化,也不能在構造函數的初始化列表中初始化
2. static
static數據成員只能在類外,即類的實現文件中初始化,也不能在構造函數中初始化,不能在構造函數的初始化列表中初始化;
3. const
const數據成員只能在構造函數的初始化列表中初始化;
1.7 explicit
explicit禁止了隱式轉換類型,用來修飾構造函數。原則上應該在所有的構造函數前加explicit關鍵字,當你有心利用隱式轉換的時候再去解除explicit,這樣可以大大減少錯誤的發生。如果一個構造函數 Foo(int) ;則下面的語句是合法的:
Foo f;
f = 12; // 發生了隱式轉換,先調用Foo(int)用12構建了一個臨時對象,然后調用賦值運算符復制到 f 中
如果給構造函數加了explicit,即 explicit Foo(int);就只能進行顯示轉換,無法進行隱式轉換了:
f = 12; // 非法,隱式轉換
f = Foo(12); // 合法,顯示轉換
f = (Foo)12;//合法,顯示轉換,C風格
1.8 extern
extern可以置於變量或者函數前,以標示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。此外extern也可用來進行鏈接指定
typedef 數據類型 數據類型的別名
typedef的使用非常簡單,在typedef關鍵字之后,分別跟上數據類型和相應的綽號就定義了這個數據類型的別名,在接下來的程序中就可以使用這個別名來指代這個數據類型了。
在武林中,是給大名鼎鼎的人物取外號,為的是唬人;而在C++世界中,是為那些比較復雜難以書寫的數據類型取外號,為的是偷懶。例如,覺得unsigned char無符號字符類型的書寫比較煩瑣時,可以使用typedef為它定義一個簡單易記的別名,然后使用這個別名作為數據類型來定義無符號字符類型的變量:
// 為無符號字符類型unsigned char定義一個別名uchar
typedef unsigned char uchar;
有了這個簡單的別名,就可以用它來指代無符號字符類型,用作數據類型定義變量:
// 定義一個uchar類型的變量,實際上就是unsigned char類型的變量
uchar a;
使用別名之后的程序代碼,是不是書寫起來更加簡單,同時也更加簡潔易懂呢?所以,在以后遇到類似情況的時候,使用typedef為復雜類型取一個簡單的別名,不僅自己寫起來很方便,別人讀起來也很輕松。這樣兩全其美的事情,何樂而不為呢?
這里大家可能有個疑問,利用前面學習的宏,將復雜類型定義成一個宏,不也同樣可以達到化繁為簡的目的嗎?它們兩個有什么區別呢?typedef是為復雜數據類型定義一個別名,而不只是像宏一樣簡單的替換。這一點在同時定義指針類型的多個變量時特別有用。例如,想定義兩個int*指針類型的變量,自然地,使用宏我們可能會這樣寫:
// 定義指針類型的宏
#define PINT int*
// 使用宏定義兩個變量
PINT pInt1,pInt2;
然而,這段看起來再正確不過的代碼,實際的效果卻與我們的預想大相徑庭。經過宏替換后,上面定義指針變量的代碼變為:
// 宏替換后的實際代碼
int* pInt1, pInt2;
這不是在定義兩個指針變量,而是在定義一個int指針類型變量pInt1和另一個int類型變量pInt2。想使用宏在同一行內方便地定義多個指針變量是行不通的,解決問題的辦法就是用typedef為指針類型定義一個別名,然后使用這個別名作為數據類型,就可以在一行內定義多個指針類型的變量了:
// 為指針類型int*定義一個別名PINT
typedef int* PINT;
// 同時定義多個指針類型變量
PINT pInt1, pInt2;
這時,PINT已經成為一種新的數據類型,自然它可以在同一行內同時定義多個PINT類型的變量,而這種新類型本質上還是int指針類型,也就相當於同時定義了多個int指針類型的變量。
typedef的另外一個重要用途是為復雜的類型定義簡單的別名。請看下面這行代碼:
int* (*pFunc)(int, char*);
實際上,這行代碼所定義的是一個函數指針pFunc,它所能夠指向的函數的返回值類型是int*,兩個參數分別是int類型和char*類型。如果只是定義一個這種類型的函數指針,那還可以勉強接受;如果要定義多個,那么多次書寫這么復雜的既難寫又難懂的語句,恐怕只有“撞牆”了。好在使用typedef可以為這種復雜的類型定義一個簡單的別名,使用這個別名就可以輕松定義多個這種類型的變量。
// 定義函數指針類型為PFUNC
typedef int* (*PFUNC)(int, char*);
// 使用PFUNC定義多個函數指針變量
PFUNC pFunc1, pFunc2;