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 可以避免名字沖突