extern的使用詳解(多文件編程)——C語言


extern——關鍵字

extern是C語言中的一個關鍵字,一般用在變量名前或函數名前,作用是用來說明“此變量/函數是在別處定義的,要在此處引用”,extern這個關鍵字大部分讀者應該是在變量的存儲類型這一類的內容中

遇到的,下面先分析C語言不同的存儲類型

 

在C語言中變量和函數有數據類型存儲類型兩個屬性,因此變量定義的一般形式為:存儲類型 數據類型 變量名表;

C語言提供了一下幾種不同的存儲類型:

(1)  自動變量(auto)

(2)  靜態變量(static)

(3)  外部變量(extern)

(4)  寄存器變量(register)

(上面的auto、static、extern、register都是C語言的關鍵字),這里只分析extern關鍵字的使用

外部變量(全局變量)extern----全局靜態存儲區

標准定義格式:extern 類型名 變量名;

如果在所有函數之外定義的變量沒有指定其存儲類別,那么它就是一個外部變量,它的作用域是從它的定義點到本文件的末尾在單個源文件中的確是這樣,如果有多個源文件,全局變量的作用范圍不是從變量定義處到該文件結尾,而是在其他文件中也有效),但是如果要在定義點之前或者其他文件中使用它,那么就需要使用關鍵字extern對其進行聲明(注意不是定義,編譯器並不為其分配內存)

 

Tips:

定義:表示創建變量或分配存儲單元

聲明:說明變量的性質,但並不分配存儲單元

extern int i; //是聲明,不是定義,沒有分配內存
int i; //是定義

如果在聲明的時候給變量賦值,那么就和去掉extern直接定義變量賦值是等價的

extern int a = 10;//盡量不要寫這種定義方式
int a = 10;//上述兩條語句等價

 

(注意上面的不同語句對聲明和定義的區分,對源文件中的局部變量來說是成立的(也就是.c文件),而對於源文件中的全局變量(外部變量)int a和在頭文件中的int a就不能用上面的語句來解釋聲明和定義的區別)

 

補充:定義和聲明的一個小坑,對於int a;來說,在源文件中,如果是全局變量的話就是聲明,如果是局部變量的話就是定義

全局變量:

文章前面提到過一句話:如果在所有函數之外定義的變量沒有指定其存儲類別,那么它就是一個外部變量,意思就是這里的int a;等價於extern int a;相當於聲明,聲明是可以多次的

 

局部變量:

 

文章中只提到了extern(外部變量),這里補充下auto(自動變量),由於auto(自動變量)極為常用,所以C語言把它設計成缺省的存儲類型,即auto 可以省略不寫,在main函數內部的變量int a也是局部變量,

相當於auto int a;相當於定義,定義只能一次

 

謹記:聲明可以多次,定義只能一次

外部變量保存在靜態存儲區內,在程序運行期間分配固定的存儲單元,其生存期是整個程序的運行期,沒有顯式初始化的外部變量由編譯程序自動初始化為0(extern說明符來擴展全局變量的作用域,

可以將全局變量的作用域擴展到其他文件,但不能限制全局變量的作用域

 

extern作用於函數名和變量名時的區別:

讀者應該會發現,函數聲明時並沒有使用 extern 關鍵字,這是因為,函數的定義有函數體,函數的聲明沒有函數體,編譯器很容易區分定義和聲明,所以對於函數聲明來說,有沒有extern 都是一樣的

但是作用於變量名時extern關鍵字就不是可有可無的了,全局變量在外部使用聲明時,extern關鍵詞是必須的,如果變量無extern修飾且沒有顯式的初始化,就成為了變量的定義,因此此時必須加extern,

全局變量在不指定初值時會自動初始化為0

 

多文件編程

C語言代碼是由上到下依次執行的,不管是變量還是函數,原則上都要先定義再使用,否則就會報錯。但在實際開發中,經常會在函數或變量定義之前就使用它們,這個時候就需要提前聲明(extern)

頭文件中包含的都是函數聲明,而不是函數定義

最好不要在頭文件中定義變量,例如全局變量

這里的int a是個全局變量的定義,所以如果這個頭文件被多次引用的話,a會被重復定義,這顯然是不允許的,下面舉一個簡單多文件編程的例子

 

 

從上面的四張圖可以看出,一共用了3個文件,兩個c文件(源文件)一個h文件(頭文件),我在my.c中寫了一個函數名為Max的函數,在頭文件中聲明了這個Max函數,在有main的這個test1.c這個

源文件中用#include "my.h"包含了這個頭文件,然后在函數中就可以直接使用Max函數了(注意頭文件中只是聲明,沒有定義,而且不建議在頭文件中定義函數,會引起很多不必要的麻煩

 

在《高質量C/C++編程指南》一書中,對此也有說明:

【建議1-2-1】頭文件中只存放“聲明”,而不存放“定義”。

在C++語法中,類的成員函數可以在聲明的同時被定義,並且自動成為內聯函數。這雖然會帶來書寫上的方便,但卻造成了風格不一致,弊大於利。建議將成員函數的定義分開,不論該函數體有多么小。

【建議1-2-2】不提倡使用全局變量,盡量不要在頭文件中出現現象 extern int value 這類聲明。

作者:奄奄不息 
原文:https://blog.csdn.net/qq_41209741/article/details/84108962 

 

問題一:如果在my.c這個文件中定義了一個全局變量,想要在main函數中使用,該怎么做呢?

方法一:在含有main函數的源文件test1.c中加extern聲明

運行結果:

 

方法二:在頭文件中my.h中對my.c中的全局變量進行聲明,再在test1.c中include頭文件my.h

運行結果:

 

防止C語言頭文件被重復包含

頭文件包含命令 #include 的效果與直接復制粘貼頭文件內容的效果是一樣的,預處理器實際上也是這樣做的,它會讀取頭文件的內容,然后輸出到 #include 命令所在的位置,頭文件包含是一個

遞歸(循環)的過程,如果被包含的頭文件中還包含了其他的頭文件,預處理器會繼續將它們也包含進來;這個過程會一直持續下去,直到不再包含任何頭文件,這與遞歸的過程頗為相似,

遞歸包含會導致一個問題,就是重復引入同一個頭文件,重復引入同一頭文件有什么問題呢,當你在頭文件中定義變量或者函數時(注意是定義不是聲明,多次聲明是沒有問題的)多次引入頭文件就會報“變量被多次定義”的錯誤

下面來還原這種錯誤:

 

 

上面一共使用了3個頭文件k1.h、k2.h、my.h在k1.h和k2.h中都包含了my.h,而且在my.h中有一個全局變量int k = 10,在test1.c中包含了上述三個頭文件

編譯結果

 

 

解決方法:

方法一:使用條件編譯#ifndef(如果讀者對條件編譯不了解,可以先看一下這一篇隨筆預處理命令使用詳解----#if、#endif、#undef、#ifdef、#else、#elif

 

解釋一下上面的條件編譯語句,如果_MY_H這個宏名沒有被定義,那么定義_MY_H這個宏名,並且定義全局變量int k = 10,這樣做就避免了頭文件的重復包含引起的,變量或函數的重復定義問題

我們可以在stdio.h這個“標准輸入\輸出頭文件”中看到類似的用法

格式就像下面這個樣子:

#ifndef _INC_STDIO #define _INC_STDIO
/* 頭文件內容 */
#endif

這種宏保護方案使得程序員可以“任性”地引入當前模塊需要的所有頭文件,不用操心這些頭文件中是否包含了其他的頭文件

但也不是沒有缺點

#ifndef的方式依賴於宏名不能沖突,這不光可以保證同一個文件不會被包含多次,也能保證內容完全相同的兩個文件不會被不小心同時包含

缺點就是如果不同頭文件的宏名不小心“撞車”,可能就會導致頭文件明明存在,編譯器卻硬說找不到聲明的狀況

 

方法二:使用#pragma once

#pramgma once是微軟編譯器獨有的,也是后來才有的,所以知道的人並不是很多,用的人也不是很多,因為他不支持跨平台。如果你想寫跨平台的代碼,最好使用條件編譯,如果想使用#pragma once,

只需在頭文件開頭加上#pragma once即可

 

#pragma帶來的好處是:你不必再費勁想個宏名了,當然也就不會出現宏名碰撞引發的奇怪問題,壞處也有,#pragma once是由編譯器提供保證:同一個文件不會被包含多次。注意這里所說的“同一個文件”是指物理上的一個文件,而不是指內容相同的兩個文件,如果某個頭文件有多份拷貝,這個方法就不能保證他們不被重復包含。當然,相比宏名碰撞引發的“找不到聲明”的問題,重復包含更容易被發現並修正

 

總結:1.#ifndef 由語言支持所以移植性好

    2.#pragma 可以避免名字沖突 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM