C++ 頭文件說明

如果一個程序足夠大,代碼功能很多,可以想象,不可能把代碼寫在一個cpp文件里。我們需要模塊化,這樣的好處很多,方便分工合作,可讀性提高,調用也方便。

這個要怎么做呢?

很簡單直接當前cpp文件目錄下再新建一個test.cpp,如下:

里面就一句代碼:

int a=50;

就行;

然后ConsoleAppliciation1.cpp主程序代碼:

#include <iostream>
#include <string>
using namespace std;
extern int a;int main()
{cout << a;
}

然后編譯運行(編譯器會自動編譯所有文件):

很明顯,認到了外部變量a;?

關鍵是這個語句 extern int a;聲明a是個外部變量。起了作用,如果沒有這個聲明,則程序無法正常執行。

這個是針對多文件的嗎?

不只是,這個是聲明int a?是個外部變量。

只要在main函數里,訪問變量a前,按上下順序沒有看到變量的a的定義,都需要用這個extern聲明一下,否則編譯器不認識。

比如,如下語句:


#include <iostream>
#include <string>
using namespace std;
extern int a;int main()
{cout << b;
}
int b = 60;

可以看到?編譯直接報錯,如下:

不能識別變量“b",所以我們也要在最前面加上extern int b;即使在同一個文件中。

這樣再次編譯就正常了。?

那么函數也是同樣的道理,比如test.cpp?如下代碼:

#include <iostream>
void test()
{std::cout << 100;}

主文件:

#include <iostream>
#include <string>
using namespace std;
void test();
int main()
{test();
}

可以看到在最前面有void test();聲明,那么這里為什么沒有extern關鍵字?其實這里你加上也是可以的,因為跟變量不同,函數聲明可以省略extern,默認是extern;

那么同理,你的函數定義是main函數后面,?即使在同一個文件中,你也是要聲明一下函數的。

那么現在有一個問題,既然訪問其它文件的變量需要extern聲明一下,如果不加聲明,是否可以在兩個cpp文件里定義了一個同名的變量。

比如test.cpp

int a;

主文件cpp:

#include <iostream>using namespace std;
int a;
int main() {}

答案是不行,會報錯,如下:

如果你很疑惑,那么看下例代碼,同樣是未聲明extern;你就能理解了,跟這個差不多的道理:

#include <iostream>using namespace std;
int a;
int main() {}
int a;

結果都是不能重定義,這一點要明白。

最終的結果是兩個cpp,不能定義一樣的變量。但這并不是因為全局變量的作用域于所有cpp文件,全局變量作用域只是針對當前cpp文件。

不能定義是因為鏈接obj時是不能有相同的變量名。所以取消extern聲明,也是不能定義相同的變量。

這種現象如果很難理解,你可以在單文件里也找到類似現象。

#include <iostream>using namespace std;
extern int a;
int main() {}
int a;

下面的那個int a的作用域是從定義處開始,到文件結束,不是對于整個CPP文件的,但你不能說,根據作用域,就可以在最上面再定義一個int a了(取消extern聲明),這很顯然會引起沖突的。?

只不過一個是編譯時報錯,不能重定義,而兩個cpp里不能重定義,是編譯通過了(因為編譯時都是一次次單獨編譯CPP文件的),變成中間文件obj?鏈接的時候會報錯,不能有相同的外部鏈接變量。

那么,可以想象,如果分工合作,在多個cpp文件中,定義全局變量,總會不小心定義了相同的全局變量。

?要如何解決:

1.可以使用static修飾變量,這樣的全局變量,就只針對單cpp文件,不能被其它cpp文件訪問,也不會引起沖突,如:test.h

static int a;

這樣就沒問題了。?

2.可以使用命名空間的方法,這里就不示例代碼了,只是提供一種思路。

3.那么函數,跟變量是同樣的道理,不能在多個cpp文件里重復定義,只能定義一次,然后extern聲明引用。如果實在要定義,加上static關鍵字。聲明不被外部鏈接。只在當前cpp文件里有效。

明白了上面這些,我們可能需要使用大量的聲明,變量和函數(以后還會有類)

如果要有多個cpp文件要使用這些,那么每次都打這么一串代碼,實在麻煩。而且閱讀體驗也較差。

所以我們可以把這些聲明做成一個頭文件。

比如現在有test.cpp文件,如下代碼:

#include<iostream>
using namespace std;
int a = 5;
void test()
{cout << "\ntest函數\n";
}

然后是test.h頭文件:

 void test();extern int a;

主文件ConsoleApplication1.cpp?包含頭文件,然后調用:

#include <iostream>
#include"test.h"
using namespace std;int main() {cout << "a值:" << a;test();
}

運行結果:

這里的#include"test.h",實際是預處理命令,即:將test.h里的代碼插入當前位置(非編譯).

所以實際是這樣:

#include <iostream>
void test();
extern int a;
using namespace std;int main() {cout << "a值:" << a;test();
}

本質跟我們之前手動聲明是一樣的。

那么這里又有幾個問題,既然#include只是簡單的插入代碼,

或者為什么test.h和test.cpp聲明和定義要分開寫呢?我全寫在test.h里。

首先回答test.h里是可以定義變量,或者具體函數代碼,但十分不建議這樣寫,如果你定義了變量a,前面說過了,多個文件#include test.h,鏈接時會報重復定義。這樣極容易造成混亂。

所以標準的做法是分開寫,如果一個cpp里的變量和函數要被其它文件使用,聲明部分要分開寫成一個頭文件。以表示共用。

#ifndef

講到頭文件,這里不得不提一下ifndef?宏定義相關。

比如,將上面test.h代碼改成這樣:


#ifndef TEST_H
#define TEST_H
void test();
extern int a;#endif

上面的語句意思是,如果沒有定義宏TEST_H,則執行#endif之前的語句,就是定義宏TEST_H,然后聲明函數test和變量a.

這樣做的好處是防止代碼重復包含,比如一個cpp里多次引用了頭文件,它只會插入一次代碼,如果識別到了宏TEST_H.?

如類似這樣:

#include <iostream>using namespace std;
#include"test.h"
#include"test.h"
int main() {test();
}

注意這里的宏定義只是對當前cpp文件進行判斷,因為本質上宏定義也是預處理,只是單純替換文本。

所以

#ifndef TEST_H
#define TEST_H

#endif

這些語句,就像你在一個文件里#define max 100

然后你在另一個文件里使用max,是不合法的,只作用于當前文件,所以如果在另一個文件用#ifndef?判斷max,肯定也是未定義的。?

引申:那么如果你在test.h里面定義了一個變量a,而不是聲明,就不要指望這個#ifndef?幫你解決前面多個文件包含test.h沖突的問題,而且這樣即使解決了(比如編譯器改了邏輯,宏列表對所有cpp有效),也只是假象,因為它會把代碼都清掉了(除了第一次文件),其它文件根本就訪問不到這個變量a,自然也不會有沖突了。

我們要做的就是按規矩寫代碼。這里就告訴了我們為什么要這樣做。這樣就能避免很多問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/898355.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/898355.shtml
英文地址,請注明出處:http://en.pswp.cn/news/898355.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Lambda 表達式的語法:

在 Java 中&#xff0c;Lambda 表達式&#xff08;也稱為匿名方法&#xff09;是一種簡潔的表示方法接口&#xff08;Functional Interface&#xff09;實現的方式。它是 Java 8 引入的特性&#xff0c;目的是提高代碼的簡潔性和可讀性。 Lambda 表達式的語法&#xff1a; La…

C#零基礎入門篇(18. 文件操作指南)

## 一、文件操作基礎 在C#中&#xff0c;文件操作主要通過System.IO命名空間中的類來實現&#xff0c;例如File、FileStream、FileInfo等。 ## 二、常用文件操作方法 ### &#xff08;一&#xff09;文件讀取 1. **使用File.ReadAllText方法讀取文件內容為字符串** …

每日一題--內存池

內存池&#xff08;Memory Pool&#xff09;是一種高效的內存管理技術&#xff0c;通過預先分配并自主管理內存塊&#xff0c;減少頻繁申請/釋放內存的系統開銷&#xff0c;提升程序性能。它是高性能編程&#xff08;如游戲引擎、數據庫、網絡服務器&#xff09;中的核心優化手…

【Linux系統】Linux進程終止的N種方式

Linux系列 文章目錄 Linux系列前言一、進程終止的概念二、進程終止的場景三、進程終止的實現3.1 程序退出碼3.2 運行完畢結果正常3.3 運行完畢結果異常3.4 程序異常退出 總結 前言 進程終止是操作系統中&#xff0c;進程的一個重要階段&#xff0c;他標志著進程生命周期的結束…

正則表達式引擎深入探討

正則表達式引擎&#xff08;Regular Expression Engine&#xff09;是正則表達式得以“活起來”的核心。它是一個精密的軟件組件&#xff0c;負責接收正則表達式和輸入文本&#xff0c;解析模式并執行匹配或替換操作&#xff0c;最終輸出結果——可能是簡單的“是否匹配”&…

java面試題,什么是動態代理?、動態代理和靜態代理有什么區別?說一下反射機制?JDK Proxy 和 CGLib 有什么區別?動態代理的底層

什么是動態代理&#xff1f; 動態代理是在程序運行期&#xff0c;動態的創建目標對象的代理對象&#xff0c;并對目標對象中的方法進行功能性增強的一種技術。 在生成代理對象的過程中&#xff0c;目標對象不變&#xff0c;代理對象中的方法是目標對象方法的增強方法。可以理解…

【工具類】Java的 LocalDate 獲取本月第一天和最后一天

博主介紹&#xff1a;?全網粉絲22W&#xff0c;CSDN博客專家、Java領域優質創作者&#xff0c;掘金/華為云/阿里云/InfoQ等平臺優質作者、專注于Java技術領域? 技術范圍&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大數據、物…

嵌入式開發之STM32學習筆記day06

基于STM32F103C8T6的開發實踐——從入門到精通01 1. 引言 STM32系列微控制器是STMicroelectronics推出的一款高性能、低功耗的32位微控制器&#xff0c;廣泛應用于嵌入式系統中。STM32F103C8T6是其中非常受歡迎的一款&#xff0c;憑借其強大的性能、豐富的外設接口和低廉的價格…

學習使用 Git 和 GitHub 開發項目的教程推薦

Git 和 GitHub 是現代軟件開發中不可或缺的工具&#xff0c;無論你是個人開發者還是團隊成員&#xff0c;掌握它們都能極大提升效率。本文精選了一系列優質教程資源&#xff0c;涵蓋從基本 Git 命令到進階多人協作的內容。這些教程既有文字形式&#xff0c;也有視頻或交互式資源…

golang中的接口

1.簡介 在go中的接口是以一種類型,一種抽象的類型。接口(interface)是一組函數method的集合,go中的接口不能包含任何變量。在go中接口中的所有方法都沒有方法體,接口定義了一個對象的行為規范,只定義規范不實現。接口體現了程序的多態和高內聚低耦合的思想。go中的接口也是…

AI 浪潮下,職場的變與不變

如今&#xff0c;AI 如迅猛颶風&#xff0c;極速席卷職場&#xff0c;徹底攪亂了原有的秩序。你是否留意到&#xff0c;身邊的工作方式正悄然生變&#xff1f;今天&#xff0c;【探星 AI 研習社】就為大家深入剖析&#xff0c;AI 如何改寫職場劇本。無論你是大學生還是職場資深…

匯川EASY系列之以太網通訊(MODBUS_TCP做主站)

匯川Easy系列以太網通訊中(MODBUSTCP,plc做主站),終于可以不用使用指令就可以完成了,全程通過簡單的配置就可通訊。本文將通過EASY系列PLC與調試助手之間完成此操作。具體演示如下; 關于主站和從站的介紹 A/請求:即主動方 向被動方發送的一個要求的信息。 B/主站:發…

npm error gyp info

在使用 npm 安裝 Node.js 包時&#xff0c;可能會遇到各種錯誤&#xff0c;其中 gyp 錯誤是比較常見的一種。gyp 是 Node.js 的一個工具&#xff0c;用于編譯 C 代碼。這些錯誤通常發生在需要編譯原生模塊的 npm 包時。下面是一些常見的原因和解決方法&#xff1a; 常見原因及…

Oracle 19C分區表索引小結

一、大佬說&#xff08;楊廷琨&#xff09; LOCAL索引的最大好處是在進行分區操作&#xff0c;比如TRUNCATE PARTITION, DROP PARTITION時&#xff0c;不會出現索引INVALID的情況&#xff0c;不影響索引的可用性。由于GLOBAL索引所有的數據存儲在一起&#xff0c;因此當執行分…

AutoHub場景演示|帶您領略智能自動化操作的全新體驗

AutoHub是一款由OpenCSG推出的基于前沿大型語言模型&#xff08;LLM&#xff09;的瀏覽器自動化工具&#xff0c;旨在通過智能對話交互和自動化技術&#xff0c;幫助用戶更高效地瀏覽網頁和完成任務。它不僅能夠自動化繁瑣的網頁操作&#xff0c;還能夠為用戶提供精準的信息檢索…

深入解析 Linux 聲卡驅動:從架構到實戰

在嵌入式 Linux 設備中&#xff0c;音頻功能的實現離不開 Linux 聲卡驅動。而 ALSA (Advanced Linux Sound Architecture) 作為 Linux 內核的音頻框架&#xff0c;提供了一整套 API 和驅動模型&#xff0c;幫助開發者快速集成音頻功能。本篇文章以 WM8960 音頻編解碼器&#xf…

thinkphp5模型查詢數據庫,查出來的字段直接修改成另外的名字

在ThinkPHP5中,如果你希望在查詢數據庫時將返回的字段名直接修改為其他名稱,可以通過以下幾種方式實現: 方法1:使用 field 方法指定字段別名 在查詢時通過 field 方法直接為字段指定別名(使用 AS 關鍵字)。 示例代碼: // 使用Db類查詢 $result = Db::name(user)->…

關于前端指令

在前端開發中&#xff0c;指令&#xff08;Directives&#xff09;通常指在框架中使用的一種特殊的語法或機制&#xff0c;用于擴展 HTML 的功能。常見的指令主要存在于前端框架中&#xff0c;如 Vue.js、Angular 等。下面我們將分別介紹 Vue.js 和 Angular 中的常用指令&#…

虛擬地址空間(下)進程地址空間(上)

一.關于頁表組成 1.權限&#xff08;rwx) 作用&#xff1a;如1.讓代碼區變成只讀的 2.寫時拷貝的實現&#xff1a;子進程創建時其頁表指向的父進程代碼和數據權限都是只讀的&#xff0c;子進程試圖修改&#xff0c;觸發錯誤&#xff0c;系統開始寫時拷貝。 來源&#xff1a;…

【區塊鏈 + 航運物流】豐溯 - 區塊鏈溯源平臺 | FISCO BCOS 應用案例

豐溯是順豐科技推出的區塊鏈溯源平臺&#xff0c; 采用 FISCO BCOS 底層開源框架&#xff0c; 為農副食品、 冷鏈生鮮等企業客戶及消費 者提供關鍵流通節點的溯源信息服務&#xff0c;形成從源頭到消費者端全鏈路透明的信息鏈。 在商貿消費領域&#xff0c; 溯源一直是保障產品…