C语言学习记录——이십구 指针详解(2)

目录

一、数组参数、指针参数

一维数组传参

二维数组传参

一级指针传参

二级指针传参

二、函数指针

三、函数指针数组

用途:转移表

四、回调函数

五、指向函数指针数组的指针


一、数组参数、指针参数

在写代码时难免要把数组或者指针传给函数,那函数的参数该如何设计呢?

一维数组传参

void test(int arr[])
void test(int arr[10])
void test(int* arr)
void test2(int* arr[20])
void test2(int** arr)

int main()
{
    int arr1[10] = { 0 };
    int* arr2[20] = { 0 };
    test(arr1);
    test2(arr2);
    return 0;
}

test对应的三种方式,都对。数组大小可以省略,传过去的是数组名,首元素地址,所以可用一个指针接收。test2里,第二个方法,arr2中元素类型就是int*类型,所以用一个指针接收,这个指针就是一个二级指针。

可传指针,可传数组。

二维数组传参

void test(int arr[3][5])
void test(itn arr[][5])
//void test(int arr[3][])
//void test(int arr[][])
//void test(int* arr)
//void test(int** arr)
void test(int(*arr)[5])

int main()
{
    int arr[3][5] = { 0 };
    test(arr);
    return 0;
}

看//后的,二维数组传参,形参的设计只能省略第一个[]的数字,对于一个二维数组,不能不知道一行多少元素;针对一级指针,二级指针,所对应的对象都不是二维数组,要用到之前所写的数组指针,也就是(int (*arr)[5])

一级指针传参


void print(int* p, int sz)
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d", *(p + i));
    }
}

int main()
{
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int* p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    print(p, sz);
    return 0;
}

这是一个很简单的一级指针传参,现在加点思考:当一个函数的参数部分是一级指针的时候,函数能接收什么参数?

void test1(int* p1)
{}

int main()
{
    int a = 10;
    int* p1 = &a;
    test1(&a);
    test1(p1);
    return 0;
}

a的地址放到了p1里,所以传&a和p1都可。

二级指针传参

void test1(int** p)
{}

int main()
{
    int a = 10;
    int* p = &a;
    int** pp = &p;
    test1(pp);
    test1(&p);
    return 0;
}

两者也都可以。二级指针和一级指针地址都可,还有其他的传参,int *arr[],传过去数组名,也就是首元素地址,类型为int*。三种方式,二级指针本身,一级指针地址,存放一级指针的数组。

二、函数指针

指向函数的指针。数组指针是指向数组的指针。

int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int a = 10;
    int b = 20;
    printf("%p\n", Add);
    printf("%p\n", &Add);
    return 0;
}

&Add和Add是一样的,这里可以联想一下arr,不过没有函数首元素一说。&函数名、函数名都是函数的地址。要存放Add的地址,这里可以继续联想到arr,int (*p)[10] = &arr,int*类型的指针变量p,指向10个元素的arr,那么Add也一样,int(*pa)(int, int) = Add。那么利用这个指针使用函数:

printf("%d\n", (*pa)(2, 3))

这样就可以计算2+3的值。

所以函数指针就是一个存放函数地址的指针。不同函数的地址定义的函数指针也不相同。

void Print(const char* str)
{
    printf("%s\n", str);
}

int main()
{
    void (*p)(const char*) = Print;
    (*p)("hello world");
    return 0;
}

p存放Print,前面是函数的返回类型,后面括号是函数的参数类型。然后解引用p使用函数,输入要传入的参数。

这就是函数指针的内容。接下来看两个代码

( * ( void (*) () ) 0 ) ()

void  (*signal (int,  void (*) (int) ) ) (int)

先看这个void (*p)  (char*)  =  Print

去掉p就是这个函数的类型,也就是void (*)  (char*)。p必须和*连在一起。理解了这个,在看上面两个。

( * ( void (*) () ) 0 ) (),单独看void (*) (),是一个函数的类型,函数无参,给这部分上括号放在0前面也就是强制类型转换,把0做成一个函数名,也就是这个函数的地址,再在前面放上*,解引用,就开始调用这个函数了,最后的 (),调用函数,无参。所以这是一个调用函数过程,函数类型是void (*) (),函数名是0。

void  (*signal (int,  void (*) (int) ) ) (int) 再看这个,如果把signal (int,  void (*) (int) )看成p,那么整体就是void (*p) (int),这是一个函数指针,指针变量是p,而这个p又是一个函数指针,名字为signal,函数参数是一个整型int,和一个函数指针void (*) (int)。

我们可以简化它。

typdef这个关键字可重命名,不过面对函数指针,重命名的名字也得放在*后面

typedef void (*pfun_t)  (int)

或者pfun_t  signal(int,  pfun_t)

对于这个函数的描述:

signal是一个函数声明。

signal函数的参数有2个,第一个是int, 第二个是函数指针,该函数指针指向的函数的参数的int,返回类型是void。

signal函数的返回类型也是一个函数指针,该函数指针指向的函数的参数是int, 返回类型是void。

关于函数指针再写一个东西

int main()
{
    int (*p)(int, int) = Add;
    printf("%d\n", p(2, 3));
    printf("%d\n", Add(2, 3));
    printf("%d\n", (*p)(2, 3));
    return 0;
}

三种方法都可。*p解引用找到函数;Add的地址给了p后,p就相当于Add,所以前两个都可。无论是**p,***p,结果都一样,所以这个*更像是一个摆设,可加可不加。

三、函数指针数组

int main()
{
    int* arr[5];
    int(*pa)(int, int) = Add;
    int(*parr[4])(int, int) = { Add, Sub, Mul, Div };
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        printf("%d\n", parr[i](2, 3));
    }
    return 0;
}

  int(*parr[4])(int, int) = {Add, Sub, Mul, Div} 就是一个函数指针数组,存放函数指针的数组,每个函数类型相同。

用途:转移表

计算器

void menu()
{
    printf("*********************************\n");
    printf("********* 1.Add   2.Sub *********\n");
    printf("********* 3.Mul   4.Div *********\n");
    printf("************ 0.Exit *************\n");
}

int main()
{
    int x = 0, y = 0;
    do
    {
        menu();
        printf("请选择: ");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("请输入两个操作数: ");
            scanf("%d%d", &x, &y);
            printf("%d\n", Add(x, y));
            break;
        case 2:
            printf("请输入两个操作数: ");
            scanf("%d%d", &x, &y);
            printf("%d\n", Sub(x, y));
            break;
        case 3:
            printf("请输入两个操作数: ");
            scanf("%d%d", &x, &y);
            printf("%d\n", Mul(x, y));
            break;
        case 4:
            printf("请输入两个操作数: ");
            scanf("%d%d", &x, &y);
            printf("%d\n", Div(x, y));
            break;
        case 0:
            printf("退出\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    return 0;
}

四个函数分别是加减乘除,定义函数就不写了,非常简单。也可以实现更多的功能,只要在case里面涉及到的函数里面加即可,但是如果一直加好多功能,switch语句将会非常臃肿。这里就可以用到函数指针数组。

int main()
{
    int x = 0, y = 0, input = 0;
    int (*pfarr[5])(int, int) = { 0, Add, Sub, Mul, Div };
    do
    {
        menu();
        printf("请选择: ");
        scanf("%d", &input);
        if (input >= 1 && input <= 4)
        {
            printf("请输入操作数: ");
            scanf("%d%d", &x, &y);
            int ret = pfarr[input](x, y);
            printf("%d\n", ret);
        }
        else if (input == 0)
            printf("退出\n");
        else
            printf("选择错误\n");
    } while (input);
    return 0;
}

如果相加函数,往数组里加入函数名,menu里面做一下改动即可。可声明pfarr[],元素个数有实际决定,这样以后也不需要改。这就是函数指针数组的一个用途。

四、回调函数

回调函数就是通过函数指针调用的函数。通过这个函数来解决之前的一个问题。

void menu()
{
    printf("*********************************\n");
    printf("********* 1.Add   2.Sub *********\n");
    printf("********* 3.Mul   4.Div *********\n");
    printf("************ 0.Exit *************\n");
}

int main()
{
    int x = 0, y = 0;
    do
    {
        menu();
        printf("请选择: ");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("请输入两个操作数: ");
            scanf("%d%d", &x, &y);
            printf("%d\n", Add(x, y));
            break;
        case 2:
            printf("请输入两个操作数: ");
            scanf("%d%d", &x, &y);
            printf("%d\n", Sub(x, y));
            break;
        case 3:
            printf("请输入两个操作数: ");
            scanf("%d%d", &x, &y);
            printf("%d\n", Mul(x, y));
            break;
        case 4:
            printf("请输入两个操作数: ");
            scanf("%d%d", &x, &y);
            printf("%d\n", Div(x, y));
            break;
        case 0:
            printf("退出\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    return 0;
}

程序很冗杂。从case1到case4,其中的不同就是函数不同。

void Calc(int(*pf)(int, int))
{
    int x = 0;
    int y = 0;
    printf("请输入两个操作数: ");
    scanf("%d%d", &x, &y);
    printf("%d\n", pf(x, y));
}

int main()
{
    int x = 0, y = 0, input = 0;
    int (*pfarr[5])(int, int) = { 0, Add, Sub, Mul, Div };
    do
    {
        menu();
        printf("请选择: ");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            Calc(Add);
            break;
        case 2:
            Calc(Sub);
            break;
        case 3:
            Calc(Mul);
            break;
        case 4:
            Calc(Div);
            break;
        case 0:
            printf("退出\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    return 0;
}

每次调用时,指针变量pf接收函数的地址,通过地址调用函数。这时候被调用的函数就是回调函数。

五、指向函数指针数组的指针

int main()
{
    int arr[10] = { 0 };
    int(*p)[10] = &arr;
    int(*pfArr[4])(int, int);
    int(*(*ppfArr)[4])(int, int) = &pfArr;
    return 0;
}

arr一个数组可以取出地址,放入到一个数组指针里,同样,一个函数也可以放到一个函数指针数组,而对于一个函数指针数组,也可以有一个指针指向它。int(*(*pfArr)[4])(int, int) = &pfArr,pfArr是一个函数指针数组,指向的函数类型为int,参数类型为int,现在把pfArr的地址取出来,虽然他是函数指针数组,但也是一个数组,ppfArr指向这个数组,数组元素个数有为4,每个元素类型就是int(*)(int, int)。ppfArr是一个指向函数指针数组的指针,本质上是一个数组指针,pfArr是一个函数指针数组,pf是一个函数指针。

关于回调函数还需要再写一点,放在下一篇笔记里。

结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值