一、bool 類型
邏輯型也稱布爾型,其取值為true(邏輯真)和false(邏輯假),儲存位元組數在不同編譯系統中可能有所不同,VC++中為1個位元組。
聲明方式:bool result; result=true;
可以當作整數用(true一般為1,false為0)
把其它類型的值轉換為布爾值時,非零值轉換為true,零值轉換為false,注意會發生截斷。
二、const 限定符
(1)、用const給字面常量起個名字(標識符),這個標識符就稱為標識符常量;因為標識符常量的聲明和使用形式很像變數,所以也稱常變數。
定義的一般形式:
const 資料類型 常量名=常量值; 資料類型 const 常量名=常量值;
例如: const float PI=3.14159f;
注意事項:
常變數在定義時必須初始化;
常變數初始化之後,不允許再被賦值;
正如我在這裡所說,其實加了關鍵字const只是提示編譯器這個變數是常量,如果我們在接下來的操作中試圖更改它,編譯器會報錯,而並不是真正的常量,事實上某些情形下通過指標也是可以更改的(編譯器警示告),什麼情況下完全不能修改呢,當A是加const限定且初始化的全域變數,此時A位於.rodata段(linux
下)。此外const 用於修飾指標時可以參考這裡。
(2)、const 與 #define
const定義的常量與#define定義的符號常量的區別:
const定義的常量有類型,而#define定義的沒有類型,編譯可以對前者進行型別安全檢查,而後者僅僅只是做簡單替換
const定義的常量在編譯時間分配記憶體,而#define定義的常量是在先行編譯時進行替換,不分配記憶體。
範圍不同,const定義的常變數的範圍為該變數的範圍範圍。而#define定義的常量範圍為它的定義點到程式結束,當然也可以在某個地方用#undef取消
#define定義的常量,容易產生副作用:
//Effective C++ 3rd的一個例子。
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
int a = 5;
int b = 0;
CALL_WITH_MAX(++a, b); //a被累加二次
CALL_WITH_MAX(++a, b+10); //a被累加一次
在這裡,調用f之前,a的遞增次數竟然取決於“它被拿來和誰比較”
此外,定義常量還可以用enum,在c++ 中盡量用const、enum替換#define定義常量,用inline 替換帶參數的宏定義;但 #define 在底層編程中是必不可少的,下面舉個例子:
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
|
#include<iostream> using namespace std;#define STR(a) #a #define CAT(a,b) a##b int main(void) { int xy = 100; cout << STR(ABCD) << endl; // #ABCD => "ABCD" cout << CAT(x, y) << endl; // x##y => xy
return 0; } |
如果是完全的c++ 菜鳥,這裡還得稍微解釋一下細節,iostream 是c++標準庫的一個io流標頭檔,跟C語言不太一樣的是一般沒有.h 尾碼,using namespace 表示命名空間,簡單理解就是統一的函數首碼,類比pthread庫如pthread_mutex_init, pthread_mutex_lock 用c++ 方式來表示可能是 pthread::mutex::lock。
cout是輸出資料流對象,<<操作符在C語言中是左移位元運算操作符,在這裡被重載成輸出操作符,之所以能並列輸出是因為如cout<<xxx 返回的是cout 的引用,以後還會再提。參數宏定義的意義就很清楚了,查看下輸出即可。
我們知道printf函數帶有可變參數,函數式宏定義也可以帶可變參數,同樣是在參數列表中用...表示可變參數。例如:
C++ Code
1 2 3 4 5 6
|
|
#define showlist(...) printf(#__VA_ARGS__) #define report(test, ...) ((test)?printf(#test):\ printf(__VA_ARGS__))showlist(The first, second, and third items.); report(x > y, "x is %d but y is %d", x, y); |
預先處理之後變成: C++ Code
1 2
|
|
printf("The first, second, and third items."); ((x > y) ? printf("x>y") : printf("x is %d but y is %d", x, y)); |
在宏定義中,可變參數的部分用__VA_ARGS__表示,實參中對應...的幾個參數可以看成一個參數替換到宏定義中__VA_ARGS__所在的地方。
(三)、結構體對齊
什麼是記憶體對齊
編譯器為每個“資料單元”按排在某個合適的位置上。
C、C++語言非常靈活,它允許你幹涉“記憶體對齊”
為什麼要對齊
效能原因:在對齊的地址上訪問資料快。
如何對齊
第一個資料成員放在offset為0的位置
其它成員對齊至min(sizeof(member),#pragma pack(n)所指定的值)的整數倍。
整個結構體也要對齊,結構體總大小對齊至各個成員中最大對齊數的整數倍。
舉個例子,
struct test
{
char a;
double b;
char c;
};
根據規則1,a 在位置0;根據規則2,因為VC預設pack為8,可以通過項目-》屬性-》c/c++ -》代碼產生-》結構體成員對齊選項修改,也可以使用#pragma pack(n) 來修改,#pragma pack() 取消修改,那麼b 佔據8~15;根據規則2,c在16;現在總共17個位元組,根據規則3,結構體總大小需對齊到8的整數倍,即總共是24個位元組。
如果將pack 修改為4,則總大小為16。在VC上pack 共有1,2,4,8,16 等5種選擇,而linux g++ 則只有1,2,4 可選,預設是4。
(四)、域運算子
C++中增加的範圍標識符 ::
用於對與局部變數同名的全域變數進行訪問
用於表示類的成員,以後講到類的時候再詳談
(五)、new、delete 運算子
(1)、new operator
new運算子可以用於建立堆空間,成功返回首地址,失敗拋出異常
文法:
指標變數=new 資料類型(值);
指標變數=new 資料類型[長度n];
例如:
int *p; p=new int(3);
char *pStr=new char[50];
(2)、delete operator
delete運算子 可以用於釋放堆空間
文法:
delete 指標變數;
delete [] 指標變數;
例如:
delete p;
delete [] pStr; // 類似 delete pStr[0] , delete pStr[1], ....
(3)、new 和 delete 執行的步驟
new operator
記憶體配置(operator new),類似malloc
調用建構函式,講到類再說
delete operator
調用解構函式,講到類再說
釋放記憶體(operator delete),類似free
實際上new 有三種用法,包括operator new、new operator、placement new,new operator 包含operator new,而placement new 則沒有記憶體配置而是直接調用建構函式,具體的差異以後再談。
(六)、函數重載、name managling 與extern "C"
(1)、函數重載
相同的範圍,如果兩個函數名稱相同,而參數不同,我們把它們稱為重載overload,函數重載又稱為函數的多態性(靜態)
函數重載不同形式:
形參數量不同
形參類型不同
形參的順序不同
形參數量和形參類型都不同
調用重載函數時,編譯器通過檢查實際參數的個數、類型和順序來確定相應的被調用函數。
函數的重載跟函數的覆蓋、函數的隱藏是不同的,這一點以後再講。
合法的重載例子:
int abs(int i); long abs(long l);double abs(double d);
非法的重載例子:
int abs(int i);
long abs(int i); void abs(int i);
//如果傳回型別不同而函數名相同、形參也相同,則是不合法的,編譯器會報"語法錯誤"。
(2)、name managling 與extern "C"
name managling這裡把它翻譯為名字改編,C++為了支援函數重載,需要將函數名根據參數的不同進行name managling以便區分。
extern “C” 可以實現C與C++混合編程,即對C語言寫的函數不進行改名,一般在C的標頭檔中使用,如果標頭檔被C程式碼封裝含並用C編譯器編譯,則__cplusplus 沒有定義,extern “C" 被略過,如果標頭檔被C++程式碼封裝含並被C++編譯器編譯,存在__cplusplus 定義故extern "c" 提示編譯器不要對 {} 內函數進行改名。
#ifdef __cpluscplus
extern “C”
{
#endif
...
#ifdef __cpluscplus
}
#endif
(七)、帶預設形參值的函數
函式宣告或者定義的時候,可以給形參賦一些預設值,調用函數時,若沒有給出實參,則按指定的預設值進行工作。
* 函數沒有聲明時,在函數定義中指定形參的預設值
* 函數既有定義又有聲明時,聲明時指定後,定義後就不能再指定預設值
* 預設值的定義必須遵守從右至左的順序,如果某個形參沒有預設值,則它左邊的參數就不能有預設值。
void func1(int a, double b=4.5, int c=3); //合法
void func1(int a=1, double b, int c=3); //不合法
* 函數調用時,實參與形參按從左至右的順序進行匹配
* 重載的函數中如果形參帶有預設值時,可能產生二義性
C++ Code
1 2 3 4 5 6 7 8
|
|
int add(int x = 5, int y = 6); int add(int x = 5, int y = 6, int z = 7); int main(void) { int sum; sum = add(10, 20); return 0; } |
sum=add(10,20)語句產生二義性ambiguity,可以認為該語句是調用第一個函數,也可以是第二個,因此編譯器不能確定調用的是哪一個函數。
參考:
C++ primer 第四版
Effective C++ 3rd
C++編程規範