预处理,宏定义

合集下载

宏的底层原理

宏的底层原理

宏的底层原理宏的底层原理指的是宏的实现原理和运行机制。

宏是一种代码生成工具,它允许程序员在编译时根据一定的规则自动生成代码。

在C/C++中,宏是由预处理器进行处理的,预处理器会在编译之前对代码中的宏进行展开。

宏定义通过预处理指令`#define`来完成,它的基本语法是`#define 宏名替换文本`。

在代码中使用宏时,预处理器会将宏名替换为对应的文本。

宏展开是宏的核心操作,它是在预处理阶段完成的。

当编译器遇到一个宏调用时,它会首先检查是否有与该宏名对应的宏定义。

如果有,编译器就会将宏调用替换为宏定义中的替换文本,并继续编译剩下的代码。

宏的替换文本可以包含表达式、语句和其他宏调用。

在替换过程中,编译器会按照一定的规则将这些内容展开。

具体展开的过程如下:1. 参数替换:如果宏定义中包含参数,那么在展开宏时需要将实际参数替换到宏的参数位置。

参数替换可以通过宏定义的形参和实参之间的对应关系来完成。

2. 预处理运算符替换:宏定义可以包含一些预处理运算符,例如`#`、``等。

在宏展开过程中,编译器会根据这些运算符的规则来处理宏定义中的这些内容。

3. 嵌套展开:宏的替换文本可以包含其他宏调用。

在展开宏时,如果替换文本中还存在其他宏调用,那么编译器会继续展开这些宏调用,直到所有的宏调用都被展开完毕。

4. 宏定义自身的递归调用:宏的替换文本中可以包含对宏自身的调用。

当遇到这种情况时,编译器会进行递归调用,直到达到指定的递归层数或发生宏展开错误。

需要注意的是,宏展开是在编译时完成的,它并不会像函数调用那样引入额外的运行时开销。

这使得宏在一些需要频繁调用的场景中非常有用,例如计算简单的表达式或对数据进行简单的操作。

然而,宏也具有一些局限性,例如它不能返回值、不能处理复杂的逻辑和数据结构等。

总的来说,宏的底层原理是通过预处理器对代码中的宏进行展开,将宏调用替换为宏定义中的替换文本。

宏展开过程中考虑了参数替换、预处理运算符替换、嵌套展开和宏定义自身的递归调用等因素。

C语言中的三种预处理功能

C语言中的三种预处理功能

C语言中的三种预处理功能C语言中的三种预处理功能导语:预处理指令是以#号开头的代码行。

#号必须是该行除了任何空白字符外的第一个字符。

#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。

整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。

下面是C语言三种预处理功能,欢迎阅读:指令用途# 空指令,无任何效果#include 包含一个源代码文件#define 定义宏#undef 取消已定义的宏#if 如果给定条件为真,则编译下面代码#ifdef 如果宏已经定义,则编译下面代码#ifndef 如果宏没有定义,则编译下面代码#elif 如果前#if条件不为真,当前条件为真,则编译下面代码,其实就是else if的简写#endif 结束一个#if……#else条件编译块#error 停止编译并显示错误信息特殊符号预编译程序可以识别一些特殊的符号。

预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

注意,是双下划线,而不是单下划线。

FILE 包含当前程序文件名的字符串LINE 表示当前行号的整数DATE 包含当前日期的字符串STDC 如果编译器遵循ANSI C标准,它就是个非零值TIME 包含当前时间的字符串//例#includeint main(){printf("Hello World! ");printf("%s ",__FILE__);printf("%d ",__LINE__);return 0;}1. 宏定义不带参数宏定义又称为宏代换、宏替换,简称“宏”。

预处理(预编译)工作也叫做宏展开:将宏名替换为字符串,即在对相关命令或语句的含义和功能作具体分析之前就要换。

格式:#define 标识符字符串其中标识符就是所谓的符号常量,也称为“宏名”。

例:#define Pi 3.1415926//把程序中出现的Pi全部换成3.1415926 说明:(1)宏名一般用大写;(2)使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。

c++ 宏定义规则

c++ 宏定义规则

c++ 宏定义规则
C++中的宏定义是通过预处理器指令#define来实现的。

宏定义的一般规则包括以下几点:
1. 格式,宏定义的格式为#define 宏名值。

宏名是标识符,值可以是常量、表达式或函数。

例如,#define PI 3.14159。

2. 不带分号,宏定义不需要以分号结尾,因为它本身就是一个预处理器指令。

3. 命名规则,宏名遵循标识符命名规则,通常使用大写字母来表示宏,以便与变量区分开来。

4. 作用域,宏定义的作用域是从定义点到文件结束,它是全局的,可以在文件的任何地方使用。

5. 宏替换,在预处理阶段,编译器会将代码中出现的宏名替换为宏值。

例如,#define SQUARE(x) ((x)(x)),在代码中使用SQUARE(3)会被替换为((3)(3))。

6. 参数化宏,宏定义可以带参数,通过宏参数可以实现代码的通用性和灵活性。

例如,#define MAX(x, y) ((x) > (y) ? (x) : (y))。

7. 注意事项,在使用宏定义时,需要注意宏替换可能带来的副作用,例如参数多次被计算、宏值带有副作用等问题。

总之,宏定义是C++中一种强大的预处理器功能,可以用来定义常量、简化代码、实现通用算法等,但在使用时需要注意规范和潜在的问题。

希望我的回答能够帮助你更全面地理解C++中宏定义的规则。

c语言的预处理指令分3种  1宏定义  2条件编译  3文件包含

c语言的预处理指令分3种  1宏定义  2条件编译  3文件包含

c语⾔的预处理指令分3种 1宏定义 2条件编译 3⽂件包含宏简介1.C语⾔在对源程序进⾏编译之前,会先对⼀些特殊的预处理指令作解释(⽐如之前使⽤的#include⽂件包含指令),产⽣⼀个新的源程序(这个过程称为编译预处理),之后再进⾏通常的编译所有的预处理指令都是以#开头,并且结尾不⽤分号2.预处理指令分3种 1> 宏定义 2> 条件编译 3> ⽂件包含3.预处理指令在代码翻译成0和1之前执⾏4.预处理的位置是随便写的5.预处理指令的作⽤域:从编写指令的那⼀⾏开始,⼀直到⽂件结尾,可以⽤#undef取消宏定义的作⽤6.宏名⼀般⽤⼤写或者以k开头,变量名⼀般⽤⼩写 宏定义可以分为2种:不带参数的宏定义和带参数的宏定义。

⼀、不带参数的宏定义1.⼀般形式#define 宏名字符串⽐如#define ABC 10右边的字符串也可以省略,⽐如#define ABC2.作⽤它的作⽤是在编译预处理时,将源程序中所有"宏名"替换成右边的"字符串",常⽤来定义常量.3.使⽤习惯与注意1> 宏名⼀般⽤⼤写字母,以便与变量名区别开来,但⽤⼩写也没有语法错误2> 对程序中⽤双引号扩起来的字符串内的字符,不进⾏宏的替换操作。

3> 在编译预处理⽤字符串替换宏名时,不作语法检查,只是简单的字符串替换。

只有在编译的时候才对已经展开宏名的源程序进⾏语法检查4> 宏名的有效范围是从定义位置到⽂件结束。

如果需要终⽌宏定义的作⽤域,可以⽤#undef命令5> 定义⼀个宏时可以引⽤已经定义的宏名#define R 3.0#define PI 3.14#define L 2*PI*R#define S PI*R*R举例1 #include <stdio.h>2#define COUNT 434int main()5 {6char *name = "COUNT";78 printf("%s\n", name);910int ages[COUNT] = {1, 2, 67, 89};1112#define kCount 41314for ( int i = 0; i<COUNT; i++) {15 printf("%d\n", ages[i]);16 }1718// 从这⾏开始,COUNT这个宏就失效19#undef COUNT2021//int a = COUNT 写这个报错2223return0;24 }⼆、带参数的宏定义1.⼀般形式#define 宏名(参数列表) 字符串2.作⽤在编译预处理时,将源程序中所有宏名替换成字符串,并且将字符串中的参数⽤宏名右边参数列表中的参数替换3.使⽤注意1> 宏名和参数列表之间不能有空格,否则空格后⾯的所有字符串都作为替换的字符串2> 带参数的宏在展开时,只作简单的字符和参数的替换,不进⾏任何计算操作。

C语言 数据类型与宏定义

C语言  数据类型与宏定义

6
注意: 注意:
1)宏替换时仅仅是将源程序中与宏名相同的标识符替换 成宏的内容文本,并不对宏的内容文本做任何处理。 2)C语言程序员通常用大写字母来定义宏名,以便与变 量名相区别。 3 3)宏定义时,如果字符串太长,需要写多行,可以在行 尾使用反斜线“\”续行符。例如: #define LONG_STRING "this is a very long \ string that is used as an example" 注意双引号包括在替代的内容之内。
11
(9)在定义宏时,如果宏是一个表达式,那么一定要将这个表 )在定义宏时,如果宏是一个表达式, 达式用() ()括起来 达式用()括起来
#define X 10 #define Y 20 #define Z X+Y void main() { int a=2,b=3; a*=Z; b=b*Z; printf("a=%d,b=%d\n", a,b); }
19
文件包含两种格式
1)使用尖括号<>:直接到系统指定的“文件包含目 录去查找被包含的文件。 2)使用双引号” ”:系统首先到当前目录下查找被 包含文件,如果没找到,再到系统指定的“文件包 含目录”去查找。一般情况下,使用双引号比较保 险。 注意 ” ”之间可以指定包含文件的路径。如 #include”c:\\prg\\p1.h”表示将把C盘prg目录下的 文件p1.h的内容插入到此处(字符串中要表示‘\’, 必须使用‘\\’)。
22
void main() { 程序实例如下 float price1,price2,sumprice; 源程序 scanf("%f%f",&price1,&price 2); #define USA 0 sumprice=price1+price2; #define ENGLAND 1 printf("sum=%.2f%s",sumpri #define FRANCE 2 #define ACTIVE_COUNTRY ce,currency); } USA #if ACTIVE_COUNTRY==USA char *currency="dollar"; //有效 #elif ACTIVE_COUNTRY==ENG LAND char *currency="pound"; #else char *currency="france"; #endif

简述汇编语言程序运行步骤

简述汇编语言程序运行步骤

简述汇编语言程序运行步骤汇编语言程序是一种低级语言,它直接操作计算机硬件资源。

了解汇编语言程序运行步骤对于理解计算机的底层工作原理以及编写高效的代码至关重要。

本文将简述汇编语言程序的运行步骤,以帮助读者对该过程有一个清晰的了解。

汇编语言程序的运行步骤可以大致分为如下几个环节:预处理、编译、汇编、链接和运行。

以下将详细描述每个步骤的功能和过程。

1. 预处理:在预处理环节,汇编语言程序会经过预处理器的处理。

预处理器主要负责处理宏定义、头文件包含、条件编译等指令,以生成一份经过预处理的源代码文件。

预处理环节的目标是去除源代码中的注释、展开宏定义、处理条件编译等操作,为后续步骤做准备。

2. 编译:编译是将预处理得到的源代码转化为汇编语言代码的过程。

编译器将预处理后的源代码进行词法分析、语法分析、语义分析等操作,生成相应的汇编语言代码。

编译器还会进行优化操作,以提高程序的执行效率。

3. 汇编:汇编是将编译得到的汇编语言代码转化为机器代码的过程。

在这一步骤中,汇编器将汇编语言代码转化为计算机可以理解和执行的二进制指令。

4. 链接:链接是将多个目标文件链接在一起,形成一个可执行文件的过程。

在这一步骤中,链接器将编译得到的目标文件与系统库文件进行链接,解析符号引用,生成最终的可执行文件。

链接的目标是生成一个包含所有必要信息的可执行文件,以便能够正确地执行程序。

5. 运行:运行是将可执行文件加载到计算机的内存中,并执行其中的指令。

在运行过程中,处理器按照指令的顺序执行程序,对数据进行相应的处理,最终得到程序的结果。

以上即为汇编语言程序的运行步骤。

通过对这些步骤的简要描述,读者可以对程序的整个运行过程有一个初步的了解。

深入理解每个步骤的原理和细节,对于编写高效的汇编语言程序至关重要。

因此,建议读者在掌握基本步骤的基础上,进一步学习汇编语言的相关知识,以提升自己的编程能力。

总结起来,汇编语言程序的运行步骤包括预处理、编译、汇编、链接和运行。

gcc编译过程的四个阶段

gcc编译过程的四个阶段

gcc编译过程的四个阶段1. 预处理(Preprocessing):预处理是编译过程的第一阶段。

预处理器负责对原始源文件进行处理,主要完成以下几个任务:-处理宏定义:预处理器会将源文件中的宏定义展开为相应的代码片段,并将其保存在一个临时文件中。

-处理条件编译指令:预处理器会根据条件编译指令的结果决定是否包含或排除一些代码片段。

- 处理#include指令:预处理器会将源文件中的#include指令所引用的其他文件插入到该指令所在的位置。

-移除注释:预处理器会删除源文件中的注释。

预处理后的文件成为扩展名为.i的中间文件,它包含了所有宏定义及展开后的代码。

编译是编译过程的第二阶段。

编译器将预处理生成的中间文件进行词法分析、语法分析和语义分析,生成相应的汇编代码。

主要过程如下:- 词法分析器将预处理生成的中间文件分解为一个个的词法单元(Token)。

- 语法分析器根据词法单元组织成的语法结构,生成抽象语法树(Abstract Syntax Tree,AST)。

-语义分析器对抽象语法树进行语义检查,包括类型检查和语义错误检查,确保程序的语义正确。

编译器将生成的汇编代码保存为扩展名为.s的汇编文件。

3. 汇编(Assembling):汇编是编译过程的第三阶段。

汇编器(Assembler)将编译器生成的汇编代码翻译成机器码,并生成目标文件。

具体过程如下:- 汇编器将汇编代码中的每一条汇编指令翻译成对应的机器码,同时为每个标号(Label)生成对应的地址。

-汇编器进行符号解析,将代码中引用的变量和函数与目标文件中的符号表进行匹配,生成正确的指令和地址。

汇编器将目标文件保存为扩展名为.o的目标文件。

4. 链接(Linking):链接是编译过程的最后阶段。

链接器(Linker)将目标文件与其他必要的库文件进行合并,生成最终的可执行文件或动态链接库。

主要过程如下:-链接器将目标文件中的函数和变量引用与其他目标文件中的定义进行匹配,解析外部引用,生成相应的引用表。

C语言中的预处理器和宏定义

C语言中的预处理器和宏定义

C语言中的预处理器和宏定义在C语言中,预处理器和宏定义是两个重要的概念。

它们可以帮助我们在编写程序时实现更高效、更灵活的代码处理。

本文将详细介绍C语言中的预处理器和宏定义的作用、使用方法和常见应用场景。

一、预处理器的作用预处理器是C语言中的一个特殊程序,它在编译之前对源代码进行处理。

其主要作用有以下几个方面:1. 宏替换:预处理器可以通过宏替换将源代码中的宏标识符替换为对应的宏定义。

这样可以提高代码的可读性和维护性,同时也可以减少代码冗余,提高代码复用性。

2. 文件包含:预处理器可以通过#include指令将其他文件的内容包含到当前文件中。

这样可以将代码分散到不同的文件中进行编写,提高代码的模块化,方便代码的重用和维护。

3. 条件编译:预处理器可以通过条件编译指令(如#ifdef、#ifndef、#if、#elif)根据条件判断来选择性地编译特定的代码片段。

这样可以根据不同的编译环境或需求,编写针对性的代码。

二、宏定义的作用宏定义是预处理器中的一个重要功能,可以将一段代码或表达式定义为一个宏,并在代码中以宏名的形式调用。

宏定义的作用有以下几个方面:1. 代码复用:宏定义可以将一段常用的代码片段定义为宏,并在代码中多次调用,提高代码的复用性和可维护性。

例如,可以将一段打印调试信息的代码定义为宏,并在需要时进行调用。

2. 简化代码:宏定义可以将一些繁琐的操作或表达式简化为一个简单的宏调用,提高代码的可读性和整洁性。

例如,可以将一些复杂的数学计算过程定义为宏,并在代码中直接调用,减少代码的冗余。

3. 编译时计算:宏定义是在预处理阶段进行展开的,因此可以用宏实现一些在编译时期就能确定的常量计算。

例如,可以通过宏定义来计算圆的周长、面积等,避免在运行时进行重复的计算。

三、预处理器和宏定义的使用方法在C语言中,预处理器和宏定义的使用方法较为简单,具体步骤如下:1. 宏定义的格式:宏定义的格式通常为:#define 宏名称替换内容。

C语言文件的编译到执行的四个阶段

C语言文件的编译到执行的四个阶段

C语言文件的编译到执行的四个阶段C语言程序的编译到执行过程可以分为四个主要阶段:预处理、编译、汇编和链接。

1.预处理:在这个阶段,编译器会执行预处理指令,将源代码中的宏定义、条件编译和包含其他文件等操作进行处理。

预处理器会根据源代码中的宏定义替换相应的标识符,并去除注释。

预处理器还会将包含的其他文件插入到主文件中,并递归处理这些文件。

处理后的代码被称为预处理后的代码。

2.编译:在这个阶段,编译器将预处理后的代码转换成汇编代码。

汇编代码是一种低级的代码,使用符号来表示机器指令。

编译器会对源代码进行词法分析、语法分析和语义分析,生成相应的中间代码。

中间代码是一种与特定硬件无关的代码表示形式,便于后续阶段的处理。

3.汇编:在这个阶段,汇编器将中间代码转化为机器可以执行的指令。

汇编器会将汇编代码翻译成二进制形式的机器指令,并生成一个目标文件。

目标文件包含了机器指令的二进制表示以及相关的符号信息。

4.链接:在C语言中,程序通常由多个源文件组成,每个源文件都经过了预处理、编译和汇编阶段得到目标文件。

链接器的作用就是将这些目标文件合并成一个可执行文件。

链接器会解析目标文件中的符号引用,找到其对应的定义并进行连接。

链接器还会处理库文件,将使用到的函数和变量的定义从库文件中提取出来并添加到目标文件中。

最终,链接器生成一个可以直接执行的可执行文件。

以上是C语言程序从编译到执行的四个阶段。

每个阶段都有特定的任务,并负责不同层次的代码转换和处理。

通过这四个阶段,C语言程序可以从源代码转换为机器能够执行的指令,并最终被计算机执行。

C语言预处理命令--宏定义

C语言预处理命令--宏定义

C语⾔预处理命令--宏定义⼀、宏讲解1、宏定义宏(Macro),是⼀种的称谓。

⾥的宏是⼀种(Abstraction),它根据⼀系列预定义的规则替换⼀定的⽂本模式。

或在遇到宏时会⾃动进⾏这⼀模式替换。

2、C语⾔宏定义的常规⽤法1) 定义符号常量#define PI 3.1415926#define MAX_N 100002) 定义傻⽠表达式(注意,定义的这种表达式⼀不⼩⼼很容易出现bug,下⽂会讲)#define S(a, b) a * b#define MAX(a, b) (a) > (b) ? (a) : (b)3) 定义代码段#define P(a) {\ printf("%d\n", a);\} ps:编译器对于宏的解析是很严谨的,只能⽀持⼀⾏解析,\是起连接作⽤,表⽰当⾏的宏代码与下⼀⾏宏连接在⼀起,使得编译器当成⼀⾏看待。

3、编译器预定义的宏在C语⾔中,我们有很多预定义的宏,就是C语⾔帮程序员预先定义好的宏,可以让我们使⽤。

宏说明__DATE__ ⽇期:Mmm dd yyyy__TIME__ 时间:hh:mm:ss__LINE__ 当前源⽂件的代码⾏号__FILE__ ⽂件名__func__ 函数名/⾮标准__FUNC__ 函数名/⾮标准__PRETTY_FUNCTION__ 更详细的函数信息/⾮标准4、预定义命令-条件式编译函数说明#ifdef DEBUG 是否定义了DEBUG宏#ifndef DEBUG 是否没有定义DEBUG宏#if MAX_N == 5 宏MAX_N是否等于5#elif MAX_N == 4 否则宏MAX_N是否等于4#else#endif5、预定义命令从上图可以看到: 预编译 将.c ⽂件转化成 .i⽂件 使⽤的gcc命令是:gcc –E 对应于预处理命令cpp 编译 将.c/.h⽂件转换成.s⽂件 使⽤的gcc命令是:gcc –S 对应于编译命令 cc –S 汇编 将.s ⽂件转化成 .o⽂件 使⽤的gcc 命令是:gcc –c 对应于汇编命令是 as 链接 将.o⽂件转化成可执⾏程序 使⽤的gcc 命令是: gcc 对应于链接命令是 ld 总结起来编译过程就上⾯的四个过程:预编译、编译、汇编、链接。

C语言预处理命令总结大全:宏定义

C语言预处理命令总结大全:宏定义

C语⾔预处理命令总结⼤全:宏定义C程序的源代码中可包括各种编译指令,这些指令称为预处理命令。

虽然它们实际上不是C语⾔的⼀部分,但却扩展了C程序设计的环境。

本节将介绍如何应⽤预处理程序和注释简化程序开发过程,并提⾼程序的可读性。

ANSI标准定义的C语⾔预处理程序包括下列命令:#define,#error,#include,#if,#else,#elif,#endif,#ifdef,#ifndef,#undef,#line,#pragma等。

⾮常明显,所有预处理命令均以符号#开头,下⾯分别加以介绍。

⼀ #define命令#define定义了⼀个标识符及⼀个串。

在源程序中每次遇到该标识符时,均以定义的串代换它。

ANSI标准将标识符定义为宏名,将替换过程称为宏替换。

命令的⼀般形式为:#define ID string注意:1该语句没有分号。

在标识符和串之间可以有任意个空格,串⼀旦开始,仅由⼀新⾏结束。

2宏名定义后,即可成为其它宏名定义中的⼀部分。

3 宏替换仅仅是以⽂本串代替宏标识符,前提是宏标识符必须独⽴的识别出来,否则不进⾏替换。

例如:#define XYZ this is a tes使⽤宏printf("XYZ");//该段不打印"this is a test"⽽打印"XYZ"。

因为预编译器识别出的是"XYZ"4如果串长于⼀⾏,可以在该⾏末尾⽤⼀反斜杠' \'续⾏。

#defineLONG_STRING"this is a very long\string that is used as an example"5 C语⾔程序普遍使⽤⼤写字母定义标识符。

6 ⽤宏代换代替实在的函数的⼀⼤好处是宏替换增加了代码的速度,因为不存在函数调⽤的开销。

但增加速度也有代价:由于重复编码⽽增加了程序长度。

编译预处理的三种形式

编译预处理的三种形式

编译预处理的三种形式1. 什么是编译预处理?在介绍编译预处理的三种形式之前,我们首先需要了解编译预处理的概念。

编译预处理是指在源代码被编译之前,对源代码进行一系列的预处理操作,以便能够更好地满足编译过程中的需求。

2. 宏替换2.1 什么是宏定义?宏定义是一种将一个标识符与一段代码片段绑定在一起的机制。

当源代码中出现宏定义的标识符时,编译器会将其替换为对应的代码片段。

2.2 宏替换的过程宏替换是指在编译阶段,将源代码中的宏定义进行替换的过程。

它的基本原理是以文本替换的方式进行,将宏定义中的标识符替换为对应的代码片段。

2.3 宏替换的例子假设我们有以下的宏定义:#define MAX(a, b) ((a) > (b) ? (a) : (b))那么在源代码中使用宏定义:int max = MAX(3, 5);经过宏替换之后,源代码变为:int max = ((3) > (5) ? (3) : (5));3. 文件包含3.1 什么是文件包含?文件包含是指在源代码中通过指令的方式将另一个文件的内容包含到当前文件中。

它可以帮助我们将代码模块化,提高代码的可维护性和复用性。

3.2 文件包含的指令在C语言中,我们可以使用以下的指令来进行文件包含:#include <filename.h>#include "filename.h"3.3 文件包含的例子假设我们有一个名为“utils.h” 的文件,其中定义了一些实用函数:#ifndef UTILS_H#define UTILS_Hint add(int a, int b);int subtract(int a, int b);#endif在源代码中使用文件包含:#include "utils.h"int main() {int result = add(3, 5);return 0;}编译器在编译过程中会将文件包含的指令替换为对应文件的内容,因此在编译阶段,上述源代码等价于:int add(int a, int b);int subtract(int a, int b);int main() {int result = add(3, 5);return 0;}4. 条件编译4.1 什么是条件编译?条件编译是指根据指定的条件,选择性地包含不同的代码块。

C语言中的预编译宏定义

C语言中的预编译宏定义

C语言中的预编译宏定义C语言中的预编译宏定义导语:C初学者可能对预处理器没什么概念,这是情有可原,下面是C中的预编译宏定义,一起来学习下吧:(一) 预处理命令简介预处理命令由#(hash字符)开头, 它独占一行, #之前只能是空白符. 以#开头的语句就是预处理命令, 不以#开头的语句为C中的代码行. 常用的预处理命令如下:#define 定义一个预处理宏#undef 取消宏的定义#include 包含文件命令#include_next 与#include相似, 但它有着特殊的用途#if 编译预处理中的条件命令, 相当于C语法中的if语句#ifdef 判断某个宏是否被定义, 若已定义, 执行随后的语句#ifndef 与#ifdef相反, 判断某个宏是否未被定义#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif 之后的语句, 相当于C语法中的else-if#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else 之后的语句, 相当于C语法中的else#endif #if, #ifdef, #ifndef这些条件命令的结束标志.defined 与#if, #elif配合使用, 判断某个宏是否被定义#line 标志该语句所在的行号# 将宏参数替代为以参数值为内容的字符窜常量## 将两个相邻的标记(token)连接为一个单独的标记#pragma 说明编译器信息#warning 显示编译警告信息#error 显示编译错误信息(二) 预处理的文法预处理并不分析整个源代码文件, 它只是将源代码分割成一些标记(token), 识别语句中哪些是C语句, 哪些是预处理语句. 预处理器能够识别C标记, 文件名, 空白符, 文件结尾标志.预处理语句格式: #command name(...) token(s)1, command预处理命令的名称, 它之前以#开头, #之后紧随预处理命令, 标准C允许#两边可以有空白符, 但比较老的编译器可能不允许这样. 若某行中只包含#(以及空白符), 那么在标准C中该行被理解为空白. 整个预处理语句之后只能有空白符或者注释, 不能有其它内容.2, name代表宏名称, 它可带参数. 参数可以是可变参数列表(C99).3, 语句中可以利用""来换行.e.g.# define ONE 1 /* ONE == 1 */等价于: #define ONE 1#define err(flag, msg) if(flag)printf(msg)等价于: #define err(flag, msg) if(flag) printf(msg)(三) 预处理命令详述1, #define#define命令定义一个宏:#define MACRO_NAME(args) tokens(opt)之后出现的MACRO_NAME将被替代为所定义的标记(tokens). 宏可带参数, 而后面的标记也是可选的.对象宏不带参数的宏被称为"对象宏(objectlike macro)"#define经常用来定义常量, 此时的宏名称一般为大写的字符串. 这样利于修改这些常量.e.g.#define MAX 100int a[MAX];#ifndef __FILE_H__#define __FILE_H__#include "file.h"#endif#define __FILE_H__ 中的宏就不带任何参数, 也不扩展为任何标记. 这经常用于包含头文件.要调用该宏, 只需在代码中指定宏名称, 该宏将被替代为它被定义的内容.函数宏带参数的宏也被称为"函数宏". 利用宏可以提高代码的运行效率: 子程序的调用需要压栈出栈, 这一过程如果过于频繁会耗费掉大量的CPU运算资源. 所以一些代码量小但运行频繁的代码如果采用带参数宏来实现会提高代码的运行效率.函数宏的参数是固定的情况函数宏的定义采用这样的方式: #define name( args ) tokens其中的args和tokens都是可选的. 它和对象宏定义上的区别在于宏名称之后不带括号.注意, name之后的左括号(必须紧跟name, 之间不能有空格, 否则这就定义了一个对象宏, 它将被替换为以(开始的字符串. 但在调用函数宏时, name与(之间可以有空格.e.g.#define mul(x,y) ((x)*(y))注意, 函数宏之后的参数要用括号括起来, 看看这个例子:e.g.#define mul(x,y) x*y"mul(1, 2+2);" 将被扩展为: 1*2 + 2同样, 整个标记串也应该用括号引用起来:e.g.#define mul(x,y) (x)*(y)sizeof mul(1,2.0) 将被扩展为 sizeof 1 * 2.0调用函数宏时候, 传递给它的参数可以是函数的返回值, 也可以是任何有意义的语句:e.g.mul (f(a,b), g(c,d));e.g.#define (stmt) stmt( a=1; b=2;) 相当于在代码中加入 a=1; b=2 .( a=1, b=2;) 就有问题了: 预处理器会提示出错: 函数宏的参数个数不匹配. 预处理器把","视为参数间的分隔符.((a=1, b=2;)) 可解决上述问题.在定义和调用函数宏时候, 要注意一些问题:1, 我们经常用{}来引用函数宏被定义的内容, 这就要注意调用这个函数宏时的";"问题.example_3.7:#define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp}如果这样调用它: "swap(1,2);" 将被扩展为: { unsigned long _temp=1; 1=2; 2=_tmp};明显后面的;是多余的, 我们应该这样调用: swap(1,2)虽然这样的调用是正确的, 但它和C语法相悖, 可采用下面的方法来处理被{}括起来的内容:#define swap(x,y)do { unsigned long _temp=x; x=y; y=_tmp} while (0)swap(1,2); 将被替换为:do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);在Linux内核源代码中对这种do-while(0)语句有这广泛的应用.2, 有的函数宏是无法用do-while(0)来实现的, 所以在调用时不能带上";", 最好在调用后添加注释说明.eg_3.8:#define incr(v, low, high)for ((v) = (low),; (v) <= (high); (v)++)只能以这样的形式被调用: incr(a, 1, 10) /* increase a form 1 to 10 */函数宏中的参数包括可变参数列表的情况C99标准中新增了可变参数列表的内容. 不光是函数, 函数宏中也可以使用可变参数列表.#define name(args, ...) tokens#define name(...) tokens"..."代表可变参数列表, 如果它不是仅有的'参数, 那么它只能出现在参数列表的最后. 调用这样的函数宏时, 传递给它的参数个数要不少于参数列表中参数的个数(多余的参数被丢弃).通过__VA_ARGS__来替换函数宏中的可变参数列表. 注意__VA_ARGS__只能用于函数宏中参数中包含有"..."的情况.e.g.#ifdef DEBUG#define my_printf(...) fprintf(stderr, __VA_ARGS__)#else#define my_printf(...) printf(__VA_ARGS__)#endiftokens中的__VA_ARGS__被替换为函数宏定义中的"..."可变参数列表.注意在使用#define时候的一些常见错误:#define MAX = 100#define MAX 100;=, ; 的使用要值得注意. 再就是调用函数宏是要注意, 不要多给出";".注意: 函数宏对参数类型是不敏感的, 你不必考虑将何种数据类型传递给宏. 那么, 如何构建对参数类型敏感的宏呢? 参考本章的第九部分, 关于"##"的介绍.关于定义宏的另外一些问题(1) 宏可以被多次定义, 前提是这些定义必须是相同的. 这里的"相同"要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab, 注释...e.g.#define NULL 0#define NULL /* null pointer */ 0上面的重定义是相同的, 但下面的重定义不同:#define fun(x) x+1#define fun(x) x + 1 或: #define fun(y) y+1如果多次定义时, 再次定义的宏内容是不同的, gcc会给出"NAME redefined"警告信息.应该避免重新定义函数宏, 不管是在预处理命令中还是C语句中, 最好对某个对象只有单一的定义. 在gcc中, 若宏出现了重定义, gcc会给出警告.(2) 在gcc中, 可在命令行中指定对象宏的定义:e.g.$ gcc -Wall -DMAX=100 -o tmp tmp.c相当于在tmp.c中添加" #define MAX 100".那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢?---若-DMAX=1, 则正确编译.---若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1.注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc 赋予该宏默认值(1), 如: -DVAL == -DVAL=1(3) #define所定义的宏的作用域宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别e.g.#define ONE 1sum = ONE + TWO /* sum = 1 + TWO */#define TWO 2sum = ONE + TWO /* sum = 1 + 2 */#undef ONEsum = ONE + TWO /* sum = ONE + 2 */char c[] = "TWO" /* c[] = "TWO", NOT "2"! */(4) 宏的替换可以是递归的, 所以可以嵌套定义宏.e.g.# define ONE NUMBER_1# define NUMBER_1 1int a = ONE /* a = 1 */2, #undef#undef用来取消宏定义, 它与#define对立:#undef name如够被取消的宏实际上没有被#define所定义, 针对它的#undef 并不会产生错误.当一个宏定义被取消后, 可以再度定义它.3, #if, #elif, #else, #endif#if, #elif, #else, #endif用于条件编译:#if 常量表达式1语句...#elif 常量表达式2语句...#elif 常量表达式3语句......#else语句...#endif#if和#else分别相当于C语句中的if, else. 它们根据常量表达式的值来判别是否执行后面的语句. #elif相当于C中的else-if. 使用这些条件编译命令可以方便地实现对源代码内容的控制.else之后不带常量表达式, 但若包含了常量表达式, gcc只是给出警告信息.使用它们可以提升代码的可移植性---针对不同的平台使用执行不同的语句. 也经常用于大段代码注释.e.g.#if 0{一大段代码;}#endif常量表达式可以是包含宏, 算术运算, 逻辑运算等等的合法C常量表达式, 如果常量表达式为一个未定义的宏, 那么它的值被视为0.#if MACRO_NON_DEFINED == #if 0在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用下面介绍的#ifdef或#ifndef.注意: #if, #elif, #else之后的宏只能是对象宏. 如果name为名的宏未定义, 或者该宏是函数宏. 那么在gcc中使用"-Wundef"选项会显示宏未定义的警告信息.4, #ifdef, #ifndef, defined.#ifdef, #ifndef, defined用来测试某个宏是否被定义#ifdef name 或 #ifndef name它们经常用于避免头文件的重复引用:#ifndef __FILE_H__#define __FILE_H__#include "file.h"#endifdefined(name): 若宏被定义,则返回1, 否则返回0.它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. defined用于在一条判断语句中声明多个判别条件:#if defined(VAX) && defined(UNIX) && !defined(DEBUG)和#if, #elif, #else不同, #indef, #ifndef, defined测试的宏可以是对象宏, 也可以是函数宏. 在gcc中使用"-Wundef"选项不会显示宏未定义的警告信息.5, #include , #include_next#include用于文件包含. 在#include 命令所在的行不能含有除注释和空白符之外的其他任何内容.#include "headfile"#include#include 预处理标记前面两种形式大家都很熟悉, "#include 预处理标记"中, 预处理标记会被预处理器进行替换, 替换的结果必须符合前两种形式中的某一种.实际上, 真正被添加的头文件并不一定就是#include中所指定的文件. #include"headfile"包含的头文件当然是同一个文件, 但#include 包包含的"系统头文件"可能是另外的文件. 但这不值得被注意. 感兴趣的话可以查看宏扩展后到底引入了哪些系统头文件.关于#include "headfile"和#include 的区别以及如何在gcc中包含头文件的详细信息, 参考本blog的GCC笔记.相对于#include, 我们对#include_next不太熟悉. #include_next 仅用于特殊的场合. 它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊: 从当前头文件所在目录之后的目录来搜索头文件.比如: 头文件的搜索路径一次为A,B,C,D,E. #include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件.可参考cpp手册进一步了解#include_next6, 预定义宏标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:__LINE__ 当前语句所在的行号, 以10进制整数标注.__FILE__ 当前源文件的文件名, 以字符串常量标注.__DATE__ 程序被编译的日期, 以"Mmm dd yyyy"格式的字符串标注.__TIME__ 程序被编译的时间, 以"hh:mm:ss"格式的字符串标注, 该时间由asctime返回.__STDC__ 如果当前编译器符合ISO标准, 那么该宏的值为1__STDC_VERSION__ 如果当前编译器符合C89, 那么它被定义为199409L, 如果符合C99, 那么被定义为199901L.我用gcc, 如果不指定-std=c99, 其他情况都给出__STDC_VERSION__未定义的错误信息, 咋回事呢?__STDC_HOSTED__ 如果当前系统是"本地系统(hosted)", 那么它被定义为1. 本地系统表示当前系统拥有完整的标准C库.gcc定义的预定义宏:__OPTMIZE__ 如果编译过程中使用了优化, 那么该宏被定义为1.__OPTMIZE_SIZE__ 同上, 但仅在优化是针对代码大小而非速度时才被定义为1.__VERSION__ 显示所用gcc的版本号.可参考"GCC the complete reference".要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null7, #line#line用来修改__LINE__和__FILE__.e.g.printf("line: %d, file: %s ", __LINE__, __FILE__);#line 100 "haha"printf("line: %d, file: %s ", __LINE__, __FILE__);printf("line: %d, file: %s ", __LINE__, __FILE__);显示:line: 34, file: 1.cline: 100, file: hahaline: 101, file: haha8, #pragma, _Pragma#pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下:#pragma GCC name token(s)#pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的.(1) #pragma GCC dependencydependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息.e.g.在demo.c中给出这样一句:#pragma GCC dependency "temp-file"然后在demo.c所在的目录新建一个更新的文件: $ touch temp-file, 编译: $ gcc demo.c 会给出这样的警告信息: warning: current file is older than temp-file如果当前文件比指定的文件新, 则不给出任何警告信息.还可以在在#pragma中给添加自定义的警告信息.e.g.#pragma GCC dependency "temp-file" "demo.c needs to be updated!"1.c:27:38: warning: extra tokens at end of #pragma directive1.c:27:38: warning: current file is older than temp-file注意: 后面新增的警告信息要用""引用起来, 否则gcc将给出警告信息.(2) #pragma GCC poison token(s)若源代码中出现了#pragma中给出的token(s), 则编译时显示警告信息. 它一般用于在调用你不想使用的函数时候给出出错信息.e.g.#pragma GCC poison scanfscanf("%d", &a);warning: extra tokens at end of #pragma directiveerror: attempt to use poisoned "scanf"注意, 如果调用了poison中给出的标记, 那么编译器会给出的是出错信息. 关于第一条警告, 我还不知道怎么避免, 用""将token(s)引用起来也不行.(3) #pragma GCC system_header从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码. 系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示. (除非用#warning显式指明).(这条#pragma语句还没发现用什么大的用处)由于#pragma不能用于宏扩展, 所以gcc还提供了_Pragma:e.g.#define PRAGMA_DEP #pragma GCC dependency "temp-file"由于预处理之进行一次宏扩展, 采用上面的方法会在编译时引发错误, 要将#pragma语句定义成一个宏扩展, 应该使用下面的_Pragma语句:#define PRAGMA_DEP _Pragma("GCC dependency "temp-file"")注意, ()中包含的""引用之前引该加上转义字符.9, #, ###和##用于对字符串的预处理操作, 所以他们也经常用于printf, puts之类的字符串显示函数中.#用于在宏扩展之后将tokens转换为以tokens为内容的字符串常量.e.g.#define TEST(a,b) printf( #a "<" #b "=%d ", (a)<(b));注意: #只针对紧随其后的token有效!##用于将它前后的两个token组合在一起转换成以这两个token 为内容的字符串常量. 注意##前后必须要有token.e.g.#define TYPE(type, n) type n之后调用:TYPE(int, a) = 1;TYPE(long, b) = 1999;将被替换为:int a = 1;long b = 1999;(10) #warning, #error#warning, #error分别用于在编译时显示警告和错误信息, 格式如下:#warning tokens#error tokense.g.#warning "some warning"注意, #error和#warning后的token要用""引用起来!(在gcc中, 如果给出了warning, 编译继续进行, 但若给出了error, 则编译停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不编译.【C语言中的预编译宏定义】。

程序编译的四个步骤

程序编译的四个步骤

程序编译的四个步骤程序编译是将源代码翻译成目标代码的过程,目标代码是可以被机器直接执行的二进制代码。

程序编译的过程通常分为四个步骤:预处理、编译、汇编和链接。

1.预处理。

在编译过程中,首先进行的是预处理。

预处理器负责处理源代码中的宏定义和条件编译指令,将宏展开,去掉注释,扩展头文件,还可以通过条件编译指令控制编译过程中的流程。

预处理器通常将预处理后的代码输出为一个中间文件或者内存缓冲区。

2.编译。

编译器则会将经过预处理后的代码翻译成汇编代码,汇编代码通常以汇编语言的格式来描述程序的逻辑,这种格式相对于机器代码更容易理解和调试,但是仍然不够高层次,需要转换为机器码才能被计算机执行。

在编译的过程中,编译器还会进行语法分析和语义分析,检查代码是否符合语法规范,处理类型、函数、变量等定义,确保语义正确,将汇编代码输出为一个中间文件或者内存缓冲区。

3.汇编。

将编译器生成的汇编代码转换为可执行目标代码的过程称为汇编。

在汇编中,汇编器会将汇编代码转换为机器码,进行符号解析(将符号转换为地址)、指令重定位(修改代码的位置)、修补机器码和生成目标代码的重要操作,生成目标代码文件。

4.链接。

在程序编译的最后一个步骤是链接:将生成的目标代码文件与其它代码文件以及必要的库文件链接在一起生成可执行的程序。

链接的过程主要完成符号解析、重定位、生成可执行文件的一些信息等工作。

通过链接可以实现单独编译的目的,即将多个编译完成的目标文件链接在一起形成可执行程序。

链接器会将目标文件中的代码和库文件中的代码整合在一起,更新函数、变量的引用信息,生成可执行的二进制文件。

综合来看,程序编译的四个步骤是预处理、编译、汇编和链接。

预处理和编译是将源代码转换为汇编代码过程中的基本步骤,汇编和链接则是将汇编代码变为可执行代码的两个关键步骤。

不同的编译器或链接器也可能有其它的扩展和优化,但总体上遵循这四个步骤的基本流程。

VC预处理指令和宏定义

VC预处理指令和宏定义

刚接触到MFC编程的人往往会被MFC 向导生成的各种宏定义和预处理指令所吓倒,但是预处理和宏定义又是C语言的一个强大工具。

使用它们可以进行简单的源代码控制,版本控制,预警或者完成一些特殊的功能。

一个经典的例子 使用预处理与宏定义最经典的例子莫过于加在一个头文件中以避免头文件被两次编译。

试想这种的情况,有一个文件headerfile.h 它被包含在headerfile1.h中,同时在headerfile2.h 中也被包含了,现在有一个CPP文件,implement.cpp 包含了headerfile1.h 和headerfile2.h: 假设headerfile.h 中定义了一个全局变量iglobal 。

在编译的时候编译器两次编译headerfile,也就会发现iglobal被定义了两次,这时就会发生变量重定义的编译错误。

传统的解决办法是使用#ifdef 以及#endif 来避免头文件的重复编译,在上面的例子中,只需要加上这么几行: 仔细的考虑上面的宏定义,会发现当编译器编译过一次headerfile.h以后,sma rtnose_2002_6_21_headerfile_h 这个宏就被定义了,以后对headerfile.h的编译都会跳过int iglobal 这一行。

当然smartnose_2002_6_21_headerfile_h 这个宏是可以任意定义的,但是这个宏本身不能和其它文件中定义的宏重复,所以MFC在自动生成的文件中总是使用一个随机产生的长度非常长的宏,但我觉得这没有必要,我建议在这个宏中加入一些有意义的信息,比方作者,文件名,文件创建时间等等,因为我们有时候会忘记在注释中加入这些信息。

在中我们不会再看见这些宏定义了,因为在这里会普遍使用一个预处理指令: 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。

c语言程序编译的流程

c语言程序编译的流程

c语言程序编译的流程C语言是一种高级编程语言,它是一种通用的编程语言,可以用于开发各种类型的应用程序。

C语言程序编译的流程是指将C语言源代码转换为可执行文件的过程。

本文将详细介绍C语言程序编译的流程。

C语言程序编译的流程可以分为以下几个步骤:1. 预处理预处理是C语言程序编译的第一步。

在这个步骤中,编译器会对源代码进行一些预处理操作,例如宏替换、头文件包含等。

预处理器会将源代码中的宏定义替换为宏定义中的内容,并将头文件中的内容插入到源代码中。

预处理后的代码称为预处理文件。

2. 编译编译是C语言程序编译的第二步。

在这个步骤中,编译器会将预处理文件转换为汇编代码。

汇编代码是一种低级语言,它是机器语言的一种表现形式。

编译器会将C语言代码转换为汇编代码,这个过程称为编译。

3. 汇编汇编是C语言程序编译的第三步。

在这个步骤中,汇编器会将汇编代码转换为机器语言代码。

机器语言是计算机可以直接执行的语言,它是由0和1组成的二进制代码。

汇编器会将汇编代码转换为机器语言代码,这个过程称为汇编。

4. 链接链接是C语言程序编译的最后一步。

在这个步骤中,链接器会将机器语言代码和库文件链接在一起,生成可执行文件。

库文件是一些预编译的代码,它们可以被多个程序共享。

链接器会将程序中使用到的库文件链接到程序中,生成可执行文件。

以上就是C语言程序编译的流程。

下面我们将详细介绍每个步骤的具体内容。

1. 预处理预处理是C语言程序编译的第一步。

在这个步骤中,编译器会对源代码进行一些预处理操作,例如宏替换、头文件包含等。

预处理器会将源代码中的宏定义替换为宏定义中的内容,并将头文件中的内容插入到源代码中。

预处理后的代码称为预处理文件。

预处理器的工作原理是将源代码中的宏定义和头文件包含替换为实际的代码。

例如,下面是一个简单的宏定义:#define PI 3.1415926在预处理阶段,预处理器会将源代码中的所有PI替换为3.1415926。

这样,程序中所有使用到PI的地方都会被替换为3.1415926。

预处理指令--宏定义

预处理指令--宏定义

预处理指令--宏定义预处理指令简介•C语⾔在对源程序进⾏编译之前,会先对⼀些特殊的预处理指令作解释(⽐如之前使⽤的#include⽂件包含指令),产⽣⼀个新的源程序(这个过程称为编译预处理),之后再进⾏通常的编译•为了区分预处理指令和⼀般的C语句,所有预处理指令都以符号“#”开头,并且结尾不⽤分号•预处理指令可以出现在程序的任何位置,它的作⽤范围是从它出现的位置到⽂件尾。

习惯上我们尽可能将预处理指令写在源程序开头,这种情况下,它的作⽤范围就是整个源程序⽂件•C语⾔提供的预处理指令主要有:宏定义、⽂件包含、条件编译宏定义分类•宏定义可以分为2种:–不带参数的宏定义–带参数的宏定义⼀、不带参数的宏定义•1.⼀般形式•#define 宏名字符串•⽐如 #define ABC 10•右边的字符串也可以省略,⽐如#define ABC2.作⽤•它的作⽤是在编译预处理时,将源程序中所有"宏名"替换成右边的"字符串",常⽤来定义常量。

•接下来写个程序根据圆的半径计算周长宏定义#include <stdio.h>// 源程序中所有的宏名PI在编译预处理的时候都会被3.14所代替#define PI 3.14// 根据圆的半径计radius算周长float girth(float radius) {return 2 * PI *radius;}int main (){float g = girth(2);printf("周长为:%f", g);return 0;}3.使⽤习惯与注意1> 宏名⼀般⽤⼤写字母,以便与变量名区别开来,但⽤⼩写也没有语法错误•2> 对程序中⽤双引号扩起来的字符串内的字符,不进⾏宏的替换操作。

⽐如:#define R 10int main (){char *s = "Radio";return 0;}•在第1⾏定义了⼀个叫R的宏,但是第4⾏中"Radio"⾥⾯的'R'并不会被替换成103> 在编译预处理⽤字符串替换宏名时,不作语法检查,只是简单的字符串替换。

预处理命令--带参宏定义

预处理命令--带参宏定义

预处理命令--带参宏定义
带参宏定义
1.带参宏定义的⼀般格式
#define 宏名(形参表) 语⾔符号字符串
字符串中包含在括弧中所指定的参数,如:
#define S(a,b) a*b
Area = S(3,2);
2.带参宏的调⽤和宏展开
(1)调⽤格式:宏名(实参表)
(2)宏展开:⽤宏调⽤提供的实参字符串,直接置换宏定义命令⾏中、相应形参字符串,⾮形参字符保持不变。

3.说明
(1)定义有参宏时,宏名与左圆括号之间不能留有空格。

否则,C编译系统将空格以后的所有字符均作为替代字符串,⽽将该宏视为⽆参宏。

(2)有参宏的展开,只是将实参作为字符串,简单地置换形参字符串,⽽不做任何语法检查。

在定义有参宏时,在所有形参外和整个字符串外,均加⼀对圆括号。

(3)虽然有参宏与有参函数确实有相似之处,但不同之处更多,主要有以下⼏个⽅⾯:
1)调⽤有参函数时,是先求出实参的值,然后再复制⼀份给形参。

⽽展开有参宏时,只是将实参简单地置换形参。

2)在有参函数中,形参是有类型的,所以要求实参的类型与其⼀致;⽽在有参宏中,形参是没有类型信息的,因此⽤于置换的实参,什么类型都可以。

有时,可利⽤有参宏的这⼀特性,实现通⽤函数功能。

3)使⽤有参函数,⽆论调⽤多少次,都不会使⽬标程序变长,但每次调⽤都要占⽤系统时间进⾏调⽤现场保护和现场恢复;⽽使⽤有参宏,由于宏展开是在编译时进⾏的,所以不占运⾏时间,但是每引⽤1次,都会使⽬标程序增⼤1次。

预处理-01-无参宏定义

预处理-01-无参宏定义

预处理-01-⽆参宏定义在之前,已多次使⽤过以“#”号开头的预处理命令。

如包含命令#include <stdio.h>,宏定义命令#define PI 3.1415926等。

在源程序中这些命令都放在函数之外,⽽且⼀般都放在源⽂件的前⾯,它们称为预处理部分。

1.⽆参宏定义 ⽆参宏的宏名后不带参数。

其定义的⼀般形式为: #define 标识符字符串 其中的“#”表⽰这是⼀条预处理命令。

凡是以“#”开头的均为预处理命令。

define 为宏定义命令。

“标识符”为所定义的宏名。

“字符串”可以是常数、表达式、格式串等。

例如: #define PI 3.1415926,它的作⽤是指定标识符PI来代替数3.1415926。

在编写源程序时,所有的3.1415926都可由PI代替,⽽对源程序作编译时,将先由预处理程序进⾏宏代换,即⽤3.1415926表达式去置换所有的宏名PI,然后再进⾏编译。

2.对于宏定义还要说明以下⼏点:(⼀)宏定义是⽤宏名来表⽰⼀个字符串,在宏展开时⼜以该字符串取代宏名,这只是⼀种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。

如有错误,只能在编译已被宏展开后的源程序时发现。

(⼆)宏定义不是说明或语句,在⾏末不必加分号,如加上分号则连分号也⼀起置换。

(三)宏定义必须写在函数之外,其作⽤域为宏定义命令起到源程序结束。

如要终⽌其作⽤域可使⽤# undef命令。

(四)宏名在源程序中若⽤引号括起来,则预处理程序不对其作宏代换。

实例如下:#include <stdio.h>#define PI 3.1415926void fun(void);void main(){double s;int r;printf("Please enter the radius : ");scanf("%d", &r);s = PI * r * r;printf("\n\nThe area of the roundness = %g\n\n", s);fun();}void fun(void){printf("Now the PI = %g\n\n", PI);printf("PI\n\n"); // PI在引号中应该是表⽰常量字符串,不替换……}View Code(五)宏定义允许嵌套,在宏定义的字符串中可以使⽤已经定义的宏名。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
class Test {
public void test() {
Console.WriteLine(parameters.para); } } static class parameters { public static string para = "something"; }
宏定义与静态变量:
静态变量与宏定义其实没啥关系,所谓的静态,也只是说它存储的位置实在“全局变量的 内存区”但是它使用起来还是有范围限制的。
预定义:
预定义算一种特殊的机制吧,就是有一些变量或函数需要在我们程序被编译之前就被搞出 来,那么就需要在编译器发挥作用之前启动一个叫预编译器的东西,它会搜索程序里边带 "#" 的代码,然后把它们提前给编译了。这之后再启动编译器进行编译。预定义指令其实 是为了配合这种机制而定义出来的旨在我们的程序中对需要与处理的代码段进行标识的一 种命令。他的作用包括宏定义,文件包含,和条件编译。
宏定义与常量:
宏定义与常量都有“换其名曰”的作用,但是宏定义的作用范围只能是全局的,而常量是 在编译过程中由编译器搞出来的,所以常量的作用域有全局和局部之分。如果在一个函数 中定义了一个常量,那么这个常量的作用范围就仅限于这个函数。而如果在这个函数中定 义了一个宏定义,那么这个宏定义在函数外也是可以访问定的。为嘛?因为人家是在编译 之前就被搞出来的。跟这个函数其实没多大关系,所以偶感觉如果要定义一个宏,干脆直 接定义在文件头部分。简单明了。
余下的预定义指令就挂在下边,比较好理解。可以说预编译机制是一个“无奈”的机制, 预编译指令最终的归宿应该是被嵌入到代码中,与其他的代码一起被编译器编译。
最后---在许多年之后---将 Cpp 放逐到程序开发环境里,与其它附加性语言工具放到一起, 那里才是她应该呆的地方。” 一个大牛说的,了解了就行。
#error
输出一个错误信息
叨完了,文件包含就是我们常见的#include,这里头还是要啰嗦一下气 候的规则#include<iostream.h>和#include<iostream>是由于标准的不一样,由于有些大 牛觉得".h"文件作为头文件表示不利于统一,所以就早早的把这个给去掉了。还有另外的 原因就是<iosteam>和<iostream.h>里边的标准也变了,说白了就是不是一个文件了,里边 的东东发生了变化,用#include<iostream>会包含进去一个比较新版本的 iostream(扩展名 未知). <>和""的区别在于查找的位置不一样,<>从标准库的头文件中开始查找,""从用户 自定义的头文件库中开始查找。
宏定义:
其作用就是“换其名曰”,给程序中的一段特殊的代码--函数,数据取了个简单明了的名 字。不过有一点这家伙的作用范围是全局的。即使它是在某一个函数块中定义的。这个其 实也容易理解。因为宏定义是预定义的一种,在我们的程序之前,由预编译器(Cpp)提前编 译出来了,那个时候程序里的结构是个啥样子编译器压根就不知道。所以作用范围是全局 的是“必须”的。在 C#中宏定义是不被支持的!!俺觉得这个决定是对的。因为这玩意儿 本身就破坏类的封装性,如果需要全局静态变量,完全可以定义一个静态类的静态成员。
#define
宏定义
#undef
未定义宏
#include
文本包含
#ifdef
如果宏被定义就进行编译
#ifndef
如果宏未被定义就进行编译
#endif
结束编译块的控制
#if
表达式非零就对代码进行编译
#else
作为其他预处理的剩余选项进行编译
#elif
这是一种#else 和#if 的组合选项
#line
改变当前的行数和文件名称
相关文档
最新文档