日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

如何防止头文件被重复包含或引用?

發(fā)布時(shí)間:2023/12/20 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何防止头文件被重复包含或引用? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、#pragma once?( 比較常用)

只要在頭文件的最開(kāi)始加入這條指令就能夠保證頭文件被編譯一次,這條指令實(shí)際上在VC6中就已經(jīng)有了,但是考慮到兼容性并沒(méi)有太多的使用。

#pragmaonce是編譯相關(guān),就是說(shuō)這個(gè)編譯系統(tǒng)上能用,但在其他編譯系統(tǒng)不一定可以,也就是說(shuō)移植性差,不過(guò)現(xiàn)在基本上已經(jīng)是每個(gè)編譯器都有這個(gè)定義了。

#pragmaonce這種方式,是微軟編譯器獨(dú)有的,也是后來(lái)才有的,所以知道的人并不是很多,用的人也不是很多,因?yàn)樗恢С挚缙脚_(tái)。如果你想寫(xiě)跨平臺(tái)的代碼,最好使用上一種。這是一種由編譯器提供支持的方式,防止同一文件的二次編譯,這里的同一文件指的是物理文件。

他也是有弊端的:

假如你的某一個(gè)頭文件有多份拷貝,那么這些文件雖然在邏輯上都是一樣的,但是在物理上他們卻是不同的,所以當(dāng)你把這些文件包含的時(shí)候,就會(huì)發(fā)現(xiàn)真的都包含進(jìn)來(lái)了,然后就是編譯錯(cuò)誤了。還有,當(dāng)物理上的同一文件被嵌套包含的時(shí)候,使用第一種方法預(yù)處理會(huì)每一次打開(kāi)該文件做判斷的,但是第二種方法則不會(huì),所以在此#pragma once會(huì)更快些。下面舉例說(shuō)明

?? // Test1.h
??? #ifndefine ?TEST1_H
??? #defineTEST1_H
??? ...
??? #endif
????
??? // Test2.h
??? #pragma once????????
??? ...
????
??? // Test.cpp
??? #include "Test1.h"?????// line 1
??? #include "Test1.h"?????// line 2
??? #include "Test2.h"?????// line 3
??? #include "Test2.h"?????// line 4
這里的Test2.h是同一物理文件

預(yù)處理器在執(zhí)行這四句的時(shí)候,先打開(kāi)Test1.h然后發(fā)現(xiàn)里面的宏TEST1_H沒(méi)有被定義,所以會(huì)包含這個(gè)文件,第二句的時(shí)候,同樣還是會(huì)打開(kāi)Test2.h的發(fā)現(xiàn)宏已定義,就不包含該文件按了。第三句時(shí),發(fā)現(xiàn)之前沒(méi)有包含Test2,h則會(huì)把該文件包含進(jìn)來(lái),執(zhí)行第四句的時(shí)候,發(fā)現(xiàn)該文件已經(jīng)被包含了,所以不用打開(kāi)就直接跳過(guò)了

?

二、條件編譯

?

#include"a.h"

#include"b.h"

看上去沒(méi)什么問(wèn)題。如果a.h和b.h都包含了一個(gè)頭文件x.h。那么x.h在此也同樣被包含了兩次,只不過(guò)它的形式不是那么明顯而已。

多重包含在絕大多數(shù)情況下出現(xiàn)在大型程序中,它往往需要使用很多頭文件,因此要發(fā)現(xiàn)重復(fù)包含并不容易。要解決這個(gè)問(wèn)題,我們可以使用條件編譯。如果所有的頭文件都像下面這樣編寫(xiě):
#ifndef _HEADERNAME_H
#define _HEADERNAME_H

...//(頭文件內(nèi)容)

#endif

那么多重包含的危險(xiǎn)就被消除了。當(dāng)頭文件第一次被包含時(shí),它被正常處理,符號(hào)HEADERNAME_H被定義為1。如果頭文件被再次包含,通過(guò)條件編譯,它的內(nèi)容被忽略。符號(hào)HEADERNAME_H按照被包含頭文件的文件名進(jìn)行取名,以避免由于其他頭文件使用相同的符號(hào)而引起的沖突。

但是,你必須記住預(yù)處理器仍將整個(gè)頭文件讀入,即使這個(gè)頭文件所有內(nèi)容將被忽略。由于這種處理將托慢編譯速度,所以如果可能,應(yīng)該避免出現(xiàn)多重包含。

?

?

問(wèn)題:test-1.0使用#ifndef只是防止了頭文件被重復(fù)包含(其實(shí)本例中只有一個(gè)頭件,不會(huì)存在重復(fù)包含的問(wèn)題),但是無(wú)法防止變量被重復(fù)定義。如以下代碼:

?

//vs 2012 : test.c


#include <stdio.h>
#include "test.h"

extern i;
extern void test1();
extern void test2();

int main()
{
?? test1();
?? printf("ok/n");
?? test2();
?? printf("%d/n",i);
?? return 0;
}



//vs 2012 : test.h


#ifndef _TEST_H_
#define _TEST_H_

char add1[] = "www.shellbox.cn/n";
char add2[] = "www.scriptbox.cn/n";
int i = 10;
void test1();
void test2();

#endif




//vs 2012 : test1.c

--
#include <stdio.h>
#include "test.h"

extern char add1[];

void test1()
{
?? printf(add1);
}




//vs 2012 : test2.c

#include<stdio.h>
#include "test.h"

extern char add2[];
extern i;

void test2()
{
?? printf(add2);
?? for (; i > 0; i--)?
?? ??? printf("%d-", i);
}

?

?錯(cuò)誤分析:
由于工程中的每個(gè).c文件都是獨(dú)立的解釋的,即使頭文件有
#ifndef _TEST_H_ #define _TEST_H_ .... #enfif
在其他文件中只要包含了test.h就會(huì)獨(dú)立的解釋,然后每個(gè).c文件生成獨(dú)立的標(biāo)示符。在編譯器鏈接時(shí),就會(huì)將工程中所有的符號(hào)整合在一起,由于文件中有重名變量,于是就出現(xiàn)了重復(fù)定義的錯(cuò)誤。

解決方法:
在.c文件中定義變量,然后再建一個(gè)頭文件(.h文件),在所有的變量聲明前加上extern,注意這里不要對(duì)變量進(jìn)行的初始化。然后在其他需要使用全局變量的.c文件中包含.h文件。編譯器會(huì)為.c生成目標(biāo)文件,然后鏈接時(shí),如果該.c文件使用了全局變量,鏈接器就會(huì)鏈接到定義變量的.c文件?。

//vs 2012 : test.h

//-------------------------------

#ifndef _TEST_H_

#define _TEST_H_

?

extern int i;

extern char add1[];

extern char add2[];

?

void test1();

void test2();

?

#endif

?

?

//vs 2012 : test.c

//-------------------------------

#include <stdio.h>

#include "test.h"

?

?

int i = 10;

char add1[] = "www.shellbox.cn/n";

char add2[] = "www.scriptbox.cn/n";

extern void test1();

extern void test2();

?

int main()

{

?? test1();

??printf("ok/n");

?? test2();

??printf("%d/n",i);

?? return 0;

}

?

//vs 2012 : test1.c

//-------------------------------

#include <stdio.h>

#include "test.h"

?

extern char add1[];

?

void test1()

{

?? printf(add1);

}

?

?

//vs 2012 : test2.c

//-------------------------------

#include <stdio.h>

#include "test.h"

?

extern char add2[];

extern int i;

?

void test2()

{

?? printf(add2);

?? for (; i > 0;i--)

?????? printf("%d-",i);

}

問(wèn)題擴(kuò)展:?變量的聲明有兩種情況:

???(1) 一種是需要建立存儲(chǔ)空間的(定義、聲明)。例如:int a在聲明的時(shí)候就已經(jīng)建立了存儲(chǔ)空間。?
??? (2) 另一種是不需要建立存儲(chǔ)空間的(聲明)。例如:extern int a其中變量a是在別的文件中定義的。
??? 前者是"定義性聲明(defining declaration)"或者稱(chēng)為"定義(definition)",而后者是"引用性聲明(referncingdeclaration)"。從廣義的角度來(lái)講聲明中包含著定義,但是并非所有的聲明都是定義,例如:int a它既是聲明,同時(shí)又是定義。然而對(duì)于extern a來(lái)講它只是聲明不是定義。一般的情況下我們常常這樣敘述,把建立空間的聲明稱(chēng)之為"定義",而把不需要建立存儲(chǔ)空間稱(chēng)之為"聲明"。很明顯我們?cè)谶@里指的聲明是范圍比較窄的,也就是說(shuō)非定義性質(zhì)的聲明。

例如:在主函數(shù)中?
int main()
{
??? extern int A; //這是個(gè)聲明而不是定義,聲明A是一個(gè)已經(jīng)定義了的外部變量
?????????????????//注意:聲明外部變量時(shí)可以把變量類(lèi)型去掉如:extern A;
??? dosth();????? //執(zhí)行函數(shù)
}

int A;??????????? //是定義,定義了A為整型的外部變量(全局變量)?


????外部變量(全局變量)的"定義"與外部變量的"聲明"是不相同的,外部變量的定義只能有一次,它的位置是在所有函數(shù)之外,而同一個(gè)文件中的外部變量聲明可以是多次的,它可以在函數(shù)之內(nèi)(哪個(gè)函數(shù)要用就在那個(gè)函數(shù)中聲明)也可以在函數(shù)之外(在外部變量的定義點(diǎn)之前)。系統(tǒng)會(huì)根據(jù)外部變量的定義(而不是根據(jù)外部變量的聲明)分配存儲(chǔ)空間的。對(duì)于外部變量來(lái)講,初始化只能是在"定義"中進(jìn)行,而不是在"聲明"中。所謂的"聲明",其作用,是聲明該變量是一個(gè)已在后面定義過(guò)的外部變量,僅僅是在為了"提前"引用該變量而作的"聲明"而已。extern只作聲明,不作定義。?

??? 用static來(lái)聲明一個(gè)變量的作用有二:
??? (1) 對(duì)于局部變量用static聲明,則是為該變量分配的空間在整個(gè)程序的執(zhí)行期內(nèi)都始終存在
??? (2) 外部變量用static來(lái)聲明,則該變量的作用只限于本文件模塊


(此部分參考自:如何防止頭文件被重復(fù)包含、嵌套包含

?

三、前置聲明:

?

在編寫(xiě)C++程序的時(shí)候,偶爾需要用到前置聲明(Forward declaration)。下面的程序中,帶注釋的那行就是類(lèi)B的前置說(shuō)明。這是必須的,因?yàn)轭?lèi)A中用到了類(lèi)B,

而類(lèi)B的聲明出現(xiàn)在類(lèi)A的后面。如果沒(méi)有類(lèi)B的前置說(shuō)明,下面的程序?qū)⒉煌ㄟ^(guò)編譯,編譯器將會(huì)給出類(lèi)似“缺少類(lèi)型說(shuō)明符”這樣的出錯(cuò)提示。

?

//?A.h??

#include?"B.h"??

class?A??

{??

????B?b;??

public:??

????A(void);??

????virtual?~A(void);??

};??

??

//A.cpp??

#include?"A.h"??

A::A(void)??

{??

}??

??

??

A::~A(void)??

{??

}??

??

//?B.h??

#include?"A.h"??

class?B??

{??

????A?a;??

public:??

????B(void);??

????~B(void);??

};??

??

//?B.cpp??

#include?"B.h"??

B::B(void)??

{??

}??

??

??

B::~B(void)??

{??

}



編譯一下A.cpp,不通過(guò)。再編譯B.cpp,還是不通過(guò)。編譯器去編譯A.h,發(fā)現(xiàn)包含了B.h,就去編譯B.h。編譯B.h的時(shí)候發(fā)現(xiàn)包含了A.h,但是A.h已經(jīng)編譯過(guò)了(其實(shí)沒(méi)有編譯完成,可能編譯器做了記錄,A.h已經(jīng)被編譯了,這樣可以避免陷入死循環(huán)。編譯出錯(cuò)總比死循環(huán)強(qiáng)點(diǎn)),就沒(méi)有再次編譯A.h就繼續(xù)編譯。后面發(fā)現(xiàn)用到了A的定義,這下好了,A的定義并沒(méi)有編譯完成,所以找不到A的定義,就編譯出錯(cuò)了。

?

這時(shí)使用前置聲明就可以解決問(wèn)題:

//?A.h??

#include?"B.h"?

class B; //前置聲明

class?A??

{



private:??

????B ?b;??

public:??

????A(void);??

????virtual?~A(void);??

};??

??

//A.cpp?

#include?"A.h"??

A::A(void)??

{??

}??

??

??

A::~A(void)??

{??

}??

??

//?B.h??

#include?"A.h"??

class?B??

{

private:???

????A?a;??

public:??

????B(void);??

????~B(void);??

};??

??

//?B.cpp?

#include?"B.h"??

B::B(void)??

{??

}??

??

??

B::~B(void)??

{??

}


test.cpp


int main()

{

B* b = new B();

A* a = new A();

delete a;

delete b;

return 0;

}

?

類(lèi)的前置聲明是有許多的好處的。

我們使用前置聲明的一個(gè)好處是,從上面看到,當(dāng)我們?cè)陬?lèi)A使用類(lèi)B的前置聲明時(shí),我們修改類(lèi)B時(shí),只需要重新編譯類(lèi)B,而不需要重新編譯a.h的(當(dāng)然,在真正使用類(lèi)B時(shí),必須包含b.h)。

另外一個(gè)好處是減小類(lèi)A的大小,上面的代碼沒(méi)有體現(xiàn),那么我們來(lái)看下:

?

//a.h??

class?B;??

class?A??

{??

????....??

private:??

????B?*b;??

....??

};??

//b.h??

class?B??

{??

....??

private:??

????int?a;??

????int?b;??

????int?c;??

};??

?

我們看上面的代碼,類(lèi)B的大小是12(在32位機(jī)子上)。

如果我們?cè)陬?lèi)A中包含的是B的對(duì)象,那么類(lèi)A的大小就是12(假設(shè)沒(méi)有其它成員變量和虛函數(shù))。如果包含的是類(lèi)B的指針*b變量,那么類(lèi)A的大小就是4,所以這樣是可以減少類(lèi)A的大小的,

特別是對(duì)于在STL的容器里包含的是類(lèi)的對(duì)象而不是指針的時(shí)候,這個(gè)就特別有用了。在前置聲明時(shí),我們只能使用的就是類(lèi)的指針和引用(因?yàn)橐靡彩蔷佑谥羔樀膶?shí)現(xiàn)的)。

?

為什么我們前置聲明時(shí),只能使用類(lèi)型的指針和引用呢?

看下下面這個(gè)類(lèi):

?

class?A??

{??

public:??

????A(int?a):_a(a),_b(_a){}?//?_b?is?new?add??

??????

????int?get_a()?const?{return?_a;}??

????int?get_b()?const?{return?_b;}?//?new?add??

private:??

????int?_b;?//?new?add??

????int?_a;??

};??

?

上面定義的這個(gè)類(lèi)A,其中_b變量和get_b()函數(shù)是新增加進(jìn)這個(gè)類(lèi)的。

改變:

第一個(gè)改變當(dāng)然是增加了_b變量和get_b()成員函數(shù);

第二個(gè)改變是這個(gè)類(lèi)的大小改變了,原來(lái)是4,現(xiàn)在是8。

第三個(gè)改變是成員_a的偏移地址改變了,原來(lái)相對(duì)于類(lèi)的偏移是0,現(xiàn)在是4了。

上面的改變都是我們顯式的、看得到的改變。還有一個(gè)隱藏的改變。

隱藏的改變是類(lèi)A的默認(rèn)構(gòu)造函數(shù)和默認(rèn)拷貝構(gòu)造函數(shù)發(fā)生了改變。

由上面的改變可以看到,任何調(diào)用類(lèi)A的成員變量或成員函數(shù)的行為都需要改變,因此,我們的a.h需要重新編譯。

如果我們的b.h是這樣的:

?

//b.h??

#include?"a.h"??

class?B??

{??

...??

private:??

????A?a;??

};??

?

那么我們的b.h也需要重新編譯。

如果是這樣的:

?

//b.h??

class?A;??

class?B??

{??

...?

private:??

???A?*a;??

};??

?

那么我們的b.h就不需要重新編譯。

像我們這樣前置聲明類(lèi)A

classA;

是一種不完整的聲明,只要類(lèi)B中沒(méi)有執(zhí)行需要了解類(lèi)A的大小或者成員的操作,則這樣的不完整聲明允許聲明指向A的指針和引用。

而在前一個(gè)代碼中的語(yǔ)句

Aa;

是需要了解A的大小的,不然是不可能知道如果給類(lèi)B分配內(nèi)存大小的,因此不完整的前置聲明就不行,必須要包含a.h來(lái)獲得類(lèi)A的大小,同時(shí)也要重新編譯類(lèi)B。

再回到前面的問(wèn)題,使用前置聲明只允許的聲明是指針或引用的一個(gè)原因是只要這個(gè)聲明沒(méi)有執(zhí)行需要了解類(lèi)A的大小或者成員的操作就可以了,所以聲明成指針或引用是沒(méi)有

執(zhí)行需要了解類(lèi)A的大小或者成員的操作的

?

前置聲明解決兩個(gè)類(lèi)的互相依賴(lài)

??? // A.h?

class B;?

class A?

{?

??? B* b;?

public:?

??? A(B* b):b(b)

{} ?

? ? ? ? void something()

{

b->something();

}

??? };?

??? ?

??? //A.cpp?

??? #include "B.h"?

??? #include "A.h"?

??? A::A(B * b)?

??? {?

??? ??? b= new B;?

??? }?

??? ?

??? ?

??? A::~A(void)?

??? {?

? ? ?delete b;

??? }?

??? ?

??? // B.h?

??? class A;?

??? class B?

??? {?

??? ??? A a;?

??? public:?

??? ??? B(void);?

void something()

{

cout<<"something happend ..."<<endl;?

}

??? ???~B(void);?

??? };?

??? ?

??? // B.cpp?

??? #include "A.h"?

??? #include "B.h"?

??? B::B(void)?

??? {?

??? ??? a= New A;?

??? }?

??? ?

??? ?

??? B::~B(void)?

??? {?

??? }


test.cpp


int main()

{

B * n = new B();

A *a = new A(b);


delete a;

delete b;

return 0;

}


編譯之后發(fā)現(xiàn)錯(cuò)誤:使用了未定義的類(lèi)型B;

? ? ?->something 的左邊必須指向類(lèi)/結(jié)構(gòu)/聯(lián)合/類(lèi)型

原因:

1.???????(1)處使用了類(lèi)型B的定義,因?yàn)檎{(diào)用了類(lèi)B中的一個(gè)成員函數(shù)。前置聲明class B;僅僅聲明了有一個(gè)B這樣的類(lèi)型,而并沒(méi)有給出相關(guān)的定義,類(lèi)B的相關(guān)定義,是在類(lèi)A后面出現(xiàn)的,因此出現(xiàn)了編譯錯(cuò)誤;

2.???????代碼一之所以能夠通過(guò)編譯,是因?yàn)槠渲袃H僅用到B這個(gè)類(lèi)型,并沒(méi)有用到類(lèi)B的定義。

?

解決辦法是什么?

將類(lèi)的聲明和類(lèi)的實(shí)現(xiàn)(即類(lèi)的定義)分離。如下所示:


? ??// A.h?

class?B;?

class?A?

{?

????B* b;?

public:?

??? A(B* b):b(b)

{} ?

? ? ? ??void something();

~A(void)

??? };?

? ? ? ? ?

????// B.h?

????class?A;?

????class?B?

??? {?

??? ????A?a;?

????public:?

??? ??? B(void);?

void something();

??? ???~B(void);?

??? };?


? ??//A.cpp?

????#include?"B.h"?

????#include?"A.h"?

????A::A(B * b)?

??? {?

??? ??? b=?new?B;?

??? } ? ??

? ? ? ??void something()

{

b->something();

}? ?

????A::~A(void)?

??? { ?}?


??? ?

????// B.cpp?

????#include?"A.h"?

????#include?"B.h"?

????B::B(void)?

??? {?

??? ??? a= New A;?

??? }?

??? ?

void B::something()

{

cout<<"something happend ..."<<endl;?

}

??? ?

????B::~B(void)?

??? { ? }


test.cpp


int main()

{

B * n = new B();

A *a = new A(b);


delete a;

delete b;

return 0;

}


結(jié)論:

前置聲明只能作為指針或引用,不能定義類(lèi)的對(duì)象,自然也就不能調(diào)用對(duì)象中的方法了。

?

而且需要注意,如果將類(lèi)A的成員變量B* b;改寫(xiě)成B& b;的話,必須要將bA類(lèi)的構(gòu)造函數(shù)中,采用初始化列表的方式初始化,否則也會(huì)出錯(cuò)。


總結(jié)

以上是生活随笔為你收集整理的如何防止头文件被重复包含或引用?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。