首页 > 其他分享 >《c和指针》第14章 预处理器

《c和指针》第14章 预处理器

时间:2024-12-27 22:00:41浏览次数:5  
标签:__ 14 代码 定义 编译 处理器 endif 指针 define

第14章 预处理器

14.1 预定义符号

预处理器在编译之前会对源代码进行处理,它识别一些预定义符号,这些符号提供了有关编译环境和源文件的信息。常见的预定义符号有:

  • __LINE__:当前源代码文件中的行号,是一个整数常量。在调试时,它可以帮助定位代码中的错误位置。例如:
#include <stdio.h>
int main() {
    printf("This line number is %d\n", __LINE__);
    return 0;
}

运行上述代码,会输出当前printf语句所在的行号。

  • __FILE__:当前源代码文件的名称,是一个字符串常量。它有助于在多个源文件的项目中确定错误或日志的来源。
  • __DATE__:表示源代码被编译的日期,格式为 "Mmm dd yyyy",例如 "Aug 10 2023",是一个字符串常量。
  • __TIME__:表示源代码被编译的时间,格式为 "hh:mm:ss",是一个字符串常量。

14.2 #define

#define是预处理器指令,用于定义符号常量或宏。

14.2.1 宏

宏是一种代码替换机制,通过#define定义一个标识符,在编译预处理阶段,预处理器会将源代码中出现的该标识符替换为指定的文本。例如:

#define PI 3.14159

在后续代码中,所有出现PI的地方都会被替换为3.14159。宏不仅可以定义常量,还能定义更复杂的代码片段。例如:

#define SQUARE(x) ((x) * (x))

这里定义了一个宏SQUARE,它接受一个参数x,并将其替换为((x) * (x))。使用时,如int result = SQUARE(5);,在预处理后会变成int result = ((5) * (5));

14.2.2 #define替换

在预处理阶段,预处理器按照从左到右的顺序对源文件进行扫描,当遇到#define定义的标识符时,就会进行替换。替换过程简单直接,不会进行语法检查,所以在定义宏时要特别小心,确保替换后的代码在语法上是正确的。例如:

#define ADD(a, b) a + b
int result = ADD(3, 5) * 2;

这里宏ADD简单地将a + b替换进来,所以上述代码在预处理后变为int result = 3 + 5 * 2;,结果是13,而不是可能期望的(3 + 5) * 2 = 16。为了避免这种错误,在定义宏时要适当添加括号,如#define ADD(a, b) ((a) + (b))

14.2.3 宏与函数

宏和函数都可以实现代码复用,但它们有一些重要区别:

  • 调用方式:函数调用在运行时进行,需要传递参数、保存现场、跳转等操作,有一定的开销;而宏替换在编译预处理阶段完成,没有运行时开销。
  • 类型检查:函数调用会进行参数类型检查,不符合类型要求会报错;宏只是简单的文本替换,不进行类型检查。例如:
#define MAX(a, b) ((a) > (b)? (a) : (b))
int main() {
    int result1 = MAX(3, 5);
    double result2 = MAX(3.5, 2.1);
    return 0;
}

这里宏MAX可以用于不同类型的数据,而函数要实现同样功能则需要重载(在C++中)或使用泛型(C11引入的特性)。

  • 代码膨胀:宏替换会导致代码膨胀,因为每次使用宏都会将其展开;函数调用不会导致代码膨胀,无论调用多少次,函数代码只有一份。

14.2.4 带副作用的宏参数

如果宏的参数在表达式中有副作用(如自增、自减运算符),可能会导致意想不到的结果。例如:

#define MULTIPLY(a, b) ((a) * (b))
int num = 3;
int result = MULTIPLY(num++, num++);

这里宏展开后是((num++) * (num++)),由于自增运算符的副作用,不同编译器对num的求值顺序可能不同,导致结果不确定。所以在使用带副作用的参数时要特别小心。

14.2.5 命名约定

为了与变量和函数名区分开,通常宏名使用全大写字母命名,单词之间用下划线分隔。例如MAX_VALUEMIN_ELEMENT等,这样可以提高代码的可读性,让开发者一眼就能识别出宏。

14.2.6 #undef

#undef指令用于取消之前通过#define定义的宏。例如:

#define PI 3.14159
// 一些使用PI的代码
#undef PI
// 之后再使用PI会报错,因为它已被取消定义

#undef在需要临时改变宏定义或避免宏定义冲突时很有用。

14.2.7 命令行定义

在编译时,可以通过命令行参数定义宏。例如,在Linux系统下使用gcc编译器:

gcc -DDEBUG main.c

main.c中就可以使用DEBUG这个宏,例如:

#include <stdio.h>
int main() {
#ifdef DEBUG
    printf("Debug mode is on.\n");
#endif
    return 0;
}

这样通过命令行开关可以方便地控制代码的调试输出。

14.3 条件编译

条件编译允许根据一定条件决定是否编译源代码的一部分。

14.3.1 是否被定义

#ifdef#ifndef#endif指令用于检查某个宏是否被定义。

  • #ifdef:如果指定的宏已被定义,则编译后续代码块,直到遇到#endif。例如:
#define DEBUG
#ifdef DEBUG
    printf("This is a debug message.\n");
#endif
  • #ifndef:与#ifdef相反,如果指定的宏未被定义,则编译后续代码块。例如:
#ifndef VERSION
    #define VERSION "1.0"
#endif

#if defined()#if!defined()也能实现类似功能,且更灵活,可以与其他条件结合使用。例如:

#define DEBUG
#define RELEASE
#if defined(DEBUG) &&!defined(RELEASE)
    printf("Debug build.\n");
#elif defined(RELEASE) &&!defined(DEBUG)
    printf("Release build.\n");
#endif

14.3.2 嵌套指令

条件编译指令可以嵌套使用,以实现更复杂的条件判断。例如:

#define OS_WINDOWS
#define DEBUG

#ifdef OS_WINDOWS
    #ifdef DEBUG
        printf("Windows debug build.\n");
    #else
        printf("Windows release build.\n");
    #endif
#else
    #ifdef DEBUG
        printf("Non - Windows debug build.\n");
    #else
        printf("Non - Windows release build.\n");
    #endif
#endif

通过嵌套的条件编译,可以根据不同的操作系统和编译模式来生成不同的代码。

14.4 文件包含

文件包含指令#include用于将一个源文件的内容插入到另一个源文件中。

14.4.1 函数库文件包含

当包含系统提供的函数库头文件时,使用尖括号<>。例如:

#include <stdio.h>
#include <stdlib.h>

预处理器会在系统默认的库文件目录中查找这些头文件。

14.4.2 本地文件包含

对于自定义的头文件,使用双引号""。例如:

#include "myheader.h"

预处理器首先会在当前源文件所在目录中查找头文件,如果找不到,再到系统默认目录中查找。

14.4.3 嵌套文件包含

一个头文件可以包含其他头文件,形成嵌套包含。例如,main.c包含header1.h,而header1.h又包含header2.h。在处理嵌套包含时,要注意避免头文件的重复包含,可以使用#ifndef#define#endif来防止重复定义。例如在header1.h中:

#ifndef HEADER1_H
#define HEADER1_H
// 头文件内容
#include "header2.h"
#endif

14.5 其他指令

除了上述常见的预处理器指令,还有一些其他指令:

  • #error:用于在编译时生成错误信息。例如:
#ifndef __STDC__
#error This code requires a standard C compiler.
#endif

如果__STDC__未定义(即不是标准C编译器),预处理器会输出错误信息,终止编译。

  • #pragma:提供了一种与编译器相关的指令,用于设置编译器的特定选项。例如,#pragma once在一些编译器中用于确保头文件只被包含一次,与#ifndef#define#endif功能类似,但更简洁。不同编译器对#pragma的支持和具体用法可能不同。

14.6 总结

预处理器是C语言编译过程中的重要组成部分,通过预定义符号、#define、条件编译、文件包含等指令,为程序员提供了灵活控制代码编译的能力。预定义符号提供了编译环境和源文件的信息;#define用于定义常量和宏,实现代码的替换和复用;条件编译允许根据不同条件生成不同的代码;文件包含则方便了代码的模块化和复用。合理使用预处理器指令可以提高代码的可维护性、可移植性和可读性,但也要注意避免宏定义中的错误和潜在问题,如代码膨胀、副作用等。掌握预处理器的使用是成为熟练C语言开发者的重要一步。

标签:__,14,代码,定义,编译,处理器,endif,指针,define
From: https://blog.csdn.net/qq_40844444/article/details/144777874

相关文章

  • 9.4-14域横向-CobaltStrike&SDN&RDP
    域横向RDP-mimikatz1、RDP明文密码链接2、RDP密文hash链接域横向SPN服务-探针,请求,导出,破解,重写SPN扫描当计算机加入域时,主SPN会自动添加到域的计算机账号的ServicePrincipalName属性中。在安装新的服务后,SPN也会被记录在计算机账号的相应属性中。SPN扫描也称为“扫描Kerber......
  • Hadoop YARN:调度性能优化实践14
      背景YARN作为Hadoop的资源管理系统,负责Hadoop集群上计算资源的管理和作业调度。美团的YARN以社区2.7.1版本为基础构建分支。目前在YARN上支撑离线业务、实时业务以及机器学习业务。离线业务主要运行的是HiveonMapReduce,SparkSQL为主的数据仓库作业。实时业务主要运......
  • 148. 排序链表
    题目链接解题思路:在链表上使用排序算法。注意,不能使用快排,因为快排的最差时间复杂度是O(n^2),数组形式的快排,以随机数划分能够得到O(n*logn),但是链表的形式,不太好以随机数的方式划分。所以最好的排序方法是使用归并排序。先用快慢指针,将链表分成两部分,然后两部分分别归并排......
  • 146. LRU 缓存
    题目链接解题思路:用链表+哈希表。链表从头串到尾,淘汰时,从尾部开始淘汰。每次get时,如果找到了,则把这个节点移到头部。每次put,新建一个节点,放在头部,如果容量不够了,则淘汰尾部的数据。哈希表的作用是,能快速通过key找到链表中的节点。代码classLRUCache:classNode:......
  • 14.列表框文本域和文件域
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>列表框文本域和文件域</title></head><body><form><p>国家:<!--下拉框<sele......
  • Lockpass(密码管理器) v0.0.14
    Lockpass是一款密码管理工具,是作者参考1password做的,目前功能尚不完善,作者会在使用过程中不断优化。软件采用双密码机制,主密码和25字符的key(软件生成)组合成超强密码。软件有随机密码生成工具。保险库功能可以将不同的密码信息分类保存,支持切换不同的账号。软件特色双密码......
  • 141. 环形链表
    题目链接解题思路:一个快指针,一个慢指针,如果二者相等了,说明有环。如果快指针为空了,说明没环代码#Definitionforsingly-linkedlist.#classListNode:#def__init__(self,x):#self.val=x#self.next=NoneclassSolution:defhas......
  • 140. 单词拆分 II
    题目链接解题思路:和139题类似,只不过要把所有的结果存起来而已代码classSolution:defcheck(self,s:str,i:int,s2:str)->int:ifi+len(s2)>len(s):return-1forchins2:ifch!=s[i]:......
  • rust学习十五.1、智能指针基本概念
    本文没有什么需要特别详细阐述的内容,基本都是一些基础性的概念和一些空洞的定义。一、基本概念指针-拥有一个指向一个堆数据的地址的变量。本身是变量,但其数据就是一个地址。智能指针-一种特别的指针(也是变量),除了指向数据的地址,通常还具有元数据和其它功能。智能指针通常使......
  • GA/T1400视图库平台EasyCVR关于网络故障排查的思路和常用排错方法
    在当今这个高度依赖网络的时代,无论是企业还是个人,网络的稳定性和可靠性都至关重要。然而,网络故障的发生往往不可避免,它们可能会影响到我们的日常工作和沟通效率。对于经常与电脑、交换机等网络设备打交道的朋友来说,掌握一些基本的网络故障排查和处理技巧是非常必要的。本文将为大......