文章目錄
- 為了給富豪面子,小碼農寫了這篇博客——
- 程式環境和預處理
- ==**帝都富豪與富家總裁聯動文章 [身價過億的女總裁對小碼農說你按鍵會了嗎](https://blog.csdn.net/qq_42832862/article/details/120678330?spm=1001.2014.3001.5502)**==
- 程式的翻譯環境和執行環境
- 詳解編譯+鏈接
- 編譯環境
- 預處理詳解
- 預定義符號
- #define
- **==#define 定義符號==**
- **==注意==**
- **==#define 定義宏==**
- ==**注意**==
- define和typedef的區別
- #define替換規則
- 注意
- #和##
- 帶副作用的宏引數
- 宏和函式對比
- **那為什么不用函式來完成這個任務?**
- 原因有二:
- 當然和函式相比宏也有劣勢的地方:
- 宏和函式的一個對比
- 命名約定
- #undef
- 命令列定義(VS不支持,linux這個電腦沒有哈哈下面就是概念自己看)
- 條件編譯
- 比如說:
- 常見的條件編譯指令
- #if 常量運算式
- 注意
- 常量運算式由前處理器求值,
- 多個分支的條件編譯
- 判斷是否被定義
- 嵌套指令
- 檔案包含
- 頭檔案被包含的方式
- 本地檔案包含
- 庫檔案包含
- 嵌套檔案包含
- ==**帝都富豪與富家總裁聯動文章 [身價過億的女總裁對小碼農說你按鍵會了嗎](https://blog.csdn.net/qq_42832862/article/details/120678330?spm=1001.2014.3001.5502)**==
為了給富豪面子,小碼農寫了這篇博客——
程式環境和預處理
帝都富豪與富家總裁聯動文章 身價過億的女總裁對小碼農說你按鍵會了嗎
程式的翻譯環境和執行環境
在ANSI C的任何一種實作中,存在兩個不同的環境
第一種是翻譯環境,在這個環境中源代碼被轉換為可執行的機器指令
第二種是執行環境,他用于實際執行代碼

詳解編譯+鏈接
編譯環境

組成一個程式的每個源檔案通過編譯程序分別轉換成目標代碼(object code),
每個目標檔案由聯結器(linker)捆綁在一起,形成一個單一而完整的可執行程式,
聯結器同時也會引入標準C函式庫中任何被該程式所用到的函式,而且它可以搜索程式員個人的程式庫,將其需要的函式也鏈接到程式中,


預處理詳解
預定義符號
__FILE__ //進行編譯的源檔案
__LINE__ //檔案當前的行號
__DATE__ //檔案被編譯的日期
__TIME__ //檔案被編譯的時間
__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
這些預定義符號都是語言內置的


但是gcc是支持的 gcc對c語言的支持非常好
#define
在c語言中#define是可以做兩件事的
#define 定義符號
語法:#define name stuff

#define MAX 1000
#define reg register //為 register這個關鍵字,創建一個簡短的名字
#define do_forever for(;;) //用更形象的符號來替換一種實作
#define CASE break;case //在寫case陳述句的時候自動把 break寫上,
// 如果定義的 stuff過長,可以分成幾行寫,除了最后一行外,每行的后面都加一個反斜杠(續行符),
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )

注意
在define定義識別符號的時候,要不要在最后加上;
#define MAX 1000;
#define MAX 1000

#define 定義宏

#define 機制包括了一個規定,允許把引數替換到文本中,這種實作通常稱為宏(macro)或定義宏(define macro),

我宏不加括號會咋樣

是不是覺的可以了,好我們就用你那個括號看看少了什么

這恰恰也就證明了預處理(編譯)只是進行文本操作
注意
引數串列的左括號必須與name緊鄰,如果兩者之間有任何空白存在,引數串列就會被解釋為stuff的一部分,

define和typedef的區別
一個是替換另一個是重定義

有一個題目

#define替換規則
在程式中擴展#define定義符號和宏時,需要涉及幾個步驟,
- 在呼叫宏時,首先對引數進行檢查,看看是否包含任何由#define定義的符號,如果是,它們首先被替換,
- 替換文本隨后被插入到程式中原來文本的位置,對于宏,引數名被他們的值替換,
- 最后,再次對結果檔案進行掃描,看看它是否包含任何由#define定義的符號,如果是,就重復上述處理程序,
注意
- 宏引數和#define 定義中可以出現其他#define定義的變數,但是對于宏,不能出現遞回,
- 當前處理器搜索#define定義的符號的時候,字串常量的內容并不被搜索,
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fcKLeT0E-1633785801136)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211009081444979.png)]
#和##
如何把引數插入到字串中?
使用#,把一個宏引數變成對應的字串,

使用##
##于它兩邊的符號合成一個符號,
它允許宏定義從分離的文本片段創建識別符號,

帶副作用的宏引數
當宏引數在宏的定義中出現超過一次的時候,如果引數帶有副作用,那么你在使用這個宏的時候就可能出現危險,導致不可預測的后果,副作用就是運算式求值的時候出現的永久性效果,
x+1;//不帶副作用
x++;//帶有副作用


宏和函式對比
宏通常被應用于執行簡單的運算,比如在兩個數中找出較大的一個,
#define MAX(a, b) ((a)>(b)?(a):(b))
那為什么不用函式來完成這個任務?
原因有二:
- 用于呼叫函式和從函式回傳的代碼可能比實際執行這個小型計算作業所需要的時間更多,所以宏比函式在程式的規模和速度方面更勝一籌,
- 更為重要的是函式的引數必須宣告為特定的型別,所以函式只能在型別合適的運算式上使用,反之這個宏怎可以適用于整形、長整型、浮點型等可以用于>來比較的型別,宏是型別無關的,
當然和函式相比宏也有劣勢的地方:
- 每次使用宏的時候,一份宏定義的代碼將插入到程式中,除非宏比較短,否則可能大幅度增加程式的長度,
- 宏是沒法除錯的,(因為我們除錯是在運行階段除錯的,宏是在編譯階段就替換了)
- 宏由于型別無關,也就不夠嚴謹,
- 宏可能會帶來運算子優先級的問題,導致程容易出現錯,
宏有時候可以做函式做不到的事情,比如:宏的引數可以出現型別,但是函式做不到,

宏和函式的一個對比
| 屬性 | #define定義宏 | 函式 |
|---|---|---|
| 代碼長度 | 每次使用時,宏代碼都會被插入到程式中,除了非常小的宏之外,程式的長度會大幅度增長 | 函式代碼只出現于一個地方;每次使用這個函式時,都呼叫那個地方的同一份代碼 |
| 執行速度 | 更快 | 存在函式的呼叫和回傳的額外開銷,所以相對慢一些 |
| 運算子優先級 | 宏引數的求值是在所有周圍運算式的背景關系環境里,除非加上括號,否則鄰近運算子的優先級可能會產生不可預料的后果,所以建議宏在書寫的時候多些括號, | 函式引數只在函式呼叫的時候求值一次,它的結果值傳遞給函式,運算式的求值結果更容易預測, |
| 帶有副作用的引數 | 引數可能被替換到宏體中的多個位置,所以帶有副作用的引數求值可能會產生不可預料的結果, | 函式引數只在傳參的時候求值一次,結果更容易控制, |
| 引數型別 | 宏的引數與型別無關,只要對引數的操作是合法的,它就可以使用于任何引數型別, | 函式的引數是與型別有關的,如果引數的型別不同,就需要不同的函式,即使他們執行的任務是不同, |
| 除錯 | 宏是不方便除錯的 | 函式是可以逐陳述句除錯的 |
| 遞回 | 宏是不能遞回的 | 函式是可以遞回的 |
命名約定
一般來講函式的宏的使用語法很相似,所以語言本身沒法幫我們區分二者,
那我們平時的一個習慣是:
把宏名全部大寫
函式名不要全部大寫
#undef
這條指令用于移除一個宏定義

命令列定義(VS不支持,linux這個電腦沒有哈哈下面就是概念自己看)
許多C 的編譯器提供了一種能力,允許在命令列中定義符號,用于啟動編譯程序,
例如:當我們根據同一個源檔案要編譯出不同的一個程式的不同版本的時候,這個特性有點用處,(假定某個程式中宣告了一個某個長度的陣列,如果機器記憶體有限,我們需要一個很小的陣列,但是另外一
個機器記憶體大寫,我們需要一個陣列能夠大寫,)
條件編譯
滿足條件代碼就參與編譯,不滿足條件,代碼就不參與編譯
比如說:
除錯性的代碼,洗掉可惜,保留又礙事,所以我們可以選擇性的編譯,

常見的條件編譯指令
#if 常量運算式
#if 常量運算式
//...
#endif

注意
必須是常量運算式,變數是不行的

自己想想為什么只能是常量:這些變數是在運行的時候才會創建,但是#if 是在什么時候處理的呢,是在預編譯階段就處理好了,在創建這個區域變數的前面就處理好了
常量運算式由前處理器求值,
//常量運算式由前處理器求值,
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif

多個分支的條件編譯
#if 常量運算式
//...
#elif 常量運算式
//...
#else
//...
#endif

判斷是否被定義
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

嵌套指令
#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次,
頭檔案被包含的方式
本地檔案包含
#include "filename"
查找策略:先在源檔案所在目錄下查找,如果該頭檔案未找到,編譯器就像查找庫函式頭檔案一樣在標準位置查找頭檔案,如果找不到就提示編譯錯誤,
庫檔案包含
#include <filename.h>
查找頭檔案直接去標準路徑下去查找,如果找不到就提示編譯錯誤,
這樣是不是可以說,對于庫檔案也可以使用 “” 的形式包含?
答案是肯定的,可以,
但是這樣做查找的效率就低些,當然這樣也不容易區分是庫檔案還是本地檔案了,
嵌套檔案包含
見我單片機那一偏博客應該詳細一點
帝都富豪與富家總裁聯動文章 身價過億的女總裁對小碼農說你按鍵會了嗎
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/307353.html
標籤:其他
