0


指针的进阶


这个章节,我们继续探讨指针的高级主题


1. 字符指针

1.1 使用

** 1.1.1 对字符使用 一般我们对字符指针的使用如下:**

#include <stdio.h>
int mian()
{
    char ch = 'w';
    char* pc = &ch;


    return 0;
}

** 1.1.2 对字符串使用**

** 而字符指针可以对字符串进行定义,但是只指向字符串的首元素地址,解引用访问时,只访问首地址的内容,即第一个元素,代码如下:**

#include <stdio.h>
int mian()
{
    char* ps = "abcdef";
    printf("%c\n", *ps);
    return 0;
}


** 此处存在问题,如果用的是2022版本的vs,此时会警告,当我们使用这种方式去初始化指针,我们对ps内存放的字符进行修改,我们发现程序会崩溃,常量字符串不可以修改。**

int main()
{
    char ch = 'w';

    char* ps = "abcdef";
    *ps = 'w';

    printf("%c\n", *ps);
    return 0;
}

** 我们可以在前面用const修饰一下,这样就可以啦,如下:**

int main()
{
    char ch = 'w';
    const char* ps = "abcdef";
//    *ps = 'w';

    printf("%s\n", ps);
    return 0;
}

** 打印的时候,打印字符串%s,给出首地址即可 printf("%s\n", ps);**

1.1.3 对字符数组使用

** 如果想修改,我们可以定义字符数组:**

#include <stdio.h>

int main()
{
    char arr[] = "abcdef";
    char* p = arr;

    return 0;
}

1.2 面试题

#include <stdio.h>
int main()
{
    char str1[] = "hello you.";
    char str2[] = "hello you.";
    const char* str3 = "hello you.";
    const char* str4 = "hello you.";
    if (str1 == str2)
        printf("str1 and str2 are same\n");
    else
        printf("str1 and str2 are not same\n");

    if (str3 == str4)
        printf("str3 and str4 are same\n");
    else
        printf("str3 and str4 are not same\n");

    return 0;
}

** 我们来详细解释一下这个题目,const修饰的指针str3\str4是无法改变的,既然存的是一样的并且无法改变的内容,为了优化内存,只开辟同一个空间来存储内容,所指向的是同一个地址;但是str1\str2是两个独立的空间,所指向的地址也是两个独立空间的不同的地址,此题比较的不是内容,比较的是地址,图解如下:**

** 如果要比较字符串的内容,要用strcmp,此处不过多介绍。**

2. 指针数组

2.1 基本使用

** 我们初始化一个指针数组:**

int main()
{
    char* arr[5] = { "zbcdef","zhangsan","hehe","wangcai","ruhua" };

    return 0;
}

** 我们将其打印:**

int main()
{
    char* arr[5] = { "zbcdef","zhangsan","hehe","wangcai","ruhua" };
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        printf("%s\n", arr[i]);
    }

    return 0;
}

2.2 一维数组模拟二维数组

int main()
{
    int arr1[5] = { 1,2,3,4,5 };
    int arr2[5] = { 2,3,4,5,6 };
    int arr3[5] = { 3,4,5,6,7 };
    int arr4[5] = { 5,6,7,8,9 };

    int* arr[4] = { arr1,arr2,arr3,arr4 };

    return 0;
}

     **将其打印:**
//用一维数组模拟二维数组
int main()
{
    int arr1[] = { 1,2,3,4,5};
    int arr2[] = { 2,3,4,5,6};
    int arr3[] = { 3,4,5,6,7};
    int arr4[] = { 4,5,6,7,8};
 
    int* arr[4] = { arr1,arr2,arr3,arr4 };
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        int j = 0;
        for (j = 0; j < 5; j++)
        {
            printf("%d ", arr[i][j]);
    //同数组的引用,不需要解引用,我们拿到的也是数组首地址,再对其操作
    //arr[j]=*(arr+j),可以看成已经解引用过
        }
        printf("\n");
    }
    return 0;
}
 

2.3 总结

int* arr1[10]; //整形指针的数组

char *arr2[4]; //一级字符指针的数组

char **arr3[5];//二级字符指针的数组

3. 数组指针

3.1 定义

** 指向数组地址的指针**


//整型指针——指向整型的指针——存放整型变量的地址
//int* p1;
//字符指针——指向字符的指针——存放字符变量的地址
//char* p2;
//数组指针——指向数组的指针——存放数组的地址


int main()
{
    int a = 10;
    int* p1 = &a;

    char ch = 'w';
    char* p2 = &ch;

    int arr[10] = { 1,2,3,4,5 };
    int (* pa)[10] = &arr;//取出的是数组的地址,存放到pa中,pa是数组指针变量
    //int(*)[10]——数组指针类型

    return 0;
}

** 数组指针定义时,(pa)说明这是指针,然后结合【10】说明是数组,int说明其指向的是整型数组,说明这是指向整型数组的指针。int()[10]——数组指针类型**

int (*p)[10];

//解释:pa先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以pa是一个 指针,指向一个数组,叫数组指针。

//这里要注意:[]的优先级要高于号的,所以必须加上()来保证pa先和结合。

3.2 数组名&数组名

int main()
{
    int arr[10];
    printf("%p\n", arr);

    printf("%p\n", &arr[0]);
    printf("%p\n", &arr);

    return 0;
}

    ** **

** 数组的首地址,数组首元素的地址,数组的地址均指向同一个地址,从数值上看是一样的。我们再来看看区别:**

int main()
{
    int arr[10];
    printf("%p\n", arr);
    printf("%p\n", arr+1);

    printf("%p\n", &arr[0]);
    printf("%p\n", &arr[0]+1);

    printf("%p\n", &arr);
    printf("%p\n", &arr+1);

    return 0;
}


** 指针的加减跳过的字节取决于指针的类型**

3.3特殊

//数组名是数组首元素的地址

两个例外

1.sizeof(数组名)(此时表示整个数组)

2.&数组名

3.4 如何使用

3.4.1 举个例子(一维数组)

void printf1(int arr[10], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);

    }
    printf("\n");
}

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


    printf1(arr, sz);

    return 0;
}

** 我们在传首地址时,相当于传arr,然后我们可以修改为:*

void printf1(int *arr, int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *(arr+i));

    }
    printf("\n");
}

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


    printf1(arr, sz);

    return 0;
}

** 我们进而修改为数组指针来使用,如下:**

void printf1(int(*p)[10], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", (*p)[i]);//p内存放arr首地址,*p==*&arr==arr
                          //为了取出数组元素,(*p)[i]==arr[i];
    }
    printf("\n");
}

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

    printf1(&arr, sz);

    return 0;
}

** p内存放arr首地址,p==&arr==arr;为了取出数组元素,(p)[i]==arr[i];*

但是一维数组里,我们使用数组指针终究是麻烦的,一般不适合用于一维数组情况。

3.4.2 举个栗子(二维数组)

**     我们打印一个二维数组:**

数组传参,数组接收:

printf3(int arr[3][5], int r, int c)
{
    int i = 0, j = 0;
        for (i = 0; i < r; i++)
        {
            for (j = 0; j < c; j++)
            {
                printf("%d ", arr[i][j]);
            }
            printf("\n");
        }
}

void test2()
{
    int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6,},{3,4,5,6,7} };
    printf3(arr,3,5);
}

int main()
{
    test2();

    return 0;
}

数组传参,指针接收(数组指针常用形式)

二维数组的首元素,就是二维数组的第一行;

arr是二维数组的数组名,数组名是数组首元素的地址,arr就是第一行的地址

#include<stdio.h>

void printf4(int(*p)[5],int r,int c )
{
    int i = 0;
    for (i = 0; i < r; i++)
    {
        int j = 0;
        for (j = 0; j < c; j++)
        {
            printf("%d ", * (*(p + i) + j));//p+i指向某一行,解引用找出某一行
                          //对某一行的首地址加j,再解引用找出元素
        }
        printf("\n");
    }
}


void test2()
{
    int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6,},{3,4,5,6,7} };
    printf4(arr,3,5);
}
int main()
{
    test2();
    return 0;
}

3.5 小结

**int arr[5]; 整形数组,五个整型元素的数组
int parr1[10]; 指针数组,十个指针元素的数组
int (parr2)[10]; 数组指针,指向数组的指针

*int (parr3[10])[5]; 数组

** parr3有十个元素,是存放数组指针的数组**

4. 数组传参和指针传参

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])//ok?        正确,数组传参,形参可以是数组,也可以是指针
                            //    数组传参,传的不是整个数组,不需要创建一个数组,可以不写大小
{}
void test(int arr[10])//ok?        正确,多写了大小,虽然大小没有实际作用
{}
void test(int* arr)//ok?        正确,形参是指针
{}
void test2(int* arr[20])//ok?    正确,形参是数组,同实参,也可以省略数组大小
{}
void test2(int** arr)//ok?        正确,详细见图
{}
int main()
{
    int arr[10] = { 0 };        //整型数组

    int* arr2[20] = { 0 };        //整形指针数组
    test(arr);
    test2(arr2);
}

4.2 二维数组传参

void test(int arr[3][5])//ok?    形参是数组,正确
{}
void test(int arr[][])//ok?    形参是数组,错误,不能均省略
{}
void test(int arr[][5])//ok?    形参是数组,正确,可以只有列数
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//ok?        形参是指针,一维数组
{}                              //指向arr第一行的首地址,操作会产生问题

void test(int* arr[5])//ok?    此处arr与【5】结合,是数组
{}                              //我们想要形参是指针

void test(int(*arr)[5])//ok?    形参指针,每个指针指向一个含五个元素的整型数组
{}
void test(int** arr)//ok?        错误,传出去arr是行地址,一维数组的地址
{}                              //不能用二级指针,二级指针接收一级指针的地址
int main()
{
    int arr[3][5] = { 0 };
    test(arr);
}

4.3 一级指针传参

#include <stdio.h>
void print(int* p, int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d\n", *(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]);
    //一级指针p,传给函数
    print(p, sz);
    return 0;
}

当一个函数的参数部分为一级指针的时候,函数能接收什么参数(即传什么参数)?

void test(int* p)//参数是一级指针时
{

}

int main()
{
    int a = 0;
    test(&a);//ok    传变量地址

    int* ptr = &a;
    test(ptr);//    传一级指针本身

    int arr[10];
    test(arr);//    传一维数组数组名

}

4.4 二级指针传参

#include <stdio.h>
void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);
 test(&p);
 return 0;
}

当函数的参数为二级指针的时候,可以接收什么参数?

void test(int** p)
{

}

int main()
{
    int** ptr = ;
    test(ptr);//ok    传二级指针

    int* p2 = ;
    test(&p2);//    传一级指针地址

    int* arr[10];
    test(arr);//    传指针数组的数组名
}

5. 函数指针

5.1 引入介绍

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

int main()
{
    int arr[10] = { 0 };
    printf("%p\n", &arr);
    printf("%p\n", arr);

    printf("%p\n", Add);
    printf("%p\n", &Add);

    return 0;
}

     **数组名与取地址数组名的值相同,但是意义不一样,一个是数组首元素的地址,一个是数组的地址**

** **** 对于函数名,两个没有区别,函数名与取地址函数名拿到的都是函数地址。**

对于函数指针,假定指针pf指向函数Add,声明这是一个指针变量,(pf),其类型是int函数,且函数的参数是int,我们可以写出 int(pf)(int x,int y)=&Add;

    //pf就是函数指针变量
    int (*pf)(int x, int y) = &Add;
    //int (*pf)(int x, int y) = Add;或者这样写

** 我们定义了一个函数指针,那么我们如何调用它呢?**

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

int main()
{

    //pf就是函数指针变量
    int (*pf)(int x, int y) = &Add;

    int sum=(*pf)(3, 5);//对pf解引用找到Add函数,使用Add函数
    printf("%d\n", sum);

    return 0;
}

** 我们考虑到Add是存放于pf中的,类比int sum=Add(3,5);int sum=(pf)(3,5);*

我们能否去掉前面的解引用符号,直接使用pf代替Add呢?

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

int main()
{
    
    //pf就是函数指针变量
    int (*pf)(int x, int y) = &Add;

    int sum=pf(3, 5);
    printf("%d\n", sum);

    return 0;
}

** 所以也是可以的,由此可知此处没有实际作用,也可以多写几个,加帮助理解,但是语法上没有用。**

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

int main()
{
    //pf就是函数指针变量
    int (*pf)(int x, int y) = &Add;

    int sum=(*******pf)(3, 5);
    printf("%d\n", sum);

    return 0;
}

5.2 ((void ()() )0 )();

** 我们来解读一下这个有意思的代码**

int main()
{
    (*(void (*)() )0 )();
    
    return 0;
}

** 0前面的()内放置void ()();我们对其补充void (p)(),这是一个函数指针,且函数的返回类型为void。
所以0前面的括号内放置了一个类型,而类型放在()里,强制类型转换。
(void (
)() )0——把0当做一个函数的地址,0强制类型转换成像void (
)()的函数类型的地址,0地址内存放这样的函数。
前面加去找到这个地址内存放的这样类型的函数,并调用函数,无参加上()。*

** 把0直接转换成一个void()()的函数指针,然后去调用0的地址处的函数,前面解引用的同5.1的内容,是可以省略的**

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

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

    return 0;
}

   **  上述代码是一个函数声明,声明的函数叫signal,signal函数的第一个参数是int类型,第二个参数是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void,signal函数的返回类型也是函数指针类型,该函数指针指向的函数参数是int,返回类型是void。**

** 如何简化呢?**

    //void ( *signal(int, void(*)(int) ) )(int);
    //函数类型及返回类型都是void(*)(int),我们typedef 一下

    //typedef void(*)(int) pf_t;但是语法要求pf_t应该放置于*旁边
    typedef void(* pf_t)(int);

    pf_t signal(int, pf_t);

6. 函数指针数组——转移表

6.1 介绍

int p;//整型指针*

int arr【5】;//整型指针数组*

函数指针

int(*pf)(int,int);

函数指针数组

*int (pfArr[4])(int,int);

其可以存放多个参数相同,及返回类型相同的函数地址

int Add(int x, int y)
{
    return x + y;

}

int Sub(int x, int y)
{
    return x - y;

}

int main()
{
    int(*pfArr[2])(int, int) = { Add,Sub };

    return 0;
}

我们尝试调用这个函数指针数组

int Add(int x, int y)
{
    return x + y;

}

int Sub(int x, int y)
{
    return x - y;

}

int main()
{
    int(*pfArr[2])(int, int) = { Add,Sub };

    int ret = pfArr[0](2, 3);
    printf("%d\n", ret);

    ret = pfArr[1](2, 3);
    printf("%d\n", ret);

    return 0;
}

6.2 应用——写一个整数计算器

6.2.1 函数主体

    为了能够不断地计算,我们可以利用do while循环,为了使计算器的打印更明确,我们写一个菜单函数,如下:
void menu()
{
    printf("************************\n");
    printf("**** 1.add     2.sub ***\n");
    printf("*****3.mul     4.div ***\n");
    printf("*****0.exit          ***\n");
    printf("************************\n");
}
int main()
{
    int input = 0;

    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            break;
        case 2:
            break;
        case 3:
            break;
        case 4:
            break;
        case 0:
            break;
        default:
            break;
        }
    }while(input)
    return 0;
}

6.2.2 各函数块的编写

一、Add函数的初步编写

   ** 我们如果需要键入操作1,那么我们调用Add函数,此时我们还需键入两个操作数,x,y,操作后的结果,我们用ret来接收,由此可写出如下的代码**
void menu()
{
    printf("************************\n");
    printf("**** 1.add     2.sub ***\n");
    printf("*****3.mul     4.div ***\n");
    printf("*****0.exit          ***\n");
    printf("************************\n");
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        printf("请输入两个操作数:>");
        scanf("%d %d", &x, &y);
        switch (input)
        {
        case 1:
            ret=Add(x, y);
            printf("%d\n", ret);
            break;
        case 2:
            break;
        case 3:
            break;
        case 4:
            break;
        case 0:
            break;
        default:
            break;
        }
    }while(input)
    return 0;
}

二、 其他函数块的初步编写

** 同理,我们可以编写如下的部分**

void menu()
{
    printf("************************\n");
    printf("**** 1.add     2.sub ***\n");
    printf("*****3.mul     4.div ***\n");
    printf("*****0.exit          ***\n");
    printf("************************\n");
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

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

6.2.3 各函数详细编写

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 menu()
{
    printf("************************\n");
    printf("**** 1.add     2.sub ***\n");
    printf("*****3.mul     4.div ***\n");
    printf("*****0.exit          ***\n");
    printf("************************\n");
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        printf("请输入两个操作数:>");
        scanf("%d %d", &x, &y);

        switch (input)
        {
        case 1:
            ret = Add(x, y);
            printf("%d\n", ret);
            break;
        case 2:
            ret = Sub(x, y);
            printf("%d\n", ret);
            break;
        case 3:
            ret = Mul(x, y);
            printf("%d\n", ret);
            break;
        case 4:
            ret = Div(x, y);
            printf("%d\n", ret);
            break;
        case 0:
            printf("退出计算器\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    return 0;
}

** 6.2.4 完善改进**

** 我们发现其中一个问题,当我们只输入一个操作数的时候,其不报错,我们可以对其进行修改,修改后代码如下:**

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 menu()
{
    printf("************************\n");
    printf("**** 1.add     2.sub ***\n");
    printf("*****3.mul     4.div ***\n");
    printf("*****0.exit          ***\n");
    printf("************************\n");
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);

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

** 而且我们的代码日后若想增加更多的功能,其会越写越长。我们需要简化代码。我们可以定义一个函数指针数组—— int (pfArr[5])(int ,int) = { 0,Add,Sub,Mul,Div };*

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 menu()
{
    printf("************************\n");
    printf("**** 1.add     2.sub ***\n");
    printf("*****3.mul     4.div ***\n");
    printf("*****0.exit          ***\n");
    printf("************************\n");
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    int (*pfArr[5])(int ,int) = { 0,Add,Sub,Mul,Div };


    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        printf("请输入两个操作数:>");
        scanf("%d %d", &x, &y);

        ret=pfArr[input](x, y);
        printf("%d\n", ret);

    } while (input);
    return 0;
}

** 此时代码能够正常运行,但是会有一些问题:**


** 当我们输入0时,其也让我们输入两个操作数,我们对input的内容进行条件判断**

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 menu()
{
    printf("************************\n");
    printf("**** 1.add     2.sub ***\n");
    printf("*****3.mul     4.div ***\n");
    printf("*****0.exit          ***\n");
    printf("************************\n");
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    int (*pfArr[5])(int ,int) = { 0,Add,Sub,Mul,Div };


    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        if (input == 0)
        {
            printf("退出计算器\n");
            break;
        }
        if (input >= 1 && input <= 4)
        {
            printf("请输入两个操作数:>");
            scanf("%d %d", &x, &y);

            ret = pfArr[input](x, y);
            printf("%d\n", ret);
        }
        else
        {
            printf("选择错误\n");
        }
        
    } while (input);
    return 0;
}

** 此时计算器的编写已经比较完善了,也方便后续对计算器功能的增加**

7. 指向函数指针数组的指针

函数指针

int (*pf)(int ,int);

函数指针数组

int (*pfArr[4])(int ,int);

指向函数指针数组的指针

应该是什么什么=&pfArr;

函数指针数组的指针类型,我们在函数指针数组的类型上进行修改int (*ptr[4])(int ,int);

改名称int (*(*ptr)[4])(int ,int);

其是指针,不是数组,所以可得int (*(*ptr)[4])(int ,int) =&pfArr;

8. 回调函数

8.1 定义及介绍

 **   回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。**

8.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;

}

void menu()
{
    printf("************************\n");
    printf("**** 1.add     2.sub ***\n");
    printf("*****3.mul     4.div ***\n");
    printf("*****0.exit          ***\n");
    printf("************************\n");
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);

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

    ** **

** 我们发现红色框内的代码十分相似,代码显得重复繁冗,我们尝试用calc函数来代替红色框内的代码,但是红色框内的计算方式不同,我们同样的需要去调用计算的函数,我们去调用函数的地址,而函数的地址,应该放入函数的指针,假定为p,参数是int ,int ,返回类型是int ,void calc(int(p)(int,int)),例如我们取Add地址,即void calc(int(Add)(int,int)),
我们调用p即调用相对应的传地址的计算函数,ret = p(x, y);,例如Add,即ret=Add(x.y)代码修改如下:

#include<stdio.h>
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 menu()
{
    printf("************************\n");
    printf("**** 1.add     2.sub ***\n");
    printf("*****3.mul     4.div ***\n");
    printf("*****0.exit          ***\n");
    printf("************************\n");
}

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

int main()
{
    int input = 0;
    

    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        printf("请输入两个操作数:>");
        scanf("%d %d", &x, &y);

        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;
}

** 当p去调用Add时,Add就是回调函数;当p去调用Sub时,Sub就是回调函数。**

9 qsort函数

qsort函数——c语言标准库提供的排序函数,该函数的排序是快速排序

9.1 对冒泡排序的回顾

** 图解冒泡排序**

void bubble_sort(int arr[], int sz)
{
    int i = 0;
    //趟数
    for (i = 0; i < sz - 1; i++)
    {
        //一趟冒泡排序的过程
        int j = 0;
        for (j = 0; j < sz-1-i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}

void print(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main()
{
    //冒泡排序
    //对整型数据进行排序
    int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
    int sz = sizeof(arr) / sizeof(arr[0]);

    bubble_sort(arr, sz);
    print(arr, sz);


    return 0;
}

** 而bubble_sort冒泡排序的缺陷也非常明显,其只能对整数进行冒泡排序,所以我们来看看qsort函数。**

9.2 qsort 详解(可排序任意类型的数据)

** 我们检索一下它的用法:**

void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));



void qsort(void* base,
    size_t num,
    size_t size,
    int (*compar)(const void*, const void*)//函数指针//compar比较,*,其是个指针,
                                      //指向函数,函数参数是const void* const void*
                                              //函数返回类型是int 

使用时要引入头文件#include<stdlib.h>

** base 是指向待排序的数组的第一个对象的指针,该对象将转换为 .

void*

**

num 数组中由指向的元素数。即上文的sz

size 数组中每个元素的大小(以字节为单位)。指向比较两个元素的函数的指针。
compar 此函数被重复调用 by 以比较两个元素。

   ** 对于void* base,由于qsort函数可以排序任何类型,而base是指向待排序数组的第一个对象的指针,由于待排序的数组可以是任意类型,所以我们不能直接对其定义指针类型,例如int*,char*等,这样我们一旦去处理其他类型的时候都会出现问题,由于传递的指针可能是int,char等类型,我们可以用void*来处理,void*可以接收任意指针类型的地址。**

** 但是其解引用的时候,需要先强制类型转换,再解引用,例如 voidp=&i;*

(int )p=200;也不能直接p++的操作。

** num,我们排序的时候需要知道待排序元素的个数,得清楚自己需要排多少。**

 **   size,我们需要知道待排序元素的大小,因为我们不知道元素类型,所以当我们知道元素的大小,就知道其偏移量,就能够找到下一个元素的地址,找到下一个元素。**

** compar比较两个元素的大小的函数指针,会比较才能排序。**

比较:

两个整型使用关系运算符比较大小;

两个字符串,使用strcmp比较大小;

两个结构体,也得制定比较方式;

由于比较的方式不同,所以比较的时候需要用不同的方法,用哪个传哪个,所以增加一个compar函数,其需要待比较的两个元素的指针,返回类型是int

9.3 整型排序

9.3.1 整型升序排序


** 我们来尝试写一下整型升序排序代码,如下:**

#include<stdio.h>
#include<stdlib.h>

void print(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int cmp_int(const void* e1, const void* e2)
{
    //此处明确e1,e2类型,可强制转换类型
    if (*(int*)e1 > *(int*)e2)
        return 1;
    else if (*(int*)e1 < *(int*)e2)
        return -1;
    else
        return 0;
}
//测试qsort函数功能
void test2()
{
    int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
    print(arr, sz);

}

int main()
{
    test2();
    return 0;
}

** 此处比较写得比较繁冗,我们对其cmp_int 部分修改,如下:**

#include<stdio.h>
#include<stdlib.h>

void print(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int cmp_int(const void* e1, const void* e2)
{
    //此处明确e1,e2类型,可强制转换类型
    return (*(int*)e1 - *(int*)e2);
    
}
//测试qsort函数功能
void test2()
{
    int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
    print(arr, sz);

}

int main()
{
    test2();
    return 0;
}



9.3.2 整型降序排序

** 我们再进一步修改成整型降序排列的代码,如下:**

#include<stdio.h>
#include<stdlib.h>

void print(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int cmp_int(const void* e1, const void* e2)
{
    //此处明确e1,e2类型,可强制转换类型
    return (*(int*)e2 - *(int*)e1);
    
}
//测试qsort函数功能
void test2()
{
    int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
    print(arr, sz);

}

int main()
{
    test2();
    return 0;
}

9.4 结构体排序

9.4.1 按name排序

** 我们再来尝试写一下结构体数据的排序,我们假设一个含有name与年龄的结构体,我们按其名字进行排序:**


** 我们需要用到strcmp函数,其返回值同qsort,可以直接return,不需要if else判断,其使用需要引入#include<string.h>头文件**


#include<stdio.h>
#include<string.h>
#include<stdlib.h>


struct Stu
{
    char name[20];
    int age;
};


int cmp_by_name(const void* e1, const void* e2)
{
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}


//测试qsort排序结构体数据
void test3()
{
    struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };
    //按照名字比较
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_by_name);
}


int main()
{
    //test2();
    test3();
    return 0;
}

9.4.2 按年龄排序

一、升序

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

struct Stu
{
    char name[20];
    int age;
};


int cmp_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

//测试qsort排序结构体数据
void test3()
{
    struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };

    int sz = sizeof(s) / sizeof(s[0]);
    //按年龄
    qsort(s, sz, sizeof(s[0]), cmp_by_age);

}


int main()
{
    //test2();
    test3();
    return 0;
}

二、降序

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

struct Stu
{
    char name[20];
    int age;
};


int cmp_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e2)->age - ((struct Stu*)e1)->age;
}

//测试qsort排序结构体数据
void test3()
{
    struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };

    int sz = sizeof(s) / sizeof(s[0]);
    //按年龄
    qsort(s, sz, sizeof(s[0]), cmp_by_age);

}


int main()
{
    //test2();
    test3();
    return 0;
}

9.5 用冒泡排序模拟qsort

9.5.1 模拟整数排序

   ** 我们学习了qsort的逻辑结构,我们能否用此逻辑结构来完善改进我们的冒泡排序呢?**

   

    **我们接收的可能是任意类型的数据,所以红框部分的代码需要修改,我们考虑到比较的地址指向的类型不确定,所以base参数的类型我们写作void*,其指向待比较数据的地址;**

我们还需知道比较的个数sz;

比较的数据的大小width,单位是字节;

不同类型数据的比较方法不同,我们还需一个比较函数,来实现不同类型数据的比较,该cmp函数的参数是const void e1,const void e2,其返回类型是int.**

     冒泡排序的内部只需修改比较部分,我们比较的两个参数该如何找到,首先我们对base强制类型转换成char类型,然后该指针类型已经转换完成后,我们再对其+-,比如(char*)base+j*width与(char*)base+(j+1)*width进行比较。找到 j 下标的元素,与 j+1 下标的元素进行比较。

    交换部分,如果是再创建一个变量,由于我们不知道内部存储什么类型的变量,我们无法明确创建什么类型的变量,我们考虑数据存储时,小端存储内四个字节,我们将对应字节内部的数据进行交换即可,即两个整型的相互交换,所以我们写一个Swap函数,来交换我们需要交换的数据,同时我们也需要知道数据所占的字节大小,即width。所以我们可以写出
void Swap(char* buf1,char* buf2,int width)
{

}
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);


** 图解:**

** 具体代码如下:**

#include<stdio.h>

void Swap(char* buf1,char* buf2,int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}


void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2))
{
    int i = 0;
    //趟数
    for (i = 0; i < sz - 1; i++)
    {
        //一趟冒泡排序的过程
        int j = 0;
        for (j = 0; j < sz - 1 - i; j++)
        {
            if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
            {
                //交换
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);

            }
        }
    }
}

int cmp_int(const void* e1, const void* e2)
{
    //此处明确e1,e2类型,可强制转换类型
    return (*(int*)e1 - *(int*)e2);

}

void print(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
    

    //冒泡排序
    //对整型数据进行排序
void test4()
{
    int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
    int sz = sizeof(arr) / sizeof(arr[0]);

    bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    print(arr, sz);
}

int main()
{

    test4();

    return 0;
}

9.5.2 模拟结构体按name排序

** 图解:**

#include<stdio.h>


struct Stu
{
    char name[20];
    int age;
};


void Swap(char* buf1,char* buf2,int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}


void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2))
{
    int i = 0;
    //趟数
    for (i = 0; i < sz - 1; i++)
    {
        //一趟冒泡排序的过程
        int j = 0;
        for (j = 0; j < sz - 1 - i; j++)
        {
            if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
            {
                //交换
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);

            }
        }
    }
}

int cmp_int(const void* e1, const void* e2)
{
    //此处明确e1,e2类型,可强制转换类型
    return (*(int*)e1 - *(int*)e2);

}

int cmp_by_name(const void* e1, const void* e2)
{
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}


void print(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
    

void test5()
{
    struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };
    //按照名字比较
    int sz = sizeof(s) / sizeof(s[0]);
    bubble_sort(s, sz, sizeof(s[0]), cmp_by_name);
}

int main()
{

    test5();

    return 0;
}

9.5.3 模拟结构体按age排序

#include<stdio.h>


struct Stu
{
    char name[20];
    int age;
};


void Swap(char* buf1,char* buf2,int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}


void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2))
{
    int i = 0;
    //趟数
    for (i = 0; i < sz - 1; i++)
    {
        //一趟冒泡排序的过程
        int j = 0;
        for (j = 0; j < sz - 1 - i; j++)
        {
            if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
            {
                //交换
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);

            }
        }
    }
}

int cmp_int(const void* e1, const void* e2)
{
    //此处明确e1,e2类型,可强制转换类型
    return (*(int*)e1 - *(int*)e2);

}

int cmp_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

void print(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
    

void test5()
{
    struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };

    int sz = sizeof(s) / sizeof(s[0]);
    bubble_sort(s, sz, sizeof(s[0]), cmp_by_age);
}

int main()
{

    test5();

    return 0;
}

标签: c语言

本文转载自: https://blog.csdn.net/m0_64388758/article/details/126793576
版权归原作者 komorebi-filpped 所有, 如有侵权,请联系我们删除。

“指针的进阶”的评论:

还没有评论