1.預(yù)定義符號(hào)
__FILE__ //進(jìn)行編譯的源文件
__LINE__ //文件當(dāng)前的行號(hào)
__DATE__ //文件被編譯的日期
__TIME__ //文件被編譯的時(shí)間
__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
這些預(yù)定義符號(hào)都是C語言內(nèi)置的。
舉個(gè)例子:
#include <stdio.h>
int main() {
printf("%s\n", __FILE__);//如:1.c
printf("%d\n", __LINE__);// 5
printf("%s\n", __DATE__);// Jul 30 2023
printf("%s\n", __TIME__);// 10:13:20 記錄的時(shí)間是編譯的時(shí)間
printf("%d\n", __STDC__); //1 也可能是未定義 不遵循ANSI C
return 0;
}
2 #define
2.1 #define定義標(biāo)識(shí)符
#define
定義標(biāo)識(shí)符形式:
#define 標(biāo)識(shí)符 值
其中,標(biāo)識(shí)符
是你希望定義的名稱,而 值
可以是一個(gè)數(shù)值、一個(gè)字符串或一個(gè)表達(dá)式。
例子:
#include <stdio.h>
#define MAX 100
#define STR "Hello Wrold"
#define do_forever for (;;)
int main() {
printf("%d\n", MAX);//100
printf(STR); //Hello World
do_forever; //死循環(huán)
return 0;
}
#define
只是進(jìn)行簡單的文本替換,沒有類型檢查和錯(cuò)誤檢查。
建議
#define
后面不要加分號(hào)
#include <stdio.h>
#define MAX 1000;
int main() {
int max = 0;
if (3 > 5) {
//max = MAX; //報(bào)錯(cuò) 因?yàn)镸AX ==1000; 出現(xiàn)了兩個(gè)分號(hào)
max = MAX//正確
} else {
max = 0;
}
return 0;
}
2.2 #define定義宏
#define機(jī)制包括了一個(gè)規(guī)定,允許把參數(shù)替換到文本中,這種實(shí)現(xiàn)通常稱為宏或定義宏。
下面是宏的聲明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一個(gè)由逗號(hào)隔開的符號(hào)表,它們可能出現(xiàn)在stuff中。
例子:
#include <stdio.h>
//函數(shù)解決
int Max_hanshu(int x, int y) {
return x > y ? x : y;
}
//宏解決
#define MAX(x, y) (x > y ? x : y)
int main() {
int a = 10;
int b = 20;
int max = Max_hanshu(a, b);
int m = MAX(a, b);
printf("%d\n", max); //20
printf("%d\n", m); //20
return 0;
}
注意:
參數(shù)列表的左括號(hào)必須與name緊鄰。
如果兩者之間有任何空白存在,參數(shù)列表就會(huì)被解釋為stuff的一部分。
例如:
#define SQUARE( x ) x * x
這個(gè)宏接收一個(gè)參數(shù) x . 如果在上述聲明之后,你把
SQUARE( 5 );
置于程序中,預(yù)處理器就會(huì)用下面這個(gè)表達(dá)式替換上面的表達(dá)式:
5 * 5
警告:
這個(gè)宏存在一個(gè)問題:
觀察下面的代碼段:
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
乍一看,你可能覺得這段代碼將打印36這個(gè)值。 事實(shí)上,它將打印11。
為什么?
替換文本時(shí),參數(shù)x被替換成a + 1,所以這條語句實(shí)際上變成了:
printf ("%d\n",a + 1 * a + 1 );
這樣就比較清晰了,由替換產(chǎn)生的表達(dá)式并沒有按照預(yù)想的次序進(jìn)行求值。
在宏定義上加上兩個(gè)括號(hào),這個(gè)問題便輕松的解決了:
#define SQUARE(x) (x) * (x)
這樣預(yù)處理之后就產(chǎn)生了預(yù)期的效果:
printf ("%d\n",(a + 1) * (a + 1) );
這里還有一個(gè)宏定義:
#define DOUBLE(x) (x) + (x)
定義中我們使用了括號(hào),想避免之前的問題,但是這個(gè)宏可能會(huì)出現(xiàn)新的錯(cuò)誤。
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));
這將打印什么值呢?
warning:
看上去,好像打印100,但事實(shí)上打印的是55.
我們發(fā)現(xiàn)替換之后:
printf ("%d\n",10 * (5) + (5));
乘法運(yùn)算先于宏定義的加法,所以出現(xiàn)了55。
這個(gè)問題,的解決辦法是在宏定義表達(dá)式兩邊加上一對(duì)括號(hào)就可以了。
#define DOUBLE( x) ( ( x ) + ( x ) )
#include <stdio.h>
#define SQUARE(X) X *X
#define SQUARE1(X) (X) * (X)
#define DOUBLE(X) (X) + (X)
#define DOUBLE1(X) ((X) + (X))
int main() {
printf("%d\n", SQUARE(5)); // 25
printf("%d\n", SQUARE(5 + 1)); // 5+1*5+1 == 11
printf("%d\n", SQUARE1(5 + 1));// 36
printf("%d\n", DOUBLE(6)); // 12
printf("%d\n", DOUBLE(6 + 1)); // 14
printf("%d\n", 10 * DOUBLE(6)); // 66 10*(6)+(6) ==66
printf("%d\n", 10 * DOUBLE1(6));//120
return 0;
}
總結(jié):
所以用于對(duì)數(shù)值表達(dá)式進(jìn)行求值的宏定義都應(yīng)該用這種方式加上括號(hào),避免在使用宏時(shí)由于參數(shù)中的操作符或鄰近操作符之間不可預(yù)料的相互作用。
2.3 #define的替換規(guī)則
在程序中擴(kuò)展#define定義符號(hào)和宏時(shí),需要涉及幾個(gè)步驟。
- 在調(diào)用宏時(shí),首先對(duì)參數(shù)進(jìn)行檢查,看看是否包含任何由#define定義的符號(hào)。如果是,它們首先被替換。
- 替換文本隨后被插入到程序中原來文本的位置。對(duì)于宏,參數(shù)名被他們的值所替換。
- 最后,再次對(duì)結(jié)果文件進(jìn)行掃描,看看它是否包含任何由#define定義的符號(hào)。如果是,就重復(fù)上述處理過程。
注意:
- 宏參數(shù)和#define定義中可以出現(xiàn)其他#define定義的符號(hào)。但是對(duì)于宏,不能出現(xiàn)遞歸。
- 當(dāng)預(yù)處理器搜索#define定義的符號(hào)的時(shí)候,字符串常量的內(nèi)容并不被搜索。
示例 1 - 合法的宏定義:
#define PI 3.14159
#define CIRCLE_AREA(radius) (PI * (radius) * (radius))
double area = CIRCLE_AREA(2.5); // 宏 CIRCLE_AREA 使用了已定義的宏 PI
示例 2 - 非法的宏定義(遞歸):
// 這是一個(gè)非法的宏定義,宏 AREA 使用了它自身
#define AREA(x) (x > 0 ? x * x : AREA(x))
int result = AREA(5); // 這將導(dǎo)致宏展開的無限循環(huán),造成編譯錯(cuò)誤
2.4 #和##
#
運(yùn)算符可以將宏參數(shù)轉(zhuǎn)換為字符串常量。它允許你在宏定義中將參數(shù)轉(zhuǎn)換為字符串字面值。
實(shí)例:
#define STRINGIFY(x) #x
int main() {
int num = 42;
const char* str = STRINGIFY(num);
// 在宏展開時(shí),num 被轉(zhuǎn)換為字符串 "42"
printf("num as a string: %s\n", str); // Output: "num as a string: 42"
return 0;
}
##
運(yùn)算符用于在宏定義中將兩個(gè)標(biāo)記粘貼在一起。它允許你將多個(gè)標(biāo)識(shí)符組合成一個(gè)新的標(biāo)識(shí)符。
實(shí)例:
#define CONCAT(x, y) x ## y
int main() {
int num1 = 10;
int num2 = 20;
int result = CONCAT(num, 1) + CONCAT(num, 2);
// 在宏展開時(shí),CONCAT(num, 1) 變成 num1,CONCAT(num, 2) 變成 num2
// 所以,result 的值就是 num1 + num2,即 10 + 20
printf("result: %d\n", result); // Output: "result: 30"
return 0;
}
2.5 帶副作用的宏參數(shù)
當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過一次的時(shí)候,如果參數(shù)帶有副作用,那么你在使用這個(gè)宏的時(shí)候就可能出現(xiàn)危險(xiǎn),導(dǎo)致不可預(yù)測的后果。副作用就是表達(dá)式求值的時(shí)候出現(xiàn)的永久性效果。
有副作用的代碼:
int main() {
int a = 1;
int b = a + 1;// b=2,a=1
a = 1;
b = ++a;// b=2,a=2 帶有副作用的代碼,a的值發(fā)生了改變
int ch = getchar();//讀一個(gè)字符,緩沖區(qū)少一個(gè)字符
return 0;
}
x+1;//不帶副作用
x++;//帶有副作用
MAX宏可以證明具有副作用的參數(shù)所引起的問題。
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//輸出的結(jié)果是什么?
這里我們得知道預(yù)處理器處理之后的結(jié)果是什么:
z = ( (x++) > (y++) ? (x++) : (y++));
所以輸出的結(jié)果是:
x=6 y=10 z=9
2.6 宏和函數(shù)對(duì)比
宏通常被應(yīng)用于執(zhí)行簡單的運(yùn)算。 比如在兩個(gè)數(shù)中找出較大的一個(gè)。
那為什么不用函數(shù)來完成這個(gè)任務(wù)?
原因有二:
1.用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實(shí)際執(zhí)行這個(gè)小型計(jì)算工作所需要的時(shí)間更多。
所以宏比函數(shù)在程序的規(guī)模和速度方面更勝一籌。2.更為重要的是函數(shù)的參數(shù)必須聲明為特定的類型。 所以函數(shù)只能在類型合適的表達(dá)式上使用。反之這個(gè)宏怎可以適用于整形、長整型、浮點(diǎn)型等可以用于>來比較的類型。 宏是類型無關(guān)的。
實(shí)例:
#include <stdio.h>
int Max(int x, int y) {
return x > y ? x : y;
}
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main() {
int a = 10;
int b = 20;
//函數(shù)的方式
int m1 = Max(a, b);
printf("%d\n", m1);
//宏的方式
int m2 = MAX(a, b);
printf("%d\n", m2);
return 0;
}
宏的缺點(diǎn):當(dāng)然和函數(shù)相比宏也有劣勢的地方:
每次使用宏的時(shí)候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序 的長度。
宏是沒法調(diào)試的。
宏由于類型無關(guān),也就不夠嚴(yán)謹(jǐn)。
宏可能會(huì)帶來運(yùn)算符優(yōu)先級(jí)的問題,導(dǎo)致程容易出現(xiàn)錯(cuò)。
宏有時(shí)候可以做函數(shù)做不到的事情。比如:宏的參數(shù)可以出現(xiàn)類型,但是函數(shù)做不到。
#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
int main(){
int *p = malloc(10, sizeof(int));
MALLOC(10, int); //類型作為參數(shù)
return 0;
}
宏和函數(shù)的一個(gè)對(duì)比
屬性 | #define宏 | 函數(shù) |
---|---|---|
代碼長度 | 每次使用時(shí),宏代碼都會(huì)被插入到程序中。除了非常 小的宏之外,程序的長度會(huì)大幅度增長 | 函數(shù)代碼只出現(xiàn)于一個(gè)地方;每 次使用這個(gè)函數(shù)時(shí),都調(diào)用那個(gè) 地方的同一份代碼 |
執(zhí)行速 度 | 更快 | 存在函數(shù)的調(diào)用和返回的額外開 銷,所以相對(duì)慢一些 |
操作符 優(yōu)先級(jí) | 宏參數(shù)的求值是在所有周圍表達(dá)式的上下文環(huán)境里, 除非加上括號(hào),否則鄰近操作符的優(yōu)先級(jí)可能會(huì)產(chǎn)生不可預(yù)料的后果,所以建議宏在書寫的時(shí)候多些括號(hào)。 | 函數(shù)參數(shù)只在函數(shù)調(diào)用的時(shí)候求 值一次,它的結(jié)果值傳遞給函數(shù)。表達(dá)式的求值結(jié)果更容易預(yù)測。 |
帶有副 作用的 參數(shù) | 參數(shù)可能被替換到宏體中的多個(gè)位置,所以帶有副作用的參數(shù)求值可能會(huì)產(chǎn)生不可預(yù)料的結(jié)果。 | 函數(shù)參數(shù)只在傳參的時(shí)候求值一 次,結(jié)果更容易控制。 |
參數(shù)類 型 | 宏的參數(shù)與類型無關(guān),只要對(duì)參數(shù)的操作是合法的, 它就可以使用于任何參數(shù)類型。 | 函數(shù)的參數(shù)是與類型有關(guān)的,如果參數(shù)的類型不同,就需要不同的函數(shù),即使他們執(zhí)行的任務(wù)是相同的。 |
調(diào)試 | 宏是不方便調(diào)試的 | 函數(shù)是可以逐語句調(diào)試的 |
遞歸 | 宏是不能遞歸的 | 函數(shù)是可以遞歸的 |
2.7 命名約定
一般來講函數(shù)的宏的使用語法很相似。所以語言本身沒法幫我們區(qū)分二者。 那我們平時(shí)的一個(gè)習(xí)慣是:
把宏名全部大寫
函數(shù)名不要全部大寫
3.#undef
這條指令用于移除一個(gè)宏定義。
#include <stdio.h>
#define M 100
int main() {
printf("%d\n");
#undef M
printf("%d\n", M);//報(bào)錯(cuò),M的宏定義已經(jīng)被移除
return 0;
}
4.命令行定義
C語言是一種通用的編程語言,它允許開發(fā)者通過編寫命令行程序與計(jì)算機(jī)進(jìn)行交互。命令行程序是指在命令行界面(也稱為終端或命令提示符)中運(yùn)行的程序。用戶可以通過輸入命令和參數(shù)來調(diào)用這些程序,并從程序的輸出中獲取結(jié)果。
在C語言中,命令行參數(shù)是通過main函數(shù)的參數(shù)傳遞給程序的。main函數(shù)是C程序的入口點(diǎn),它有兩個(gè)參數(shù):argc
和argv
。
- argc:表示命令行參數(shù)的數(shù)量,包括程序本身。它是一個(gè)整數(shù)類型的變量。
- argv:是一個(gè)指向字符指針數(shù)組的指針,用于存儲(chǔ)命令行參數(shù)的字符串。每個(gè)字符串代表一個(gè)命令行參數(shù)。其中,argv[0]存儲(chǔ)的是程序的名稱(執(zhí)行文件的名稱),argv[1]存儲(chǔ)的是第一個(gè)命令行參數(shù),以此類推。
int main(int argc, char *argv[]) {
// Your code here
return 0;
}
示例說明:
假設(shè)我們有一個(gè)程序叫做"my_program",編譯后生成可執(zhí)行文件"my_program.exe"(在Windows上),然后我們?cè)诿钚兄羞\(yùn)行該程序,輸入如下:
my_program hello world
在這個(gè)例子中,argc的值將是4,因?yàn)橛兴膫€(gè)參數(shù):程序名稱"my_program"、“hello”、“world”,以及一個(gè)隱含的表示字符串結(jié)束的null指針。
argv數(shù)組將包含以下內(nèi)容:
argv[0] -> "my_program"
argv[1] -> "hello"
argv[2] -> "world"
argv[3] -> NULL
#include <stdio.h>
int main(int argc, char *argv[]) {
for (int i = 0; i < argc; i++) {
printf("Argument %d: %s\n", i, argv[i]);
}
return 0;
}
輸出結(jié)果:
Argument 0: my_program
Argument 1: hello
Argument 2: world
5.條件編譯
條件編譯是一種預(yù)處理指令,它允許在編譯階段根據(jù)不同的條件選擇性地包含或排除代碼片段。條件編譯可以用于根據(jù)不同的編譯條件來控制程序的行為,比如在不同平臺(tái)上使用不同的代碼或啟用/禁用特定功能。
條件編譯使用預(yù)處理指令#ifdef
、#ifndef
、#else
、#endif
、#if
、#elif
和#define
等來實(shí)現(xiàn)。這些指令都以井號(hào)(#)開頭,并且在編譯前被預(yù)處理器處理。
下面是C語言條件編譯的基本指令:
1.#ifdef
和 #ifndef
:
#ifdef 宏名
// 如果宏已定義,則編譯這里的代碼
#else
// 如果宏未定義,則編譯這里的代碼
#endif
#ifdef
用于檢查一個(gè)宏是否已經(jīng)定義,如果已定義,則編譯 #ifdef
和 #endif
之間的代碼,否則跳過這部分代碼。
#ifndef
則與 #ifdef
相反,它用于檢查一個(gè)宏是否未定義,如果未定義,則編譯 #ifndef
和 #endif
之間的代碼。
2.#else
:
#ifdef 宏名
// 如果宏已定義,則編譯這里的代碼
#else
// 如果宏未定義,則編譯這里的代碼
#endif
#else
用于在 #ifdef
或 #ifndef
條件不滿足時(shí),編譯 #else
和 #endif
之間的代碼。
3.#if
、#elif
和 #endif
:
#if 表達(dá)式
// 如果表達(dá)式為真,則編譯這里的代碼
#elif 其他表達(dá)式
// 如果其他表達(dá)式為真,則編譯這里的代碼
#else
// 如果前面的條件都不滿足,則編譯這里的代碼
#endif
#if
允許根據(jù)一個(gè)表達(dá)式的結(jié)果來決定是否編譯其后的代碼。#elif
用于檢查前面的條件不滿足時(shí),繼續(xù)檢查其他條件。#else
則用于處理前面的條件都不滿足的情況。
嵌套條件編譯
#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
6.文件包含
6.1 頭文件被包含的方式
1.本地文件包含" "
查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)準(zhǔn)位置查找頭文件。
如果找不到就提示編譯錯(cuò)誤。
#include"add.h"
int main(){
printf("hehe\n");
return 0;
}
2.庫文件包含 < >
查找頭文件直接去標(biāo)準(zhǔn)路徑下去查找,如果找不到就提示編譯錯(cuò)誤。
這樣是不是可以說,對(duì)于庫文件也可以使用 " " 的形式包含?
答案是肯定的,可以。但是這樣做查找的效率就低些,當(dāng)然這樣也不容易區(qū)分是庫文件還是本地文件了。
6.2 嵌套文件包含
comm.h和comm.c是公共模塊。
test1.h和test1.c使用了公共模塊。
test2.h和test2.c使用了公共模塊。
test.h和test.c使用了test1模塊和test2模塊。
這樣最終程序中就會(huì)出現(xiàn)兩份comm.h的內(nèi)容。這樣就造成了文件內(nèi)容的重復(fù)。
如何解決這個(gè)問題?
答案:條件編譯。
每個(gè)頭文件的開頭寫:文章來源:http://www.zghlxwxcb.cn/news/detail-622429.html
#ifndef __TEST_H__
#define __TEST_H__
//頭文件的內(nèi)容
#endif //__TEST_H__
或者:文章來源地址http://www.zghlxwxcb.cn/news/detail-622429.html
#pragma once
到了這里,關(guān)于<C語言> 預(yù)處理和宏的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!