前面的讨论中,我们已经明确了,一个函数的参数可以是指针,对一个函数,缺省编译器给它分配了一个指针,可以用一个指针变量指向这个函数指针。同样函数的返回类型也可是指针类型。所以通常意义的指针函数和函数指针是指的两个完全不同的东西。所谓的指针函数是指返回类型是指针变量的函数。而函数指针则是这个函数在编译器分配下的地址。与此类似的概念是指针数组和指向指针的指针概念。指针数组指的是数组元素的类型是指针。

image

指针还可以指向指针变量,形成指针指变量**p,其实理解起来有时候比较累,但是只要记住一点*p就是一个地址变量。**p也还是一个地址变量。总结下来如下表所示:

image

从上图还可以看出如下几点:

【1】,指针变量存储是内存地址,也就是指针,它是一个整数,因此,可以加减、赋值。如p=&a;p=array; p =&array[i];p++;p=max;p1=p2;

【2】指针变量指向空值p=NULL;

【3】void 通常在函数中用来限定函数的返回值和入参如void fun(void)。但在ANSI C中定义了一个void 指针和const指针。void 指针不指向一个确定的类型,仅仅用来存放一个地址,它默认下是可以指向任何其它类型的,但需要进行强制转换。对const指针需要根据情况来区分const是限制类型还是限制指针地址。

image

7.预处理指令

在C语言中规定反是以#开头的均为预处理命令,在编译的过程中,编译器首先要处理的就是这些预处理指令。这些预处理指令有头文件包含指令和宏定义指令。宏在编译之前已被处理掉,因此反编译是反编译不出宏的。宏定义是定义在程序文件开头,只对当前程序起作用。而且也是按顺序从前往后,如某个函数不想用这个宏,可直接用#undef终止。宏定义与typedef是不一样的,typedef是一条语句,是在编译时确定的。

image

从上面还可以看出宏还可以像一个函数一下定义,只是它只是简单的表达式替换。注意宏字符串名必须紧跟括号,不能中间空格,并且通常后面表达式需要将宏参数用括号括开。如#define SQ(y) (y)*(y) 和#define SQ(y) ((y)*(y))会在不同的场景下产生不同的效果。如下图所示:

image

下面列出一些常见的宏定义规则:

【1】带参宏定义中,宏名与形参表之间不能有空格如不能定义#define MAX   (a,b)  a>b?a,b

【2】带参宏定义中,形参不分配内存,因此不作类型定义。实参需要类型定义。这与函数不同

【3】带参宏定义中,形参表达式会因为具体情况产生多义性,因此定义宏参表达式式用括号全部括起来比较保险。

【4】带参宏定义与函数有可能产生不同的结果,因为函数参数是传值。需要注意。

【5】能用const定义的情况就不要用宏定义,或者使用inline函数来定义

预处理指令中除了前面的宏常量的定义之外还有两块,一块是头文件包含指令,通常头文件都是用来声明函数或者宏常量。头文件可以内嵌。包含命令中的文件名可以用双引号也可以用尖括号,但是两种形式是有所区别的,使用尖括号表示在包含文件目录中去查找(包含目录是指用户在设置环境时设置的),而不是源文件目录去查找。

#include “stdio.h”

#include <math.h>

使用双引号则表示先在当前的源文件目录中查找,若未找到则在包含目录中查找。第二块就是条件编译,通过设置不同的条件来控制编译不同的程序代码,产生不同的编译代码,如一份代码,通过条件编译控制,编译出针对不同OS平台的目标代码。条件编译分如下几种:

【1】如果标识符已被#define命令定义过,则编译前段,否则后段。

image

【2】如果标识符未被#define 命令定义过,则编译前段,否则后段。 通常用来判断一个宏量是否被定义过。

image

【3】如果常量表达式不等于0,执行前段,否则执行后段,这个常用来注释大段代码

image

下面是一个示例:

image