第 24 講:預(yù)處理指令
編譯器指令,或者叫做預(yù)處理指令(Preprocessor),指的是在程序用編譯器翻譯為機(jī)器可以識(shí)別和執(zhí)行的指令前,所做的指令操作。它可以控制程序在不同執(zhí)行狀態(tài)下執(zhí)行不同的結(jié)果。為區(qū)分其他編程語句,我們使用井號(hào) #
進(jìn)行開頭。
#include
指令
這個(gè)指令用于導(dǎo)入頭文件。什么是頭文件呢?在使用特定的編程函數(shù),諸如 printf
、scanf
等時(shí),由于每一個(gè)編程函數(shù)都有不同的功能,所以為了方便管理,它們各自被放在不同的頭文件下,在使用時(shí),需要根據(jù)它本身屬于哪個(gè)頭文件,先要導(dǎo)入,才能使用。否則編譯器找不到這個(gè)函數(shù)。
它有兩種導(dǎo)入方式:引號(hào)導(dǎo)入和尖括號(hào)導(dǎo)入。如果寫成這樣:
則表示它是一個(gè)系統(tǒng)自帶的編程函數(shù)。它會(huì)先找系統(tǒng)自帶的函數(shù)庫,發(fā)現(xiàn)文件存在就可以打開。如果不存在,再找到你建立這個(gè)C語言程序代碼下,文件夾下是否有這個(gè)頭文件。
反之,如果要先找當(dāng)前文件夾下的頭文件的話,我們使用引號(hào)導(dǎo)入。
注意,編譯器指令都是不用分號(hào)結(jié)尾的。
#define
指令
真·常量定義
它又被稱為宏定義(Macro Defining),是將代碼里所有代碼的寫法替換成宏定義過的寫法。比如,在代碼里寫很多計(jì)算圓的公式,它會(huì)大量使用到圓周率的值(約 3.14159),可如果代碼內(nèi)直接寫值的話,如果需要對代碼作改動(dòng),就需要挨個(gè)改掉它們,就相對麻煩,這個(gè)時(shí)候我們可以使用 #define
指令替換它們,然后改值時(shí),只需要改 #define
一處的值即可。
另外,一般而言,像是這樣的指令寫的東西(3.14159)是一個(gè)常數(shù),所以我們一般將常數(shù)全部大寫變量名,即 PI 的形式,不過這只是一般而言,也可以不大寫。
騷操作
不過,宏定義下的寫法是直接替換的,也就是說,你這么寫完全可以:
只是,一部分 IDE,例如 DevCpp 的編譯器不能執(zhí)行中文字符的宏定義替換罷了。不過,#define
定義語句中間的空格是嚴(yán)格規(guī)定的,只能在 #define
后和定義名稱后有空格,這樣來控制替換,否則系統(tǒng)會(huì)無法判別。
宏定義有時(shí)候也會(huì)背鍋
另外,#define
有一處需要注意的替換。它甚至可以替換函數(shù),但函數(shù)的替換方式可能有一些惡心:
它們是不同的。
看懂了嗎?上面的 a
和 b
都有括號(hào),所以下面替換后,也帶括號(hào);上面沒有括號(hào),下面也沒有括號(hào)。于是根據(jù)優(yōu)先級(jí)的不同,計(jì)算結(jié)果分別是 4 * 6 = 24 和 2 + 6 + 3 = 11。
#undef
指令
甚至還可以使用 #undef 定義名
的方式來撤銷結(jié)束定義。比如
#define
符號(hào)指令
我們也可以在定義符號(hào)的時(shí)候不為其賦值:
這樣定義的符號(hào)在本文件里的任意位置都可以用,或你把它寫進(jìn)頭文件里,然后對其使用了 #include
引入了這個(gè)頭文件時(shí)可用。
這種用法需要配合下面要說到的 #if
指令才能發(fā)揮作用。
#if
、#else
、#elif
和 #endif
指令
這四個(gè)指令單純就是為了和 C++ 語言配合使用得比較多。在 C++ 里,有些常量的定義數(shù)值是和 C 不一致的,好在它提供了 #define
的宏指令指定了語言類別。
在 C++ 里,系統(tǒng)定義了 __cplusplus
符號(hào)。如果我們查看 __cplusplus
符號(hào)是否存在,就可以判斷當(dāng)前使用的文件和語言是 C 語言還是 C++,進(jìn)而去區(qū)分程序的執(zhí)行代碼。
例如上面的 5 行指令,先查詢當(dāng)前代碼環(huán)境是否是 C++。如果是 C++,則會(huì)執(zhí)行第 2 行的 #define
定義語句,讓 NULL
常量定義為 0(這是 C++ 里的定義方式。C++ 的空是用 0 表示而不是 (void *)0
表示的;否則,沒有查到 __cplusplus
,說明是 C 語言環(huán)境,那么 NULL
的定義方式就是(void *)0
了。
另外,#if defined
可以簡寫為 #ifdef
指令:
另外,你對條件取反,也是被允許的:
#ifndef
NULL
由于頭文件 *.h
是不區(qū)分 C 還是 C++ 語言的,所以保證程序允許正常,這些指令和賦值都是不可少的。#elif
指令類似于 else if
條件判斷,這里就不講了,因?yàn)橛玫臋C(jī)會(huì)很少。
#pragma
雜注指令
最后來說一下雜注(Pragma)。雜注是預(yù)處理指令里最復(fù)雜的一種,這是因?yàn)樗拿帧半s注”就表示了所有亂七八糟的指令,不好取名或用得比較少的時(shí)候,就直接用 #pragma
指令來搞了,所以它的分支也非常多。我們這里來看一些常用的。至于不常用的,如果你碰到了,直接查詢網(wǎng)上給出的文檔即可。
#pragma warning
指令
#pragma warning
指令用于忽略、恢復(fù)和只提示一次警告信息。假如你的代碼里出現(xiàn)了警告錯(cuò)誤,例如下面代碼
這是最基礎(chǔ)的使用 scanf
函數(shù)的方法了。不過編譯器會(huì)提出警告 C6031,提示你忽略了 scanf
的返回值。scanf
的返回值表示正確輸入到變量里的變量總個(gè)數(shù)。如果你輸入了一個(gè)合適的數(shù)字,a
正常賦值,那么這個(gè)返回值就是 1。
它提示這個(gè)信息,提示你最好不要忽略返回值的使用,而是這樣用:
但是,顯然我們沒有必要這么去處理,因?yàn)槲覀冚斎胍粋€(gè)整數(shù)數(shù)值是預(yù)期的,所以我們可以忽略煩人的警告信息,用法是這樣的:
這樣的話,你就看不到后續(xù)使用的所有有關(guān) scanf
上輸入信息的返回值未使用的報(bào)錯(cuò)了。
如果你把代碼改為
表示從此處開始往下的代碼,重新又開始對 C6031 忽略返回值的警告報(bào)錯(cuò)。
這樣則是僅報(bào)告一次這樣的警告信息,其它的都忽略。
#pragma region
和 #pragma endregion
指令
忘了這個(gè)了嗎?這個(gè)就是最開始介紹的,用于分割語義的指令。這兩個(gè)指令是配合使用的,用于分割代碼段,這一段代碼里的所有執(zhí)行內(nèi)容表示某個(gè)含義。另外,寫了這樣的指令后,IDE 就可以識(shí)別這一塊代碼是我們指定好的代碼塊,甚至可以允許我們手動(dòng)折疊和展開它們。
#pragma once
指令
最后一個(gè)指令僅用于頭文件里,表示這個(gè)頭文件只會(huì)被編譯一次。這是因?yàn)?,很多時(shí)候,相同的指令會(huì)被分散放到不同的頭文件里;而反之也可能存在多個(gè)相同名稱的頭文件。這樣的話,如果我們使用 #pragma once
指令可以保證這個(gè)文件整體只會(huì)被編譯一次,防止多次反復(fù)編譯出現(xiàn)隱藏的 bug 和錯(cuò)誤。
當(dāng)然,如果執(zhí)行邏輯都不同的同名頭文件最好還是不要用一樣的頭文件名,防止這些錯(cuò)誤的出現(xiàn)。