指针、数组指针、指针数组、函数指针、函数指针数组、函数指针的数组的指针

指针

地址的形象化称为指针,意思是通过它能够找到以它为地址的内存单元。

举两个例子,说明指针的用法:

  • 指针与变量(将num的值改变为20)
int num=10;
int* p=#
*p=20;

  • 指针与数组(打印数组)
void print_arr(int *p,int sz)
{
    int i=0;
    for(i=0;i<sz;i++)
    {
        printf("%d ",*(p+i));
    }
}
int arr[]={1,2,3,4,5,6,7,8,9};
int sz=sizeof(arr)/sizeof(arr[0]);
print_arr(arr,sz);

指针数组

一个数组里存放的都是同一个类型的指针,是数组

定义 type *p[n];

[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。
这里执行p+1是错误的,这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。

int * a[10] ;它里边放了10个整型指针,由于它是一个数组,已经在栈区分配了10个(int * )的空间,也就是32位机上是40个byte,每个空间都可以存放一个int型变量的地址,这个时候你可以为这个数组的每一个元素初始化,或者单独做个循环去初始化它。

数组指针 :

一个指向一维或者多维数组的指针,是指针

定义 type (*p)[n];

()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

int arr[10]={0};
printf("%p",arr);
printf("%p",arr+1);
printf("%p",&arr+1);


我们可以看到产生的结果是截然不同的。这里是因为数组的地址和数组首元素的地址值是相同的,但是意义不同。

数组和指针的传参

//一维数组的传参:
void test1(int arr1[]){}
void test1(int arr1[10]){}
void test1(int *arr1){}
void test2(int *arr2[20]){}
void test2(int **arr2){}
int main()
{
    int arr1[10]={0};
    int *arr2[20]={0};
    test1(arr1);
    test2(arr2);
}
//二维数组的传参:
void test(int arr[3][5]){}
void test(int arr[][5]){}
void test(int (*arr)[5]){}
int main()
{
    int arr[3][5]={0};
    test(arr);
}

数组传参总结:

  • 当函数名作为函数参数传递时,实际传递给函数的是一个指向数组第一个元素的指针。函数所接收到的参数实际上是原参数的一份拷贝,所以函数可以对其进行操纵而不会影响实际的参数。

  • 对指针参数执行间接访问操作允许函数修改原先的数组元素。数组形参既可以声明为数组,也可以声明为指针。这两种声明形式只有当它们作为函数的形参时才是相等的。

  • 多维数组实际上是移位数组的一种特型,就是它的每个元素本身也是一个数组。多维数组名的值是一个指向它第一个元素的指针,也就是一个指向数组的指针。

  • 多维数组名作为参数传递给一个函数时,它所对应的函数形参的声明中必须显示指明第2维(和接下去所有维)的长度。

//一级指针的传参:
void test1(int *p1)
//二级指针的传参:
void test2(int **p2)

指针传参总结:

  • 当一个函数的参数部分为一级指针的时候,函数能接收数组、一级指针

  • 当一个函数的参数部分为二级指针的时候,函数能接收二级指针、一级指针的地址、指针数组

指针和数组的定义、声明

我们知道:定义是不存在的时候要让他存在,而声明是不知道的是让他知道。定义和声明指向的同一片空间。

我们在两个源文件敲写如下代码
这里写图片描述

在test.c中的extern表示将arr和p是外部文件定义的变量,在使用的时候去其他模块查找。也可以理解为声明了一个外部的变量。通过结果我们可以分析到:声明的其实就是定义的变量本身。

所以我们可以用这种方法验证数组和指针是不是一样的。如果是,则在两个文件中也可以正确打印字符;否则,说明不是。

通过上面两个例子,我们可以知道数组和指针是不相同的

那么我们怎么理解,并且把bug修改,使之正确打印想要的结果呢?、

  • 图片1:sum.c中的arr是个数组,数组有6个元素;test.c中arr是个字符指针,4个字节。我们知道声明和定义指的同一片空间,那么test.c中的arr指向sum.c中”abcde”的前4个字节的内容(小端存储0x64636261),并且站在test.c角度上打印字符串是以这个内容为地址访问它所指向的内容向后打印。所以是错误的。
//改正:
printf("%s\n",(char *)&arr);
  • 图片2:sum.c中的p是个字符指针,4 个字节,它指向内存中存放字面常量“abcdef”的地址(即a的地址);test.c中的p是个数组,最多有4个字节。站在test.c角度上打印字符串是打印以数组名p为地址所指向的空间内容向后打印。所以是错误的。
//改正:
printf("%s\n",(char *)*(int *)p);
//或者:
printf("%s",*(char **)p);

函数指针 :

如果在程序中定义了一个函数、在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针。

函数指针和其他指针一样,在函数指针执行间接访问前必须把它初始化为指向某个函数。

int fun(int);
int (*pfun)(int)=&fun;
 第二个声明创建了函数指针pfun,并把它初始化指向函数fun。
 在函数指针的初始化之前具有fun的原型是十分重要的,
 否则编译器就无法检查fun的类型是否和pfun所指向的类型一致。

初始化表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转换成函数指针。

注意:
使用函数时,&fun和fun都是函数的地址,意义相同
使用数组时,&arr为数组的地址,arr为数组首元素地址。&arr和arr意义不同

int ret;
ret=fun(10);
ret=(*pfun)(10);
ret=(pfun)(10);

其实这三条代码含义都一样:

  • 第一条语句简单地使用名字调用函数fun。函数名fun首先被转换成一个函数指针,该指针指定函数在内存中的位置。然后,函数调用操作符调用该函数,执行开始于这个地址的代码。

  • 第二条语句对pfun执行间接访问操作,它把函数指针转换成一个函数名。这个转换并不是真正需要的,因为编译器在执行函数调用操作符之前又会把它转换回去。不过,这条语句执行效果好第一条完全一样。

  • 第三条语句和前两条语句的效果一样。间接访问操作并非必须,因为编译需要的是一个函数指针。

当了解函数指针的概念后,我们来看两个代码:

(*(void (*)())0)();
void (*signal(int,void (*)(int)))(int);

像这样的表达式恐怕会令每个c程序员内心都“不寒而栗”,那么是什么意思?

1.第一段代码:
首先是将常量0强制类型转换为 void (*)() ,即函数指针类型。该函数指针指向的函数无参数、返回类型为void。
其次* (void (*)())0 取出该函数地址,即函数名。
最后结合(),相当于fun(),含义为调用地址为0 的函数。

2.第二段代码:
首先signal是一个函数,它有两个参数。一个是整型,一个是函数指针类型。
该函数指针指向的函数有一个整型参数,返回类型为void。
其次signal函数的返回类型是函数指针,该函数指针指向的函数有一个整型参数,返回类型为void。

//代码2太复杂,我们使用重定义简化一下:
typedef void(*)(int) pfun_t;
//那么代码变成
pfun_t (signal(int,pfun_t));

函数指针就是指向函数体第一行可执行语句的一个指针。至于他有什么作用呢?
相信,大家中学的时候都学过积分这个鬼东西。不知道还记不记得积分最原始的计算方法!
对,没错,无限细分,求面积。好的,我们接下来就给大家一个应用函数指针有关积分计算的例子。

#include <stdio.h>  
//Calculate用于计算积分。一共三个参数。
//第一个参数为函数指针func,指向待积分函数。
//第二、三个参数为分别为积分上下限  
double Calculate(double(*func)(double x),
                    double a, double b)  
{  
    double dx = 0.0001;//细分的区间长度  
    double sum = 0;  
    for (double xi = a+dx; xi <= b; xi+=dx)  
    {  
       double area = func(xi)*dx;  
       sum +=area;  
    }  
    return sum;  
}    
double func_1(double x)  
{  
    return x*x;  
}     
double func_2(double x)  
{  
    return x*x*x;  
}     
void main()  
{  
    printf("%lf\n", Calculate(func_1, 0, 1));  
    printf("%lf\n", Calculate(func_2, 0, 1));  
}  
//Calculate这个积分函数有一个参数是函数指针,
//那么我们在调用的时候只用传入具体的函数名,
//他就能计算出这个函数的积分

函数指针数组 :

是一个其元素是函数指针的数组,且其元素是一个指向函数入口地址的指针。
那么函数指针数组如何定义呢?

int (*pfun)(); //函数指针
int (*pfun[10])(); //函数指针数组

pfun先和 [ ]结合,说明pfun是个数组,数组的每个元素是 int (*)() 类型的函数指针。
简直就和普通的数组一模一样。没错,无非他存储的是函数指针而已。

现在,我们已经了解了函数指针和函数指针数组,那么我们将两者结合使用,也就是俗称的转换表
创建一个转换表需要两个步骤:

  • 声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。

  • 用 result = oper_func [oper] (op1,op2) 替换整条 switch 语句。
    oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。

下面我们使用转换表实现一个袖珍式的计算器

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void menu()
{
    printf("*************************\n");
    printf("***   1.add   2.sub   ***\n");
    printf("***   3.mul   4.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;
}
void calc(int(*pfun)(int ,int))
//函数的地址传参,使用函数指针
//该指针指向的函数有两个整型参数,返回类型为int
{
    int x = 0;
    int y = 0;
    int ret = 0;
    printf("请输入两个数:\n");
    assert(2==scanf("%d%d", &x, &y));
    ret = pfun(x, y);
    printf("ret=%d\n", ret);
}
int main()
{
    int(*pfun[5])(int, int) = { 0, Add,Sub,Mul,Div};
    //函数指针的数组(作用相当于switch语句)---转移表
    //指向+、-、*、/这样的函数,参数为两个整型,
//返回类型为整型,数组第一个元素地址的下标为0,
//联系整个代码Add定义的为1,为了更好使用,
//所以将第一个空过去(设置为0)
    int choose = 0;
    do{
        menu();
        printf("请选择:");
        assert(1 == scanf("%d", &choose));
        if (choose >= 1 && choose <= 4)
            calc(pfun[choose]);
        //函数地址传参,将使用的函数
        else if (choose == 0){
            printf("退出程序\n");
            break;
        }
        else
            printf("非法输入!\n");
    } while (choose);
    system("pause");
    return 0;
}

函数指针数组的指针 :

指向函数指针数组的指针是一个指针,指针指向一个数组,数组的每一个元素都是函数指针。

int (*pfun)();//函数指针
int (*pfun[10]();//函数指针数组
int (*(*pfun)[10])();//函数指针的数组的指针

其实这个概念我们只要了解,并且理清三者之间怎么变换就可以了。
在这里就不过多介绍。。。

猜你喜欢

转载自blog.csdn.net/sifanchao/article/details/80088165