深入理解指针和数组、指针和函数



指针

在了解了指针之后,可以得出,指针就是一个变量,用来存放地址的变量。地址就相当于门牌号,而指针就是存放它的变量,对指针进行解引用(*)则相当于拿一把钥匙去开对应的门牌号的门,打开门,访问其中的变量。

指针的大小在32位平台上是4个字节,在64位平台上是8个字节。

如有以下代码:

数组

先看下面的例子:

int a[5] = {0};

这里定义了一个数组,放有5个int型的数据,a就是一个数组。数组在内存中的存储如下:

指针和数组的区别

指针和数组之间没有任何关系!!!

指针就是指针,指针变量在32位系统下,永远占4个byte,其值为某一个内存的地址。

数组就是数组,其大小与元素的类型和个数有关。

我们首先了解一下指针和数组它们分别可以怎样访问:

在函数内部有以下定义:如:

A)char *p = “abcdef”;

B)char a[] = “abcdef”;

我们可以通过以指针的形式访问和以下标的形式访问来分别对指针和数组进行访问,结果如下:

通过上述分析,我们可以明白指针和数组的访问方式了。由此,我们可以进一步分析出指针和数组没有任何关系,它们是两码事。

定义为数组,声明为指针

文件1中定义如下:

char a[100];

文件2中声明如下:

extern char *a;

那么在文件2中可以访问到文件1中数组中的内容吗,来看如下分析:

定义为指针,声明为数组

文件1中定义如下:

char *p = “abcdefg”;

文件2中声明如下:

extern char p[];

在文件1中,编译器分配4个byte空间并命名为p,同时p里保存了字符串的首字符的地址。而在文件2中,编译器认为p是一个数组,其大小为4个byte,数组内保存的是char类型的数据。在文件2中使用p的过程如下:

经过上述对在不同文件下的分析,我们可以看到,指针和数组是两码事,它们没有任何关系。

指针数组

指针数组,首先,它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定,它是用来存储指针的数组。

如: int *p[10];

“[ ]” 的优先级比 “ * ” 高。
p要先与 “[ ]” 结合,构成一个数组,数组名为p,“int *”表示的是存放在数组中元素的类型为整型指针。由此,我们知道,这是一个存放10个int类型的指针的数组,即指针数组。p在内存中的存放如下:

数组指针

数组指针,首先,它是一个指针,它指向一个数组。只要是指针,在32位系统下永远占4个byte,它是用来存储数组的地址的。

如: int (*a)[10];

a先与 “ * ”结合,构成一个指针,指针名为a,int [10]表示的是存放在指针中的地址的类型是存放有10个整型元素的数组。由此,我们知道,这是一个指向10个int类型数据的指针,即数组指针。a在内存中的存放如下:

函数指针

顾名思义,函数指针就是函数的指针。它是一个指针,指向一个函数。只要是指针,在32位系统下就是4个byte,它是用来存储函数的地址的。

如: char*
(*fun)(char
*p1,char
*p2 );

fun先与 “ * ” 结合,构成一个指针,指针名为fun,char * (char *p1,char
*p2)这是一个函数。由此,我们知道,这是一个指向函数的指针,函数的参数是两个类型为char
*的p1和p2,函数的返回类型为char
*。

让我们来看两段有趣的代码:

1、(*(void(
*)())0)();

分析:

  1. void(*)(),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
  2. (void(*)())0,这是将0强制转换为函数指针类型,0是一个地址,也就是说,一个函数存在首地址为0的一段空间内。
  3. (*
    (void(*)())0),这是取0地址开始的一段内存里面的内容,其内容就是保存在首地址为0的一段空间内的函数。
  4. (*
    (void(*)())0)(),这是对上一步所拿到的函数进行调用。

2、void(*signal(int,void(
*)(int)))(int);

分析:

  1. void(*)(int),这是一个函数指针,该函数指针指向的是函数参数为int,返回类型为void的函数。
  2. void(*signal(int,void(
    *)(int)))(int); signal是一个函数声明,它有两个参数,一个是int类型,另一个是上述所说的函数指针类型;signal函数的返回类型是void(
    *)(int)类型。

函数指针数组

既然指针可以存放在一个数组里,那么函数指针也可以存放在数组里,根据上述函数指针所举的例子:char*
(*fun)(char
*p1,char
*p2 );这是一个函数指针,把该式修改一下,得到:

char
*
(*fun[3])(char
*p1,char
*p2 );

分析:

首先,fun先与中括号 “[ ]” 结合,说明fun是一个存放三个元素的数组,然后将fun[3]暂时拿开,可以看到剩下的是一个函数指针,则说明,fun是一个存有三个函数指针的数组。函数指针数组是用来存放函数的地址的。

有如下代码:

#include <stdio.h>
#include <string.h>
char *fun1(char *p)
{
    printf("%s\n",p);
    return p;
}
char *fun2(char *p)
{
    printf("%s\n",p);
    return p;
}
char *fun3(char *p)
{
    printf("%s\n",p);
    return p;
}
int main()
{
    char *(*pf[3])(char *p);
    pf[0] = fun1;//可以直接使用函数名
    pf[1] = &fun2;//可以用函数名加上取地址符
    pf[2] = &fun3;
    pf[0]("fun1");
    pf[1]("fun2");
    pf[2]("fun3");
    return 0;
}

可以看到,在main函数中定义了一个名为pf的函数指针数组,用来存放函数的地址,通过对数组中元素的使用来调用相对应的函数。

转移表

通过对函数指针数组有了一定的了解之后,我们可以用它来实现一个转移表,即所谓的计算器。

代码如下:

#include <stdio.h>

void menu()
{
    printf("**********************\n");
    printf("*** 1.add2.sub ***\n");
    printf("*** 3.mul4.div ***\n");
    printf("*** 0.exit ***\n");
    printf("**********************\n");
}

int add(int x, int y)
{
    return x+y;
}
int sub(int x, int y)
{
    return x-y;
}
int mul(int x, int y)
{
    return x*y;
}
int div(int x, int y)
{
    return x/y;
}

int main()
{
    int x = 0;
    int y = 0;
    int input = 0;
    int ret = 0;
    int (*p[5])(int x,int y) = {0,add,sub,mul,div};
    do
    {
        menu();
        printf("请选择:\n");
        scanf("%d",&input);
        if((input>=1 && input<=4))
        {
            printf("请输入操作数:\n");
            scanf("%d%d",&x,&y);
            ret = (*p[input])(x,y);
            printf("ret = %d\n",ret);
        }
        else if(input == 0)
        {
            printf("退出计算\n");
            break;
        }
        else
        {
            printf("输入有误,请重新输入:\n");
        }
    }while(input);

    return 0;
}

通过将函数的地址存放到一个数组里,在使用时通过数组里存放的元素(即函数的地址)来调用该函数。

指向函数指针数组的指针

在前面,我们提到了函数指针数组,它是用来存放函数地址的一个数组,那么这里的指向函数指针数组的指针不就是指向上述数组的指针嘛,存放的就是函数指针数组的地址呀。根据上述函数指针数组所举的例子:
char
*
(*fun[3])(char
*p1,char
*p2 );
把该式修改一下,得到:

char
*
(*(
*fun)[3])(char
*p1,char
*p2 );
让我们来解析一下这个式子,首先,fun先与 “ * ” 结合,构成一个指针,再与 “ [ ] ” 结合,说明这个指针指向的是一个存放三个元素的数组(即fun中存放的是一个存放有三个元素的数组的地址),再根据其余的符号可以得到,这三个元素的类型是函数指针。
根据函数指针数组的代码改写如下:

#include <stdio.h>
#include <string.h>
char *fun1(char *p)
{
    printf("%s\n",p);
    return p;
}
char *fun2(char *p)
{
    printf("%s\n",p);
    return p;
}
char *fun3(char *p)
{
    printf("%s\n",p);
    return p;
}
int main()
{
    char *(*pf[3])(char *p);
    char *(*(*a)[3])(char *p);
    a = &pf;
    pf[0] = fun1;//可以直接使用函数名
    pf[1] = &fun2;//可以用函数名加上取地址符
    pf[2] = &fun3;
    a[0][0]("fun1");
    a[0][1]("fun2");
    a[0][2]("fun3");
    return 0;
}

可以看到,在main函数里先定义了一个函数指针数组,用来存放三个函数的地址,然后定义了一个指向函数指针数组的指针来存放这个数组的地址(即数组首元素的地址),通过找到数组的地址来访问数组中的元素来实现对三个函数的调用。


上述内容均为学习过程中的总结,如有不足之处,请指正。

猜你喜欢

转载自blog.csdn.net/cottonrose_orange/article/details/81395431