预处理

(1)从会敲代码开始,我们就知道编译器的重要性,没有这东西,我们的代码就是一堆字符而已。而编译器编译程序的步骤

主要有四个——预处理、编译、汇编和链接,最后得到可执行的目标文件。

 四个步骤各自的工作会是怎么样的呢?不知为何,对这个东西有了点兴趣。以GCC为例,主要工作是这样的:

预处理阶段会调用cpp,

编译阶段会调用cc得到汇编程序,

汇编阶段调用as得到目标代码,

链接时调用链接程序ld 得到可执行文件。

由于四个步骤(其实本质上就是四个可执行代码)早就被编译器打包,一并替我们打理了,我们的工作是轻松了,

对于这几个过程究竟是怎样进行的,恐怕还是有个问号在脑中打转。

后面3个阶段的具体工作机制,我没有深入了解过,但还是可以说说预处理的。

(2)预处理器是在真正的编译开始之前由编译器调用的独立程序。预处理器可以删除注释、包含其他文件以及执行宏(宏macro是一段重复文字的简短描写)替代在探究预处理的操作机制之前,了解预处理器是个什么东西还是很有必要的。

预处理器不止一种,而C/C++的预处理器就是其中最低端的一种——词法预处理器。

这种预处理器做的主要是进行文本替换、宏展开、删除注释这类简单工作。再具体一点就是,预处理器cpp就是负责展开源文件中的宏,并把”#include ”的内容插入这类的工作。

C/C++预处理器什么都不会做,只是做宏替换和文本替换。

★  C/C++预处理是不会做任何语法检查的,不仅是因为它不具备语法检查功能,也因为预处理命令不属于C/C++语句(这也是定义宏时不要加分号的原因),语法检查是编译器要做的事情。

★  通过预处理之后,我们得到的是也仅仅是真正的源代码。

★  还有就是,GCC这个东西确实很强大。如果是用VC这种IDE,恐怕就不能看到,预处理原来是这么个好玩的东西了。

(3)C语言提供的三种预处理功能的其中一种,这三种预处理包括:宏定义、文件包含条件编译。宏定义和操作符的区别是:宏定义是替换,不做计算,也不做表达式求解。

一,宏定义

A:不带参数
格式:#define 标识符 字符串,其中的标识符就是所谓的符号常量,也称为“宏名”。
(1)宏名一般用大写
(2)使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。例如:数组大小常用宏定义
(3)预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
(4)宏定义末尾不加分号
(5)宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。
(6)可以用#undef命令终止宏定义的作用域
(7)宏定义允许嵌套
(8)字符串( " " )中永远不包含宏
(9)宏定义不分配内存,变量定义分配内存。
(10)宏定义不存在类型问题,它的参数也是无类型的。

B:带参数
除了一般的字符串替换,还要做参数代换
格式:
#define宏名(参数表) 字符串
例如:#define S(a,b) a*b
area=S(3,2);第一步被换为area=a*b; ,第二步被换为area=3*2;
类似于函数调用,有一个哑实结合的过程:
(1)实参如果是表达式容易出问题
#define S(r) r*r
area=S(a+b)  ;  
第一步换为area=r*r  ;   第二步被换为area=a+b*a+b ;

正确的宏定义是#define S(r) ((r)*(r))
(2)宏名和参数的括号间不能有空格
(3)宏替换只作替换,不做计算,不做表达式求解
(4)函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存
(5)宏的哑实结合不存在类型,也没有类型转换。
(6)宏展开使源程序变长,函数调用不会
(7)宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)

记住:#define第一位置第二位置
(1) 不替换程序中字符串“ ”里的东西。
(2) 第一位置只能是合法的标识符(可以是关键字)
(3) 第二位置如果有字符串,必须把""配对。
(4) 只替换与第一位置完全相同的标识符(一个标识符要整体识别不能拆分开分别识别)

特殊例子:
#define FUN(a) "a"
" "内的字符不被当成形参,即使它和参数一模一样,也不会替换成实参对应字符串。

那么,你会问了,我要是想让这里输入FUN(345)它就替换成"345"该怎么实现呢?用下面2)知识:#define  FUN(a)  #a

C预处理器提供了以下,以帮助您创建宏:

1)Macro Continuation (\)

宏通常必须被包含在一个单一的线。宏延续运算符用于继续宏一行太长。例如:
#define message_for(a, b) \
       printf(#a " and " #b ": We love you!\n") 
2)Stringize (#)
stringize或数字符号运算符('#'),使用时,在宏定义,宏参数转换成一个字符串常量。这个操作符只可用于在一个宏,有一个指定的参数或参数列表。例如:
#include 
#define  message_for(a, b)  \
    printf(#a " and " #b ": We love you!\n")
int main(void)
{
   message_for(Carole, Debra);
   return 0;
}
When the above code is compiled and executed, it produces the following result:

Carole and Debra: We love you!

3)Token Pasting (##)
在宏定义令牌粘贴运算符(##)结合两个参数。它允许两个单独的宏定义中的令牌(参数)被加入到一个单独的标记

如果有#defineFUN(a,b)  vo##a##b()
那么FUN(id ma,in)会被替换成void main()

二,条件编译

条件编译指令将决定哪些代码被编译,而哪些是不被编译的。可以根据 表达式的值或者某个特定的宏是否被定义来确定编译条件。

#if、#else、#elif和#endif指令

一般形式有如下几种
(1)
1
2
3
4
5
#if表达式
//语句段1
#else
//语句段2]
#endif
如果 表达式为真,就编译语句段1,否则编译语句段2 [1]  
(2)
1
2
3
4
5
6
7
#if表达式1
//语句段1
#elif表达式2
//语句段2
#else
//语句段3
#endif
如果表达式1真,则编译语句段1,否则判断表达式2;如果表达式2为真,则编译语句段2,否则编译语句段3

#ifdef和#ifndef

(1)#ifdef的一般形式:[1] 
1
2
3
#ifdef宏名
//语句段
#endif
作用:如果在此之前已定义了这样的宏名,则编译语句段。
(2)#ifndef的一般形式:[1] 
1
2
3
#ifndef宏名
//语句段
#endif
作用:如果在此之前没有定义这样的宏名,则编译语句段。
#else可以用于#ifdef和#ifndef中,但#elif不可以。

#error

指令将使 编译器显示一条错误信息,然后停止编译。

#line

指令可以改变 编译器用来指出警告和错误信息的文件号和行号。

#pragma

指令没有正式的定义。 编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
#define LETTER1
int  main( int  argc, char *argv[])
{
char  str[20]= "CLanguage" ,c;
int  i;
i=0;
while ((c=str[i])!= '\0' )
{
i++;
#ifdef LETTER1
if (c>= 'a' &&c<= 'z' )
c=c-32;
#else
if (c>= 'A' &&c<= 'Z' )
c=c+32;
#endif
printf ( "%c" ,c);
}
return0;
}

三,文件包含

(1)文件包含的两种格式区别编辑
文件包含有两种格式,分别是:#include "file" 和 #include <file>
这两格式的区别在于:
1.使用双引号,系统首先到当前目录下查找被包含的文件,如果没找到,再到系统指定的"包含文件目录"(由用户在配置环境时设置)去找。
2.使用尖括号:直接到系统指定的"包含文件目录"去查找。
通常使用双引号比较保险

(2)文件包含的特点编辑
文件包含的特点:
编译预处理时,预处理程序将查找指定的被包含文件,并将其复制插入到#include命令出现的位置上
② 常用在文件头部的被包含文件,称为“标题文件”或“头部文件”,常以“h”(head)作为后缀,简称头文件。在头文件中,除可包含宏定义外,还可包含外部变量定义、结构类型定义等。
一条包含命令,只能指定一个被包含文件。如果要包含多个文件,则要用多条包含命令。例如,文件f1.h中要使用到文件f2.h和文件f3.h的内容,则可在文件f1.h中用两个文件包含命令分别包含文件f2
.h和文件f3.h,即在文件f1.h中定义:
  #include "f2.h"
  #include "f3.h"
  在使用多个#include命令时,顺序是一个值得注意的问题。上例中,如果文件f1.h包含文件f2.h,而文件2要用到文件f3.h,则在f1.h中#include定义的顺序应该是:
  #include "f3.h"
  #include "f2.h"
  这样文件f1.c和文件f2.h都可以使用文件f3.h的内容。
文件包含可以嵌套,即被包含文件中又包含另一个文件。例如,文件f2.h中要使用到文件f1.h的内容,文件f3.h要使用到文件f2.h的内容,则可在文件f2.h中用#include "f1.h"命令,在文件f3.h中用#include "f2.h"命令,即定义如下:
  文件f1.h:
  {
  … … 
  }
  文件f2.h:
  #include "f1.h"
  int max()
  {
  … …
  }
  文件f3.h:
  #include "f2.h"
  main
  {
  … …
  }
#include命令一般用来把C语言提供的标准库头文件(如stdio.h、math.h)包含到程序中。程序员也可以自己定义一个头文件,写入一些常用的函数原型、宏定义、结构和联合类型定义等,然后将它包含到程序中。例如:

#include "stdio.h" (标准输入/输出函数库)
#include "math.h" (数学函数库)
#include "stdlib.h" (常用函数库)
#include "string.h" (字符串处理函数库)







































  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值