【C語言】 —— 預處理詳解(下)

【C語言】 —— 預處理詳解(下)

  • 前言
  • 七、# 和 \##
    • 7.1 # 運算符
    • 7.2 ## 運算符
  • 八、命名約定
  • 九、# u n d e f undef undef
  • 十、命令行定義
  • 十一、條件編譯
    • 11.1、單分支的條件編譯
    • 11.2、多分支的條件編譯
    • 11.3、判斷是否被定義
    • 11.4、嵌套指令
  • 十二、頭文件的包含
    • 12.1 頭文件被包含的方式
      • (1) 本地文件被包含的方式
      • (2)庫文件包含
    • 12.2 嵌套文件包含
  • 十三、 其他預處理指令

前言

??在上期【C語言】 —— 預處理詳解(下)的學習中,我們詳細介紹了預處理中宏的相關知識,相信大家都收獲不少。別急還有,本期讓我們繼續學習預處理方面的其他知識吧。

七、# 和 ##

7.1 # 運算符

  • # 運算符將宏的一個參數轉換成字符串字面量。它進允許出現在帶參數的宏的替換列表中
  • # 運算符所執行的操作可理解為“字符串化”

??什么意思呢?
??我們先來做一個鋪墊:

int mian()
{printf("hello"   "world\n");printf("helloworld\n");return 0;
}

??上述兩句代碼有什么區別呢?我們一起來看看:

在這里插入圖片描述

??可以看到,兩個字符串和一個字符串的效果是一樣的。C語言會把兩個字符串天然連成一個字符串,中間加空格也沒用。

??現在有這么一個場景:

int main()
{int a = 1;printf("The value of a is %d\n", a);int b = 20;printf("The value of b is %d\n", b);float f = 8.5f;printf("The value of f is %f\n", f);return 0;
}

??我們發現三句代碼的邏輯都是非常相像的,但又有些許不同
??
??那我們想既然他們這么相像,能不能把他們封裝成一個函數,以方便使用呢?
??但是函數是做不到的這個功能的
??那怎么辦呢?
??我們可以嘗試用宏來解決呢

#define Print(n, format) printf("The value of n is " format "\n", n)int main()
{int a = 1;Print(a, "%d");//printf("The value of a is %d\n", a);int b = 20;Print(b, "%d");//printf("The value of b is %d\n", b);float f = 8.5f;Print(f, "%f");//printf("The value of f is %f\n", f);return 0;
}

??
運行結果:

在這里插入圖片描述

??我們發現 n n n 一直沒變,那應該怎么修改呢?
??這時就應該用到我們的 # 運算符了:# 將宏的一個參數轉換成字符串字面量,即 n n n 變成 “ n ” “n” n

??這時,我們再運用拼接大法就成了

#define Print(n, format) printf("The value of " #n " is " format "\n", n)

??
??不懂?看下面的解釋就懂啦
在這里插入圖片描述

??

7.2 ## 運算符

??## 可以把位于他兩邊的符號合成一個符號,它允許宏定義從分離的文本片段創建標識符## 被稱為記號粘合。這樣的連接必須產生一個合法的表示符,否則其結果就是未定義的。
??
??前面我們曾說,寫一個求兩個數的較大值的函數,對于不同的數據類型就需要寫不同的函數。

int int_max(int x, int y)
{return x > y ? x : y;
}
float float_max(float x, float y)
{return x > y ? x : y;
}

??

在這里插入圖片描述

??這樣難免太過繁瑣,而且兩函數之間有很多相似的地方。
??那有沒有辦法能快速地創建出這樣的函數呢?就像一個函數的模具一樣,套一下一個函數就出來了
??
??我們可以這樣來寫一個

#define GENERIC_MAX(type) \type type##_max(type x, type y)\{\return x > y ? x : y;\}

??

#define GENERIC_MAX(type) \type type##_max(type x, type y)\{\return x > y ? x : y;\}GENERIC_MAX(int);    //相當于定義了一個函數int_max
GENERIC_MAX(float);  //相當于定義了一個函數float_maxint main()
{int r1 = int_max(3, 5);printf("%d\n", r1);float r2 = float_max(2.3f, 7.6f);printf("%f\n", r2);return 0;
}

??
運行結果:

在這里插入圖片描述

??
??我們也可以在 g c c gcc gcc 環境下觀察預處理后的 .i 文件,有個更直觀地了解

在這里插入圖片描述

??當然,這樣生成的函數也是不方便調試的

??那這里 ## 起到什么作用呢?

??加了 ##,編譯器才會認為他們是一個符號
??讓我們來看看不加 ## 的效果:

在這里插入圖片描述

??

八、命名約定

??一般來講,函數和宏的使用語法很相似,所以語言本身沒法幫我們區分二者
??
我們平時的一個習慣是:

  • 把宏名全部大寫
  • 函數名不要全部大寫

??當然,這些命名規則并不是絕對的
??比如 o f f s e t offset offset 這個宏就寫成了全小寫
??
o f f s e t offset offset 是用來計算結構體成員相對于結構體起始位置的偏移量的
??

九、# u n d e f undef undef

??# u n d e f undef undef 指令用來移出一個宏定義

在這里插入圖片描述
??上述代碼,在 169 行使用 # u n d e f undef undef 移除了宏 MAX。在移除之前的 168 行調用時沒問題的,但移除之后的 170 行調用就會報錯

??

十、命令行定義

??許多 C 的編譯器(不包括VS)提供了一種能力,允許在命令行中定義符號。用于啟動編譯過程
??
??例如:當我們根據同一個源文件要編譯出一個程序的不同版本的時候,這個特性就有用處。(假定某個程序中聲明了一個某長度的數組,如果機器內存有限,我們需要一個很小的數組,但是另外一個機器內存大些,我們需要的數組能夠大些)

在這里插入圖片描述

在這里插入圖片描述

??命令行定義式在預處理階段處理的,在預處理階段時,上述代碼中 s z sz sz 的值已經確定了

??

十一、條件編譯

??在編譯一個程序的時候如果將一條(一組語句)編譯或者放棄是很方便的。因為我們可以用條件編譯指令
??
??條件編譯指令就是這段代碼我想讓你編譯就編譯,不想讓你編譯你就不要編譯了。我們可以給他設定一個條件,條件為真,這段代碼就參與編譯,條件為假,這段代碼就不要編譯了。
??
比如說:
??一些調試性的代碼,刪除可惜,保留又礙事,所以我們可以選擇性編譯
??

在這里插入圖片描述

??
常用的條件編譯指令:

11.1、單分支的條件編譯

#if 常量表達式//···
#endif

在這里插入圖片描述

??

11.2、多分支的條件編譯

#if  常量表達式//···
#elif  常量表達式//···
#else//···
#endif

??哪條語句為真,就執行哪條語句

#define M 1
int main()
{
#if M == 0printf("hello\n");
#elif M == 1printf("world\n");
#elif M == 2printf("csdn\n");
#endifprintf("886\n");return 0;
}

??

11.3、判斷是否被定義

#if defined(symbol)
#ifdef symbol//上面兩個的反面
if !defined(symbol)
#ifndef symbol

在這里插入圖片描述

??

11.4、嵌套指令

#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif#endif

??

十二、頭文件的包含

12.1 頭文件被包含的方式

(1) 本地文件被包含的方式

# include "filename"

??查找策略:先在源文件所在的工程目錄下查找,如果頭文件未找到,編譯器就像查找庫函數頭文件一樣在標準位置查找頭文件
??如果再找不到就編譯錯誤
??
L i n u x Linux Linux 環境的標準頭文件路徑(頭文件放在哪):

 /usr/include

VS 環境的標準頭文件路徑:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//這是VS2013的默認路徑

??

(2)庫文件包含

#include <filename.h>

??查找頭文件直接去標準路徑下去查找,如果找不到就提示編譯錯誤
??
??這樣是不是可以說,對于庫文件也可以使用 “ ” 的形式包含
??答案是肯定的,但是這樣做查找的效率比較低,當然這樣也不容易區分是庫文件還是本地文件
??

12.2 嵌套文件包含

??學習了前面的(編譯和鏈接),我們知道頭文件的包含在預處理階段就是直接將該文件的代碼拷貝到包含頭文件的地方
??
??如果一個頭文件被包含了 10 次,那就實際被編譯了 10 次,如果重復包含,對編譯的壓力就比較大

t e s t . c test.c test.c

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{
return 0;
}

t e s t . h test.h test.h

void test();
struct Stu
{
int id;
char name[20];
};

??但在一個工程中,一個文件難免被包含多次,那么如何解決這個問題呢?
??答案:條件編譯

#ifndef __TEST_H__
#define __TEST_H__//頭文件的內容
#endif

??
怎么理解呢?

  • 當第一次包含頭文件時,要不要編譯呢?先進行判斷
  • __TEST_H__這個符號并沒有被定義進行編譯
  • 緊接著定義__TEST_H__符號
  • 之后再次包含該頭文件,發現__TEST_H__已被定義不再對之后包含的該頭文件進行編譯

不過上面這種寫法比較麻煩,還有另外一種寫法:

#pragma once

??效果與上面的方式是一樣的
??這樣就可以避免頭文件的重復引入
??

十三、 其他預處理指令

#error
#pragma
#line
···
#pragma pack()//在結構體部分介紹

有興趣的小伙伴可以閱讀 《C語言深度解剖》
??
??
??
??
??


??好啦,本期關于預處理的知識就介紹到這里啦,希望本期博客能對你有所幫助。同時,如果有錯誤的地方請多多指正,讓我們在C語言的學習路上一起進步!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/44022.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/44022.shtml
英文地址,請注明出處:http://en.pswp.cn/web/44022.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

淺層神經網絡示例

輸出層采用sigmoid激活&#xff0c;隱藏層采用tanh激活 import h5py import numpy as npfrom project_02.code.planar_utils import load_planar_dataset, plot_decision_boundarydef sigmoid(z):s 1 / (1 np.exp(-z))return sdef init_parameters(n_x, n_h, n_y):"&qu…

如何在 Objective-C 中實現多態性,并且它與其他面向對象編程語言的多態性實現有何差異?

在Objective-C中&#xff0c;多態性可以通過使用父類的指針來調用子類的方法來實現。具體來說&#xff0c;可以定義一個父類的指針&#xff0c;然后將子類的實例賦值給這個指針。這樣&#xff0c;即使使用父類的指針來調用方法&#xff0c;實際上會調用子類的方法。 需要注意的…

Day1每日編程題日記:數字統計、兩個數組的交集、點擊消除

前言&#xff1a;該篇用于記錄自看。曾回看昨天的做題代碼&#xff0c;竟然會覺得陌生&#xff0c;這竟然是我寫的&#xff0c;細細讀了一下&#xff0c;原來我當時是這么想的。因此我覺得記代碼沒有實際用處&#xff0c;重點是領悟了思想&#xff0c;這樣子代碼就在心中&#…

HashMap----源碼解讀

源碼分析&#xff1a; public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable 在類的開頭聲明了幾個常量&#xff0c;以下是較為重要的&#xff1a; /*** 定義初始容量大小為16*/ static final int DEFAULT_I…

探索【Python面向對象】編程:新時代的高級編程范式詳解

目錄 1. 面向對象編程概念&#xff08;OOP&#xff09; 1.1 什么是類和對象&#xff1f; 1.2 類的定義 1.3 類和對象的關系 1.4 小李的理解 2. 抽象 2.1 抽象的概念 2.2 抽象類和方法 2.3 小李的理解 3. 類和實例 3.1 類的定義和實例化 3.2 類的屬性和方法 3.3 小…

如何使用Python在企業微信中發送測試結果?操作看這里!

在日常的自動化測試工作中&#xff0c;一般會需要把測試結果同步到工作群里&#xff0c;方便信息同步。那么我們今天就使用企業微信和Pythonrequests庫來演示一下具體如何操作吧&#xff01; 01 準備 開始之前&#xff0c;我們應該確保已經安裝了python環境&#xff0c;并且要…

DNS知識點

??打牌 : da pai ge的個人主頁 ???個人專欄 : da pai ge的博客專欄 ??寶劍鋒從磨礪出,梅花香自苦寒來 ? 目錄 一、DNS概念 二 hosts 文件 三 DNS優缺點 三 客戶端域名解析順序(優先級)…

8.9分王者“水刊”!1區IEEE-Trans,國人主編坐鎮!發文量2倍增長,擴刊趨勢明顯!

關注GZH【歐亞科睿學術】&#xff0c;第一時間了解最新期刊動態&#xff01; 本期&#xff0c;小編給大家推薦的是一本IEEE旗下王者“水刊”。該期刊目前處于擴刊狀態&#xff0c;接收跨學科領域&#xff0c;領域認可度高&#xff0c;還可選擇非OA模式無需版面費&#xff0c;是…

PPTP、L2TP、IPSec、IPS 有什么區別?

隨著互聯網的發展&#xff0c;保護網絡通信的安全越來越重要。PPTP、L2TP、IPSec、IPS是常見的網絡安全協議和技術&#xff0c;在保護網絡通信安全方面發揮著不同的作用和特點。下面介紹PPTP、L2TP、IPSec、IPS之間的區別。 點對點隧道協議&#xff08;PPTP&#xff09;是一種用…

對素數的一種新理解

素數是除了1和它自身沒有其它因數的自然數&#xff08;不包括1&#xff09;。素數被認為是自然數的基礎&#xff0c;就像自然界的原子一樣&#xff0c;可以通過若干個素數的乘積表示所有大于1的自然數&#xff0c;而且這種表示是唯一的&#xff08;不考慮素數的順序&#xff09…

HTTP協議分析/burp/goby/xray

一、HTTP簡介 HTTP(超文本傳輸協議)是今天所有web應用程序使用的通信協議。最初&#xff0c;HTTP只是一個為獲取基于文本的靜態資源而開發的簡單協議&#xff0c;后來人們以名種形式擴展和利用它.使其能夠支持如今常見的復雜分布式應用程序。HTTP使用一種用于消息的模型:客戶端…

Golang異常處理機制

go語言使用error來處理錯誤&#xff0c;用panic和recover來處理異常 error go語言的錯誤處理有兩個發展階段&#xff0c;以go1.13版本為分水嶺&#xff0c;在1.13版本之前&#xff0c;標準庫對error的支持非常有限&#xff0c;僅有errors.New()和fmt.Errorf()兩個函數來構造e…

javaweb中的請求與響應--基于postman工具的應用(附帶postman的詳細安裝步驟)

一、前言 后端的第一天感覺難度就上來了&#xff0c;可能是基礎太過薄弱了吧。目前看視頻已經有點跟不上了&#xff0c;果然15天想要拿下還是太勉強了點。30天還差不多。不知道讀者們有沒有好好的去學這方面的知識&#xff0c;沒有什么是學不會的&#xff0c;關鍵是堅持。 Po…

幾個小創新模型,KAN組合網絡(LSTM、GRU、Transformer)回歸預測,python預測全家桶再更新!...

截止到本期&#xff0c;一共發了9篇關于機器學習預測全家桶Python代碼的文章。參考往期文章如下&#xff1a; 1.終于來了&#xff01;python機器學習預測全家桶 2.機器學習預測全家桶-Python&#xff0c;一次性搞定多/單特征輸入&#xff0c;多/單步預測&#xff01;最強模板&a…

蘿卜快跑的狠活

蘿卜快跑作為百度旗下的自動駕駛出行服務平臺&#xff0c;在科技應用上展現了多項領先的技術。以下是蘿卜快跑采用的一些主要科技“狠活”&#xff1a; 自動駕駛技術&#xff1a; 蘿卜快跑主要使用了百度Apollo的L4級自動駕駛技術&#xff0c;該技術能夠應對海量的城市道路場景…

C++:重定義

派生類和基類的同名成員問題 派生類中再實現一個基類中的方法會怎樣 (1)代碼實驗&#xff1a;派生類和基類中各自實現一個內容不同但函數原型完全相同的方法&#xff0c;會怎么樣 (2)結論&#xff1a;基類對象調用的是基類的方法&#xff0c;派生類對象調用執行的是派生類中重…

進程調度篇

在操作系統的廣闊領域中&#xff0c;進程調度是其中一個至關重要的環節。它如同操作系統的“交通警察”&#xff0c;負責在多個等待CPU執行的進程間進行高效、公平的分配。本文將帶您了解進程調度的基本概念、重要性、常用算法…… 1. 進程調度的基本概念 1.1 進程調度的定義 …

【FreeRTOS】freeRTOS的Tmr Svc任務優先級配置

1、Tmr Svc是個FreeRTOS的軟件定時器任務&#xff0c;他可以收集各任務的狀態 2、他的優先級可以通過宏 configTIMER_TASK_PRIORITY 來配置&#xff0c;默認是2 3、修改為31后&#xff0c;程序總是啟動不了&#xff0c; 4、后面才發現原來FreeRTOS的默認最大優先級號配置的是…

工具指南 - jenkins

一、接入SonarQube 掃描代碼 SonarQube是一個用于管理代碼質量的開放平臺&#xff0c;可以快速的定位代碼中潛在的或者明顯的錯誤。 1.1 源碼管理 如果源碼托管在SVN&#xff0c;需要進行Subversion配置&#xff1a; Repository URL&#xff1a;源碼地址&#xff0c;比如https:…

一鍵優雅為Ubuntu20.04服務器掛載新磁盤

itopen組織1、提供OpenHarmony優雅實用的小工具2、手把手適配riscv qemu linux的三方庫移植3、未來計劃riscv qemu ohos的三方庫移植 小程序開發4、一切擁抱開源&#xff0c;擁抱國產化 一、小于2T磁盤掛載方式 1.1 安裝磁盤到電腦后啟動系統 1.2 查找未分區的磁盤 打…