
前言:
本文為C語言預處理的下篇,將繼續講解C語言預處理的基礎知識,
🚪 傳送門:樓下大爺看完直呼簡單!C語言預處理(上)
一、命令列編譯
? 什么是命令列編譯?
💡 在編譯的時候通過命令列的方式對其進行相關的定義,叫做命令列編譯,
📚 介紹:許多C的編譯器提供的一種能力,允許在命令列中定義符號,用于啟動編譯程序,當我們根據同一個源檔案要編譯出不同的一個程式的不同版本的時,可以用到這種特性,增加靈活性,
💬 例子:假如某個程式中宣告了一個某個長度的陣列,假如機器甲記憶體有限,我們需要一個很小的資料,但是機器丙的記憶體較大,我們需要一個大點的陣列,
#include <stdio.h>
int main() {
int arr[ARR_SIZE];
int i = 0;
for (i = 0; i < ARR_SIZE; i++) {
arr[i] = i;
}
for (i = 0; i < ARR_SIZE; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
🚩 gcc 環境下測驗:(VS 里面不太好演示)
gcc test.c -D ARR_SIZE=5
ls
a.out test.c
./a.out
0 1 2 3 4 5
gcc test.c -D ARR_SIZE=20
./a.out
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
二、條件編譯
0x00 介紹
📚 在編譯一個程式時,通過條件編譯指令將一條陳述句(一組陳述句)編譯或者放棄是很方便的,
💬 除錯用的代碼洗掉了可惜,保留了又礙事,我們就可以使用條件編譯來選擇性地編譯:
#include <stdio.h>
#define __DEBUG__ // 就像一個開關一樣
int main(void)
{
int arr[10] = {0};
int i = 0;
for (i = 0; i < 10; i++) {
arr[i] = i;
#ifdef __DEBUG__ // 因為__DEBUG__被定義了,所以為真
printf("%d ", arr[i]); // 就列印陣列
#endif // 包尾
}
return 0;
}
🚩 運行結果:1 2 3 4 5 6 7 8 9 10
? 如果不想用了,就把 #define __DEBUG__ 注釋掉:
#include <stdio.h>
// #define __DEBUG__ // 關
int main(void)
{
int arr[10] = {0};
int i = 0;
for (i = 0; i < 10; i++) {
arr[i] = i;
#ifdef __DEBUG__ // 此時ifdef為假
printf("%d ", arr[i]);
#endif
}
return 0;
}
🚩 (代碼成功運行)
0x01 條件編譯之常量運算式

📚 介紹:如果常量運算式為真,參加編譯,反之如果為假,則不參加編譯,
💬 代碼演示:常量運算式為真
#include <stdio.h>
int main(void) {
#if 1
printf("Hello,World!\n");
#endif
return 0;
}
🚩 運行結果:Hello,World!
💬 代碼演示:常量運算式為假
#include <stdio.h>
int main(void) {
#if 0
printf("Hello,World!\n");
#endif
return 0;
}
🚩 (代碼成功運行)
💬 當然也可以用宏替換,可以表示地更清楚:
#include <stdio.h>
#define PRINT 1
#define DONT_PINRT 0
int main(void) {
#if PRINT
printf("Hello,World!\n");
#endif
return 0;
}
0x02 多分支的條件編譯

📚 介紹:多分支的條件編譯,直到常量運算式為真時才執行,
💬 代碼演示:
#include <stdio.h>
int main(void) {
#if 1 == 2 // 假
printf("rose\n");
#elif 2 == 2 // 真
printf("you jump\n");
#else
printf("i jump\n")
#endif
return 0;
}
🚩 代碼運行結果:you jump
0x03 條件編譯判斷是否被定義

📚 定義:ifdef 和 if defined() ,ifndef 和 if !defined() 效果是一樣的,用來判斷是否被定義,
💬 代碼演示:
#include <stdio.h>
#define TEST 0
// #define TEST2 // 不定義
int main(void) {
/* 如果TEST定義了,下面參與編譯 */
// 1
#ifdef TEST
printf("1\n");
#endif
// 2
#if defined(TEST)
printf("2\n");
#endif
/* 如果TEST2不定義,下面參與編譯 */
// 1
#ifndef TEST2
printf("3\n");
#endif
// 2
#if !defined(TEST2)
printf("4\n");
#endif
return 0;
}
0x04 條件編譯的嵌套
📚 和 if 陳述句一樣,是可以嵌套的:
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
三、檔案包含
我們已經知道,#include 指令可以使另外一個檔案被編譯,就像它實際出現于 #include 指令的地方一樣,替換方式為,前處理器先洗掉這條指令,并用包含檔案的內容替換,這樣一個源檔案被包含10次,那就實際被編譯10次,
0x00 頭檔案被包含的方式

📚 < > 和 " " 包含頭檔案的本質區別:查找的策略的區別
① " " 的查找策略:先在源檔案所在的目錄下查找,如果該頭檔案未找到,則在庫函式的頭檔案目錄下查找,(如果仍然找不到,就提示編譯錯誤)
Linux環境 標準頭檔案的路徑:
/usr/include
VS環境 標準頭檔案的路徑:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
② < > 的查找策略:直接去標準路徑下去查找,(如果仍然找不到,就提示編譯錯誤)
? 既然如此,那么對于庫檔案是否也可以使用 " " 包含?
💡 當然可以,但是這樣做查找的效率就低些,當然這樣也不容易區分是庫檔案還是本地檔案了,為了效率不建議這么做,
💬 代碼演示:
① add.h
int Add(int x, int y);
② add.c
int Add(int x, int y) {
return x + y;
}
③ test.c
#include <stdio.h>
#include "add.h"
int main(void) {
int a = 10;
int b = 20;
int ret = Add(a, b);
printf("%d\n", ret);
return 0;
}
🚩 運行結果:30
0x01 嵌套檔案的包含
? 頭檔案重復引入的情況:

comm.h 和 comm.c 是公共模塊,
test1.h 和 test1.c 使用了公共模塊,
test2.h 和 test2.c 使用了公共模塊,
test.h 和 test.c 使用了 test1 模塊和 test2 模塊,
這樣最終程式中就會出現兩份 comm.h 的內容,這樣就造成了檔案內容的重復,
? 那么如何避免頭檔案的重復引入呢?
💡 使用條件編譯指令,每個頭檔案的開頭寫:
#ifndef __TEST_H__
#define __TEST_H__
// 頭檔案的內容
#endif
? 如果嫌麻煩,還有一種非常簡單的方法:
#pragma once // 讓頭檔案即使被包含多次,也只包含一份
💭 筆試題:選自《高質量C/C++編程指南》
① 頭檔案中的 ifnde / define / endif 是干什么用的?
答:防止頭檔案被重復多次包含,
② #include <filename.h> 和 #include "filename.h" 有什么區別?
答:尖括號是包含庫里面的頭檔案的,雙引號是包含自定義頭檔案的,它們在查找策略上不同,尖括號直接去庫目錄下查找,而警號雙引號是現去自定義的代碼路徑下查找,如果找不到頭檔案,則在庫函式的頭檔案目錄下查找,
參考資料:
陳正沖. 《C語言深度解剖》[M]. 第三版. 北京航空航天大學出版社, 2019.
位元科技. C語言進階[EB/OL]. 2021[2021.8.31]. .
林銳博士. 《高質量C/C++編程指南》[M]. 1.0. 電子工業, 2001.7.24.
本章完,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/296600.html
標籤:python
