C語言零基礎徹底掌握預處理下篇
1、條件編譯
1.1 條件編譯如何使用
C語言提供的條件編譯的功能可以讓我們按照不同的條件去編譯不同的程序部分,從而產(chǎn)生不同目標代碼文件。
第一種形式:
#ifdef 標識符
程序段1
#else
程序段2
#endif
它的功能是,如果標識符已經(jīng)被 #define 定義了,則只會對程序段1進行編譯,不會對程序段2進行編譯,如果沒有被定義則反之,如果我們不需要程序段2,也可以省去 #else 和他對應的程序段。

第二種形式:
#ifndef 標識符
程序段1
#else
程序段2
#endif
第二種形式與第一種形式的區(qū)別是將 ifdef 改為 ifndef,它的功能是,如果標識符沒有被 #dfine 定義,則對程序段1進行編譯,不會對程序段2進行編譯,如果被定義了則反之,如果我們不需要程序段2,也可以省去 #else 和他對應的程序段。

第三種形式:
#if 常量表達式
程序段1
#else
程序段2
#endif
第三種形式的功能是:如果常量表達式的值為真(非0),則對程序段1進行編譯,否則對程序段2進行編譯,因此可以使程序在不同條件下,完成不同的功能。
至于里面還可以添加 #elif 命令,意義與 else if 相同,形成一個 if else 階梯狀語句,可進行多種編譯選擇。

注意:如果定義空宏則會報錯,因為 #if 后面必須要更常量表達式!
1.2 用 #if 模擬 #ifdef

此代碼的意思是,如果 PRINT 宏被定義了,則執(zhí)行第一個打印函數(shù),否則執(zhí)行第二個打印函數(shù),同時我們也可以模擬 #ifndef,只需前面加個邏輯非就可以 ' ! ',例如:#if (!defined(PRINT))
就這樣完了嗎?其實并沒有,在更復雜的項目中,往往會出現(xiàn)兩個或多個宏需要同時定義才能滿足需求,我舉一個很簡單的例子,如果我定義了 C 宏和 CPP 宏,我才可以編譯所對應的代碼:

如上代碼就需要兩個宏都被定義才能編譯下面的程序段,相信學習過邏輯與的小伙伴應該很容易理解吧,那么我們如果需要兩個都未定義才能編譯下面的程序段呢?如何寫?
兩個都未定義才編譯: #if (!defined(C) && !defined(CPP))前面分別加邏輯非就可以 ' ! '
或者:#if (!(defined(C) || defined(CPP)))本代碼中邏輯或只要有一個被定義,就為真,然后執(zhí)行邏輯非,這樣也能保證兩個都未定義才進行編譯!
至于最后用不用大括號給括起來,我的建議是括起來,這樣我們閱讀代碼會更直觀!
既然出現(xiàn)了邏輯與,是不是也可以出現(xiàn)邏輯或呢?當然上面已經(jīng)有例子了,但是這里我就不一一演示了,感興趣的可以下來自己去嘗試一下。
條件編譯支持嵌套:

這里其實和我們平常用的 if 嵌套式是似的,也很容易理解,這里我們就不細說,有一點要注意的就是,條件編譯每個 #if 都需要有對應的 #endif 來結束
1.3 為何要有條件編譯
我們先對我們上面2小節(jié)的內容做一個總結:條件編譯本質上是讓編譯器對代碼進行裁剪!
本質認識:條件編譯,其實就是編譯器根據(jù)實際情況,對代碼進行裁剪,而這里 “實際情況” ,取決于代碼平臺,代碼本身的業(yè)務邏輯。
- 可以只保留當前最需要的代碼邏輯,其他去掉,可以減少生成代碼的大小
- 可以寫出跨平臺的代碼,讓一個具體業(yè)務,在不同平臺編譯的時候,可以有同樣的表現(xiàn)
條件編譯都用在哪些地方呢?
張三有個公司,公司有個項目,項目對應的軟件又有專業(yè)版,免費版,精簡版等等...
難道每個版本都對應著不同的代碼嗎?不是的,這樣維護起來太麻煩了,其實所謂不同的版本,本質就是功能上的有和無,所以在技術層面上,為了更好的維護,當然可以使用條件編譯,需要哪個版本,就是用條件編譯裁剪就行。
著名的 Linux 內核,功能上,其實也是用條件編譯進行功能裁剪的,用來滿足不同平臺的軟件。
2、文件包含
2.1 #include 究竟干了什么
我相信 #include 對于每個編程小伙伴來說都不陌生,很多人寫 C 語言第一件事就是寫上 #include <stdio.h> 可能老師會告訴你們這是包含標準輸入輸出頭文件,至于如何包含的,可能不會跟你講。那今天我們就來通過預處理來看一看到底是如何包含的:
我們來寫上一小段代碼:

前面說過,預處理會將頭文件展開,去注釋,宏替換,條件編譯等等
在 Linux 環(huán)境下我們可以執(zhí)行命令:gcc -E test.c -o test.i保留預處理之后的文件并命名為 test.i
為了更好的對比,我們執(zhí)行 vim 命令模式下的 vs 指令:vs/sur/include/tdio.h 也就是打開標準輸入輸出的頭文件:

看到預處理的結果之后,發(fā)現(xiàn)文件大小比我們實際代碼要大得多!
結論:#include 本質是把頭文件相關內容,拷貝到源文件中。
2.2 防止頭文件重復包含的條件編譯是如何做到的
既然我們會包含頭文件,那有沒有可能存在頭文件重復被包含的可能性呢?導致我們頭文件被重復拷貝?
這里可能會有很多老師也教過,同學們啊,我們寫頭文件的時候一定要寫如下代碼啊,這是防止頭文件重復包含的?。?/p>
#ifndef _TEST_H_ #define _TEST_H_ #include <stdio.h> #define MAX 999 int g_val = 10; extern void Print(); ... #endif
如上代碼很多小伙伴都知道在#ifndef _TEST_H_ 和 #endif 之間寫的頭文件包含,宏定義,全局變量,函數(shù)聲明,都不會被重復拷貝,為什么呢?他是如何做到的?我們實驗證明 (如下兩張圖最右邊是預處理之后的結果) :
如下代碼是沒有帶上條件編譯防止頭文件重復包含,但在源文件已經(jīng)重復包含的例子:

我們加上#ifndef _TEST_H_ 和 #endif在來看重復包含的效果:

已經(jīng)沒有重復拷貝的情況了,看來確實有防止頭文件重復包含的效果!
那么這條語句是如何做到的呢?
我們前面學過 #ifndef 如果沒有定義這個宏,則執(zhí)行后續(xù)語句,當?shù)谝淮挝覀冾^文件展開的時候,確實沒有定義_TEST_H_ 這個宏,所以會執(zhí)行后續(xù)的語句,但是在第一次展開的時候我們立馬定義了_TEST_H_ 宏,所以我們重復包含頭文件第二次展開的時候,這個宏已經(jīng)被定義了,所以也就不會去執(zhí)行#ifndef后續(xù)語句了!
結論:所有頭文件都得帶上條件編譯,防止頭文件重復包含!當然也可以直接 #pragma once
重復包含的一定會報錯嗎?顯然是不會的,但是會引起多次拷貝,會影響編譯效率。
3、選學內容
3.1 #error 預處理
#error 預處理指令的作用是:編譯程序時,只要遇到 #error 就會生成一個編譯錯誤提示消息,并停止編譯:

3.2 #line 預處理
#line 的作用時改變當前行數(shù)和文件名稱,他們是在編譯程序中預先定義的標識符。這里我就不給你們看運行結果了,感興趣的可以復制代碼下去自行了解下哦:
int main()
{
printf("%s, %d\n", __FILE__, __LINE__); //C預定義符號,代表當前文件名和代碼行號
#line 60 "hehe.h" //定制化完成
printf("%s, %d\n", __FILE__, __LINE__);
return 0;
}本質其實是可以定制化你的文件名稱和代碼行號,很少使用!
3.3 #pragma 預處理
3.3.1 #pragma message
message 參數(shù)他能在編譯信息輸出窗口中輸出相應的信息,這對于源代碼信息的控制是非常重要的。
#define TEST
int main()
{
#ifdef TEST
#pragma message("TEST macor activated!")
#endif
return 0;
}當我們定義了 TEST 這個宏后,應用程序在編譯時就會在編譯輸出窗口里顯示TEST macor activated! 因此我們就不會因為不記得自己定義的一些宏而著急了!
3.3.2 #pragma once
這個還是比較常用的,只要在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次,但是考慮到兼容性的問題,并沒有太多的使用。
3.3.3 #pragma warning
#pragma warning(disable : 4507 34; once : 4385; error : 164) //等價于: #pragma warning(disable : 4507 34) //不顯示 4507 和 34 號警告信息 #pragma warning(once : 4385) //4385 號警告信息僅報告一次 #pragma warning(error : 164) //把 164 號警告信息作為一個錯誤
當使用 windows vs 環(huán)境的小伙伴們,在使用庫函數(shù)的時候比如 scanf 會說這個函數(shù)不安全,推薦你使用 scanf_s,那我們要保證代碼可以移植性如何辦呢?通過查看報錯發(fā)現(xiàn)是 4996 報錯,那我們則可以:
#pragma warning(disable : 4996) //這樣就解決問題了!
3.3.4 #pragma pack
設置結構體內存對齊,我們還沒更新到結構體,加上用的并不算多,所以感興趣的可以先去自行研究哦。
3.4 # 和 ##
假設說我們今天定義了一個打印宏:
#define PRINT(x) printf("hello x is %d.\n", ((x)*(x)))調用宏 PRINT(8); 則會輸出:hello x is 64.
如果你希望字符串中包含宏參數(shù),那我們就可以使用 "#",它可以把語言符號轉換成字符串:
#define PRINT(x) printf("hello "#x" is %d.\n", ((x)*(x)))這樣調用PRINT(8); 則會輸出:hello 8is 64.
## 使用起來也很簡單,就是將兩個相連的符號,連接成為一個符號:
#define XNAME(n) x##n
如果這樣使用宏: XNAME(8)則會被展開成為:x8
在 "#" 或 "##" 預處理操作符相關的計算次序,如果未被指定則會產(chǎn)生問題,為了避免該問題,在單一的宏定義中只能使用其中一種操作符。除非是必須使用,否則盡量不適用這兩個預處理操作符!
到此這篇關于C語言零基礎徹底掌握預處理下篇的文章就介紹到這了,更多相關C語言預處理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用C/C++讀取matlab中.mat格式數(shù)據(jù)的操作
這篇文章給大家介紹了使用C/C++讀取matlab中.mat格式數(shù)據(jù)的操作,文中通過圖文結合的方式介紹的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2023-12-12
c++ 類函數(shù)作為模板參數(shù)實現(xiàn)方式詳解
這篇文章主要介紹了c++ 類函數(shù)作為模板參數(shù)實現(xiàn)方式,在實現(xiàn)中加入增強邏輯,這種方式對代碼侵入性過高,而且無法控制該邏輯是否需要,如果不需要的話又得重新修改代碼實現(xiàn),需要的朋友可以參考下2023-03-03

