天天看點

C++:關于類以及h/cpp檔案的一些實用知識

一、VC編譯原理

解決重定義問題,首先要明白VC的編譯原理:

  • VC隻編譯cpp檔案,這些cpp檔案構成将來的exe;
  • 當VC編譯A.cpp檔案的時候,如果遇到了語句

    #include "B.h"

    ,實質上是将"B.h"檔案中的代碼全部“複制”到A.cpp中,然後再繼續編譯A.cpp。
  • 當在B.h檔案中定義全局變量a等,即使使用了避免檔案重複包含的方法(如下節提到的兩種方法),是不能避免“A.cpp中

    #include"B.h"

    ,C.pp中

    #include"B.h"

    ,然後提示變量a重複定義”的問題,隻能保證“A.cpp中多次出現

    #include"B.h”

    而不會提示變量a重複定義”。
  • 全局變量、函數、結構體一定要在.cpp檔案中定義,在.h檔案中聲明,一定不要在h檔案中定義,否則會出現重複定義的問題。

二、頭檔案避免類重複包含的方法

1. 微軟預編譯控制

#if _MSC_VER > 1000
#pragma once
#endif
... ... // .h檔案正文
           

在.h檔案最開始的地方加上這段代碼,即可避免頭檔案重複include。但是需要注意的是#pragma once 這條語句隻有VC編譯器大于1000才可以支援(反正VC++6.0及以上是可以的),_MSC_VER就是Microsoft的C編譯器的版本。

#pragma once則由編譯器提供保證:同一個檔案不會被包含多次。注意這裡所說的“同一個檔案”是指實體上的一個檔案,而不是指内容相同的兩個檔案。帶來的好處是,你不必再費勁想個宏名了,當然也就不會出現宏名碰撞引發的奇怪問題。對應的缺點就是如果某個頭檔案有多份拷貝,本方法不能保證他們不被重複包含。當然,相比宏名碰撞引發的“找不到聲明”的問題,重複包含更容易被發現并修正。

2、宏定義方式

#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__ 
... ... // .h檔案正文
#endif
           

#ifndef的方式依賴于宏名字不能沖突,這不光可以保證同一個檔案不會被包含多次,也能保證内容完全相同的兩個檔案不會被不小心同時包含。當然,缺點就是如果不同頭檔案的宏名不小心“撞車”,可能就會導緻頭檔案明明存在,編譯器卻硬說找不到聲明的狀況 。不過如果是編譯器自動生成的類一般都會自動根據編譯次數(唯一)來自動生成唯一的宏名。

三、出現重複定義的變量的解決方法

在項目同時使用第三方庫GuiLib和CJ609Lib,編譯提示結構體

CMenuItemInfo

重定義,後來研究了一下,發現Guilib和CJ609Lib中都全局定義了同一個結構體名

CMenuItemInfo

,而解決方法有兩種:使用宏定義規避和使用命名空間

1.宏定義規避

這種方法很簡單,就是類似C++利用宏避免頭檔案重複的形式,直接給執行個體,在兩個

關于CMenuItemInfo

結構體定義的位置加上一下的宏定義就行:

#ifndef __CMenuItemInfo_LOCAL_DEFINED//避免結構體CMenuItemInfo重複定義
#define __CMenuItemInfo_LOCAL_DEFINED
struct CMenuItemInfo : public MENUITEMINFO_LOCAL {
    CMenuItemInfo()
    {
        memset(this, 0, sizeof(MENUITEMINFO_LOCAL));
        cbSize = sizeof(MENUITEMINFO_LOCAL);
    }
};
#endif//__CMenuItemInfo_LOCAL_DEFINED
           

這樣做的好處是,用

CMenuItemInfo

定義具體對象時,直接使用

CMenuItemInfo

,程式會自動使用編譯過程遇到的第一個

CMenuItemInfo

的定義。

但這種方法有一個前提:兩個結構體的定義必須一緻,也就是說上述關于

CMenuItemInfo

的兩個定義必須一緻,很巧的是Guilib和CJ609Lib關于

CMenuItemInfo

的定義确實一緻。

2.使用命名空間

這種方法更加廣泛,尤其适合于重名且定義的内容不一樣的情況。這種方法等有時間了,我再在下邊補充,本次使用了宏定義就解決了上述問題。

PS:最後補充一點,有的時候提示@.obj已經在@.obj中定義的時候,在項目屬性配置中啟用預編譯頭可能會解決這個問題,但不知道是什麼原理。

四、關于全局變量

關于程式中使用的全局變量,最好在cpp檔案中定義,然後在.h中聲明,這樣可以極大的機率避免重複定義

A.cpp檔案定義全局變量

global_time

#include "A.h "
int global_time;
void main
{
...
}
           

在A.h檔案中聲明全局變量

externint global_time;
           

當B.cpp中要使用

global_time

,直接在B.h檔案中聲明

extern int global_time;

,就行了。此時即使已經在B.h或B.cpp檔案中已經引入A.h檔案,也不會沖突。

五、 C/C++中預設參數在函數聲明(h檔案)還是定義(cpp檔案)

編譯器一般是禁止在聲明和定義同時定義預設參數值。若聲明時沒有定義預設參數值,那麼在定義成員函數時可以定義預設參數值。但這種情況下,使用函數的使用者是看不見的,很可能會帶來災難性的後果。是以為了程式的可讀性,我們最好在h檔案中聲明成員函數的時候定義預設值,而不要在定義函數的時候定義預設值。

六、C++類構造函數初始化清單

構造函數初始化清單以一個冒号開始,接着是以逗号分隔的資料成員清單,每個資料成員後面跟一個放在括号中的初始化式。例如:

class CStudent{ //為了好看,就将類的聲明與定義寫在一起,而不是分别放在h和cpp檔案中去了
public:
	int a;
	float b;
	//構造函數初始化清單
	CStudent(int x, int y):a(x),b(y)
	{}
}
           

當我們需要執行個體化對象的時候,直接這樣即可

CStuden student(10,20);
           

上面的構造函數(使用初始化清單的構造函數)顯式的初始化類的成員。和下面這樣沒使用初始化清單進行顯式的初始化,而是内部指派是一樣的。

class CStudent{ //為了好看,就将類的聲明與定義寫在一起,而不是分别放在h和cpp檔案中去了
public:
	int a;
	float b;
	//構造函數内部指派
	CStuden(int x, int y)
	{
		a=x;
		b=y;
	}
}
           

初始化和指派對内置類型的成員沒有什麼大的差別,像上面的任一個構造函數都可以。但有的時候必須用帶有初始化清單的構造函數:

  • 成員類型是沒有預設構造函數的類。若沒有提供顯示初始化式,則編譯器隐式使用成員類型的預設構造函數,若類沒有預設構造函數,則編譯器嘗試使用預設構造函數将會失敗。
  • const成員或引用類型的成員。因為const對象或引用類型隻能初始化,不能對他們指派。

一定要注意初始化清單的成員初始化順序:C++初始化類成員時,是按照聲明的順序初始化的,而不是按照出現在初始化清單中的順序。

class CStudent{
  		int a;
		int b;
		CStudent::CStudent(int x,int y):b(x),a(b)
		{}
	}
           

執行個體化的時候

CStudent stude(10,20);
           

你可能以為上面的代碼将會首先做b=x,然後做a=b,最後它們有相同的值都是10。但是編譯器先初始化a,然後是b,因為它們是按這樣的順序聲明的。結果是a将有一個不可預測的值。有兩種方法避免它,一個是總是按照你希望它們被初始化的順序聲明成員,第二個是,如果你決定使用初始化清單,總是按照它們聲明的順序羅列這些成員。這将有助于消除混淆。

繼續閱讀