在C语言中,函数虽然不是变量,但可以定义指向函数的指针。这种类型的指针可以被赋值,存放在数组中,传递给函数以及作为函数的返回值等。
  指针
  相信大家对下面的代码不陌生:
  int i=2;
  int *p;
  p=&i;
  这是简单的指针应用,也是基本的用法。再来熟悉一下什么是指针:首先指针是一个变量,它保存的并不是平常的数据,而是变量的地址。如上代码,指针p中保存的是整型变量i的地址信息。
  接下来看如何定义一个指针,既然指针也是一个变量,那么它的定义也和其它变量一样定义:如:int p;是间接寻址或间接引用运算符。上例中我们还看到了一个特别的运算符&,它是一个取地址运算符(在其他合适场合&也是按位运算运算符,&&为取交集运算符)。
  在上面的指针定义中,我们看到了定义的是一个整型指针,难道指针还有类型吗?答案是肯定的,指针只能指向某种特定类型的对象,也是说,每个指针都必须指向某种特定的数据类型(的例外:指向void类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身。)。比如,int 类型的指针不能指向char 类型的变量。
  下面我们给出一个完整的例子来说明指针的简单应用:
#include<stdio.h>
void main()
{
int a,b,c,*p;
a=1;
b=3;
p=&a;
b=*p+1;
c=*(p+1);
printf("%d %d %d %d /n",a,b,c,*p+3);
}
  运行结果为: 1 2 -858993460 4
  这是个完整的例子,可以自己在机器上调试一下,现在很多人用的都是微软的Visual Studio 开发环境,有人不知道在该开发环境中怎么写C程序以及调试C程序,具体境况可以参考附录。
  在上面例子中,看到了这样两个表达式b=p+1;和c=(p+1);前者的意思是p所指的地址里的内容加1再赋给b,相当于b=a+1;,后者是p所指的地址加1再把(p+1)所指的地址赋给c,当然我们不知道p的下一个地址里放的是什么,所以输出了一个随机值(这样的操作时很危险的,切记不要使用不确定的内存地址)。
  数组
  数组大家应该都很熟悉了,用途非常广泛。
  int a[4]={2,4,5,9};
  此语句定义一个4个空间大小的整型数组a并为它进行了初始化。
  数组的基础知识可以参考其他相应的教材,我们在这主要讨论指针和数组的结合应用。
  我们再来看个完整的例子:
#include<stdio.h>
void main()
{
int a[4]={2,4,5,9};
int *p;
p=a;
*p=*p++;
printf("%d %d %d/n",*p,*p+6,*(p+1));
}
  运行结果:4 10 5
  分析:语句p=a;表示把数组a的第0个元素的地址赋给指针p,数组名a代表的是数组a的第0个元素的地址。
  a[i]表示数组a的第i个元素,如果定义一个指针p,那么语句p=&a[0];表示可以将指针p指向数组a的第0个元素,也是说p的值为数组元素a[0]的地址。那么(p+1)引用的是数组元素a[1]的内容,p+i是数组元素a[i]的地址,(p+i)引用的是数组元素a[i]的内容。对数组元素a[i]的引用也可以写成(a+i)。可以得出结论:&a[i]与a+i的含义相同,p[i]与(p+i)也是等价的。
  虽然数组和指针有这么多通用的地方,但我们必须记住,数组名和指针之间有一个不同之处。指针是一个变量,因此语句p=a和p++都是合法的。但数组名不是变量,因此,类似于a=p和a++形式的语句是非法的。
  下面来看一个我们常用的函数strlen(char *s):
int strlen(char *s)
{
int n;
for(n=0;*s!='/0';s++)
n++;
return n;
}
  因为s是一个指针,所以对其执行自增运算是合法的。执行s++运算不会影响到strlen函数的调用者中的字符串,它仅对该指针在strlen函数中的私有副本进行自增运算。在函数定义中,形式参数char s[]和char *s是等价的。
  我们再来看一下地址算术运算:如果p是一个指向数组中某个元素的指针,那么p++将对p进行自增运算并指向下一个元素,而p+=i将对p进行加i的增量运算,使其指向指针p当前所指向元素之后的第i个元素。同其他类型的变量一样,指针也可以进行初始化。通常,对指针有意义的初始化值只能是0或者是表示地址的表达式,对后者来说,表达式所表达的地址必须是在此之前已定义的具有适当类型的数据的地址。任何指针与0进行相等或者不相等的比较运算都有意义。但是指向不同数组的元素的指针之间的算术或比较运算没有意义。指针还可以和整数进行相加或相减运算。如p+n表示指针p当前指向的对象之后第n个对象的地址。无论指针p指向的对象是何种类型,上述结论都成立。在计算p+n时,n将根据p指向的对象的长度按比例缩放,而p指向的对象的长度则取决于p的声明。例如,如果int类型占4个字节的存储空间,那么在int类型的计算中对应的n将按4的倍数来计算。
  指针的减法运算也是有意义的,如果p和q指向相同数组中的元素,且p<q,那么q-p+1是位于p和q指向的元素之间的元素的数目。我们来看一下strlen(char *s)的另一个版本:
int strlen(char *s)
{
char *p=s;
while(*p!='/0')
p++;
return p-s;
}
  程序中,p被初始化为指向s,即指向该字符串的第一个字符,while循环语句将依次检查字符串中的每个字符,直到遇到标识字符数组结尾的字符’/0’为止。由于p是指向字符的指针,所以每执行以此p++,p将指向下一个字符的地址,p-s则表示已经检查过的字符数,即字符串长度。
  总结:有效的指针运算包括相同类型指针之间的赋值运算;指针和整数之间的加减运算;指向相同数组中元素的两个指针间的减法或比较运算;将指针赋值为0或指针与0之间的比较运算。其他所有形式的指针运算都是非法的。
  再来看两条语句:
  char a[]=”I am a boy”; char *p=”I am a boy”;
  a是一个仅仅足以存放初始化字符串以及空字符’/0’的一维数组。数组中的单个字符可以进行修改,但a始终指向同一个存储位置。而p是一个指针,其初值指向一个字符串常量,之后它可以被修改以指向其他地址,但如果试图修改字符串的内容,结果是没有定义的。
  为了更容易理解数组和指针的关系,我们再来看一个函数:
  void strcpy(char *s,char *t)
  {
  int i;
  i=0;
  while((s[i]=t[i])!='/0')
  i++;
  }
  因为参数是通过值传递的,所以在strcpy函数中可以以任何方式使用参数s和t。
  下面是指针实现的几个版本:
  void strcpy(char *s,char *t)
  {
  while((*s=*t)!='/0'){
  s++;
  t++;
  }
  }
  简版本:
  void strcpy(char *s,char *t)
  {
  while(*s++=*t++)
  ;
  }
  这里,s和t的自增运算放到了循环的测试部分中。表达式*t++的值是执行自增运算之前t所指向的字符。后缀运算符++表示在读取该字符之后才改变t的值。同样,在s执行自增运算之前,字符被存储到了指针s指向的旧位置。上面的版本中表达式同’/0’的比较是多余的,因为只需要判断表达式的值是否为0即可。