C語言_預處理詳解

1. 預定義符號

C語言設置了一些預定義符號,可以直接使用,預定義符號也是在預處理期間處理的

1 __FILE__ //進行編譯的源文件
2 __LINE__//文件當前的行號
3 __DATE__ //文件被編譯的日期
4 __TIME__//文件被編譯的時間
5 __STDC__//如果編譯器遵循ANSI C,其值為1,否則未定義

舉個例子:

printf("file:%s line:%d\n",__FILE__,__LINE__);

2. #define 定義常量

語法:

#define name stuff

示例如下:

#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case
//如果定義的stuff太長,可以分為幾行寫,除了最后一行外,每行的后面都要加一個續行符(反斜杠)
#define DEBUG_PRINT printf("file:%s\tline:%s\t \date:%s\ttime:%s\n",\__FILE__,__LINE__,\__DATE__,__TIME__);

還有個問題,我們使用define定義標識符的時候,要不要最后加;?

我建議不要,因為可能會導致一些問題。
比如:

#define MAX 1000;if(condition)max=MAX;
elsemax=0;

我們都知道,沒有大括號的if語句只能跟一條語句,而if后面的語句經過替換后會有兩個 ; ,也就是兩個語句,所以會出現語法錯誤

3. #define 定義宏

本質:帶參數的文本替換。類似函數但無函數調用的開銷

這種實現被稱為宏(macro)或定義宏(define macro)

宏的申明方式如下:

#define name(parament-list) stuff

parament-list是一個由逗號隔開的符號表,可能出現在stuff

注:
參數列表的左括號必須和name緊鄰,如果兩者之間有空格存在,參數列表就會被解釋為stuff的一部分

4. 帶有副作用的宏參數

當宏參數在宏的定義中出現的次數超過一次時,如果參數帶有副作用,那使用這個宏就可能出現問題

例如:

#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);

預處理器處理之后宏展開的樣子如下:

z=((x++) > (y++) ? (x++) : (y++));

所以輸出結果為:
x=6 y=10 z=9

5. 宏替換的規則

在程序中擴展#define定義符號和宏時,涉及如下幾步:

  1. 在調用宏時,首先對參數進行檢查,看看是否包含由#define定義的符號。如果是,則首先被替換
  2. 替換文本隨后被插入到程序中原來文本的位置
  3. 最后,再次對結果文件進行掃描,看它是否包含任何由#define定義的符號。如果包含,就重復上述處理過程

注:

  1. 宏參數和#define定義中可以出現其他#define`定義的符號。但是對于宏,不能出現遞歸
  2. 當預處理器搜索#define定義的符號的時候,字符串常量的內容并不被搜索

6. 宏和函數的對比

宏通常被應用于執行簡單的運算

如在兩個數中找出較大的一個時:

#define MAX(a, b) ((a)>(b)?(a):(b))

那么問題來了,為什么不用函數呢?

原因有兩個:

  1. 用于調用函數和從函數返回的代碼可能比執行這個小型計算工作所需要的時間更多。所以宏比函數在程序的規模和速度方面更勝一籌
  2. 函數的參數必須申明為特定的類型,所以函數只能在類型適合的表達式上使用。而宏的參數是跟類型無關的,任何類型都可以參與

和函數相比宏的劣勢:

  1. 每次使用宏時,一份宏定義的代碼將插入到程序中。若宏比較長,會大幅度增加程序的長度
  2. 宏是沒法調試的
  3. 宏由于與類型無關,也就不夠嚴謹
  4. 宏可能會帶來運算符優先級的問題,從而導致程序可能出錯

宏有時也能做到函數做不到的事情。比如:宏的參數可以出現類型,但是函數做不到
如:

#define MALLOC(num,type) \(type*)malloc(num *sizeof(type))MALLOC(10,int);//類型作為參數
//預處理器替換之后:
(int*)malloc(10*sizeof(int));

7. # 和##

7.1 #運算符

#運算符會將宏的一個參數轉換為字符串字面量。它僅被允許出現在帶參數的宏的替換列表中

示例如下:
當有一個變量int a=10;的時候,想打印出:the value of a is 10

#define PRINT(n) printf("the value of "#n" is %d",n);int a=10;
PRINT(a);//會打印出the value of a is 10

PRINT(a)會被預處理為:

printf("the value of ""a" " is %d", a);

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);					\
}

使用宏,定義不同函數:

GENERIC_MAX(int) //替換到宏體內后int##_max 生成了新的符號 int_max做函數名
GENERIC_MAX(float) //替換到宏體內后float##_max 生成了新的符號 float_max做函數名int main()
{//調用函數int m = int_max(2, 3);printf("%d\n", m);//輸出 3float fm = float_max(3.5f, 4.5f);printf("%f\n", fm);//輸出 4.500000return 0;
}

在實際開發過程中##的使用很少,很難有非常貼切的例子

8. 命名約定

函數與宏的使用語法很相似,所以語言無法幫我們區分二者。
所以我們要有的一個習慣是:
把宏名全部大寫;函數名不要全部大寫

9. #undef

如果現存的一個宏名字需要被重新定義,那么它需要先被移除。這時我們就要用到了#undef

如下:

#undef NAME

10. 命令行定義

許多C 的編譯器提供了?種能力,允許在命令行中定義符號。用于啟動編譯過程。

例如:當我們根據同?個源文件要編譯出一個程序的不同版本的時候,這個特性有點用處。(假定某個程序中聲明了?個某個長度的數組,如果機器內存有限,我們需要?個很小的數組,但是另外?個機器內存大些,我們需要?個數組能夠大些。)

#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 環境演?
gcc -D ARRAY_SIZE=10 programe.c

11. 條件編譯

在編譯一個程序的時候我們要將一條語句(或一組語句)編譯或者編譯是很方便的。我們可以用條件編譯語句

比如:
調試性的代碼,刪除可惜,保留?礙事,所以我們可以選擇性的編譯

#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]);//為了觀察數組是否賦值成功。 #endif //__DEBUG__}return 0;
}

常見的條件編譯指令:

1.
#if 常量表達式//...
#endif//常量表達式由預處理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__//..
#endif2.多個分?的條件編譯
#if 常量表達式//...
#elif 常量表達式//...
#else//...
#endif3.判斷是否被定義
#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol4.嵌套指令
#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. 頭文件的包含

12.1 本地文件包含

#include"filename"

查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找庫函數頭文件一樣在標準位置查找頭文件

如果找不到就提示編譯錯誤

linux環境下標準頭文件的路徑:

/usr/include

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

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

12.2 庫文件包含

#include<filename.h>

查找頭文件直接去標準路徑下去查找,如果找不到就提示編譯錯誤。
這樣是不是可以說,對于庫?件也可以使用 “” 的形式包含?

答案是肯定的,可以,但是這樣做查找的效率就低些,當然這樣也不容易區分是庫?件還是本地?件了。

12.3 重復包含問題

test.h

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

test.c

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

test.c文件中將test.h包含5次,那么test.h文件的內容將會被拷貝5份在test.c

如果test.h 文件比較大,這樣預處理后代碼量會劇增。如果?程比較大,有公共使用的頭文件,被大家都能使用,用不做任何的處理,那么后果真的不堪設想

如何解決頭文件被重復引入的問題?
答:條件編譯

每個頭?件的開頭寫:

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

#pragma once

這樣就避免了頭文件的重復引入

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

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

相關文章

【QT】使用QT幫助手冊找控件樣式

選擇幫助—》輸入stylesheet(小寫)—》選擇stylesheet—》右側選擇Qt Style Sheets Reference 2.使用CtrlF—》輸入要搜索的控件—》點擊Customizing QScrollBar 3.顯示參考樣式表–》即可放入QT-designer的樣式表中

SQL知識合集(二):函數篇

TRIM函數 作用&#xff1a;去掉字符串前后的空格 SELECT * FROM your_table_name WHERE TRIM(column_name) ; COALESCE函數 作用&#xff1a;返回其參數中的第一個非 NULL 值。它可以接受多個參數&#xff0c;并從左到右依次評估這些參數&#xff0c;直到找到第一個非 NUL…

Cursor 工具項目構建指南: Uniapp Miniprogram 環境下的 Prompt Rules 約束

簡簡單單 Online zuozuo: 簡簡單單 Online zuozuo 簡簡單單 Online zuozuo 簡簡單單 Online zuozuo 簡簡單單 Online zuozuo :本心、輸入輸出、結果 簡簡單單 Online zuozuo : 文章目錄 Cursor 工具項目構建指南: Uniapp Miniprogram 環境下的 Prompt Rules 約束前言項目簡…

Java轉Go日記(六十):gin其他常用知識

1. 日志文件 package mainimport ("io""os""github.com/gin-gonic/gin" )func main() {gin.DisableConsoleColor()// Logging to a file.f, _ : os.Create("gin.log")gin.DefaultWriter io.MultiWriter(f)// 如果需要同時將日志寫入…

cocos單例工廠和自動裝配

cocos單例工廠和自動裝配 1 單例工廠 1.1 分析 實例字典 原理很簡單&#xff0c;只是一個map&#xff0c;確保每個類只保留一個實例&#xff1b; private static _instances new Map<string, any>();獲取與存儲實例 這邊使用的方式是生成一個唯一的id存儲在類上&…

django paramiko 跳轉登錄

在使用Django框架結合Paramiko進行SSH遠程操作時&#xff0c;通常涉及到自動化腳本的執行&#xff0c;比如遠程服務器上的命令執行、文件傳輸等。如果你的需求是“跳轉登錄”&#xff0c;即在登錄遠程服務器后&#xff0c;再通過該服務器的SSH連接跳轉到另一臺服務器&#xff0…

《C++初階之類和對象》【命名空間 + 輸入輸出 + 缺省參數 + 函數重載】

【命名空間 輸入&輸出 缺省參數 函數重載】目錄 前言&#xff1a;---------------hello world---------------比較C語言和C的第一個程序&#xff1a;hello word ---------------命名空間---------------什么是命名空間&#xff1f;怎么使用命名空間&#xff1f;怎么定義…

[USACO1.5] 八皇后 Checker Challenge Java

import java.util.*;public class Main {// 標記 對角線1&#xff0c;對角線2&#xff0c;所在x軸 是否存在棋子static boolean[] d1 new boolean[100], d2 new boolean[100], d new boolean[100]; static int n, ans 0;static int[] arr new int[14]; // 記錄一輪棋子位置…

云服務器Xshell登錄拒絕訪問排查

根據你的描述&#xff0c;使用Xshell 8登錄云服務器時顯示“拒絕訪問”&#xff0c;可能涉及多個原因。以下結合搜索結果整理出排查和解決方法&#xff0c;按優先級排序&#xff1a; 一、檢查基礎網絡與端口連通性 本地網絡與服務器IP是否可達 在本地電腦的CMD中執行 ping 服務…

Python爬蟲實戰:研究urlunparse函數相關技術

1. 引言 1.1 研究背景與意義 在當今信息爆炸的時代,互聯網上的數據量呈現出指數級增長。如何從海量的網頁數據中高效地獲取有價值的信息,成為了學術界和工業界共同關注的問題。網絡爬蟲作為一種自動獲取網頁內容的技術,能夠按照預定的規則遍歷互聯網上的網頁,并提取出所需…

Spring AI學習一

隨著Chatpt的火爆&#xff0c;現在Spring官方也開始支持AI了并推出了Spring AI框架&#xff0c;目前還沒發布正式版本&#xff0c;這里可以先看一下官方依賴的版本。 Spring官網地址可以看這里&#xff1a;Spring | Home 目前官網上是有這兩個版本&#xff1a;1.0.0和1.1.0-SN…

reverse筆記

一&#xff0c;strcat的使用方法&#xff08;在攻防世界中刷題時遇到的&#xff09; 二&#xff0c;殼&#xff08;做題遇到過但是一直不是很理解&#xff0c;今天查了一下&#xff09; 殼是一種軟件保護技術&#xff0c;能夠防止程序被輕易地分析和修改。 總而言之&#xff0…

spring4第7-8課-AOP的5種通知類型+切點定義詳解+執行順序

繼續學習&#xff0c;方便自己復查記錄 ①AOP簡介&#xff1a; 面向切面編程(也叫面向方面編程)&#xff1a;Aspect Oriented Programming(AOP)。 Spring框架中的一個重要內容。。 通過預編譯方式和運行期間動態代理實現在不修改源代碼的情況下給程序動態統一添加功能…

EscapeX:去中心化游戲,開啟極限娛樂新體驗

VEX 平臺推出全新去中心化游戲 EscapeX&#xff08;數字逃脫&#xff09;&#xff0c;創新性地將大逃殺玩法與區塊鏈技術相融合。用戶不僅能暢享緊張刺激的解謎過程&#xff0c;更能在去中心化、公正透明的環境中參與游戲。EscapeX 的上線&#xff0c;為 VEX 生態注入全新活力&…

Multi Agents Collaboration OS:Web DeepSearch System

背景&#xff1a;多智能體協作驅動網絡信息處理的范式革新 隨著大型語言模型&#xff08;LLM&#xff09;能力的突破性進展&#xff0c;人工智能正從“單點賦能”向“系統協同”演進。傳統單一智能體在復雜業務場景中逐漸顯露局限&#xff1a;面對需多維度知識整合、動態任務拆…

React 第五十三節 Router中 useRouteError 的使用詳解和案例分析

前言 useRouteError 是 React Router v6.4 引入的關鍵錯誤處理鉤子&#xff0c;用于在 路由錯誤邊界&#xff08;Error Boundary&#xff09; 中獲取路由操作過程中發生的錯誤信息。 它提供了優雅的錯誤處理機制&#xff0c;讓開發者能夠創建用戶友好的錯誤界面。 一、useRou…

[arthas]arthas安裝使用

arthas是阿里開源的一個java線上監控以及診斷工具&#xff0c;在docker容器中我們無需重啟服務&#xff0c;也不用更改代碼&#xff0c;就可以完成對應用內存、線程、日志級別的修改、方法調用的出入參、異常監測、執行耗時等&#xff0c;xxxx.xxxx.xxxxx為脫敏內容 1. 在docke…

Flask-Babel 使用示例

下面創建一個簡單的 Flask-Babel 示例&#xff0c;展示如何在 Flask 應用中實現國際化和本地化功能。這個示例將包括多語言支持&#xff08;中文和英文&#xff09;、語言切換功能以及翻譯文本的使用。 項目結構 我們將創建以下文件結構&#xff1a; 1. 首先&#xff0c;創…

[論文閱讀] 軟件工程 | 量子計算如何賦能軟件工程(Quantum-Based Software Engineering)

arXiv:2505.23674 [pdf, html, other] Quantum-Based Software Engineering Jianjun Zhao Subjects: Software Engineering (cs.SE); Quantum Physics (quant-ph) 量子計算如何賦能軟件工程 我們在開發軟件時&#xff0c;常常會遇到一些棘手的問題。比如&#xff0c;為了確保軟…

Ansible 進階 - Roles 與 Inventory 的高效組織

Ansible 進階 - Roles 與 Inventory 的高效組織 如果說 Playbook 是一份完整的“菜譜”,那么 Role (角色) 就可以被看作是制作這道菜(或一桌菜)所需的標準化“備料包”或“半成品組件”。例如,我們可以有一個“Nginx Web 服務器安裝配置 Role”、“MySQL 數據庫基礎設置 Ro…