我的研究之预处理

来源:互联网 发布:知乎怎么提问 编辑:程序博客网 时间:2024/05/25 19:59

学习C语言也有好长时间了,想在接下来的时间里对C语言及linux系统编程等的一些东西进行研究总结下来。这里先从预处理开始。

编译一个C程序涉及很多步骤,第一个步骤就是预处理。C预处理器在源代码编译前做以下工作:删除注释,插入被#include指令包含的文件的内容,定义和替换由#include指令定义的符号以及根据条件编译指令确定需要编译的代码内容。

先总结一下由预处理器定义的符号:

                                                       __FILE__                          进行编译的源文件名                  

                                                       __LINE__                            文件当前行的行号

                                                      __DATE__                           文件被编译的日期

                                                      __TIME__                            文件被编译的时间

                                                      __STDC__                            如果编译器遵循ANSI C,其值就为1,否则未定义

        他们的值或者是字符串常量,或者是十进制数字常量。_FILE_和_LINE_在调试程序时非常有用。比如定义下面的宏

    #define DEBUG_PRINT          printf("File %s line %d: x=%d\n",__FILE__,__LINE__,x)   

这种用法在调试一个程序时非常有用,变量x是你想要调试的变量,根据变量来判断情况。另外,可配合if else 语句来使用,如if .... DEBUG_PRINT;else....。注意需要在DEBUG_PRINT后面添加一个分号。

#define 与 宏

  #define 与 宏本质上是一样的,只不过#define没有参数,宏是有参数的#define。

#define定义:#define name     stuff            每当有name符号出现的地方全部替换为stuff。

宏定义         :#define name(parameter-list)     stuff            parameter-list是参数列表,有逗号分隔。注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

宏这里我觉的没什么好说的,就是完全替换,不对时加括号。就这么简单。

宏参数和#define 定义可以包含其他#define定义的符号,但是宏不可以出现递归。

预处理器中的#和##,#arg 这种结构被预处理器翻译为字符串"arg",即#可以使预处理器将一个宏参数转换为一个字符串。比如:

 #define PRINT(FORMAT,VALUE) printf("The value of " #VALUE " is " FORMAT "\n",VALUE)

int x=3;

PRINT("%d",x);

它会输出:The value of   x   is    3


谈到#define就不得不谈typedef。C语言支持一种叫做typedef的机制,可以为各种数据类型定义新名字。简单的就不说了,这里谈一下定义指针的情形。

char     *ptr_char             变量ptr_char 声明为指向字符的指针

typedef      char        *ptr_char         声明把标识符ptr_char作为指向字符的指针类型的新名字。

ptr_char        a                                声明a 是一个指向字符的指针


你应该使用typedef而不是#define来创建新的类型名,因为后者无法正确的处理指针类型,比如

#define  ptr_char     char   *

ptr_char   a,b;             正确的声明了a,但b却被声明为一个字符。

在定义更为复杂的类型名字时,如函数指针或指向数组的指针,使用typedef更为合适。







内联函数与宏本质上是两个不同的概念如果程序编写者对于既要求快速,又要求可读的情况下,则应该将函数冠以inline

先简明扼要,说下关键:
1、内联函数在可读性方面与函数是相同的,而在编译时是将函数直接嵌入调用程序的主体,省去了调用/返回指令,这样在运行时速度更快。

2、内联函数可以调试,而宏定义是不可以调试的。
内联函数与宏本质上是两个不同的概念如果程序编写者对于既要求快速,又要求可读的情况下,则应该将函数冠以inline。下面详细介绍一下探讨一下内联函数与宏定义。

一、内联函数是什么?
内联函数是代码被插入到调用者代码处的函数。如同 #define 宏(但并不等同,原因见下文),内联函数通过避免被调用的开销来提高执行效率,尤其是它能够通过调用(“过程化集成”)被编译器优化。

二、 内联函数是如何在安全和速度上取得折衷?
在 C 中,你可以通过在结构中设置一个 void* 来得到“封装的结构”,在这种情况下,指向实际数据的 void* 指针对于结构的用户来说是未知的。因此结构的用户不知道如何解释void*指针所指内容,但是存取函数可以将 void* 转换成适当的隐含类型。这样给出了封装的一种形式。

不幸的是这样做丧失了类型安全,并且也将繁琐的对结构中的每个域的访问强加于函数调用。(如果你允许直接存取结构的域,那么对任何能直接存取的人来说,了解如何解释 void* 指针所指内容就是必要的了;这样将使改变底层数据结构变的困难)。

虽然函数调用开销是很小的,但它会被累积。C++类允许函数调用以内联展开。这样让你在得到封装的安全性时,同时得到直接存取的速度。此外,内联函数的参数类型由编译器检查,这是对 C 的 #define 宏的一个改进。

 
三、为什么我应该用内联函数?而不是原来清晰的 #define 宏? 
因为#define宏定义函数是在四处是有害的:
和 #define 宏不同的是,内联函数总是对参数只精确地进行一次求值,从而避免了那声名狼藉的宏错误。换句话说,调用内联函数和调用正规函数是等价的,差别仅仅是更快:
复制代码 代码如下:

// 返回 i 的绝对值的宏
#define unsafe(i) \
         ( (i) >= 0 ? (i) : -(i) )

// 返回 i 的绝对值的内联函数
inline
int safe(int i)
{
   return i >= 0 ? i : -i;
}

int f();

void userCode(int x)
{
   int ans;

   ans = unsafe(x++);   // 错误!x 被增加两次
   ans = unsafe(f());   // 危险!f()被调用两次

   ans = safe(x++);     // 正确! x 被增加一次
   ans = safe(f());     // 正确! f() 被调用一次
}

和宏不同的,还有内联函数的参数类型被检查,并且被正确地进行必要的转换。宏定义复杂函数是有害的;非万不得已不要用。

四、如何告诉编译器使非成员函数成为内联函数?
声明内联函数看上去和普通函数非常相似:
void f(int i, char c);
当你定义一个内联函数时,在函数定义前加上 inline 关键字,并且将定义放入头文件:inlinevoid f(int i, char c){   // ...}
注意:将函数的定义({...}之间的部分)放在头文件中是强制的,除非该函数仅仅被单个 .cpp 文件使用。尤其是,如果你将内联函数的定义放在 .cpp 文件中并且在其他 .cpp文件中调用它,连接器将给出 “unresolved external” 错误。

0 0
原创粉丝点击