各位CSDN的uu們你們好呀,今天小雅蘭的內(nèi)容是程序環(huán)境和預(yù)處理的下篇知識(shí)點(diǎn),那么,這篇博客寫(xiě)完后,C語(yǔ)言的知識(shí)點(diǎn)就到這里就結(jié)束啦,后續(xù)會(huì)專(zhuān)注于刷題和讀書(shū),也是關(guān)于C語(yǔ)言的,會(huì)寫(xiě)一些數(shù)據(jù)結(jié)構(gòu)和C++的內(nèi)容,好啦,讓我們進(jìn)入程序環(huán)境和預(yù)處理的世界吧
預(yù)處理詳解
????????#和##
????????帶副作用的宏參數(shù)
????????宏和函數(shù)對(duì)比
????????命名約定
#undef
命令行定義
條件編譯
文件包含
其他預(yù)處理指令
預(yù)處理詳解
?程序環(huán)境和預(yù)處理(上)——“C”_認(rèn)真學(xué)習(xí)的小雅蘭.的博客-CSDN博客
小雅蘭上篇博客這里的知識(shí)點(diǎn)其實(shí)并沒(méi)有寫(xiě)完,繼續(xù)開(kāi)干!?。?/p>
#和##
如何把參數(shù)插入到字符串中? ?
首先我們看看這樣的代碼:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
char* p = "hello ""bit\n";
printf("hello"" bit\n");
printf("%s", p);
}
這里輸出的是不是 hello bit ?
答案是確定的:是。
我們發(fā)現(xiàn)字符串是有自動(dòng)連接的特點(diǎn)的。 ?
1. 那我們是不是可以寫(xiě)這樣的代碼?:
#define PRINT(FORMAT, VALUE)\
printf("the value is "FORMAT"\n", VALUE);
//...
PRINT("%d", 10);
這里只有當(dāng)字符串作為宏參數(shù)的時(shí)候才可以把字符串放在字符串中。
2.另外一個(gè)技巧是: 使用 # ,把一個(gè)宏參數(shù)變成對(duì)應(yīng)的字符串。
例如:想要打印出一些內(nèi)容,這些內(nèi)容的格式是the value of () is %()\n,這些內(nèi)容的以什么形式打印是不確定的,而且每個(gè)變量的名字也是不一樣的,在這樣的情況下,只能一行又一行的printf打印了嗎?顯然,這是不適合封裝成一個(gè)函數(shù)的,因?yàn)榉庋b函數(shù)時(shí),把函數(shù)的參數(shù)類(lèi)型固定寫(xiě)死了,那么,也沒(méi)有其他的更好的辦法呢?
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int a = 10;
printf("the value of a is %d\n", a);
int b = 20;
printf("the value of b is %d\n", b);
float f = 3.14f;
printf("the value of f is %f\n", f);
return 0;
}
這種方法是比較cuo的?。。?/p>
我們可以使用#define的形式,來(lái)完成這個(gè)任務(wù):
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define print_format(num,format)printf("the value of "#num" is "format,num)
int main()
{
int a = 10;
//printf("the value of a is %d\n", a);
print_format(a , "%d\n");
int b = 20;
//printf("the value of b is %d\n", b);
print_format(b , "%d\n");
float f = 3.14f;
//printf("the value of f is %f\n", f);
print_format(f , "%f\n");
return 0;
}
?
int i = 10;
#define PRINT(FORMAT, VALUE)printf("the value of " #VALUE " is "FORMAT "\n", VALUE);
//...
int main()
{
PRINT("%d", i + 3);//產(chǎn)生了什么效果?
}
代碼中的 #VALUE 會(huì)被預(yù)處理器處理為:"VALUE" .
?## 的作用
##可以把位于它兩邊的符號(hào)合成一個(gè)符號(hào),它允許宏定義從分離的文本片段創(chuàng)建標(biāo)識(shí)符。
?這樣的連接必須產(chǎn)生一個(gè)合法的標(biāo)識(shí)符。否則其結(jié)果就是未定義的。
#define ADD_TO_SUM(num, value)\
sum##num += value;
//...
int main()
{
ADD_TO_SUM(5, 10);//作用是:給sum增加10.
}
帶副作用的宏參數(shù)
當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過(guò)一次的時(shí)候,如果參數(shù)帶有副作用,那么你在使用這個(gè)宏的時(shí)候就可能出現(xiàn)危險(xiǎn),導(dǎo)致不可預(yù)測(cè)的后果。副作用就是表達(dá)式求值的時(shí)候出現(xiàn)的永久性效果。
int a=10; int b=a+1;//b得到的是11,a還是10 //不帶副作用
int a=10; int b=a++;//b得到了11,但是a也變了,變成了11 //帶有副作用
MAX宏可以證明具有副作用的參數(shù)所引起的問(wèn)題。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define MAX(X,Y)((X)>(Y)?(X):(Y))
int main()
{
int a = 3;
int b = 5;
int c = MAX(a++, b++);
printf("%d\n", c);//6
printf("%d\n", a);//4
printf("%d\n", b);//7
}
?為什么會(huì)是這樣子的一個(gè)結(jié)果呢?
這里我們得知道預(yù)處理器處理之后的結(jié)果是什么:
c = ( (a++) > (b++) ? (a++) : (b++));
?a++執(zhí)行后,a++的值是先使用,再++,但是a會(huì)自增。
那么,a++的值為3,b++的值為5,a自增為4,b自增為6,然后又執(zhí)行b++,b++的值變?yōu)?,b自增為7。
所以最后的值為:a=4? b=7? c=6
宏和函數(shù)對(duì)比
宏通常被應(yīng)用于執(zhí)行簡(jiǎn)單的運(yùn)算,比如在兩個(gè)數(shù)中找出較大的一個(gè)。
#define MAX(a, b) ((a)>(b)?(a):(b))
那為什么不用函數(shù)來(lái)完成這個(gè)任務(wù)?
原因有二:
用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實(shí)際執(zhí)行這個(gè)小型計(jì)算工作所需要的時(shí)間更多。所以宏比函數(shù)在程序的規(guī)模和速度方面更勝一籌。
更為重要的是函數(shù)的參數(shù)必須聲明為特定的類(lèi)型。 所以函數(shù)只能在類(lèi)型合適的表達(dá)式上使用。反之這個(gè)宏可以適用于整型、長(zhǎng)整型、浮點(diǎn)型等可以用于>來(lái)比較的類(lèi)型。宏是類(lèi)型無(wú)關(guān)的。
函數(shù)調(diào)用的時(shí)間花費(fèi):
- 函數(shù)調(diào)用前準(zhǔn)備(傳參、函數(shù)棧幀空間的維護(hù))
- 主要運(yùn)算
- 函數(shù)返回,返回值的處理,函數(shù)棧幀的銷(xiāo)毀?
?宏的缺點(diǎn):
當(dāng)然和函數(shù)相比宏也有劣勢(shì)的地方:
- 每次使用宏的時(shí)候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序的長(zhǎng)度。
- 宏是沒(méi)法調(diào)試的。
- 宏由于與類(lèi)型無(wú)關(guān),也就不夠嚴(yán)謹(jǐn)。
- 宏可能會(huì)帶來(lái)運(yùn)算符優(yōu)先級(jí)的問(wèn)題,導(dǎo)致程序容易出現(xiàn)錯(cuò)誤。
?下面,我們來(lái)看個(gè)例子:
????????求兩個(gè)數(shù)的較大值
方法一:函數(shù)的方法
#include<stdio.h> int Max(int a, int b) { return a > b ? a : b; } int main() { int a = 0; int b = 0; //輸入 scanf("%d %d", &a, &b); int m1 = Max(a, b); printf("%d\n", m1); return 0; }
方法二:宏的方法
#include<stdio.h> #define MAX(X,Y)((X>Y)?(X):(Y)) int main() { int a = 0; int b = 0; //輸入 scanf("%d %d", &a, &b); int m2 = MAX(a, b); printf("%d\n", m2); return 0; }
?宏有時(shí)候可以做函數(shù)做不到的事情。
比如:宏的參數(shù)可以出現(xiàn)類(lèi)型,但是函數(shù)做不到。
#include<stdio.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
//......
}
int* p2 = MALLOC(10, int);
if (p2 == NULL)
{
//......
}
return 0;
}
宏和函數(shù)的一個(gè)對(duì)比
功能比較簡(jiǎn)單的時(shí)候,可以采用宏來(lái)實(shí)現(xiàn),如果功能比較復(fù)雜,建議使用函數(shù)來(lái)實(shí)現(xiàn)?。?!
屬 性 | #define定義宏 | 函數(shù) |
代 碼 長(zhǎng) 度 | 每次使用時(shí),宏代碼都會(huì)被插入到程序中。除了非常 小的宏之外,程序的長(zhǎng)度會(huì)大幅度增長(zhǎng) | 函數(shù)代碼只出現(xiàn)于一個(gè)地方;每 次使用這個(gè)函數(shù)時(shí),都調(diào)用那個(gè) 地方的同一份代碼 |
執(zhí) 行 速 度 | 更快 | 存在函數(shù)的調(diào)用和返回的額外開(kāi) 銷(xiāo),所以相對(duì)慢一些 |
操 作 符 優(yōu) 先 級(jí) | 宏參數(shù)的求值是在所有周?chē)磉_(dá)式的上下文環(huán)境里, 除非加上括號(hào),否則鄰近操作符的優(yōu)先級(jí)可能會(huì)產(chǎn)生 不可預(yù)料的后果,所以建議宏在書(shū)寫(xiě)的時(shí)候多些括 號(hào)。 | 函數(shù)參數(shù)只在函數(shù)調(diào)用的時(shí)候求 值一次,它的結(jié)果值傳遞給函 數(shù)。表達(dá)式的求值結(jié)果更容易預(yù) 測(cè)。 |
帶 有 副 作 用 的 參 數(shù) | 參數(shù)可能被替換到宏體中的多個(gè)位置,所以帶有副作 用的參數(shù)求值可能會(huì)產(chǎn)生不可預(yù)料的結(jié)果。 | 函數(shù)參數(shù)只在傳參的時(shí)候求值一 次,結(jié)果更容易控制。 |
參 數(shù) 類(lèi) 型 | 宏的參數(shù)與類(lèi)型無(wú)關(guān),只要對(duì)參數(shù)的操作是合法的, 它就可以使用于任何參數(shù)類(lèi)型。 | 函數(shù)的參數(shù)是與類(lèi)型有關(guān)的,如 果參數(shù)的類(lèi)型不同,就需要不同 的函數(shù),即使他們執(zhí)行的任務(wù)是 相同的。 |
調(diào) 試 | 宏是不方便調(diào)試的 | 函數(shù)是可以逐語(yǔ)句調(diào)試的 |
遞 歸 | 宏是不能遞歸的 | 函數(shù)是可以遞歸的 |
命名約定
一般來(lái)講函數(shù)和宏的使用語(yǔ)法很相似。
所以語(yǔ)言本身沒(méi)法幫我們區(qū)分二者。
那我們平時(shí)的一個(gè)習(xí)慣是:
把宏名全部大寫(xiě)
函數(shù)名不要全部大寫(xiě)
#undef
這條指令用于移除一個(gè)宏定義。
#undef NAME
//如果現(xiàn)存的一個(gè)名字需要被重新定義,那么它的舊名字首先要被移除。
命令行定義
許多C 的編譯器提供了一種能力,允許在命令行中定義符號(hào)。用于啟動(dòng)編譯過(guò)程。
例如:當(dāng)我們根據(jù)同一個(gè)源文件要編譯出一個(gè)程序的不同版本的時(shí)候,這個(gè)特性有點(diǎn)用處。(假定某個(gè)程序中聲明了一個(gè)某個(gè)長(zhǎng)度的數(shù)組,如果機(jī)器內(nèi)存有限,我們需要一個(gè)很小的數(shù)組,但是另外一個(gè)機(jī)器內(nèi)存大些,我們需要一個(gè)數(shù)組能夠大些。)
#include <stdio.h>
int main()
{
int array[ARRAY_SIZE];
int i = 0;
for (i = 0; i < ARRAY_SIZE; i++)
{
array[i] = i;
}
for (i = 0; i < ARRAY_SIZE; i++)
{
printf("%d ", array[i]);
}
printf("\n");
return 0;
}
編譯指令:
//linux 環(huán)境演示
gcc -D ARRAY_SIZE=10 programe.c
條件編譯
在編譯一個(gè)程序的時(shí)候我們?nèi)绻獙⒁粭l語(yǔ)句(一組語(yǔ)句)編譯或者放棄是很方便的。因?yàn)槲覀冇袟l件編譯指令。
比如說(shuō):
調(diào)試性的代碼,刪除可惜,保留又礙事,所以我們可以選擇性的編譯。
#include <stdio.h>
#define __DEBUG__
int main()
{
int i = 0;
int arr[10] = { 0 };
for (i = 0; i < 10; i++)
{
arr[i] = i;
#ifdef __DEBUG__
printf("%d\n", arr[i]);//為了觀察數(shù)組是否賦值成功。
#endif __DEBUG__
}
return 0;
}
?
?
?常見(jiàn)的條件編譯指令:
1.
#if 常量表達(dá)式
//...
#endif
//常量表達(dá)式由預(yù)處理器求值。
如:
#define __DEBUG__ 1 #if __DEBUG__
//...
#endif
2.多個(gè)分支的條件編譯
#if 常量表達(dá)式
//...
#elif 常量表達(dá)式
//...
#else
//...
#endif
3.判斷是否被定義
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#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
文件包含
我們已經(jīng)知道, #include 指令可以使另外一個(gè)文件被編譯。就像它實(shí)際出現(xiàn)于 #include 指令的地方一樣。
這種替換的方式很簡(jiǎn)單: 預(yù)處理器先刪除這條指令,并用包含文件的內(nèi)容替換。
這樣一個(gè)源文件被包含10次,那就實(shí)際被編譯10次。
頭文件被包含的方式:
本地文件包含:
#include "filename"
查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)準(zhǔn)位置查找頭文件。
如果找不到就提示編譯錯(cuò)誤。
庫(kù)文件包含:
#include <filename.h>
查找頭文件直接去標(biāo)準(zhǔn)路徑下去查找,如果找不到就提示編譯錯(cuò)誤。
這樣是不是可以說(shuō),對(duì)于庫(kù)文件也可以使用 “” 的形式包含?
答案是肯定的:可以。
但是這樣做查找的效率就低些,當(dāng)然這樣也不容易區(qū)分是庫(kù)文件還是本地文件了。
Linux環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:
/usr/include
VS環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:
嵌套文件包含
?如果出現(xiàn)這樣的場(chǎng)景:
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è)問(wèn)題?
答案:條件編譯。
每個(gè)頭文件的開(kāi)頭寫(xiě):
#ifndef __TEST_H__
#define __TEST_H__
//頭文件的內(nèi)容
#endif ? //__TEST_H__
或者:
#pragma once
就可以避免頭文件的重復(fù)引入。
其他預(yù)處理指令
?好啦,小雅蘭的C語(yǔ)言專(zhuān)欄到此結(jié)束,謝謝uu們的觀看。
?以后還會(huì)繼續(xù)加油呀!?。?mark hidden color="red">文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-414758.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-414758.html
到了這里,關(guān)于程序環(huán)境和預(yù)處理(下)——“C”的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!