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

來源:互聯網
上載者:User

一、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++編程規範

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.