h5免费制作平台企业秀,杭州网站建设优化,做网站优化有什么作用,wordpress不能上传附件文章目录 1.编译2.预处理2.1宏定义2.1.1预定义符号2.1.2#define定义常量2.1.3#define定义宏2.1.4do-while-zero2.1.5宏的注意事项2.1.6宏与函数的对比 2.2条件编译2.3文件包含 3.offsetoff4.#与##4.1. #号4.2 ##号 1.编译
我们都知道#xff0c;一个程序如果想运行起来要经过… 文章目录 1.编译2.预处理2.1宏定义2.1.1预定义符号2.1.2#define定义常量2.1.3#define定义宏2.1.4do-while-zero2.1.5宏的注意事项2.1.6宏与函数的对比 2.2条件编译2.3文件包含 3.offsetoff4.#与##4.1. #号4.2 ##号 1.编译
我们都知道一个程序如果想运行起来要经过编译、链接然后才能生成.exe的文件。 编译⼜可以分解为三个过程
预处理有些书也叫预编译、编译汇编 预处理阶段主要处理那些源文件中以#开始的预编译指令。比如#include#define处理的规则如下
删除所有的注释该步骤在宏替换之前将所有的 #define 删除并替换所有的宏定义。处理所有的条件编译指令如 #if、#elif、#else、#ifdef、#ifndef、#endif 。处理#include 预编译指令将包含的头文件的内容插⼊到该预编译指令的位置。这个过程是递归进行的也就是说被包含的头文件也可能包含其他文件。添加行号和文件名标识方便后续编译器生成调试信息等。保留所有的#pragma的编译器指令编译器后续会使⽤。
经过预处理后的.i⽂件中不再包含宏定义因为宏已经被展开。并且包含的头⽂件都被插⼊到.i文件中。所以当我们无法知道宏定义或者头文件是否包含正确的时候可以查看预处理后的.i文件来确认。
编译阶段就是将预处理后的⽂件进行⼀系列的词法分析、语法分析、语义分析及优化 ⽣成相应的汇编代码⽂件。 汇编阶段汇编器将汇编代码转转变成机器可执⾏的指令每⼀个汇编语句⼏乎都对应⼀条机器指令。就是根据汇编指令和机器指令的对照表⼀⼀的进⾏翻译也不做指令优化。
2.预处理
C语言提供的预处理功能主要有以下三种
宏定义条件编译文件包含
2.1宏定义
2.1.1预定义符号
C语言设置了⼀些预定义符号可以直接使⽤预定义符号也是在预处理期间处理的。
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C其值为1否则未定义2.1.2#define定义常量
基本使用 巧妙使用 续航符
当需要替换的内容过长的时候可以使用 \ 实现续航。
思考在#define定义标识符的时候要不要在最后加上 ; 建议不要加上 ; 这样容易导致问题 如果是加了分号的情况等替换后if和else之间就是2条语句⽽没有⼤括号的时候if后边只能有⼀条语句 。这⾥会出现语法错误。
2.1.3#define定义宏
#define机制包括了⼀个规定允许把参数替换到⽂本中这种实现通常称为宏。 宏的声明方式
#define name( parament-list ) stuff其中的 parament-list 是⼀个由逗号隔开的符号表它们可能出现在stuff中。参数列表的左括号必须与name紧邻如果两者之间有任何空⽩存在参数列表就会被解释为stuff的⼀部分。 对于上述案例我们可以清楚的看到括号在宏中起着至关重要的作用为了让表达式的结果是我们想要的结果要尽量给宏带上括号。避免在使⽤宏时由于参数中的操作符或邻近操作符之间不可预料的相互作⽤。 要点宏在使用的时候只是原样替换不进行任何的改变。 下面代码的输出结果是什么呢
#define MAX(a, b) ((a) (b) ? (a) : (b))
int main()
{int x 5;int y 8;int z MAX(x, y);printf(x%d y%d z%d\n, x, y, z);return 0;
}z ( (x) (y) ? (x) : (y));
x--5--6
y--8--9--10
z--9
所以输出的结果是x6 y10 z92.1.4do-while-zero
假如现在我有一个代码我就想在if后面跟一条用宏定义多条语句的一条语句我可以怎么做呢 我们发现这样是可以实现目的但是有点别扭而且我们习惯在一条语句后面加分号所以在预处理后的代码中就会对应有两个分号。 那到底能怎么处理呢 这样是不是就可以满足既是一条语句还能写分号没有空语句的目的了。
2.1.5宏的注意事项
源文件的任何地方宏都可以定义与是否在函数内外无关宏替换的规则
在程序中扩展#define定义符号和宏时需要涉及几个步骤。 在调用宏时首先对参数进行检查看看是否包含任何由#define定义的符号。如果是它们首先被替换。替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏参数名被他们的值所替换。最后再次对结果⽂件进行扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程。 注意
宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏不能出现递归。当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。
宏的作用范围 从定义处开始往后都有效没有出现#undef 出现#undef 思考下面的代码 2.1.6宏与函数的对比
宏通常被应用于执行简单的运算。 比如在两个数中找出较⼤的⼀个时写成下⾯的宏更有优势⼀些。
#define MAX(a, b) ((a)(b)?(a):(b))那为什么不⽤函数来完成这个任务 原因有二
用于调用函数和从函数返回的代码可能⽐实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度⽅⾯更胜⼀筹。更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 来⽐较的类型。宏是类型⽆关的。
宏有时候可以做函数做不到的事情。比如宏的参数可以出现类型但是函数做不到。
#includestdio.h
#includestdlib.h
#define MALLOC(num, type) (type*)malloc((num) * sizeof(type))
int main()
{int* p (int*)malloc(40);int* q MALLOC(10, int);//(int*)malloc((10) * sizeof(int))return 0;
}宏和函数的⼀个对比
属性#define定义宏函数代码长度每次使用时宏代码都会被插入到程序中。除了非常小的宏之外程序的长度会大幅度增长函数的代码只出现在一个地方每次使用函数时都调用那个地方的同一份代码执行速度更快存在函数的调用和返回的额外开销所以相对慢一点操作符优先级宏参数的求值是在所有周围表达式的上下文环境里除非加上括号否则邻近操作符的优先级可能会产生不可预料的后果所以建议宏在书写的时候多些括号函数的参数只在调用时求值一次结果传递给函数。表达式的求值更容易预测带有副作用的参数参数可能被替换到宏体中的多个位置如果宏的参数被多次计算带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次结果更容易控制参数类型宏的参数与类型无关只要对参数的操作是合法的它就可以使用于任何参数类型。函数的参数与类型有关如果参数的类型不同就需要不同的函数即使他们执行的任务是相同的。调试宏是不方便调试的函数是可以逐语句调试的递归宏是不能递归的函数是可以递归的
2.2条件编译
一般情况下源程序中所有行都参加编译。但有时候希望程序中的一部分内容只在满足一定条件时才进行编译也就是对这一部分内容指定编译的条件这就是“条件编译”。 条件编译的指令有以下几种形式
#ifdef #else # endif 它的作用是若指定的标识符以定义不管真假则让程序段1参与编译否则让程序段2参与编译。 #endif必须要有#else可以没有 #ifndef #else # endif 它的作用是若指定的标识符没有定义则让程序段1参与编译否则让程序段2参与编译。这种形式与第一种恰恰相反。 #if #else # endif 它的作用是判断指定表达式的结果是否为真为真就执行程序段1否则执行程序段2. 多分支的条件编译 利用 #if 实现 #indef / #ifndef 2.3文件包含
所谓文件包含是指一个源文件可以将另一个源文件的全部内容包含进来即将另外的文件内容包含到本文件之中插入到当前位置。 C语言有两种文件包含的方式
#include #include
这两种方式有什么区别呢?- - - - -二者的查找策略不同
#include :查找头文件直接去标准路径下查找如果找不到就提⽰编译错误。#include 先在源⽂件所在⽬录下查找如果该头文件未找到编译器就像查找库函数头⽂件⼀样再去标准路径下查找头⽂件。如果找不到就提示编译错误。 这样是不是可以说对于库⽂件也可以使⽤ 的形式包含 是的可以但是这样做查找的效率就低些当然这样也不容易区分是库文件还是本地⽂文件了。 文件包含就是将指定文件内容包含到本文件之中插入到当前位置。那如果我将一个头文件重复包含会怎么样呢 包含几次就会将所包含文件的代码复制几次如果工程比较大有公共使用的头文件被⼤家都能使用又不做任何的处理那么后果真的不堪设想。 如何解决头⽂件被重复引⼊的问题 答案条件编译
#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__或
#pragma once3.offsetoff 这个其实是一个宏它的作用就是返回结构体/共用体成员在结构体/共用体中的偏移量。 既然我们已经学习了宏那就来模拟实现以下吧 我们都知道偏移量是相较于起始位置的地址那么是不是就可以将这个起始位置想象为0。 将0强转为一个结构体类型的指针然后去访问结构体的成员 取出该成员的地址这时候该成员的地址是不是就是相较于0地址处的偏移量了呢 代码如下
#define MY_OFFSETOF(type,member_name) (int)(((type*)0)-member_name)
struct stu
{char c1;int i;char c2;
};
int main()
{struct stu s { 0 };printf(%d\n, MY_OFFSETOF(struct stu, c1));printf(%d\n, MY_OFFSETOF(struct stu, i));printf(%d\n, MY_OFFSETOF(struct stu, c2));return 0;
}4.#与##
4.1. #号
平常当我们有⼀个变量 int a 10; 的时候我们想打印出 the value of a is 10 是不是会这样写 这样写是不是不能是字符串中的变量名联动起来一个变量就得写一条printf挺麻烦的。 下面了解一下#
#运算符是将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。#运算符所执⾏的操作可以理解为”字符串化“。
有了#就可以这样写
4.2 ##号
##可以把位于它两边的符号合成⼀个符号它允许宏定义从分离的⽂本⽚段创建标识符## 被称为记号粘合。 这样的连接必须产生⼀个合法的标识符否则其结果就是未定义的。 右侧为预处理后的结果