指针

1 地址和指针的概念

  如果程序中定义了一个变量,在编译时就给这个变量分配内在单元,系统根据程序中定义的变量类型,分配一定长度的空间,例如一般微机使用的C系统为整型变量分配2个字节,对实型变量分配4个字节,内存区的第一个字节有一个编号,这就是地址,它相当于旅馆的房间号,在地址所标志的内在单元中存放数据,这相当于旅馆各个房间中居住旅客一样。

  由于通过地址能找到所需的变量单元,我们可以说,地址 指向该变量单元,因此在C语言中,将地址形象化地称为 指针,意思是通过它能找到以它为地址的内存单元,一个变量的地址称为该变量的指针。

2 变量的指针和指向变量的指针变量

  变量的指针就是变量的地址,存放变量地址的变量就是指针变量,用来指向另一个变量。

2.1 定义一个指针变量

  C语言规定所有的变量在使用前必须先定义,指定基类型,并按此分配内在单元,指针变量不同于整型变量和其它类型的变量,它是用来专门存放地址的,必须将它定义为 指针类型,如:

  int i, j;

  int *pinter_1, *pointer_2;

  第1行定义了两个整型变量i,j第2行定义了两个指针变量,pointer_1,pointer_2,它们是指向整型变量的指针变量,左端的int是在定义的指针变量时必须指定的 基类型,指针变量的基类型用来指定该指针变量可以指向的变量的类型。

  定义指针变量的一般形式为

  基类型 *指针变量名

如:

  float *pointer_3;//指向实型变量的指针变量

  char *Pointer_4;//指向字符型变量的指针变量

  下面的赋值语句使一个指针变量指向一个整型变量

  pointer_1 = &i;

  pointer_2 = &j;

  将变量i 的地址存放到指针变量 pointer_1 中,因此pointer_1 就指向了变量i 。同理pointer_2也一样指向变量j。

注意:

  (1)指针变量前面的 "*" 表示变量的类型为指针变量,注意指针变量名是pointer_1, pointer_2 而不是 *pointer_1, *pointer_2,这与以前所介绍的定义变量的形式不同。

  (2)在定义指针变量时必须指定基类型,因为指针只能指向与定义类型相同的变量。

2.2 指针变量的引用

  指针变量中只能存放地址(指针),不要将一个整型量(或任何其它地址类型的数据)赋给一个指针变量,下面的赋值是不合法的:

  pointer_1 = 100;//pointer_1 为指针变量,100为整数

  有两个有关的运算符

  & 取地址运算符 * 指针运算符(或 间接访问 运算符)

如:&a 为变量a 的地址,*P为指针变量p所指向的存储单元。

#include <stdio.h>

void main()

{

  int a, b;

  int *pointer_1, *pointer_2;

  a = 100; b = 10;

  pointer_1 = &a;

  pointer_2 = &b;

  printf("%d, %d\n", a, b);

  printf("%d, %d\n", *pointer_1, *pointer_2);
}

运行结果为:

100, 10

100, 10

  补充说明 & * 两个运算符

  (1)如果已执行 pointer_1 = &a 若有

  &*pointer_1 它的含义是什么呢?

  & 与 * 运算符的优先级别相同,但按自右而左方向结合,因此先进行 *pointer_1 的运算,它就是变量 a 再执行 & 运算,因此&*pointer_1 与 &a 相同即变量 a 的地址。

  如果有 pointer_2 = &*pointer_1,它的作用就是将 &a(a的地址)赋给 pointer_2;

  (2)*&a 的含义是什么呢?先进行 &a运算,得到 a 地址,再进行 *  运算,即 &a 所指向的变量。

  (3)(*pointer_1)++ 相当于a++ ,注意括号是必要的,如果没有括号就成为了 *pointer_1 ++ ,因为 ++ 与 * 为同一优先级别,而结合方向为自右而左,相当于*(pointer_1 ++),由于++ 在pointer_1的右侧是后加,因此先对 pointer_1 的原值进行 * 运算,得到a 的值,然后使 pointer_1 的值改变,这样 pointer_1 不再指向 a 了。

2.3 指针变量作为函数参数

  函数的参数不仅可以是的整数,实型,字符型,等数据,还可以是指针类型,它的作用是将一个变量的地址传送到别一个函数中。

如:

#include <stdio.h>

void swap(int *p1, int *p2)

{

  int temp;

  temp = *p1;

  *p1 = *p2;

  *p2 = temp;
}

void main()

{

  int a, b;

  int *p1, *p2;

  a = 10; b = 20;

  p1 = &a; p2 = &b;

  if (a < b) swap(p1, p2);

  printf("%d, %d\n", *p1, *p2);
}

运行结果:

20, 10

如果有下面的函数

void swap(int *p1, int *p2)

{

  int *temp;

  *temp = *p1;

  *p1 = *p2;

  *p2 = *temp;
}

  *temp是指针变量temp所指向的变量,但temp中的并无确定的地址值,它的值是不可预见的,*temp所指向的单元也是不可以预见的,因此对*temp赋值可能会破坏系统的正常工作状况,应该将*p1的值赋线一个整型变量。

再看

void swap(int x, int y)

{

  int temp;

  temp = x;

  x = y;

  y = temp;
}

  因为函数的 单向传递的值传递方式,形参值的改变无法传给实参。

  为了使在函数中改变了的变量值能被main函数所用,不能采取上述把要改变值的变量作为参数的方法,而应该用指针变量作为参数,在函数执行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量的值的变化依然保留下来,这样就实现了通过函数使变量的值发生了变化,在主调函数中使用这些改变了的值的目的。

  如果想通过函数调用得到n个要改变的值可以:

  (1)在主调函数中设n个变量,用n 个指针变量指向它们。

  (2)然后将指针变量作实参,将这n个变量的地址传给所调用的函数的形参。

  (3)通过形参指针变量,改变n个变量的值。

  (4)主调函数中就可以使用这些改变了的值的变量。

但如下函数

void swap(int *p1, int *p2)

{

  int *p;

  p = p1;

  p2 = p1;

  p2 = p;
}

  这样也是不能实现的,因为C语言中实参变量和形参变量之间的数据传递是单向的值传递方式,指针变量作函数参数也是要遵循这一规则。

  调用函数不可能改变实参指针变量的值,但可以改变实参指针变量所指变量的值,函数调用可以得到一个返回(即函数值)而运用指针变量作参数可以得到多个变化了的值,如果不用指针变量是难以做到这一点的。

3 数组的指针和指向数组的指针变量

  一个变量有地址,一个数组包含若干个元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址,指针变量既然可以指向变量,当然也可以指向数组和数组元素,所谓数组的指针是指数组的起始地址数组元素的指针是数组元素的地址。

3.1 指向数组元素的指针

  定义一个指向数组元素的指针变量的方法

  int a[10]; //定义a为包含10个整形数据的数组

  int *p //定义p为指向整型变量的指针变量

  应当注意,如果数组为in型,则指针变量亦应指向int型,下面是对该指针元素赋值

  p = &a[0];

  把a[0]元素的地址赋给指针变量p,也就是说p指向a数组的第0号元素。

  C语言规定数组名代表数组的首地址,也就是第0号元素的地址,因此下面两个语句等价:

  p = &a[0];

  p = a;

  注意数组a不代表整个数组,上述 p = a 的作用是把a 数组的首地址赋给指针变量p而不是把数组a各元素的值赋给p。

  在定义指针变量时可以赋给初值:

  int *p = &a[0];

  它等效于

  int *p;

  p = &a[0];//不应写成 *p = &a[0];
  也可以写成如下:

  int *p = a;

  它的作用是将a数组的首地址(即a[0]的地址)赋给指针变量p(而不是赋给 *p)

3.2 通过指针引用数组元素

  C语言规定,如果指针变量p已指向数组中的一个元素,则p + 1 指向同一数组中的下一个元素(而不是将p的值简单地加1)例如,数组元素是实型,每个元素占4个字节,则 p + 1 意味着使p的值(地址)加4个字节,以使它指向下一元素,p + 1 所代表的地址实现是 p + 1 * d, d 是一个数组元素所占的字节数(对整数 d = 2, 实数 d = 4)。

  如果 p的初值为&a[0],则:

  (1)p + i 和 a + i 就是 a[i] 的地址,或者说,它们批向a 数组的第i 个元素,这里需要说明,a 代表数组首地址,a + i 也是地址,它的计算方法同 p + i 即它的实际地址为 a + i * d,例如 p + 9 和 a + 9 的值是 &a[9] ,它指向a[9]。

  (2)*(P + i) 或 *(a + i) 是 p + i 或 a + i 所指向的数组元素,即a[i] 。例如 *(p + 5) 或 *(a + 5)就是 a[5],即 *(p + 5) = *(a + 5) = a[5]。实际上,在编译时,对数组元素 a[i] 就是处理成 *(a + i),即按数组首地址加上相对位移量得到要找的元素的地址,然后找出该单元中的内容。例如,若数组a 的首地址为 1000,设数组为整型,则 a[3] 的地址就是这样计算出来的:1000 + 3 * 2 = 1006 然后从 1006 地址所标志的整型单元取出元素的值。即a[3]的值。可以看出,[] 实际上就是就变址运算符,即将 a[i] 按 a + i 计算地址,然后找出些地址单元中的值。

  (3)指向数组的指针变量也可以带下标,如 p[i] 与 *(p + i)等价。

  根据以上,引用一个数组元素可以用

  a.下标法,如 a[i] 形式。

  b.指针法,如 *(a + i) 或 *(p + 1) 其中 a 是数组名,p 是指向数组的指针变量,其初值 p = a。

下标法引用

#include <stdio.h>

void main()

{

  int a[10];

  int i;

  for(i = 0; i<10; i++)

    scanf("%d", &a[i]);

  printf("\n");

  for(i=0; i<10; i++)

    printf("%d", a[i]);
}

通过数组名计算数组元素地址,找出元素的值

#include <stdio.h>

void main()

{

  int a[10];

  int i;

  for(i=0;i<10;i++)

    scanf("&d", &a[i]);

  printf("\n");

  for(i=0;i<10;i++)

    printf("%d", *(a+i));
}

用指针变量指向数组元素

#include <stdio.h>

void main()

{

  int a[10];

  int i, *p;

  for(i=0;i<10;i++)

    scanf("%d", &a[1]);

  printf("\n");

  for(p=a; p<(a + 10);p++)

    printf("%d", *p);

}

对三种方法的比较

  (1)第1和第2方法执行效率是相同的,C编译系统是将a[i]转换为 *(a + i)处理的,即先计算元素地址,因此用第1和第2种方法找数组元素费时较多。

  (2)第3种方法比1,2 方法快,用指针变量直接指向元素,不必每次都重新计算地址,像 p ++ 这样的自加操作是比较快的,这种有规律的改变地址值(p ++) 能大提高执行效率。

  (3)用下标法比较直观,能直接知道是第几个元素,例如 a[5] 是数组中序号为 5 的元素,(注意序号是0开始计算)用地址法或指针变量的方法不直观,难以很快的判断出当然处理的是哪一个元素。

  在使用指针变量时,有几个问题要注意

  (1)指针变量可以实现本身的值的改变。如上面第3种方法是用指针变量p来指向元素,用 p++ 使 p 的值不断改变,这是合法的。如果不用 p 而使 a 变化(例如用 a ++)行不行呢,例如将上述程序的最后两行改为

  for(p=a; a<(p+10);a++)

    printf("%d", *a);

是不行的,因为a 是数组名,它是数组首地址,它的值在程序运行期间是固定不变的,是常量, a++ 是无法实现的。

  (2)要注意指针变量的当前值,请看下面的程序

#include <stdio.h>

void main()

{

  int *p, i, a[10];

  p = a;

  for(i=o;i<10;i++)

    scanf("%d", p++);

  printf("\n");

  for(i=0;i<10;i++,p++)

    printf("%d", *p);
}

  这个程序乍看起来好像没有什么问题,可是运行后得到的结果是无效的数据。

  显然输入的数值不是a 数组中的各个元素的值,原因是指针变量的初始值为 a 数组首地址,但经过第一个 for 循环读入数据后,p 已指向 a 数组的末尾,因此,在执行第二个 for 循环时,p 起始值不是 &a[0] 而是  a + 10,因此执行循环时,第次要执行 p++, p  指向的是 a 数组下面的10 个元素,而这些存储单元中的值是不可以预料的。

  解决这个问题的办法是,只要在第二个 for 循环之前加一个赋值语句

  p = a;

  使 p 的初始值回到 &a[0] 这样结果就对了。

#include <stdio.h>

void main()

{

  int *p, i, a[10];

  p = a;

  for(i=o;i<10;i++)

    scanf("%d", p++);

  printf("\n");

  p = a;

  for(i=0;i<10;i++,p++)

    printf("%d", *p);
}

  (3)从上例可以看到,虽然定义数组时指定它包含10个元素,用 p  指向数组元素,但指针变量可以数组以后的内存单元,如果有 a[10] C编译程序并不认为非法,系统把它按 *(a + 10) 处理,即先找到 (a + 10) 的值(是一个地址),然后找出它指向的单元的内容。

  在定义数组时,也可以不定义其长度,如上例对数据类型的定义可改为

  int *p, i, a[];

  对数组 a 不指定长度,用 p  来指向各个程序运行结果相同。

  (4)注意指针变量的运算,如果先使 p 指向数组 a 即 p = a 则

  a. p++ 或 p += 1,使 p 指向下一元素,即 a[1] 。若再执行 *p 取出下一个元素 a[1] 值。

  b. *p ++ 由于 ++ 和 * 同优先级,结合方向为自右而左,因此它等价于 *(p ++),作用是先得到 p 指向变量的值 *p 然后再使 p ++。

  例如下面的语句

  for(i=0;i<10;i++,p++)

    printf("%d", *p);

  可改写为

  for(i=0;i<10;i++)

    printf("%d", *p++);

  作用完全一样,它们的作用都是先输出 *p 的值,然后使 p 的值加 1 ,这样下一次循环时,*p 就是下一个元素的值。

  c. *(p ++) 与 *(++ p) 作用是不同的,前进是先取 *p 后使 p + 1 ,后者是先使 p 加 1 ,再取 *p ,若 p 初值为 a 即 &a[0],输出 *(p++)时,得a[0]的值,而输出 *(++p),则得到 a[1] 的值。

  d. (*p) ++ 表示 p 所指向的元素值加1,即 (a[0]) ++,如果 a[0] = 3,则(a[0]) ++ 的值为4,注意是元素的值加1,不是指针值加1。

  e. 如果 p 当前指向 a 数组中第 i 个元素刚

  *(p --) 相当于 a[i--],先对 p 进行 * 运算,再使 p 自减。

  *(++p) 相当于 a[i++],先使 p 自加,再作 * 运算。

  *(--p) 相当于 a[--i],先使 p 自减,再作 * 运算。

  将 ++ 和 -- 运算符用于指针变量十分有效,可以使指针变量自动向前或向后移动,指向下一个或上一个数组元素,但在使用时要小心。

3.3 数组名作函数参数

  数组名可以用作函数的形参和实参。如:

f(int arr[], int n)

{

  
}

void main()

{

  int aray[10];

  f(aray, 10);

  ....
}

  array 为实参数组名,arr 为形参数组名,当用数组名作参数时,如果形参数组中各元素的值发生变化,实参数组元素的值随之变化。

  先看数组元素作实参时的情况,如果已定义一个函数其原型为:

  void swap(int x, int y);

  假设函数的作用是将两个形参(x, y) 的值交换,今有以下的函数调用:

  swap(a[1], a[2]);

  用数组元素做实参的情况与用变量作实参时一样,是值传递方式,单向传递数据,此时 a[1] 和 a[2] 的值不会改变。

  再看用数组名作函数参数的情况,前已介绍,数组名代表数组首地址,因此,如果用数组名作实参,在调用函数时是把数组的首地址传送给形参,(注意不是把数组的值传给形参)。在上面我们指定实参数组名为 array ,用数组名 arr 作为形参,以接受实参传过来的数组首地址,这样实参数组与形参数组共占同一段内存。

  实参数组 array 的第 0 号元素和形参数组 arr 的第 0 号元素共点一个单元,同理,array[1] 与 arr[1] 共占一个单元,很好理解,实际上 能够接受并存放地址值的只能是指针变量,因此 C编译系统都是将形参数组名作为指针来处理的,从上面看到函数 f  的形参是写成数组形式的:

  f(int arr[], int n)

  但在编译时是将 arr 按指针变量处理的,相当于将函数 f 的首部写成

  f(int *arr, int n)

  以上两种写法是等价的,在调用该函数时,系统会建立一个指针变量 arr ,用来存放从调用函数传递过来的实参数组首地址,如果在 f  函数中用 sizeof 运算符测定 arr 所点字节数即 sizeof arr 的值结果为 2 (现在的系统为 4),这就证明了系统是把 arr 作为指针变量来处理的。

  需要说明的是:C语言调用函数时虚实结合的访求都是采用 值传递 方式,当用变量名作为函数参数时,传递的是变量的值,当用数组名作为函数参时,由于数组名代表的是数组起始地址,因此传递的值是数组首地址,所以要求形参为指针变量。不要错认为用数组名作函数参数时不须用 值传递 方式。

  在用数组名作为函数实参时,既然实际上相应的形参是指针变量,为什么还允许使用形参数组呢,这是因为在C语言中用下标法和指针法都可以访问一个数组(如果有一个数组 a 刚 a[i] 和 *(a + i) 无条件等价),用下标法比较直观,便于理解。

程序示例:

#include <stdio.h>
#include <stdlib.h>

#define ARRLEN 5

void inv(int arr[], int n)
{

  int i, j, m;
   j = n / 2 - 1;

  for(i=0;i<j;i++)
   {

    m = arr[i];
    arr[i] = arr[n - i - 1];

    arr[n - i - 1] = m;

  }
}

void main()
{
  int arr[ARRLEN] = {1,2,3,4,5};
  int i;

  for(i=0;i<ARRLEN;i++) 

    printf("%d ", arr[i]);

  printf("\n");
  inv(arr, ARRLEN);

  for(i=0;i<ARRLEN;i++)
    printf("%d ", arr[i]);
  printf("\n");

}

  归纳起来,如果有一个实参数组,想在函数中改变些数组的元素的值,实参与形参的对应关系有以下4种:

  a. 形参和实参都用数组名如:

  void main()

  {

    int a[10];

    ....

    f(a, 10);

  }

  f(int x[], int n)

  {

  }

  程序中实参 a 和形参 x 都已定义为数组,如前所述,传递是 a 数组的首地址,a 和 x 数组共用一段内存单元,也可以说,在调用函数期间,a 和 x 指向的是同一个数组。

  b. 实参用数组名,形参用指针变量如:

  void main()

  {

    int a[10];

    f(a, 10);

  }
  f(int *x, int n)

  {

  }

  实参 a 为数组名,形参 x 为指向整数变量的指针变量,函数开始执行时,x 指向a[0] 即 x = &a[0] 通过 x 值的改变,可以指向 a 数组的任一元素。

  c. 实参形参都用指针变量如:

  void main()

  {

    int a[10], *p;

    p = a;

    f(p, 10);

  }

  f(int *x, int n)

  {
  }

  实参 p 和 形参 x 都是指针变量,先使实参指针变量 p 指向数组 a,p 的值是 &a[0] ,然后将 p 的值传给形参指针变量 x ,x 的初始值也是 &a[0]。

  d. 实参为指针变量,形参为数组名如:

  void main()

  {

    int a[10], *p;

    p = a;

    f(p, 10);

  }

  f(int x[], int n)

  {

  }

  实参 p 为指针变量,它使指针变量 p 指向 a[0], 即 p = a 或 p = &a[0],形参为数组名 x,从前面的介绍已知,实际上将 x 作为指针变量处理,今将 a[0] 的地址传给形参 x ,使指针变量 x 指向 a[0],也可以理解为形参数组 x 取得 a 的数组的地址,x 数组和 a 数组共用同一段内存单元。

  应注意,如果用指针变量作实参,必须先使指针变量有确定的值,指向一个已定义的数组。以上四种方法,实质上都是地址的传递。

3.4 指向多维数组的指针和指针变量

  用指针变量可以指向一维数组,也可以指向多维数组,但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。

4.1 多维数组的地址

  今以二维数组为例,它的定义如下:

  int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};

  a 是一个数组名,a 数组包含3行,即3 个元素,a[0],a[1],a[2],而每一元素又是一个一维数组,它包含4个元素,即4个列元素,例如 a[0] 所代表的一维数组又包含4个元素 a[0][0], a[0][1], a[0][2], a[0][3]。

  从二维数组的角度来看,a 代表整个二维数组的首地址,也就是第0行的首地址,a + 1 代表第1行的首地址,如果二维数组的首地址为 2000 则 a + 1 为 2008 因为第0 行有 4 个整型数据,因此 a + 1 的含义是 a[1] 的地址,即 a + 4 * 2 = 2008,a + 2 代表第2 行的首地址,它的值是2016。

  a[0], a[1], a[2],既然是一维数组名,而C语言又规定了数组名代表数组的首地址,因些 a[0] 代表第 0 行一维数组中第 0 列元素的地址,即 &a[0][0] 。a[1] 的值是 &a[1][0],a[2] 的值是 &a[2][0]。

  第0行第1列元素的地址可以用 a[0] + 1 来表示,此时 a[0] + 1 中的 1 代表 1 个表元素的字节数,即2个字节,今 a[0] 的值是 2000 ,a[0] + 1 的值就是 2002而不是2008 这是因为现在是在一维数组范围内讨论问题的,正如有一个一维数组 x ,x + 1 是其弟 1 表元素地址一样,a[0] + 0,a[0] + 1,a[0] + 2,a[0] + 3 分别是 a[0][0], a[0][1], a[0][2], a[0][3]的地址,即 &a[0][0], &a[0][1], &a[0][2], &a[0][3]。

  前已述及,a[0] 和 *(a + 0) 等价,a[1] 和 *(a + 1) 等价,因此 a[0] + 1 和 *(a + 0) + 1 的值都是 &a[0][1],a[1] + 2 和 *(a + 1) + 2 的值是 &a[1][2],注意不要将 *(a+1) + 2 错写成 *(a + 1 + 2) 后者变成 *(a + 3) 相当于 a[3]。

  进一步分析,欲得到 a[0][1] 的值,用地址法怎么表示呢,既然 a[0] + 1 和 *(a + 0) + 1 是 a[0][1] 的地址,那么 *(a[0] + 1)就是 a[0][1]的值,同理 *(*(a+0) + 1) 或 *(*a + 1) 也就是 a[0][1] 的值,*(a[i] + j) 或 *(*(a + i) + j) 就是 a[i][j] 的值。务请记住 *(a + i) 和 a[i] 的等价的。

  有必要对 a[i] 的性质作进一步说明,a[i] 从形式上看是 a 数组中第 i 个元素。如果 a  是一维数组名,则 a[i] 代表 a 数组第 i 个元素所占的内存单元,a[i] 是有物理地址的,是占内存单元的,但如果 a 是二维数组,则 a[i] 是代表一维数组名,a[i] 本身并不占实际的内存单元,它也不存放 a  数组中各元素的值,它只是一个地址,(如同一个一维数组名 x 并不占内存单元而只代表地址一样)。a, a + i, a[i], *(a+i), *(a + i) + j, a[i] + j,都是地址,*(a[i] + j), *(*(a+i)+j) 是二维数组元素 a[i][j]的值。

  不要把 &a[i] 简单的理解为 a[i] 单元的物理地址,因为并不存在 a[i] 这样一个变量,它只是一种地址的计算方法,能得到第 i 行的首地址,&a[i] 和 a[i] 的值是一样的,但它们的含义是不一样的,&a[i] 或 a + i 指向行,而 a[i] 或 *(a + i) 指向列,当下标 j 为 0时,&a[i] 和 a[i] 即 a[i] + j 值相等,即指向同一位置,*(a+i) 只是 a[i] 的另一种表示形式,不要简单的地认为是 a + i 所指单元中的内容,在一维数组中 a + i 所指的是一个数组元素的存储单元,它有具体的值,而对二维数组 a + i 不是指向具体存储单元而指向行,在二维数组中 a + i = a[i] = *(a + i) = &a[i] = &a[i][0],即它们的地址值是相等的。

看下面的程序

#include <stdio.h>

#include <stdlib.h>

#define FORMAT "%X,%X\n"

void main()

{

  int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};

  printf(FORMAT, a, *a);

  printf(FORMAT, a[0], *(a + 0));

  printf(FORMAT, &a[0], &a[0][0]);

  printf(FORMAT, a[1], a +1);

  printf(FORMAT, &a[1][0], *(a+1) + 0);

  printf(FORMAT, a[2], *(a+2));

  printf(FORMAT, &a[2], a + 2);

  printf(FORMAT, a[1][0], *(*(a+1)+0));

  system("pause");
}

以下是vs2005的输出

12FF34,12FF34

12FF34,12FF34

12FF34,12FF34

12FF44,12FF44

12FF44,12FF44

12FF54,12FF54

12FF54,12FF54

9,9

4.2 指向多维数组的指针变量

  (1)指向数组元素的指针变量

#include <stdio.h>

void main()

{

  int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};

  int *p;

  for(p = a[0];p<a[0] + 12;p++)

  {

    if((p - a[0]) % 4 == 0) printf("\n");

    printf("%4d", *p);

  }
}

运行结果如下:

   1    3    5     7

   9   11  13   15

 17   19  21   23

  p 是指向整型变量的指针变量,它可以指向一般的整型变量,也可以指向整型的数组元素,第次使 p 的值加 1,以移向下一元素。

  (2)指向由m 个元素组成的一维数组的指针变量。

  上例的指针变量 p 是指向整型变量的,p + 1 所指向的元素是 p 所指向的元素的下一元素,可以改用另一方法,使 p 不是指向整型变量,而是指向一个包含 m 个元素的一维数组。这时如果 p 先指向 a[0] 即 p = &a[0] 则 p + 1 不是指向 a[0][1],而是指向 a[1],p 的增值以一维数组的长度为单位。

#include <stdio.h>

void main()

{

  int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};

  int (*p)[4], i, j;

  p = a;

  scanf("i=%d, j=%d", &i, &j);

  printf("a[%d,  %d] = %d\n", i, j , *(*(p+i)+j));
}

输入 i=1, j=2

输出 a[1, 2]=13

  int (*p)[4] 表示 p 是一个指针变量,它指向包含 4 个元素的一维数组,注意 *p 两侧的括号不可少,如果写在 *p[4] 由于方括号的运算级别高,因此 p 先与 [4] 结合,是数组,然后再与前面的 * 结合,*p[4]是指针数组。

4.3 多维数组的指针作函数参数

  一维数组的地址可以作为函数参数传递,多维数组的地址也可以作函数参数传递,在用指针变量作形参以接受实参数组名传递来的地真理地,有两种方法第一,用指向变量的指针变量,第二,用指向一维数组的指针变量。

#include <stdio.h>
#include <stdlib.h>

#define FORMAT "%X,%X\n"

void average(float *p, int n)
{
  float *p_end;
  float sum = 0, aver;
  p_end = p + n - 1;
  for(;p<=p_end;p++)
    sum = sum + (*p);
  aver = sum / n;
  printf("average = %5.2f\n", aver);
}

void search(float (*p)[4], int n)
{
  int i;
  printf("the score of No. %d are: \n", n);
  for(i=0;i<4;i++)
    printf("%5.2f ", *(*(p+n)+i));
  printf("\n");
}

void main()
{

  float score[3][4] = {{66,67,70,60}, {80,87,90,81}, {90,99,100,98}};

  average(score[0], 12);//求12个分数的平均分 也可以写成 average(*score, 12);
  search(score, 2);//求第2个学生成绩
  system("pause");
}

4 字符串的指针和指向字符串的指针变量

4.1 字符串的表示形式

  在C程序中,可以用两种方法访问一个字符串。

  (1)用字符数组存放一个字符串,然后输出该字符串。

#include <stdio.h>

void main()

{

  char string[] = "I Love China !";

  printf("%s\n", string);
}

运行输出

I Love China !

  和前面介绍的数组属性一样,string 是数组名,它代表字符数组的首地址,string[4] 代表数组中的序号为 4 的元素 v 实际是 string[4] 就是 *(string + 4), string+4 是一个地址,它指向的字符是 v。

  (2)用字符指针指向一个字符串。

  可以不定义字符数组,而定义一个字符指针,用字符指针指向字符串中的字符。

#include <stdio.h>

void main()

{

  char *string = "I Love China!";

  printf("%s\n", string);
}

  在这里没有定义字符数组,在程序中定义了一个字符指针变量 string 。给定一个字符常量 "I Love China!" ,C语言对字符串常量是按字符数组处理的,在内存开辟了一个字符数组用来存放字符串常量,程序在定义字符指针变量 string 时把字符串首地址(即存放字符串的字符数组的首地址)赋给 string 有人认为 string 是一个字符串变量,以为是在定义时把 "I Love China!" 赋给该字符串变量,这是不对的,定义 string 的部分是

  char *string = "I Love China!";

等价于下面两行:

  char *string;

  string = "I Love China!";

  可以看到 string 被定义为一个指针变量,指向字符型数据,请注意它只能指向一个字符变量或是其它字符类型数据,不能同时指向多个字符数据,更不是把 "I Love China!" 这些字符存放到 string 中(指针变量只能存放地址),也不是把字符串赋给 *string 。只是把 "I Love China!" 的首地址赋给指针变量 string 。不要认为上述定义行等价于

  char *string;

  *string = "I Love China!";

  在输出时用

  printf("%s\n", string);

  %s表示输出一个字符串,给出字符指针变量名 string,则系统先输出所指向的一个字符数据,然后自动使 string 加1,使之指向下一个字符,然后再输出一个字符如此直到遇到字符串结束标志 '\0' 为止,注意,在内存中,字符串的最后被自动加了一个 '\0' 因此在输出时能确定字符串的终止位置。

  通过字符数组名或字符指针变量可以输出一个字符串,而对一个数据型数组,是不能企图用数组名或指针变量输出它的全部元素。

例子程序:

#include <stdio.h>

void main()

{

  char a[] = "I am a boy.", b[20];

  int i;

  for(i=0;*(a+i) != '\'; i++)

    *(b + i) = *(a + i);

  *(b + i) = '\0';

  printf("string a is: %s\n", a);

  printf("string b is: ");

  for(i=0;b[i] != '\0';i++)

    printf("%c", b[i]);

  printf("\n");
}

程序输出为:

string a is: I am a boy.

string b is: I am a boy.

  程序 a 和 b 都定义为字符数组,可以通过地址访问数组元素,在 for 语句中,先检查 a[i] 是否为 '\0' (a[i] 是以 *(a + i) 形式表示)。如果不等于 '\0' ,表示字符串尚未处理完,就将 a[i] 的值赋给 b[i] ,即复制一个字符。在 for 循环中将 a 串全部复制给了 b 串,最后还应将 '\0' 复制过去故有

  *(b + i) = '\0';

也可以用指针变量

#include <stdio.h>

void main()

{

  char a[] = "I am a boy.", b[20], *p1, *p2;

  int i;

  p1 = a; p2 = b;

  for(;*p1 != '\0'; p1++, p2++)

    *p2 = *p1;

  *p2 = '\0';

  printf("string a is: %s\n", a);

  printf("string b is:");

  for(i=0; b[i] != '\0'; i++)

    printf("%c", b[i]);

  printf("\n");
}

4.2 字符串指针作函数参数

  将一个字符串从一个函数传递到另一个函数,可以用地址传递的方法,即就字符数组名作参数,或用指向字符串的指针变量作参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以得到改变了的字符串。

  (1)用字符数组作参数

例子程序:

#include <stdio.h>

void copy_string(char from[], char to[])

{

  int i = 0;

  while(from[i] != '\0')

  {

    to[i] = form[i];

    i++;

  }

  to[i] = '\0';
}

void main()

{

  char a[] = "I am a teachar.";

  char b[] = "you are a student.";

  printf("string a = %s\nstring b = %s\n", a, b);

  copy_string(a, b);

  printf("\nstring a = %s\nstring b = %s\n", a, b);
}

运行结果如下:

string a = I am a teacher.

string b = you are a student.

string a = I am a teacher.

string b = I am a teacher.

  (2)形参用字符指针变量

void copy_string(char *from, char *to)

{

  for(;*from != '\0'; from ++, to ++)

    *to = *from;

  *to = '\0';
}

对 copy_string 函数还可以简化

void copy_string(char *from, char *to)

{

  while((*to = *from) != '\0')

  {to++; from++;}
}

void copy_string(char *from, char *to)

{

  while(*from != '\0')

    *to ++ = *from ++;

  *to = '\0';

}

4.3 对使用字符指针变量和字符数组的讨论

  虽然用字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是有区别的,不应混为一谈,主要有以下几点:

  (1)字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串的首地址),决不是将字符串放到字符指针变量中。

  (2)赋值方式,对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值,

  char str[20];

  str = "I Love China";

而对字符指针变量可以采用下面的方法赋值:

  char *a;

  a = "I Love China";

但注意赋给 a 的不是字符,而是字符串的地址。

  (3)对字符指针变量赋初值:

  char *a = "I Love China";

等价于:

  char *a;

  a = "I Love China";

而对数组的初始化:

  char str[20] = {"I Love China"};

不能等价于

  char str[20];

  str[] = "I Love China";

即数组可以在变量定义时整体赋初值,但不能在赋值语句中整体赋值。

  (4)如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址,而定义一个字符指针变量时,给指针变量分配内在单元,在其中可以放一个地址值,也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋予一个地址值,则它并未具体指向一个确定的字符数据如:

  char str[10];

  scanf("%s", str);

是可以的。而常有人用下面的方法:

  char *a;

  scanf("%s", a);

目的是输入一个字符串,虽然一般也能运行,但这种方法是危险的,因为在编译时虽然给指针变量 a 分配了内存单元,a 的地址,(即&a)是已指定了,但 a 的值并没指定,在 a 单元中是一个不可预料的值,在执行 scanf 函数时要求将一个字符串输入到 a 所指向的一段内存单元(即以 a 的值(地址)开始的一段内存单元)中。而 a 的值如今却是不可预料的,它可能指向内存中空白的用户存储区中,也有可能指向已存放指令或数据的有用内存段,这就会破坏程序,甚至破坏系统,会造成严重的后果,可以改为下面的写法:

  char *a, str[10];

  a = str;

  scanf("%s", a);

行使 a 有确定值,也就是 a 指向一个数组开头,然后输入一个字符串,把它存放在以该地址开始的若干单元中。

  (5)指针变量的值是可以改变的如:

#include <stdio.h>

void main()

{

  char *a = "I Love China";

  a = a + 7;

  printf("%s\n", a);
}

运行结果如下:

China

指针变量 a 的值可以变化,输出字符串时从 a  当时所指向的单元开始输出各个字符,直到遇到 '\0' 为止,而数组名虽然代表地址,但它的值是不能改变的,下面是错的:

  char str[] = "I Love China";

  str = str + 7;

  printf("%s\n", str);

  需要说明,若定义了一个指针变量,并使它指向一个字符串,就可以用下标形式引用指针变量所指的字符串中的字符如:

#include <stdio.h>

void main()

{

  char *a = "I Love China";

  int i;

  printf("The sixth charcter is %c\n", a[5]);

  for(i=0;a[i] != '\0'; i++)

    printf("%c", a[i]);
}

运行结果如下:

The sixth charcter is e

I Love China

  程序中虽然没有定义数组 a ,但字符在内存中是以字符数组形式存放的,a[5] 按*(a + 5) 执行,即从 a 当前所指向的元素下移 5 元素位置,取出其单元中的值。

  (6)用指针变量指向一个格式字符串,可以用它代替 printf 函数中的格式字符串。如:

  char *format;

  format = "a=%d,b=%f\n";

  printf(format, a, b);

它相当于

  printf("a=%d,b=%f\n", a, b);

5 函数的指针和指向函数的指针变量

5.1 用函数指针变量调用函数

  可以用指针变量指向整型变量,字符串,数组,也可以指向一个函数,一个函数在编译时被分配给一个入口地址。这个入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用些函数。

看下面的程序:

求两个数的大者。

#include <stdio.h>

int max(int x, int y)

{

  int z;

  if(x > y)

    z = x;

  else

    z = y;

  return z;
}

void main()

{

  int a, b, c;

  scanf("%d, %d", &a, &b);

  c = max(a, b);

  printf("a=%d,b=%d,c=%d", a, b, c);
}

  main 函数中 c = max(a, b); 包括了一次函数调用,每一个函数都占用一段内存单元,它们有一个起始地址,因此,可以用一个指针变量指向一个函数,通过指针变量来访问它指向的函数。

  将 main 函数必为

void main()

{

  int (*p)(int, int);

  int a, b, c;

  p = max;

  p(a, b);

  printf("a=%d,b=%d,c=%d", a, b, c);
}

  其中 int(*p)(int, int) 定义 p 是一个指向函数的指针变量,此函数带回整型返回值,注意 *p 两侧的括弧不可少,表示 p 先与 * 结合,是指针变量,然后再与后面的() 结合,表示批指针变量指向函数,这个函数值(即函数返回的值)是整型的,如果写成 int *p() 则由于() 优先级高于 *,它就成了一个声明一个函数(这个函数的返回值是指向整型变量的指针)。

  赋值语句 p = max 的作用是将函数 max 的入口地址赋给指针变量 p 。和数组名代表数组起始地址一样,函数名代表该函数的入口地址,这时 p 就是指向函数 max 的指针变量,也就是 p 和 max  都指向函数的首地址。调用 *p 就是调用函数 max 。请注意 p 是指向函数的指针变量,它只能指向函数的入口处而不可能指向函数中间的某一条指令,因此不能用 *(p + 1) 来表示函数的下一条指令。

  在 main 函数中有一个赋值语句

  c = (*p)(a, b);

  它包括函数的调用,和 c = max(a, b) 等价,这就是用指针形式实现函数的调用。

说明:

  (1)指向函数的指针变量的一般定义形式为

  数据类型 (* 指针变量名)();

  这里的数据类型,是指函数的返回值类型。

  (2)函数的调用可以 通过函数名调用,也可以通过函数指针调用(即用指向函数的指针变量调用)。

  (3)(*p)() 表示定义一个指向函数的指针变量,它不是固定指向哪一个函数的,而只是表示定义了这样一个类型的变量,它是专门用来存放函数的入口地址的,在程序中把哪一个函数的地址赋给它,它就指向哪一个函数,在一个程序中一个指针变量可以先后指向不同的函数。

  (4)在给函数指针变量赋值时,只需要给出函数名而不必给出参数。如:

  p = max;

  (5)用函数指针变量调用函数时,只需将 (*p) 代替函数名即可(p 为指针变量名),在 (*p) 之后的括弧中根据需要写上实参。如下:

  c = (*P)(a, b);

  (6)对指向函数的指针变量,像 p +n , p++, P-- 等运算是无意义的。

5.2 用指向函数的指针作函数参数

  函数指针变量常用的用途之一是把指针作为参数传递到其它函数。

  函数的参数可以是变量,指向变量的指针变量,数组名,指向数组的指针变量等。指向函数的指针也可以作为参数,以便实现函数地传递,也就是将函数名传给形参。

  它的原理可以简述如下:有一个函数(假设函数名为 sub),它有两个形参(x1 和 x2),定义 x1 和 x2 为指向函数的指针变量,在调用函数 sub 时,实参用两个函数名 f1 和 f2 给形参传递函数地址,这样在函数 sub 中就可以调用 f1 和 f2函数了如:

  int sub(int (*x1)(int), int (*x2)(int, int))

  {

    int a, b, i, j;

    a = (*x1)(i);

    b = (*x2)(i, j);

  }

其中 i 和 j 是函数 f1 和 f2 所要求的参数。函数 sub 的形参 x1, x2(指针变量)在函数 sub 未被调用时并不占内存单元,也不指向任何函数,在 sub 被调用时,把实参函数 f1 和 f2 的入口地址传给形参指针变量 x1 和 x2 ,使 x1 和 x2 指向函数 f1 和 f2 ,这时在函数 sub 中,用 *x1  和 *x2 就可以调用函数 f1 和 f2 ,(*x1)(i)就相当于 f1(i), (*x2)(i, j)就相当于f2(i, j)。

再看如下程序:

#include <stdio.h>

int max(int x, int y)

{

  return (x>y?x:y);
}

int min(int x, int y)

{

  return (x>y?y:x);
}

int add(int x, int y)

{

  return (x + y);
}

void process(int x, int y, int (*fun)(int, int))

{

  int result;

  result = (*fun)(x, y);

  printf("%d\n", result);
}

void main()

{

  int a, b;

  scanf("%d, %d", &a, &b);

  printf("max = ");

  process(a, b, max);

  printf("min=");

  process(a, b, min);

  printf("add=");

  process(a, b,  add);
}

运行结果如下:

2, 6

max=6

min=2

add=8

  可以看到函数 process 函数一点都没有改动,只是在调用 process 函数时将实参函数名改变,这就增加了函数使用的灵活性,可以编写通用的函数来实现各种专用的功能。

6 返回指针值的函数

  一个函数要以带回一个整型值,字符值,实型值等,也可以带回指针型的数据,即地址,其概念与以前类似,只是带回的值类型是指针类型而已。

  这种带回指针值的函数,一般定义形式为:

  类型名 *函数名(参数表);

例如:

  int *a(int x, int y);

a 是函数名,调用它以后能得到一个指向整型数据的指针(地址),x, y 是函数 a 的形参,为整型。请注意在 *a 两侧没有括弧,在 a 的两侧分别为 * 和 () 运算符,而 () 优先级高于 * , a 先与 () 结合。显然这是函数形式,这个函数前面有一个 * ,表示此函数是指针型函数(函数的值是指针)。最前面的 int 表示返回的指针指向整型变量。

如下程序:

#include <stdio.h>

#include <stdlib.h>

/*

  函数说明

  float (*pointer)[4] 定义的是一个指向四个一维数组的指针,返回的是指向一个一维数组的指针

*/

float * search(float (*pointer)[4], int n)

{

  float *pt;

  pt = *(pointer + n);

  return pt;
}

void main()

{

  float score[][4] = {{60,70,80,90},{56,89,67,88},{34,78,90,66}};

  float *p; //定义接收返回的一维数组的指针

  int i, m;

  scanf("%d", &m);

  printf("The scores of NO. %d are: \n", m);

  p = search(score, m);

  for(i=0;i<4;i++)

    printf("%5.2f\t", *(p+i));//因为返回的是一维数组指针所以可以用 *(p + i) 取出内容

  system("pause");
}

7 指针数组和指向指针的指针

7.1 指针数组的概念

  一个数组,其元素均为指针类型数据,称为指针数组,也就是说指针数组中的每一个元素都相当于一个指针变量,一维指针数组的定义形式为:

  类型名 *数组名[数组长度];

例如:

  int *p[4];

由于 [] 比 * 优先级高,因此 p 先与 [4] 结合,形成 p[4] 形式,这显然是数组形式,它有4个元素,然后再与 p 前面的 * 结合,表示数组是指针类型,每个元素(相当于一个指针变量)都可以指针一个整型变量。

  指针数组,它比较适合于用来指向若干个字符串,使字符串处理更加方便灵活。

如下程序,按字母顺序(由小到大)输出

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

void sort(char *name[], int n)

{

  char *temp;

  int i, j, k;

  for(i=0;i<n-1;i++)

  {

    k = i;

    for(j=i+1;j<n;j++)

      if(strcmp(name[k], name[j]) > 0) k = j;

    if(k != i)

    {

      temp = name[i];

      name[i] = name[k];

      name[k] = temp;
    }

  }
}

void print(char *name[], int n)

{

  int i;

  for(i=0;i<n;i++)

    printf("%s \n", name[i]);
}

void main()

{

  char *name[] = {"Follow me","BASIC","Great Wall","FORTRNA","Computer design"};

  int n = 5;

  sort(name, n);

  printf(name, n);

  system("pause");
}

运行结果为:

BASIC

Computer design

FORTRAN

Follow me

Great wall

7.2 指向指针的指针

  指向指针数据的指针变量,简称为指向指针的指针。

定义的一般形式如:

  char **p;

例子程序

#include <stdio.h>

#include <stdlib.h>

void main()

{

  char *name[] = {"Follow me","BASIC","Great Wall","FORTRNA","Computer design"};

  char **p;

  int i;

  for(i=0;i<5;i++)

  {

    p = name + i;

    printf("%s\n", *p);

  }

  system("pause");
}

运行结果如下:

Follow me

BASIC

FORTRAN

Great wall

Compter design

  p 是指向指针的指针变量,在第一次执行循环体时,赋值语句 p = name + i 使 p 指向 name 数组的 0 号元素 name[0] ,*p 是 name[0] 的值,即第一个字符串的起始地址。

  指针数组的元素也可以不指向字符串,而指向整型数据或实型数据等例如:

int a[5] = {1,3,5,7,9};

int *num[5];

int **p;

int i;

for(i=0;i<5;i++)

  num[i] = &a[i];

p = num;

for(i=0;i<5;i++)

{

  printf("%d\t", **p);

  p ++;
}

7.3 指针数组作 main 函数的形参

  指针数组的一个重要应用是作为 main 函数的形参,在以往的程序中,main 函数一般写成 main() ,实际上 main 函数可以有参数例如:

  main(argc, argv)

argc, argv 就是 main 函数的形参,main 函数是由系统调用,当处于操作命令状态下,输入 main 所在的文件名(经过编译,连接后得到可执行文件名),系统就调用 main 函数,那么 main 函数的形参的值从何处得到呢,显然不可能在程序中得到,实际上实参是和命令一起给出的,也就是在一个命令行中包括命令名和需要传给 main 函数的参数,命令行的一般形式为:

  命令名 参数1 参数2 。。。参数n

命令名和各个参数之间用空格分隔,如可以写成以下形式:

  file china beijin

  请注意以上参数与 main 函数中形参的关系,main 函数中形参 argc 是指命令行中参数的个数(注意,文件名也作为一个参数,例如,本例中的 file1 也是一个参数),现在 argc 的值等于 3 (有3个命令行参数: file1, china, beijing)。main 函数的第二个形参 argv 是一人指向字符串的指针数组,也就是说带参数的 main 函数的原型是

  main(int argc, char *argv[]);

  命令行参数应当都是字符串(例如,上面命令行中的 file1, china, beijing 都是字符串),这些字符串的首地址构成一个指针数组。

  指针数组 argv 中的元素 argv[0] 的值是字符串 file1 的首地址,argv[1] 指向字符串 china ,argv[2] 指向字符串 beijing。

  如果有以下一个 main 函数,它所在的文件名为 file1:

main(int argc, char *argv[])

{

  while(argc > 1)

  {

    ++argv;

    printf("%s\n", *argv);

    -- argc;

  }
}

输入的命令行参数为

file1 china beijing

则执行以上命令行将会输出

china

beijing

8 有关指针的数据类型和指针运算的小结

8.1 有关的数据类型的小结

定义          含义

int i;          定义整型变量i

int *p          p为指向整型数据的指针变量

int a[n];        定义整型数组 a ,它有n 个元素

int *p[n];       定义指针数组 p,它由 n 个指向整型数据的指针元素组成

int (*p)[n];      p 为指向含 n 个元素的一维数组的指针变量 

int f();         f 为带回整型函数数值函数

int *p();        p 为带回一个指针的函数,该指针指向整型数据

int (*p)();       p 为指向函数的指针,该函数返回一个整型值

int **p;        p 是一个指针变量,它指向一个指向整型数据的指针变量

8.2 指针运算小结

  (1)指针变量加 减 一个整数

如:p ++, p --, p + i, p - i, p += i; p-= i

  (2)指针变量赋值

  将一个变量地址赋给一个指针变量如:

  p = &a;//将变量 a 的地址赋给 p

  p = array;//将数组 array 的首地址赋给 p

  p = &array[i];//将数组 array 第 i 个元素的地址赋给 p

  p = max;//max 为已定义的函数,将 max 的入口地址赋给 p

  p1 = p2 //p1 和 p2 都是指针变量,将 p2 的值赋给 p1

注意:不应该把一个整数赋给指针变量如:

  p = 1000;

这样赋值实际上是做不到的,只能将变量已分配的地址赋给指针变量,同亲也不应把指针变量 p 的值(地址)赋给一个整型变量 i:

  i = p;

  (3)指针变量可以有空值,即该指针变量不指向任何变量,可以这样表示如:

  p = NULL;

实际上 NULL 是整数 0 ,它使存储单元中所有的二进制位均为 0,也就是使 p 指向地址为 0 的单元,系统保证该单元不作它用(不存放有效数据)即有效数据的指针不指向 0 单元。实际上是先定义 NULL 即:

  #define NULL 0

  在 stdio.h 头文件中就有以上的 NULL 定义,它是一个符号常量,用 p = NULL; 表示 p 不指向任一有用单元,应注意,p 的值为 NULL 与未对 p 赋值是两个不同概念,前者有值(值为0),不指向任何变量,后者虽未对 p 赋值并不等于 p 无值,只是它的值是一个无法预料的值,也就是 p 可能指向一个事先未指定的单元,这种情况是很危险的。因此在引用指针变量之前应对它赋值。

  任何指针变量或地址都可以与 NULL 作相等或不相等的比较如:

  if(p == NULL)....

  (4)两个指针变量可以相减

  如果两个指针变量指向同一个数组元素,刚两个指针变量值之差是两个指针之间元素的个数。

  (5)两个指针变量比较

  若两个指针指向同一个数组元素,则可以进行比较,指向前面的元素的指针变量小于指向后面元素的指针变量。  

  如:p1 < p2 或者说表达式 p1 < p2 的值为1 (真),如果 p1 和 p2  不指向同一数组则比较无意义。

8.3 void指针类型

  ANSI 新标准增加了一种 void 指针类型,即可以定义一个指针变量,但不指定它是指向哪一种类型数据的,ANSI C标准规定用动态存储分配函数时返回 void 指针,它可以用来指向一个抽象的类型的数据,在将它的值赋给另一指针变量时要进行强制类型转换使之适合于被赋值的变量的类型。例如:

  char *p1;

  void *p2;

  p1 = (char *)p2;

  p2 = (void *)p1;

也可以将一个函数定义为 void * 类型如:

  void *fun(char ch1, char ch2)

表示函数 fun 返回的是一个地址,它指向 空类型 如需要引用此地址,也需要根据情况对之进行类型转换

  p1 = (char *)fun(ch1, ch2);

原文地址:https://www.cnblogs.com/qkhhxkj/p/2089284.html