從零開始學C++之從C到C++(一):const與#define、結構體對齊、函式過載name mangling、new/delete 等

lostinai發表於2014-04-19

http://blog.csdn.net/jnu_simba/article/details/9150415

一、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)所指定的值)的整數倍。

整個結構體也要對齊,結構體總大小對齊至各個min中最大值的整數倍。

舉個例子,

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 mangling 與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

實際上,編譯器對資料成員也會進行name mangling處理,目地是區分派生類和基類中可能的同名成員。

不同C++編譯器的name mangling 方案是不同的,這是造成不同編譯器之間存在二進位制連線相容性的主要原因之一。


(七)、帶預設形參值的函式

函式宣告或者定義的時候,可以給形參賦一些預設值,呼叫函式時,若沒有給出實參,則按指定的預設值進行工作。

* 函式沒有宣告時,在函式定義中指定形參的預設值
* 函式既有定義又有宣告時,宣告時指定後,定義後就不能再指定預設值
* 預設值的定義必須遵守從右到左的順序,如果某個形參沒有預設值,則它左邊的引數就不能有預設值。
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 = 5int y = 6);
int add(int x = 5int y = 6int z = 7);
int main(void)
{
    int sum;
    sum = add(1020);
    return 0;
}


sum=add(10,20)語句產生二義性ambiguity,可以認為該語句是呼叫第一個函式,也可以是第二個,因此編譯器不能確定呼叫的是哪一個函式。


參考:

C++ primer 第四版
Effective C++ 3rd
C++程式設計規範

相關文章