對巨集定義的作用域的一點思考
一、前言
在C語言程式碼或C++程式碼中巨集定義#ifndef……#define……#endif主要是為了避免標頭檔案重複引用,那它是怎麼一個避免機制呢?這就與巨集定義的作用域有關了。
二、巨集定義作用域
首先,C語言標準中巨集定義的作用域是,從定義位置開始,到其當前所在作用域結束,當前所在作用域只有兩個,即塊作用域(一對{}大括號的範圍)和整個檔案結尾;其次,巨集定義的變數只屬於當前這個檔案,其它檔案在沒有包含擁有巨集定義的這個檔案的情況下是無法訪問這個巨集定義變數的,注意,就算是兩個對應的標頭檔案和原始檔他們還是兩個單獨不同的檔案(如a.h和a.cpp)。
當然,有些編譯器會對巨集定義的作用域作了擴充套件,即不管巨集定義在哪裡開始,其作用域都是整個檔案,但是這是編譯器的實現原理,並不是C語言的語言標準,關於語言標準與編譯器實現原理不同
怎麼理解巨集定義作用域只屬於當前檔案?第一個例子非常簡單,一個頭檔案a.h,和一個原始檔a.cpp,其每個檔案如下:
//a.h 標頭檔案
#define b 1;
//a.cpp 原始檔,並沒有包含a.h標頭檔案
void fun()
{
int a = 1 + b; //未定義識別符號b
}
第二個例子以避免標頭檔案重複引用的巨集定義#ifndef……#define……#endif為例,首先要理解避免標頭檔案重複引用是什麼意思?其實“被重複引用”是指一個頭檔案在同一個cpp檔案中被include了多次,這種錯誤常常是由於include
首先,原始碼在編譯之前需要進行預處理(也就是預編譯),預編譯幹什麼呢?我所知道的就是進行標頭檔案展開、巨集定義替換等操作,對於上面的例子中,b.cpp檔案匯入了#include
"a.h" 和#include "c.h"兩個標頭檔案,如果在標頭檔案c.h中使用了#ifndef……#define……#endif巨集定義格式(一般是在標頭檔案開頭),那在預處理b.cpp檔案的時候,首先對a.h標頭檔案進行展開,由於a.h裡面又包含了c.h標頭檔案,所以又一次展開,這個時候c.h標頭檔案裡面的巨集定義就相當於是在b.cpp檔案裡面定義的,其作用域就是整個b.cpp檔案,在接下來的b.cpp部分都可以使用或判斷該巨集定義。展開了a.h標頭檔案之後,由於b.cpp也顯示的包含了c.h標頭檔案,所以需要再一次對其進行展開,在這一次的展開過程中,由於b.cpp檔案內已經有了c.h標頭檔案的巨集定義,所以在展開的開頭部分進行判斷為假,所以編譯器就避免了再一次對c,h標頭檔案進行展開。c.h、a.h標頭檔案和b.cpp原始檔具體如下:
//c.h標頭檔案
#ifndef C_H
#define C_H
//c.h檔案需要提供的介面宣告
//記住標頭檔案就是宣告部分
void fun_c();
#endif
//a.h標頭檔案
#ifndef A_H
#define A_H
#include "c.h"
//a.h檔案需要提供的介面宣告
void fun_a();
#endif
//b.cpp檔案
#include "a.h"
#include "c.h"
//呼叫a.h和c.h中函式
int mian()
{
fun_c();
fun_a();
}
上述程式碼在VS2010裡面編輯並沒有錯誤,而且執行也是沒有問題的,只是在執行直線需要先實現fun_c()和fun_a()兩個函式。檔案b.cpp預處理之後可能變為(虛擬碼,並不是前處理器真正的輸出程式碼)
//b.cpp預處理後文件
/*********************************
#include "a.h"替換開始
**********************************/
#define A_H
//#include "c.h" //這裡包含c.h檔案,繼續替換
#define C_H
void fun_c();
void fun_a();
/*********************************
#include "a.h"替換完成
**********************************/
/*********************************
又一次#include "c.h"替換開始
**********************************/
//#include "c.h"
#ifndef C_H //這一個判斷為假,因為前面已經定義了巨集A_H
/***********以下兩行程式碼並不替換進來*******************/
//#define C_H
//void fun_c();
#endif
/*********************************
又一次#include "c.h"替換結束,可以看到,並沒有再一次新增fun_c()函式的宣告程式碼
**********************************/
//呼叫a.h和c.h中函式
int mian()
{
fun_c();
fun_a();
}
如果以上程式碼,還不足以讓你理解巨集定義的作用域只在當前檔案範圍內,那下面對這個例子進行反面論證,上面第二個例子中#ifndef……#define……#endif的使用是為了避免在同一個檔案(不管是cpp還是h檔案)中多次包含某一標頭檔案,也就是說第一次的標頭檔案包含會展開,後面再有包含的話首先要進行巨集判斷,判斷成功才包含,但是巨集判斷的前提是這個巨集已經在當前檔案中了,比如上面例子中的b.cpp檔案,通過#include
“a.h”,其中a.h又#include“c.h”,所以在b.cpp包含a.h標頭檔案的時候,就已經把A_H和C_H這兩個巨集新增到自己本檔案的作用域內了,這難道不能說明巨集的作用範圍只屬於它的本身檔案嗎?如果不是這樣的,那b.cpp檔案為什麼不能直接就對A_H和C_H巨集進行判斷,而是必須要先#include兩個標頭檔案(見第一個例子)。三、總結
C語言標準中巨集定義的作用域是,從定義位置開始,到其當前所在作用域結束,即巨集定義只屬於當前這個檔案,其他檔案如果沒有通過#include包含這個檔案,那就不能使用這個巨集定義。
相關推薦
對巨集定義的作用域的一點思考
一、前言 在C語言程式碼或C++程式碼中巨集定義#ifndef……#define……#endif主要是為了避免標頭檔案重複引用,那它是怎麼一個避免機制呢?這就與巨集定義的作用域有關了。 二、巨集定義作用域 首先,C語言標準中巨集定義的作用域是,從定義位置開
JSP內置對象的作用域,及過濾器filter
會話 scope 時間 手工 內置 內置對象 作用 隔離 範圍 pageContext:只要跳轉頁面,就不存在。 request, 只要在當前頁面就存在 session, 只有瀏覽器關閉,才不存在。 application,只有服務器關閉後,才不存在。 如果把變量放到pag
8——對象的作用域,生存期,……
撤銷 成員 delete free 如果 動態內存 可見 生存 部分 對象的作用域、可見域和生存期與普通變量,如int型變量的作用域、可見域和生存期並無不同。 對象同樣有局部、全局和類內(稍後就將對對象成員進行介紹)之分,對於在代碼塊中聲明的局部對象,在代碼塊執行結束退
函數對象、作用域
back 內部 返回 轉賬 int str 輸出 true 操作 實例1:通過外部調取內部值函數對象+作用域(打破函數調用層級,從外部調取內部函數的值)#正常情況只能通過f1去調用裏面函數inner()的值def f1(): def inner(): p
HTML代碼中在兩個匿名函數中使用同名變量出現bug而引起的變量作用域的思考
資源管理 normal col gin image pac cor align 程序 在學習HTML的時候,為了方便地對同一個css樣式的不同值的效果進行對比,我做成了下面這個樣子。 代碼也是很典型的用於展示的格式(p元素的內容隨便寫的): 1 <head&
Bean對象的作用域及生命周期
初始 destroy 銷毀 class spring框架 destory 全局 服務 ice 1.Bean對象的作用域 Bean對象在spring容器中,可以通過scope屬性來定義Bean元素的作用域,singleton(單例:這個作用域標識的對象具有全局唯一性) pro
對ThreadLocal實現原理的一點思考
前言 在《透徹理解Spring事務設計思想之手寫實現》中,已經向大家揭示了Spring就是利用ThreadLocal來實現一個執行緒中的Connection是同一個,從而保證了事務。本篇部落格將帶大家來深入分析ThreadLocal的實現原理。 ThreadLocal是
對glPushAttrib和glPopAttrib的一點思考
先把今天遇到的問題描述下吧,本來有兩個影像圖層,我對第一個圖層設定了裁剪範圍,然後再繪製第二個圖層,此時第二個圖層不顯示,此問題僅出現在NVIDIA顯示卡上,AMD顯示卡正常,讓我鬱悶了好久。 後來通過glPushAttrib和glPopAttrib解決了此問題,在渲染前
javascript--函式基礎(函式的定義/作用域,回撥函式,即時函式,內部(私有)函式,返回函式的函式,重寫自己的函式)
函式源於數學對映運算,它定義了一種關係,這種關係使一個集合裡的每一個元素對應到另一個(可能相同的)集合裡的唯一元素 javascript中: 函式是程式碼塊,一段被封閉嚴實的程式碼塊 函式是資料:使用者可以把函式作為 值 賦值給 變數 函式是一種物件,它是一類抽象類(建構函式),所有
對軟體測試教育的一點思考
當你進入任何一個領域學習時,終究會被它的龐大所折服。當你越學越深,則越敬畏。尤其在IT行業,每日如新層出不窮的各種開發測試技巧、分析理論、知識框架的變化擴充、大資料人工智慧等等各種理論與實踐的不斷推陳出新,各種商業或開源工具軟體的花樣翻新。而在這一領域暢遊,則精神必須高度
變量對象、作用域鏈和This
bubuko 作用域鏈 14. target his blog png blank arch 變量對象 作用域鏈 This 整理自:https://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html 系列文章
對巨集定義:_INTSIZEOF(n)、va_start(ap,v)、va_arg(ap,t)、va_end(ap)的理解
程式碼:typedef char * va_list; #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) (
VUE學習筆記(一) --對VUE作用域的一些思考
我學習有一個習慣,就是在用的時候一定要搞清楚一些基本問題,要不然稀裡糊塗的,腦子裡沒有一根清晰的線,總感覺是一團亂。所以學習筆記裡不會有什麼語法之類的,這些在各大教程裡都講的很清楚,在這裡只記錄一些我自己的思考和理解,如果有不對的地方,歡迎路過的大神幫忙指點。 1.VUE中,作用域及作用範圍
轉 linux shell自定義函數(定義、返回值、變量作用域)介紹
shel 自己 lai cell define ber article clas ner linux shell 可以用戶定義函數,然後在shell腳本中可以隨便調用。下面說說它的定義方法,以及調用需要註意那些事項。 一、定義shell函數(define function)
11、函數對象、函數的嵌套、名稱空間與作用域
() update 啟動 nbsp money 有效 產生 strip() return 一、函數對象 函數對象,函數是第一類對象,即函數可以當做數據傳遞 具體特點: 1、可以被引用; 1 def foo(): 2 print(‘from fo
面對對象-變量的作用域
() 屬性 bsp 結束 局部變量 hello logs world print 變量處於不同的位置,有不同的名稱 分別是 :屬性 參數 局部變量 不同名稱的變量,其作用域是不一樣的 屬性: public class HelloWorld { int
jsp九大內置對象及四個作用域
request對象 指針 但是 實例 進行 名字空間 人的 變量 this JSP九大對象:內置對象(又叫隱含對象,有9個內置對象):不需要預先聲明就可以在腳本代碼和表達式中隨意使用。 JSP中九大內置對象為: request 請求對象
零散知識點(面向對象七大設計原則,jdbc--BaseDao,jsp九大內置對象。四個作用域)
面向 -c 隔離 logs 基礎上 面向對象 通過 介紹 family 面向對象七大設計原則: 1、開閉原則(OCP:Open-Closed Principle)2、裏氏替換原則(LSP:Liskov Substitution Principle) 3、單一職責原則(SR
JSP九大內置對象和四個作用域
直接 contex rep ges word 管理 緩沖區 狀態 開發者 現在我們先來說一下四個作用域,以便描述各大對象都分屬於什麽作用域,下面我們就通過一張簡單的表格來看看各大作用域的信息共享範圍: 第一個作用域是page,他只在當前頁面有效,也就是用戶請求的頁
九大內置對象及四大作用域
board throwable view context .net strong spa req obj 二,九大內置對象到底是哪九大呢? [plain] view plain copy print? 內置對象名 類型 request