指针详解(2)
Suzhou 29 0

数组参数和指针参数

一维数组传参

一维数组传参时参数可以写成数组也可以写成指针。

void test(int arr[])
{}
void test(int* arr)
{}
void test1(int* arr1[])
{}
void test1(int** arr1)
{}

int main()
{
    int arr[] = { 0 };
    int* arr1[10] = { 0 };
    test(arr);
    test1(arr1);
    return 0;
}    

上述代码中数组传参的两种方法都是可以的。在第1行代码和第5行代码中[]内的数字可以写也可以不写甚至可以写错,在代码中不起作用。数组传参后的类型需要车数组中元素的类型一致。

二维数组传参

1. 二维数组传参时参数可以和主函数中的数组形式保持完全一致。

void test(int arr[3][5])
{}

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

上述代码中数组传参时的行都可以省略,列都不能省略。 2. 二维数组传参时参数可以写成指向数组的指针。

void test(int(*arr)[5])
{}

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

一级指针传参时可以传递变量地址和保存地址的指针:

void test(int* a)
{}

int main()
{
    int a = 0;
    int* p = &a;
    test(&a);
    test(p);
    return 0;
}

二级指针传参时参数部分直接设计成二级指针,可以传递一级指针的地址或二级指针或指针数组:

void test(int** ptr)
{}
void test1(int** ptr)
{}
void test2(int** ptr)
{}

int main()
{
    int a = 0;
    int* p = &a;
    int** pp = &p;
    test(pp);
    test1(&p);
    int* arr[] = { 0 };
    test2(arr);  //数组名是首元素地址,数组首元素是int*类型 
    return 0;
}
函数指针

函数指针是指向函数的指针,是存放函数地址的指针。 函数的地址:

int ADD(int a, int b)
{
    return a + b;
}

int main()
{
    int a = 10;
    int b = 20;
    ADD(a, b);
    printf("%d\n", ADD(a, b));
    printf("%p\n",&ADD);
    printf("%p\n", ADD);
    return 0;
}
30
00007FF6A46913D4
00007FF6A46913D4

int ADD(int a, int b)
{
    return a + b;
}

int main()
{
    int a = 10;
    int b = 20;
    int* p1 = &ADD;
    printf("%p\n",p1);
    int(*p2)(int ,int) = ADD;
    printf("%p\n", p2);
    return 0;
}
00007FF77A981352
00007FF77A981352

::: tip 函数地址的取出有两种形式:

int* p1 = &ADD;
int(*p2)(int ,int) = ADD;

::: “&函数名”和“函数名”都表示函数的地址。

函数指针的写法: 指针详解(2) 上图中代码表示将函数ADD存在指针p中。 函数参数部分可以写参数名int x,int y也可以不写,要把所有的函数参数类型都列出来。 函数指针:

int ADD(int a, int b)
{
    return a + b;
}

int main()
{
    int a = 10;
    int b = 20;
    int(*p)(int, int) = ADD;
    printf("%d\n", p(2, 3));
    printf("%d\n", (*p)(2, 3));
    return 0;
}
5
5

上述代码中第11行和12行代码的两种写法都可以。 ::: tip 两种写法都比较容易理解。 第10行代码将函数ADD存在指针变量p中,即ADD等同于p,调用函数时p(2,3)等同于ADD(2,3)。 第12行代码符合对调用指针函数时需要解引用的定义。 :::

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

int main()
{
    void(*p)(char*) = Print; 
    (*p)("Hello World");  
    return 0;
}
Hello World

上述代码第8行将函数Print的地址存放在指针p中,第9行p中存放函数Print地址,p解引用找到地址调用传参(字符串)。 ::: warning *函数指针和数组指针一样,指针函数p和需要使用圆括号括起来,否侧指针函数p先和后面的圆括号组成函数,不能保存后面函数的地址。*

void (*p1)() = Fun - p1是函数指针
void *p2() = Fun   - p2是函数

:::

两个例题

调用0地址处的,参数为无参,返回类型为void的函数:

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

上述代码中,void (* )()是一个函数指针类型,放入圆括号中表示强制类型转换,(void (* )())后的0表示一个函数的地址,前面加* 表示解引用,找到该函数(函数无参,返回类型为void),调用该函数(* (void()())0)(),最后一对原括号表示无参。 综上该代码的含义是:*把0强制类型转换为函数指针类型,该指针指向函数无参,返回类型为void,0变为函数地址后,解引用调用0地址处的该函数。**

上述代码的实际应用场景如:编写一个独立运行于某微处理器上的C程序,当计算机启动时,硬件调用首地址为0位置的函数。


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

上述代码中,signal加后面的圆括号表示是一个函数,函数有两个参数,分别是int,void(* )(int),函数的第二个参数void(* )(int)表示函数指针(指针指向函数的参数类型为int,返回类型为void)。即signal函数的两个参数类型分别是整型和函数指针类型。函数中去掉函数名和参数剩下的是函数类型,即void (* )(int)是函数signal的类型。void ()(int)是一个函数指针,函数参数的类型是int,返回类型是void。 综上该代码的含义是:*signal是一个函数声明。signal函数的参数有2个,第一个是int,第二个是函数指针:该函数指针指向的函数的参数是int,返回类型是void。signal函数的返回类型也是一个函数指针:该函数指针指向的函数的参数是int,返回类型是void。**

::: warning 上述代码不能写成如下形式:

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

函数指针中代表指针的*必须和函数名写在同一个圆括号内。 :::

函数简化:

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

上述代码较难理解,可以简化为:

typedef void(*p)(int);
p signal(int, p);

上述代码第一行表示将void()(int)的函数指针类型简化为p。 ::: warning typedef在定义简单别名时一般将别名放在最后,如:typedef unsigned int UNI中UNI就是unsigned int类型的别名。对于函数指针来说,把函数指针重新起名,新的名字在书写时应该和*放在同一个圆括号中。上述代码中的p就是void()(int)类型的别名。 :::


函数指针数组

指针详解(2)

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 (*parr[4])(int, int) = { Add,Sub,Mul,Div };
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        printf("%d ", parr[i](2,3));
    }
    return 0;
}
5 -1 6 0

上述代码中,第20行代码的指针变量p可以存储Add的地址,也可以存储Sub、Mul、Div的地址。但变量p中只能存储一个地址,要想把所有的地址都存储起来,就需要函数指针数组。第21行代码中,parr先和优先级更高的方括号结合形成一个4个元素的数组,去掉数组名和方括号,剩下的int (*)(int, int)是函数指针类型,即函数指针数组parr[4]中有4个元素,每个元素的类型是函数指针。最后再使用大括号对数组初始化。


写出下面函数指针和函数指针数组:

char* my_strcpy(char* dest, const char* src);

char* (*pf)(char*, const char*) = my_strcpy;  //指针pf指向my_strcpy
char* (*pfrr[4])(char*, const char*) = { 0 };  //函数指针数组
函数指针数组的使用(转移表)

计算器函数:

void menu()
{
    printf("#####################\n");
    printf("###1.Add     2.Sub###\n");
    printf("###3.Mul     4.Div###\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 a = 0;
    int b = 0;
    int input = 0;
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        switch (input)
        {
            case 1:
                printf("输入两个操作数:>");
                scanf("%d %d", &a, &b);
                printf("%d\n", Add(a, b));
                break;
            case 2:
                printf("输入两个操作数:>");
                scanf("%d %d", &a, &b);
                printf("%d\n", Sub(a, b));
                break;
            case 3:
                printf("输入两个操作数:>");
                scanf("%d %d", &a, &b);
                printf("%d\n", Mul(a, b));
                break;
            case 4:
                printf("输入两个操作数:>");
                scanf("%d %d", &a, &b);
                printf("%d\n", Div(a, b));
                break;
            case 0:
                printf("退出\n");
            default:
                printf("选择错误\n");
                break;
        }
    } while (input);
    return 0;
}

使用函数指针数组简化:

void menu()
{
    printf("#####################\n");
    printf("###1.Add     2.Sub###\n");
    printf("###3.Mul     4.Div###\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 a = 0;
    int b = 0;
    int input = 0;
    int* (*parr[5])(int, int) = { 0,Add,Sub,Mul,Div };
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);

        if (input > 0 && input < 5)
        {
            printf("请输入操作数:>");
            scanf("%d %d", &a, &b);
            int ret = parr[input](a, b);
            printf("%d\n", ret);
        }
        else if(input == 0)
        {
            printf("退出\n");
        }
        else
        {
            printf("输入错误\n");
        }
    } while (input);
    return 0;
}

上述代码使用函数指针数组进行代码优化后,日后需要增加其他运算时,可以不用增加switch-case语句,避免代码随着功能增加越来越长。 ::: tip 如上述代码中第30行代码int*(*parr[5])(int, int) = { 0,Add,Sub,Mul,Div }是通过下标找到元素,再找到元素所指向的函数。 这种函数指针数组称为转移表。 :::


指向函数指针数组的指针

将函数指针数组的地址存放在一个指针中:

int(*pfarr[10])(int, int); 
int(*(*ppfarr)[10])(int, int) = &pfarr;

::: tip 思考过程: 在int( * pfarr[10])(int, int)的基础上修改,ppfarr是指向pfarr的指针,只需要将int( * pfarr[10])(int, int)中的pfarr改为ppfarr,并将ppfarr写成指针类型( * ppfarr)就可以了。 ::: 上述代码int( * ( * ppfarr)[10])(int, int) = &pfarr的类型是int( * )(int, int) ,ppfarr先和 * 结合形成指针,然后指向[4]形成有4个元素的数组,每个元素的类型就是int( * )(int, int)。


::: warning

int(*ppfarr)(int, int);  //函数指针
int(*(ppfarr)[10])(int, int);  //函数指针数组
int(*(*ppfarr)[10])(int, int);  //指向函数指针数组的指针

如上,将第3行代码中的ppfarr后加上[],又可以将该指针存储在数组中。 :::


评论区

索引目录