解析C++多文件編程問(wèn)題
一、多文件編程是什么
為了方便后期的維護(hù),分散代碼應(yīng)遵循一個(gè)基本原則:實(shí)現(xiàn)相同功能的代碼應(yīng)存儲(chǔ)在一個(gè)文件中。
C++ 代碼文件根據(jù)后綴名的不同,大致可以分為如下幾類:
.h:頭文件
.hpp:頭文件,header plus plus 的縮寫(xiě),混雜著 .h 的聲明 .cpp 的定義,OpenCV 采用
.cpp:源文件,windows
.cc:源文件,Unix/Linux
對(duì)于一些系統(tǒng)提供的庫(kù),出于版權(quán)和保密考慮,大多是已經(jīng)編譯好的二進(jìn)制文件,可能僅包含 .h 文件。
// student.h class Student{ // ... }; // student.cc #include "sudent.h" // Student 定義 // main.cc #include "student.h" int main(){ // ... }
二、如何防治頭文件被重復(fù)引入
1. 使用宏定義避免
#ifndef _NAME_H #define _NAME_H //頭文件內(nèi)容 #endif
_NAME_H 是宏的名稱。需要注意的是,這里設(shè)置的宏名必須是獨(dú)一無(wú)二的,不要和項(xiàng)目中其他宏的名稱相同。
// student.h #ifndef _STUDENT_H #define _STUDENT_H class Student{ // ... }; #endif
2. 使用 #pragma once 避免
使用 #pragma one
指令,將其附加到指定文件的最開(kāi)頭位置,則該文件就只會(huì)被 #include 一次。
#ifndef
是通過(guò)定義獨(dú)一無(wú)二的宏來(lái)避免重復(fù)引入的,這意味著每次引入頭文件都要進(jìn)行識(shí)別,所以 效率不高。但考慮到 C 和 C++ 都支持宏定義,所以項(xiàng)目中使用 #ifndef 規(guī)避可能出現(xiàn)的“頭文件重復(fù)引入”問(wèn)題,不會(huì)影響項(xiàng)目的可移植性。
#pragma once
不涉及宏定義,當(dāng)編譯器遇到它時(shí)會(huì)立刻知道當(dāng)前文件只引入一次,所以效率很高。但值得一提的是,并不是每個(gè)版本的編譯器都能識(shí)別 #pragma once 指令,一些較老版本的編譯器就不支持該指令(執(zhí)行時(shí)會(huì)發(fā)出警告,但編譯會(huì)繼續(xù)進(jìn)行),即 #pragma once 指令的兼容性不是很好。
#pragma once
只能作用于某個(gè)具體的文件,而無(wú)法向 #ifndef 那樣僅作用于指定的一段代碼。
#pragma once class Student{ // ... };
3. 使用 _Pragma 操作符
_Pragma 操作符可以看做是 #pragma 的增強(qiáng)版,不僅可以實(shí)現(xiàn) #pragma 所有的功能,還能和宏搭配使用。
這里僅介紹用 _Pragma 操作符避免頭文件重復(fù)引入。
當(dāng)處理頭文件重復(fù)引入問(wèn)題時(shí),可以將如下語(yǔ)句添加到相應(yīng)文件的開(kāi)頭:
_Pragma("once")
_Pragma("once"); class Student{ // ... };
在某些場(chǎng)景中,考慮到編譯效率和可移植性,#pragma once 和 #ifndef 經(jīng)常被結(jié)合使用來(lái)避免頭文件被 重復(fù)引入。比如說(shuō):
#pragma once #ifndef _STUDENT_H #define _STUDENT_H class Student{ // ... }; #endif
當(dāng)編譯器可以識(shí)別 #pragma once 時(shí),則整個(gè)文件僅被編譯一次;反之,即便編譯器不識(shí)別 #pragma once 指令,此時(shí)仍有 #ifndef 在發(fā)揮作用。
三、命名空間如何應(yīng)用在多文件編程中
當(dāng)進(jìn)行多文件編程時(shí),命名空間常位于 .h 頭文件中。
// student_li.h #ifndef _STUDENT_LI_H #define _STUDENT_LI_H namespace Li{ class Student{ // ... }; } #endif // student_li.cc #include "student_li.h" #include <iostream> void Li::Student::display(){ } // student_han.h #ifndef _STUDENT_HAN_H #define _STUDENT_HAN_H namespace Han{ class Student{ // ... }; } #endif // student_han.cpp #include "student_han.h" #include <iostream> void Han::Student::display(){}
注意,當(dāng)類的聲明位于指定的命名空間中時(shí),如果要在類的外部實(shí)現(xiàn)其成員方法,需同時(shí)注明所在命名空間名 和類名(例如本項(xiàng)目中的 Li::Student::display() )。
四、const常量如何在多文件編程中使用
用 const 修飾的變量必須在定義的同時(shí)進(jìn)行初始化操作(除非用 extern 修飾)
C++ 中 const 關(guān)鍵字的功能有 2 個(gè),除了表明其修飾的變量為常量外,還將所修飾變量的可見(jiàn)范圍 限制為當(dāng)前文件。這意味著,除非 const 常量的定義和 main 主函數(shù)位于同一個(gè) .cpp 文件,否則該 const 常量只能在其所在的 .cpp 文件中使用。
那么,如何定義 const 常量,才能在其他文件中使用呢?
1. 將 const 常量定義在 .h 頭文件中
// demo.h #ifndef _DEMO_H #define _DEMO_H const int num = 10; #endif // main.cc #include <iostream> #include "demo.h" int main(){ std::cout << num << std::endl; return 0; }
2. 借助 extern 先聲明再定義 const 常量
借助 extern 關(guān)鍵字,const 常量的定義也可以遵循“聲明在 .h 文件,定義在 .cpp 文件”。
// demo.h #ifndef _DEMO_H #define _DEMO_H extern const int num; // 聲明 const 常量 #endif // demo.cc #include "demo.h" const int num = 10; // main.cpp #include <iostream> #include "demo.h" int main(){ std::cout << num << std::endl; return 0; }
3. 借助 extern 直接定義 const 常量
C++ 編譯器在運(yùn)行項(xiàng)目時(shí),會(huì)在預(yù)處理階段直接將 #include 引入的頭文件替換成該頭文件中的內(nèi)容(復(fù)制粘貼),因此可以對(duì)上節(jié)代碼做修改:
// demo.cpp extern const int num = 10; // main.cpp #include <iostream> extern const int num; int main(){ std::cout << num << std::endl; return 0; }
五、多文件項(xiàng)目如何用 g++ 命令執(zhí)行
在 Linux 平臺(tái)上,雖然也有很多可用的 C++ IDE,但執(zhí)行 C++ 程序更常采用的方式是使用 g++ 命令。
除此之外,Linux 平臺(tái)還經(jīng)常編寫(xiě) makefile 來(lái)運(yùn)行規(guī)模較大的 C++ 項(xiàng)目。
C++ 程序的執(zhí)行過(guò)程分為 4 步,依次是預(yù)處理、編譯、 匯編和鏈接。在執(zhí)行 C++ 項(xiàng)目時(shí),頭文件是不需要經(jīng)歷以上這 4 個(gè)階段的,只有項(xiàng)目中的所有源文件才必須經(jīng)歷這 4 個(gè)階段。
假設(shè)有這個(gè)一個(gè) C++ 項(xiàng)目
// studetn.h class Student{ // ... }; // student.cc #include <iostream> #include "student.h" void Student::say(){ std::cout << name << "的年齡是" << age << ",成績(jī)是" << score << std::endl; } // main.cc #include "student.h" int main(){ Student *pStu = new Student; // ... delete pStu; return 0; }
預(yù)處理階段,執(zhí)行如下命令:
[root@bogon ~]# g++ -E main.cc -o main.i
[root@bogon ~]# g++ -E student.cc -o student.i
- -E 選項(xiàng)用于限定 g++ 編譯器只進(jìn)行預(yù)處理而不進(jìn)行后續(xù)的 3 個(gè)階段;
- -o 選項(xiàng)用于指定生成文件的名稱。
編譯階段,進(jìn)一步生成響應(yīng)的匯編代碼文件:
[root@bogon ~]# g++ -S main.i -o main.s
[root@bogon ~]# g++ -S student.i -o student.s
- -S 選項(xiàng)用于限定 g++ 編譯器對(duì)指定文件進(jìn)行編譯,得到的匯編代碼文件通常以“.s”作為后綴名。
將匯編文件轉(zhuǎn)換成可執(zhí)行的機(jī)器命令:
[root@bogon ~]# g++ -c main.s -o main.o
[root@bogon ~]# g++ -c student.s -o student.o
- 如果不用 -o 指定可執(zhí)行文件的名稱,默認(rèn)情況下會(huì)生成 a.out 可執(zhí)行文件。Linux 系統(tǒng)并不以文件的擴(kuò) 展名開(kāi)分區(qū)文件類型,所以 a.out 和 student.exe 都是可執(zhí)行文件,只是文件名稱有區(qū)別罷了。
最終執(zhí)行:
[root@bogon ~]# ./student.exe
從頭開(kāi)始直接生成可執(zhí)行文件:
[root@bogon ~]# g++ main.cpp student.cpp -o student.exe
六、多文件編程的底層原理
實(shí)際上,在編譯階段,編輯器會(huì)對(duì)源文件生成一個(gè)符號(hào)表,源文件中看不到的定義的符號(hào)就會(huì)存在這個(gè)表中。在鏈接階段,編譯器會(huì)在別的目標(biāo)文件中尋找這個(gè)符號(hào)的定義,如何沒(méi)有找到,會(huì)出現(xiàn)鏈接錯(cuò)誤。
定義,指的是就是將某個(gè)符號(hào)完整的描述清楚,它是變量還是函數(shù),變量類型以及變量值是多少,函數(shù)的參數(shù)有哪些以及返回值是什么等等;而“聲明”的作用僅是告訴編譯器該符號(hào)的存在,至于該符號(hào)的具體的含義,只有等鏈接的時(shí)候才能知道。
C++ 中一個(gè)符號(hào)允許被聲明多次,但只能被定義一次。
基于聲明和定義的不同,才有了多文件編程的出現(xiàn)。
所謂的頭文件,其實(shí)它的內(nèi)容跟 .cpp 文件中的內(nèi)容是一樣的,都是 C++ 的源代碼,唯一的區(qū)別在于頭文件不 用被編譯。我們把所有的函數(shù)聲明全部放進(jìn)一個(gè)頭文件中,當(dāng)某一個(gè) .cpp 源文件需要時(shí),可以通過(guò) #include 宏命令直接將頭文件中的所有內(nèi)容引入到 .cpp 文件中。這樣,當(dāng) .cpp 文件被編譯之前(也就是預(yù)處理階段),使用 #include 引入的 .h 文件就會(huì)替換成該文件中的所有聲明。
通常一個(gè)頭文件的內(nèi)容會(huì)被引入到多個(gè)不同的源文件中,并且都會(huì)被編譯,所以頭文件中一般只存放變量或者函數(shù)的聲明,不要放定義。但存在3種情況屬于定義范疇,但應(yīng)該放在 .h 文件種:
- 頭文件中定義 const 對(duì)象、static 對(duì)象頭文件中定義內(nèi)聯(lián)函數(shù),編譯器必須在編譯時(shí)就找到內(nèi)聯(lián)函數(shù)的完成定義頭文件中可以定義類。
- 類的內(nèi)部通常包含成員變量和成員函數(shù),成員變量是要等到具體的對(duì)象被創(chuàng)建時(shí)才會(huì)被定義(分配空間),但成員函數(shù)卻是需要在一開(kāi)始就被定義的,這也就是類的實(shí)現(xiàn)。
- 通常的做法是將類的定義放在頭文件中,而把成員函數(shù)的實(shí)現(xiàn)代碼放在一個(gè) .cpp 文件中。也可以直接實(shí)現(xiàn)在類內(nèi),作為內(nèi)聯(lián)函數(shù)。
到此這篇關(guān)于C++多文件編程的文章就介紹到這了,更多相關(guān)C++多文件編程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)一個(gè)閃爍的圣誕樹(shù)
本文詳細(xì)講解了C語(yǔ)言實(shí)現(xiàn)一個(gè)閃爍的圣誕樹(shù),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12C++實(shí)現(xiàn)LeetCode(312.打氣球游戲)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(312.打氣球游戲),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C++重載運(yùn)算符實(shí)現(xiàn)分?jǐn)?shù)加減乘除
這篇文章主要為大家詳細(xì)介紹了C++重載運(yùn)算符實(shí)現(xiàn)分?jǐn)?shù)加減乘除,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的推箱子小游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的推箱子小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07QT開(kāi)發(fā)應(yīng)用程序的歡迎界面實(shí)例
下面小編就為大家?guī)?lái)一篇QT開(kāi)發(fā)應(yīng)用程序的歡迎界面實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08Opencv基于CamShift算法實(shí)現(xiàn)目標(biāo)跟蹤
這篇文章主要為大家詳細(xì)介紹了Opencv基于CamShift算法實(shí)現(xiàn)目標(biāo)跟蹤,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01