⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 关于预编译.txt

📁 有关c语言预编译问题的几个总结
💻 TXT
字号:

1、预编译,也叫编译预处理。是在编译器开始编译代码之前对其进行一些处理。

   思考:编译预处理,实际应该是在词法分析阶段开始之前进行的处理吧?
   
2、预处理器一般完成下面几类工作:
   1)、将某些三元组替换为对应的字符。
   2)、将所有以反斜杠线\结尾的行与后面一行合并为同一行
   3)、将程序分解为记号流
   4)、删除所有的注释,并用单个空格替换他们
   5)、处理预处理器指令,并展开素有的宏。
   
3、三元组
  为了能够处理程序中的非ASCII字符,c语言定义了三元组。当程序中出现三元组时,预处理器就用对应的字符替换它,即使在字符串中也进行替换。下面是所有三元组和其对应的符号列表:
  
  三元组     含义
  ??=       #
  ??(       [
  ??)       ]
  ??<       {
  ??>       }
  ??/       \
  ??'       ^
  ??!       |
  ??-       ~

 思考:
    三元组所需要的代码,目前在键盘上都能打出来,似乎不必再需要用三元组来表示了吧??不知道当初的设计者,为什么需要三元组这样一种机制?
    
    
预处理器指令   

    所有的预处理指令都必须以#开始,而且#必须是该行的第一个非空白字符。#后面可以有一个或者多个空格或者tab字符。

一、关于宏定义

1、宏定义:

    #define name text
    
    个人理解:
        宏定义,是用一个名字来代表其它的符号序列。上面例子中,name即宏名,或直接叫做宏,它代表了text这个符号的序列。
        
    规定:宏定义中,将宏名与其名字后的第一个空格(应该只要是空白字符就可以吧?)后直到该行结束的字符串等价起来。(不是c语言意义上的用双引号括起来的那种序列,此处只代表符号的序列)。
    
    个人理解:
        1)、宏名和宏所代表的字符序列之间一定隔着一个空格(应该只需是一个空白字符即可)。
        2)、宏所代表的字符序列中间应该可以包含空白字符。
    
1、宏名,其要求应该是相当于一个标识符,替换(也叫展开)的时候,也是按照标识符的要求来替换的(这是个人理解)。
   例如:
   #define PI 3.1415926
   
   假设后面有这样的代码:
   
   a=PI2*3;
   b="PI*r";
   
   这两条语句中的PI都不会被替换,因为第一条语句中,c语言会把PI2整体当作是一个标识符,而第二条语句中,在双引号内的内容是不会被当作标识符的。
       
2、宏的定义位置
    
    《c语言编程(Programming in c)》中说,宏定义可以定义在任何位置,甚至可以出现在函数内部,只要在引用这个符号的语句之前即可。
    
    另外,有些资料中说,宏不能定义在函数中,必须定义在函数之外。不知道是不是因为依据c标准的版本不同而导致这种说法上的差异。《c语言编程(Programming in c)》依据的是c语言99标准。
    
   

3、宏定义的作用域问题是怎么规定的?
    书上说下面这种情况的定义是允许的,可以在一个宏名定义的位置之前就使用它呀?
    #define AREA PI*2*2
    #define PI 3.1415926
    
    哦,居然是可以的,《c语言编程(Programming in c)》(P308)中说,只要在程序中“使用”某个预定义的宏名时,确保该宏名以及“该符号所引用的所有其他符号都已经”定义了即可。
    
    理解:
    1、注意是在“使用”该宏名时,才要求必须已经定义过,而不是在定义时就要求所引用的符号必须事前已经定义过了。象上面的例子中,定义AREA的时候,显然PI这个宏名还没有定义过,这是允许的,只要AREA的使用是在PI的定义之后,就是允许的。因为如果是在PI定义之后使用AREA,则AREA使用时,AREA已经定义了,而AREA中引用的PI也已经定义过了。
    2、需要注意的另外一点是,使用时要求的不仅仅是该符号本身已经定义过了,而且要求该符号引用的符号也在使用这个符号之前已经定义过了。例如上面的例子中,使用AREA的时候,不仅要求AREA已经定义过了,而且要求AREA中引用的符号PI也必须在使用AREA之前已经定义过了。
    
    《c语言编程(Programming in c)》说,宏名其没有局部作用域的概念,不会因为花括号而限制作用域。一个语句只要定义了某个宏名,不管是在函数中还是函数外,在后续的程序中都可以使用。
    
    在有些资料中说,宏名的作用域是从定义它的位置到文件尾,除非遇到#udef结束其定义。
    这里的从定义位置到文件尾,指的应该是源代码上的,不是指实际运行中的情况(这点应该可以理解,因为预处理还没有进入到编译阶段,更不可能考虑到运行阶段的情况了)。
    
    所以,下面的情况:
    void aa(void)
    {
    }
    #define MACRO 5
    int main()
    {
        aa();
    }
    虽然aa的调用是在宏定义的后面,但是aa的定义是在宏定义的前面,所以,函数aa中是不可以使用宏MACRO的。
    
    
    需要注意的是,如果把宏定义放在头文件中,而头文件的包含其实相当于把文件中的内容插入到了包含的位置(这是个人理解,不知道是不是正确)。因此,宏名的作用域就扩展到包含头文件的文件中了。
    
    
    
4、宏定义一般定义在一行上,如果一行过长,可以使用“\”符号放在行尾进行换行。

5、带参数的宏定义中,宏名和参数的左括号之间不能有空格
   否则,预处理器就会理解成是定义了一个不带参数的宏,该宏所代表的符号序列中,包括()和参数符号。
   
   
5、宏中的#操作符号
    如果宏定义时,在参数前面加一个#操作符,那么预处理器将使该参数生成一个字符串常量(即在展开该参数之后,会将该参数用双引号括起来)。
    例如:
    #define str(x) # x
    
    则 str(testing) 经过宏替换以后就相当于 "testing"
    
    预处理器在处理#时,会将参数中的"和\转义为真正的字符(也就是在前面加上一个\)    
    而 str("hello \t world") 经过宏替换以后其实就是 "\"hello \\t world\""
    
    《c语言编程(Programming in c)》讲到这个操作符的时候,书中正文(P312)和附录有不一致的地方。正文中说,#和参数之间的空格时可选的,但在附录中又说,#和##周围不允许有空格。
    
    这个功能的比较实际的价值,可以参考下面的技巧8
    
6、宏定义中的##操作符
   改操作符的功能是,把操作符前后两个部分直接连接起来。
   例如:
   #define printx(n)  printf("%d\n",xn)
   上面的定义中xn会被作为一个整体的符号来处理,xn中的n不会被当作宏的参数来替换。如果想使得n当作参数来替换,那么可以采用下面的形式:
   #define printx(n)  printf("%d\n",x##n)
   
7、可变参数的宏
   为了向预处理器表明宏可以接受可变参数个数,可以在参数列表后面跟上三个点号(...)。在随后的宏定义表达式中,我们可以使用符号__VA_ARGS__来代表这不定个数的具体参数。
   例如:
   #define debugprn(...) printf("DEBUG:",__VA_ARGS__);
   
   
   
(二)、宏使用技巧
1、对程序中多次出现的常量用宏定义代替。可以增加程序可维护性,一旦将来需要改变常量,直接更改宏定义就可以了。
2、对于可能发生改变的常量用宏定义代替。可以增加程序的可扩展性,例如,某数组大小将来可能会有大小变化,最好在数组定义是不要直接使用常值,而使用一个宏来实现。这样涉及到这个数组大小的地方。
3、对于可能会因为编译器而有差异的地方,可以考虑用宏代替。例如,使用UINT32等,比较明确。在从十六位机,移植到32位机时,直接修改宏定义就会避免位数的不一致。
4、对某些可能需要对不同类型数据进行操作的情况下,可以考虑用宏代替函数,因为宏不进行类型检查。
  个人评论:这种情况,即是宏的优点,但也可能是宏的一个很大的缺陷。不进行类型检查,就丧失了编译器位我们提供的这一功能。在使用的时候,自己一定要特别注意类型的一致或者兼容,如果有类型转换,可能隐含带来不少麻烦。因此,个人觉得,能不使用这个特点就尽量不要使用这个特点。
5、带参数的宏定义,只是进行简单的文字替换,不想函数那样需要压栈,出栈等操作,因此是以空间换时间的一种方式。
  个人评价:考虑到上面4中提到的问题,建议,以空间换时间时,还是使用inline函数吧。
6、可以考虑用一个宏定义来取代c语言中现有的复制和相等判断运算符号,这样就不会出现二者之间的混淆了。例如:
    #define SETAS =
    #define EQUAL ==
    
   个人评论:这样做有两个明显的缺点:
   1)、需要在以后的编程中将所有使用=、和==的地方都换用新的宏,,这要求程序员更改已经多年养成的使用习惯。人的习惯是很难更改的,这样做的代价恐怕太大了。而且新的宏明显的要求程序员打更多的字,对程序员编程的压力挺大。
   2)、采用宏定义对语言本身进行修改,通常不好。至少对于不了解这两个宏定义的c程序员,在看源代码时,会比较难理解,危害了程序的可读性。
   
7、可以用来避免头文件被重复包含
   将整个头文件的内容用下面的格式括起来,就可以避免头文件中的内容被重复包含了。
   
   #ifndef HEADER_FILE_H
   #define HEADER_FILE_H
   ......
   头文件实际内容
   ......
   #endif
   
   个人评论:
      注意,每个头文件都需要定义一个自己独特的宏用来标志自己,这个宏不能和别的文件中的宏重名,否则可能会出问题。

8、用来显示变量名
   #define printint(var) printf(#var"=%i\n",var)
   ......
   printint(count);
   ......
   
   上面句子展开之后就是如下形式了
   printf("count""=%i\n",count);      
   
   
      
      
一个检验某字符是不是小写字母的宏
#define IS_LOWER_CASE(x) (((x)>='a')&&((x)<='z'))

一个转换小字母到大写字母,但保持其他符号不变的宏
#define  TO_UPPER(x) (IS_LOWER_CASE(x)?(x)-'a'+'A':(x))

   
   
  
      
   
   
   
   
(三)、宏定义中可能发生的错误
1、一般来说,宏定义的末尾不加分号。如误加了分号,则展开时可能会出现错误。可以看看下面的例子。
    #define PI 3.1425926;
    ... ...
    area=PI*2*2;
    ... ...
    
    宏展开之后,上面的语句相当于:
    area=3.1415926;*2*2;
    显然不是我们想要得到的。
    
    甚至可能产生一个更为隐蔽的错误,例如:
    #define DD 20;
    ......
    for(i=0;i<20;i++)
      sum=DD+i;
    ......
    
    实际上上面的语句展开后是:
    for(i=0;i<20;i++)
      sum=20;+i;
      
    这样不管循环多少次之后,最终的结果都是sum的值为20。而且,编译程序根本发现不了这种错误。
    
2、带参数宏定义中,宏名和参数左括号之间有空格。参考下面例子:

    #define MAX (x,y) x>y?x:y
    ......
    s=MAX(2,3);
    ......
    展开之后,实际上是:
    s=(x,y) x>y?x:y(2,3);
3、带参数宏定义中的错误,看下面的例子
    
    1)、
    #define SQUARE(x) x*x
    ......
    y=SQUARE(v+1);
    ......
    
    宏展开之后就成了:
    y=v+1*v+1;
    显然有违初衷。
    
    又例如:
    #define MAX(x,y) x>y?x:y
    ......
    s=MAX(i&j,k);
    ......
    展开后就成了:
    s=i&j>k?i&j:k;
    因为&运算符的优先级比>优先级低,所以上面展开之后的语句就相当于下面的样子了:
    s=i&(j>k)?(i&j):k;
    这显然也和我们预想的不一样。
    
    
    解决的方案是,在宏定义中在参数周围加上小括号,上面的宏定义分别改成下面的模样就不会有问题了:
    #define SQUARE(x) (x)*(x)
    #define MAX(x,y) (x)>(y)?(x):(y)
    
    
    但是,这样并没有解决所有的问题,看另外一个例子:
    2)、
    #define MAX(x,y) (x)>(y)?(x):(y)
    ......
    s=MAX(i&j,k)*100;
    ......
    结果展开之后就成了:
    s=(i&j)>(k)?(i&j):(k)*100;
    显然这和我们预想的还是不一样。
    
    这个问题的解决方案是,在整个宏的定义的外层加一层小括号,上面的宏定义分别改成下面的模样就不会有问题了:
    #define MAX(x,y) ((x)>(y)?(x):(y))
    
    个人分析理解:
       带参宏的上面这几个错误的原因在于,宏定义仅仅是符号替换。宏定义时,参数只是一个单独的符号,很容易让人误以为是一个独立的元,而实际上宏调用时,参数可以是任意的序列,不一定是一个独立的元。而开始的几个例子中,显然都是把参数假设当成了独立的元,因为参数实际上不是独立的元,这导致了参数在展开后和其他内容相互作用时,因为优先级等等的原因,导致其失去了独立性。
       最后一个例子,也在与带参宏定义与函数形式的相似,使人容易误解,宏展开以后也会象函数调用那样是一个独立的元。实际上,行展开以后,与其他内容相互作用时,也有可能出现失去独立性的现象。
       而上面给出的两个解决方案在宏定义时,在每个参数周围加上小括号就是保证参数的独立性。在整个宏的定义的外层加一层小括号,就是要保证宏展开以后的独立性。
       
    问题:不知道,在采取上面这两个方法之后,是不是就可以确保,带参数宏的使用中再不会出现这样的问题呢。或者说,这种解决方案具不具有普遍性。
    
    
    
    
    


    


   
    
二、
#include 语句可以出现在文件中的任何地方








⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -