C语言中令人头疼的指针(二)

上一篇中介绍了什么是C语言中的指针,以及指针具备哪些基本操作。本篇继续介绍C语言中指针常见的几种用法。

指针的算术运算

指针的加减运算

根据指针变量的定义,易知指针可以进行算术运算,包括加减、递加递减。具体运算规则如下:

  • 指针递增,会指向当前地址的下一个存储单元
  • 指针递减,会指向当前地址的前一个存储单元

  • 递增和递减时改变的字节数取决于指针指向的变量数据类型的长度
  • 例如ptr是指向地址1000的int指针,是32位证书。对其执行递加后指向1004,因为int型为4字节

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针中的数组地址 */
   ptr = var;
   for ( i = 0; i < MAX; i++)
   {
 
      printf("存储地址:var[%d] = %p\n", i, ptr );
      printf("存储值:var[%d] = %d\n", i, *ptr );
 
      /* 指向下一个位置 */
      ptr++;
   }
   return 0;
}

上述程序的运行结果为:

1
2
3
4
5
6
存储地址:var[0] = e4a298cc
存储值:var[0] = 10
存储地址:var[1] = e4a298d0
存储值:var[1] = 100
存储地址:var[2] = e4a298d4
存储值:var[2] = 200

指针的比较

指针可以通过关系运算符进行比较,如==、>、<。比较的前提是两个指针指向的是相关的变量,比如都指向int。举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针中第一个元素的地址 */
   ptr = var;
   i = 0;
   while ( ptr <= &var[MAX - 1] )
   {
 
      printf("存储地址:var[%d] = %p\n", i, ptr );
      printf("存储值:var[%d] = %d\n", i, *ptr );
 
      /* 指向上一个位置 */
      ptr++;
      i++;
   }
   return 0;
}

上述代码的执行结果为:

1
2
3
4
5
6
存储地址:var[0] = 0x7ffeee2368cc
存储值:var[0] = 10
存储地址:var[1] = 0x7ffeee2368d0
存储值:var[1] = 100
存储地址:var[2] = 0x7ffeee2368d4
存储值:var[2] = 200

指针数组

数组可以用来存储变量或常量,因此可以用数组来存放指针,即为指针数组。

举个例子,现在有一个整数数组,其中存放的都是整数值,相对数组中的元素访问可以通过数组的索引实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int i;
 
   for (i = 0; i < MAX; i++)
   {
      printf("Value of var[%d] = %d\n", i, var[i] );
   }
   return 0;
}

通过上述代码,可以逐个打印出数组中的整数元素。

通过指针定义可知,指针可以存储变量的地址值,通过“ * ”运算符可以访问地址对应变量的值。因此,对于数组中的每一个元素,都可以为其声明一个指针变量,指向该数组中的元素。这些指向数组元素的指针可以放在一个数组中存储,这个数组就是指针数组。对以上代码做如下改动,可以得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int i, *ptr[MAX];
 
   for ( i = 0; i < MAX; i++)
   {
      ptr[i] = &var[i]; /* 赋值为整数的地址 */
   }
   for ( i = 0; i < MAX; i++)
   {
      printf("Value of var[%d] = %d\n", i, *ptr[i] );
   }
   return 0;
}

两个代码的执行输出相同,区别在于第二个代码使用了指针数组ptr。

指针在函数中的应用

指针作为一种变量,自然是可以在函数中应用的,包括作为参数传递给函数以及从函数中作为返回值返回。下面对两种情况进行梳理。

传递指针给函数

传递指针给函数时,需要在函数参数部分声明为指针。

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <time.h>
 
void getSeconds(unsigned long *par);

int main ()
{
   unsigned long sec;


   getSeconds( &sec );

   /* 输出实际值 */
   printf("Number of seconds: %ld\n", sec );

   return 0;
}

void getSeconds(unsigned long *par)
{
   /* 获取当前的秒数 */
   *par = time( NULL );
   return;
}

上述例子中,getSeconds函数中的参数为long型指针。在main函数中调用的是&sec,因为指针就是地址,所以这个地方的参数是sec变量的地址,也就是指向sec变量的指针。

能接受指针作为参数的函数,也能接受数组作为参数,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
 
/* 函数声明 */
double getAverage(int *arr, int size);
 
int main ()
{
   /* 带有 5 个元素的整型数组  */
   int balance[5] = {1000, 2, 3, 17, 50};
   double avg;
 
   /* 传递一个指向数组的指针作为参数 */
   avg = getAverage( balance, 5 ) ;
 
   /* 输出返回值  */
   printf("Average value is: %f\n", avg );
   
   return 0;
}

double getAverage(int *arr, int size)
{
  int    i, sum = 0;      
  double avg;          
 
  for (i = 0; i < size; ++i)
  {
    sum += arr[i];
  }
 
  avg = (double)sum / size;
 
  return avg;
}

getAverage函数的第一个参数为指针,在main函数调用时的参数为balance数组。因为在C语言中已经声明一个数组(int a[2];)的话,可以用a表示指向数组的指针,初始指向数组的第一个元素。

从函数返回指针(指针函数)

指针作为一种变量,自然可以从函数中返回,不过需要注意的是不可以返回指向局部变量的指针,除非局部变量声明为static。因为局部变量是存在栈中的,函数调用结束,栈中的内存地址会被释放,该地址不再存原来的局部变量,无法访问,因此不能返回指向局部变量的指针,而static型的变量是存储在静态数据区的,函数执行结束仍然存在,所以可以返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <time.h>
#include <stdlib.h> 
 
/* 要生成和返回随机数的函数 */
int * getRandom( )
{
   static int  r[10];
   int i;
 
   /* 设置种子 */
   srand( (unsigned)time( NULL ) );
   for ( i = 0; i < 10; ++i)
   {
      r[i] = rand();
      printf("%d\n", r[i] );
   }
 
   return r;
}
 
/* 要调用上面定义函数的主函数 */
int main ()
{
   /* 一个指向整数的指针 */
   int *p;
   int i;
 
   p = getRandom();
   for ( i = 0; i < 10; i++ )
   {
       printf("*(p + [%d]) : %d\n", i, *(p + i) );
   }
 
   return 0;
}

还有一种“特殊”的指针,特殊之处在于其指向的是函数,即指针存储的时函数的首地址。C语言中规定函数名会被转换为指向这个函数的指针,除非这个函数名作为&操作符或者sizeof操作符的操作数。也就是说f = test中,test函数会被自动转换为&test,f = &test中因为已经显式地发生了转换,所以不会再自动转换。此外需要注意,指向函数的指针必须初始化或者具有0值才可以使用,且指向函数的指针无法进行自增运算,无法对函数名进行复制,也不能进行算术运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
int fun1(int,int);
int fun1(int a, int b){
    return a+b;
}
/* 要调用上面定义函数的主函数 */
int main (){
    int (*pfun1)(int,int);
    pfun1=fun1;//这里&fun1和fun1的值和类型都一样,用哪个无所谓
    int a=(*pfun1)(5,7); //通过函数指针调用函数。
    printf("%d\n",a);
    int e = fun1(5,7);
    printf("%d\n",d)
    int b = (&fun1)(5,7);
    printf("%d\n",b);
    int c = (*fun1)(5,7);
    printf("%d",c);
    return 0;
}
//根据关系 *fun1==*&fun1==fun1==&fun1 可知,以上的运行结果会得到4个5+7。
//因此在下面的函数指针数组实例中,action[2]()就相当于这里的(&fun1(5,7)),这点务必搞清楚。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
int Max(int x, int y)  //定义Max函数
{
    int z;
    if (x > y) {
        z = x;
    }else { 
        z = y;
    }
    return z;
}
int main() {//定义一个函数指针
    int(*p)(int, int);
    int a, b, c;//把函数Max赋给指针变量p, 使p指向Max函数
    p = Max;
    printf("please enter a and b:");
    scanf("%d%d", &a, &b);//通过函数指针调用Max函数
    c = (*p)(a, b);
    printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
    system("pause");
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

void test()
{
    printf("test called!\n");
}

int main()
{
    void (*f) ();
    f = test;
    f ();
    //test++;					/*error,禁止对指向函数的指针进行自增运算*/
    //test += 2;				/*禁止对函数名进行复制,也不能进行算术运算*/
    printf("%p\n",test);
    printf("%p\n",&test);
    printf("%p\n",*test);
    return 0;
}