• 【C语言】手把手带你拿指针疑难杂症


    1.指针的概念

    1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间

    2.指针变量的大小固定是4/8个字节(32/64位平台)

    3.指针是有类型的,指针的类型决定了指针±整数的步长,指针解引用的权限


    2.字符指针

    情形1:char a = ‘a’; char* p = &a; 情形2:char* p = “Mango”;


    int main()
    {
        char ch = 'w';
        char* pc = &ch;//pc指向的是一个字符常量
        char* p = "hello";	//hello是一个常量字符串, p保存的是常量字符串首字符的地址
       // *p = 'w';//err,常量字符串存放在常量区,内容不可以被修改
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    image-20220213233929431

    由于常量字符串不可以被修改,所以可以使用const修饰更好,这样编译的时候,如果有人修改了,就直接报错


    练习题
     int main()
     {
         char str1[] = "hello bit";
         char str2[] = "hello bit";
         const char* str3 = "hello bit";
         const char* str4 = "hello bit";
         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");
         }
         return 0;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    打印结果:str1 and str2 are not same str3 and str4 are same


    原因:

    1)str1和str2是不同的数组,在栈区开辟空间,str3和str4指向的是相同的常量字符串,二者保存的都是常量字符串的首字符地址, 这里str3和str4指向的是一个同一个常量字符串,

    2)C/C++会把常量字符串存储到单独的一个内存区域,当几个指针,指向同一个字符串时,他们实际会指向同一块内存,但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块,所以str1和str2不同,str3和str4不同


    3.指针数组-是数组

    int arr[10];//整型数组-存放整形的数组
    char arr[10];//字符数组-存放字符的数组
    char* arr[5]//arr是存放字符指针的数组
    int* arr[5];//arr是存放整形指针的数组
    
    • 1
    • 2
    • 3
    • 4

    例子:

    int main()
    {
        int a = 10;
        int b = 20;
        int c = 30;
        int d = 40;
        int* arr[4] = {&a,&b,&c,&d};	//arr2就是整形指针数组
        for(int i = 0;i<4;i++)
        {
            //printf("%d ",*(arr+i));//err
            //*(arr+i):数组i下标对于的内容,打印的是地址
            printf("%d ", *(*(arr + i)));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    指针数组存放整形数组数组名

    int main()
    {
        int arr1[]={1,2,3,4,5};
        int arr2[]={2,3,4,5,6};
        int arr3[]={3,4,5,6,7};
        int* parr[]={arr1,arr2,arr3};
        //数组名是首元素地址,是int*类型
        //所以用int* 的数组保存,相当于二维数组
        int i= 0;
        int j =0;
        for(i = 0;i<3;i++)
        {
            for(j = 0;j<5;j++)
            {
                printf("%d ",parr[i][j]);
            }
            printf("\n");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    相当于二维数组,parr[i] : 得到下标为i数组的起始地址,相当于拿到了数组名,parr[i][j]:得到下标为i的数组中下标为j的元素


    arr[i] == *(arr+i);
    arr[i][j] == *(*(arr+i)+j) == *(arr[i]+j)
    
    • 1
    • 2

    关于arr[i][j]的理解 *(*(arr+i)+j) == arr[i][j] (arr+i):是找到arr数组中的第i个位置的地址

    *(arr+i):找到指针数组中的第i个位置的元素,这个元素是一个数组名

    *(*(arr+i)+j):通过数组名(数组首元素地址)偏移j个长度,再解引用就能找到这个数组名起始位置向后j位置的数据


    指针数组存放字符数组数组名

    int main()
    {
        const char* arr[5] = {"abcdef","bcdefg","hehe","haha","zhangs"};
        //arr数组存放的都是常量字符串,所以用const修饰数组
        int i= 0;
        for(i = 0;i<5;i++)
        {
            printf("%s\n",arr[i]);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20220214205925089


    打印字符串只需要提供起始地址就可以了,从起始位置向后打印 打印整形则要解引用才能里面的值


    int* arr[5]	//指针数组
    //arr是数组名,数组名是首元素地址,int**类型
    
    • 1
    • 2

    //指针数组的数组名是用二级指针存放的
    int* arr[5];
    int** p = arr;
    //相当于int**p = &arr[0] 
    int** p2 = &arr[3];
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.数组指针-是指针


    int a = 10;
    int* pi = &a;	//整形的地址存放在整形指针中
    char ch = 'w';
    char* pc = ch;//字符的地址存放在字符指针中
    
    • 1
    • 2
    • 3
    • 4

    数组指针的写法

    //int *parr[10] :parr先和[]结合,是数组,数组元素类型为整形指针
    
     //若想parr是指针->用括号括起来
    int (*parr)[10] = &arr;	//取出的是数组的地址,应该存放到数组指针中
    
    //int(*parr)[10] :parr先和*结合,是指针,指向的是数组,数组有10个元素,每个元素是int类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    int* arr[10] = {0};
    int* (*p)[10] = &arr;	//取出数组的地址放到数组指针
    int** p = arr;	//数组元素为:int*类型,数组名是首元素地址,类型为:int**
    
    //arr的类型:int*
    //&arr[0]的类型:int*
    //&arr的类型:int(*)[10]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    指针的类型决定它加减整数跳过多少个字节

    数组和指针

    数组名是首元素地址 有两个例外
    1.数组名单独放在sizeof内部 :sizeof(数组名),这里的数组名表达式的是整个数组,计算的是整个数组的大小
    2.&数组名 此时的数组名标识的是整个数组,取出的是整个数组的地址,

    • &arr+1 :跳过一整个数组的内容

    数组名在传参时,会降级为首元素地址

    void Print1(int arr[],int sz);
    void Print2(int* arr,int sz);
    //上述两种写法等价
    
    • 1
    • 2
    • 3

    数组传地址时,用数组指针接收

    //err
    void Print1(int(*parr)[10],int sz)
    {
        int i = 0;
        for(i = 0;i<sz;i++)
        {
            printf("%d ",parr[i]);
        }
    }
    int main()
    {
        int arr[10] = {1,2,3,4,5,6,7,8,9,10};
        int sz = sizeof(arr)/sizeof(arr[0]);
        Print1(&arr,sz);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    打印结果:11599140 11599180 11599220 11599260 11599300 11599340 11599380 11599420 11599460 11599500 (都是随机值)

    错误原因:

    此时的parr是数组指针,类型为:int(*)parr,指向的数组有10个元素,每个元素是int类型, *parr+1 :跳过40个字节!!! 指针+1的步长取决于指针指向的类型image-20220214205949725


    正确写法:

    把一维数组当成是一个二维数组!!!!

    parr[0] == *(parr+0)->二维数组的第一行的数组名(该一维数组的数组名)
    parr[0][j] == *(*(parr+0)+j) ->二维数组第一行下标为j的元素 
    
    • 1
    • 2

    image-20220214210000867


    void Print1(int(*parr)[10],int sz)
    {
        int i = 0;
        for(i = 0;i<sz;i++)
        {
            printf("%d ",parr[0][i]);
            //也可以写成
            //printf("%d ", *(*(parr+0)+j);
            //parr[0] == *(parr+0) 
            //parr[0][j] == *(*(parr+0)+j) == (*parr)[j]
        }
    }
    int main()
    {
        int arr[10] = {1,2,3,4,5,6,7,8,9,10};
        int sz = sizeof(arr)/sizeof(arr[0]);
        Print1(&arr,sz);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    parr为指向数组的指针,解引用就相当于拿到了数组名 -> *parr == arr ->所以也可以写成:

    写法1printf("%d ",(*parr)[i]);	//相当于arr[i]
    写法2printf("%d ",parr[0][i]);
    //错误写法:*parr[i] ->parr先和[]结合
    
    • 1
    • 2
    • 3
     Print1(&arr,sz);
    void Print1(int(*parr)[10],int sz)
    
    • 1
    • 2

    int(*p1)[5]int(*p2)[6],
    p1和p2不一样!!!
    p1:数组指针,类型为int(*)[5],指向的数组有5个元素,每个元素类型为int
    p2:数组指针,类型为int(*)[6],指向的数组有6个元素,每个元素类型为int
    数组指针指向的元素个数不可以省略!!!
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.打印二维数组两种方法

    方式1:参数部分也是二维数组, 传参时:二维数组的行可以省略,列不可以省略

    void Print1(int arr[][5], int r, int c)
    //void Print1(int arr[3][5],int r ,int c)
    {
        int i= 0;
        int j = 0;
        for(i = 0;i<r;i++)
        {
            for(j = 0;j<c;j++)
            {
                printf("%d ",arr[i][j]);
            }
            printf("\n");
        }
    }
    int main()
    {
        int arr[3][5]  = {1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
        Print1(arr,3,5);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    方式2:使用数组指针

    void Print1(int(*p)[5],int r,int c)
    {
         int i= 0;
        int j = 0;
        for(i = 0;i<r;i++)
        {
            for(j = 0;j<c;j++)
            {
                printf("%d ",p[i][j]);
                //printf("%d ",*(*(p+i)+j);
                //p[i][j] == *(*(p+i)+j)
            }
            printf("\n");
        }
    }
    int main()
    {
        int arr[3][5]  = {1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
        Print1(arr,3,5);	//arr是数组名,首元素地址,二维数组的数组名是第一行的地址
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20220214210010610


    把二维数组的每一行看成是一维数组,所以相当于传的是一维数组的地址,->int(*p)[5] !!!指向的是一维数组的数组指针

    为什么方括号内元素个数是5 不是 3 :

    因为此时二维数组每一行的元素个数是5


    二维数组传参写成数组形式时,可以省略行,不可以省略列!

    • 因为对一个二维数组,可以不知道有多少行,但必须知道一行有多少元素

    二维数组的深层次理解

    image-20220214210020314


    image-20220214210033237

    arr:二维数组数组名,首元素地址:第一行的地址,指向第一行
    arr+1:第2行的地址 ,指向第二行
    *(arr+1) : 得到第二行的数组名
    *(arr+1)+j : 得到第二行下标为j的元素的地址
    *(*(arr+1)+j):得到第二行下标为j的元素
    
    • 1
    • 2
    • 3
    • 4
    • 5

    void Print(int(*p)[5],int r,int c)
        Print1(arr,3,5);
    //把arr传给p  ,p有能力接收arr,说明p == arr
    // p[i][j] == arr[i][j]
    
    • 1
    • 2
    • 3
    • 4

    6.解释代码含义

    去掉数组名->就是数组的类型 去掉数组名+元素个数->就是数组中的元素类型

    int arr[5];	//整形数组,元素类型为int,元素个数为5
    int *parr1[10];//parr1先和[]结合,parr1是一个数组,parr1为数组名,去掉数组名和元素个数->元素类型为:int* ,所以数组中的元素类型为int*,所以parr1是一个存放整形指针的数组,元素个数为10个
    int (*parr2[10])[5];//parr2先和*结合,是指针,指向的是数组,数组有10个元素,每个元素是int类型
    int(*parr3[10])[5];//parr3先和[]结合,parr3是一个数组,数组有10个元素,去掉数组名和元素个数->元素类型为:int(*)[5],每个元素是一个数组指针,指向的数组有5个元素,每个元素是int类型
    
    • 1
    • 2
    • 3
    • 4
    image-20220214210102737

    7.一维数组传参

    //写法1:
    void test(int arr[]);
    //写法2
    void test(int arr[10]);
    //写法3
    void test(int arr[100]);//虽然语法正确,但是不建议,
    //一维数组名传参会降级为指针,所以括号内[]写不写元素个数都无所谓,且元素个数可以与实际元素个数不同
    //写法4
    void test(int* p);
    int main()
    {
        int arr[10] = {0};
        test(arr);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    //写法1
    void test(int* arr[]);
    //写法2
    void test(int* arr[10]);
    //写法3
    void test(int* arr[100]);
    //同上,括号内写的元素个数不影响
    //写法4
    void test(int** p);
    int main()
    {
        int* arr[10] = {0};//数组元素类型为int*,所以arr是首元素地址,为int**类型
        test(arr);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    8.二维数组传参

    二维数组传参写成数组形式时,可以省略行,不可以省略列,

    原因:对一个二维数组来说,可以不知道有多少行,但是必须知道一列有多少个元素

    //写法1
    void test(int arr[3][5]);	//ok
    //写法2
    void test(int arr[][]);	//err,不可以行和列同时省略
    //写法3
    void test(int arr[][5]);//ok,可以省略行,不可以省略列
    //写法4
    void test(int** p);//err
    //写法5
    void test(int(*p)[5]);//ok
    //对于写法4和写法5:二维数组的数组名->首元素地址(第一行的地址),第一行是一个一维数组,相当于把一维数组的地址传过去,要使用数组指针接收
    //所以参数写成int(*p)[5]-数组指针而不是int** p-二级指针
    main()
    {
        int arr[3][5] = {0};
        test(arr);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    二级指针和二维数组没有必然关系


    9.当函数参数是一级指针可以传递的变量

    void test(int* p)
    {}
    int main()
    {
        int a = 10;
        int* p1 = &a;
        int arr[10] = {0};
        test(&a);
        test(p1);
        test(arr);
        test(NULL);	//慎重考虑
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以传递的内容

    • 1.传递一级指针变量
    • 2.传指针指向的类型变量的地址
    • 3.传一维数组的数组名
    • 4.传递空指针,但是要考虑清楚要干什么

    10.当函数参数是二级指针可以传递的变量

    void test(int** pp)
    {}
    int main()
    {
        int a = 10;
        int* pa = &a;
        int** ppa = &pa;
        
        int*arr[5];//arr是指针数组,数组中的每个元素是int*类型,数组名是首元素地址->地址类型为int
        test(ppa);
        test(&pa);
        test(arr);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以传递的内容

    • 1.一级指针的地址
    • 2.二级指针变量
    • 3.传指针数组的数组名

    11.关于time函数

    可以通过函数返回的方式带回一个时间戳 也可以通过传址的方式带回一个时间戳

    time_t time(time_t *timer)
    
    • 1

    参数说明:

    1)timer=NULL时得到当前日历时间(从1970-01-01 00:00:00到现在的秒数),

    2)timer=时间数值时,用于设置日历时间,time_t是一个long long类型,如果 timer不为空,则返回值也存储在变量 timer中,

    函数功能: 得到当前日历时间或者设置日历时间

    函数返回: 当前日历时间,

    作用:获取当前系统的时间,会返回一个时间戳


    关于time_t

    time_t是一种类型:image-20220214210128187

    time_t == long long ->打印:%lld size_t == unsigned int->打印:%u


    传参时:

    //方法1:通过函数返回的方式带回去一个时间戳
    time_ tt //存放时间戳的变量tt,tt的类型为time_t
    tt = time(NULL);
    
    //方法2:通过传址方式带回一个时间戳
    time_t tt,p;
    p = time(&tt);
    //time(&tt) ->等价于  p = time(NULL);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    #include 
    #include 
    int main()
    {
    	time_t seconds;
    	time_t times,p;
    	seconds = time(&times);
    	printf("自 1970-01-01 起的秒数 = %lld\n", seconds );
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    12.函数指针

    函数指针变量->存放函数的地址(指向函数)

    int Add(int x ,int y)
    {
        return x+y;
    }
    int main()
    {
        printf("%p\n",&Add);
        printf("%p\n",Add);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    结果发现:二者打印的结果相同

    image-20220214210135849

    和数组对比:&数组名 -数组的地址 数组名 - 数组首元素地址 二者打印出来的地址值相同,但是含义不相同

    但是函数名和&函数名一样!!


    int Add(int x, int y)
    {
        return x + y;
    }
    int main()
    {
        int arr[10] = { 0 };
        int(*parr)[10] = &arr;//parr就是数组指针变量,指向一个数组,数组有10个元素,每个元素是int类型
        int(*pf)(int, int) = Add;//此时of是用来存放函数的地址-pf就是函数指针变量
        //这样写也可以:int(*pf)(int, int) = &Add; //Add和&Add含义一样
        //这样也可以int(*pf)(int x,int y) = &Add;
        printf("%d\n", (*pf)(2, 3));	//5
        printf("%d\n", pf(2, 3));	//5
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    去掉名字就是类型

    int a = 10;// a的类型为:int
    int arr[10] = {0};// arr的类型为:int [10]
    int(*p)[10] =&arr;//p的类型->int(*)[10]
    int(*pf)(int,int) = Add;//pf为函数指针,函数指针类型为:int(*)(int,int)
    
    • 1
    • 2
    • 3
    • 4

    注意:()优先级比*高

    若写成:int*pf(int,int);	//此时pf先和()结合,是函数,参数为int int  返回类型为int*
    正确写法:int(*pf)(int,int);//此时pf才是指针,函数指针,指向的函数的参数是int,int,返回类型是int
    
    • 1
    • 2

    int(*pf)(int,int) = Add

    写法1:pf(2,3); 写法2:(*pf)(2,3) 两种写法都可以,

    对于写法1:pf能接收函数Add,说明pf == Add**

    *对于写法2pf指向函数(存放函数的入口地址),pf就可以调用函数, 当然了,前面写多个*也不会影响 *(***pf)(2,3) == pf(2,3) == (*pf)(2,3)


    13.两个奇葩代码

    代码1: (*(void(*)())0)()

    *void(ptr)() ptr是函数指针,指向的函数返回类型为void,无参数 ptr的类型为:void(*)()


    double a = 10.0; int b = 0; b = (int)a; //把a强转为int类型赋给b

    在变量的前面加一个类型->强制类型转换

    注意:强制类型转换并不会改变变量存放在内存中的内容,只是改变了如何读取变量的内存中的内容的方式


     (*(void(*)())0)()
    //void(*)() -函数指针
    解析:这是一次函数调用
     1.代码中把0强制类型转换为类型为void(*)()的一个函数的地址
     2.解引用0地址的内容,就是取0地址处的这个函数,被调用的这个函数是无参的,返回类型是void
    
    • 1
    • 2
    • 3
    • 4
    • 5

    例如:
        printf("%p\n",Add);//假设Add函数的地址为:0x0012ff40, 该地址处放着一个参数为int,int,返回类型为int的函数,
    
    • 1
    • 2

    代码2:void (*signal(int,void(*)(int)))(int)
    分析1:
      signal先和()结合->signal是函数,signal函数有两个参数,第一个是int类型,第二个是void(*)(int)类型的函数指针     
    
    • 1
    • 2

    去掉函数名和函数的参数->就是函数的返回类型

    例如: int Add(intx,int y) ->去掉函数名和函数的参数->int

    所以把signal函数的函数名和参数去掉-> void(*)(int) 所以signal函数的返回类型是函数指针类型


    解析:这是一次函数声明,声明的函数名是signal
    signal函数有两个参数,第一个是int类型,第二个是void(*)(int)的函数指针类型,
    signal函数的返回类型是一个 void(*)(int)的函数指针类型
    
    • 1
    • 2
    • 3

    将代码2改造简洁-typedef

    image-20220214210153025

    typedef void(*pfun_t)(int);	//这样写才是正确的,pfun_t == void(*)(int)  函数种子很类型
    typedef void(*)(int) pfun_t; // err 语法不支持
    
    //语法规定:要把重命名的名字放在*里
    
    所以void (*signal(int,void(\*)(int)))(int)可以简化成: pfun_t signal (int,pfun_t);
       一个参数为函数指针类型,一个参数为int类型,返回类型为函数指针类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    14.指向函数指针的数组

    int* arr[10];	//存放整形指针的数组
    //函数指针数组->存放函数指针的数组
    
    • 1
    • 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(*pf1)(int,int) = Add;
        int(*pf2)(int,int) = Sub;
        int(*pf3)(int,int) = Mul;
        int(*pf4)(int,int) = Div;
        //pfArr就是一个函数指针数组
        int(*pfArr[4])(int,int) = {Add,Sub,Mul,Div};
        //pfArr先和[]结合->是数组
        //去掉数组名和元素个数->元素类型为:int(*)(int,int) 是一个函数指针,指向的函数的参数为int,int,返回类型为int
        
        //使用:
        int ret = pfArr[0](2,3); 
        //也可以写成: int ret = (*pfArr[0])(2,3); 
        // pfArr[0] == pf1  ==Add == *(pfArr[0]) ==(*pf1)
        printf("%d\n",ret);	//5
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

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

    int arr[10];
    int(*p)[10] = &arr;	//p是一个指向整形数组的指针
    
    int* arr[10];
    int* (*p)[10] = &arr;	//p是一个指向整形指针数组的指针
    
    • 1
    • 2
    • 3
    • 4
    • 5

    int Add(int x,int y)
    {
        return x+y;
    }
    int(*pf)(int,int) = Add;	//pf是函数指针,指向的函数的返回类型为int,有两个参数,一个为int,一个为int
    int(*pfArr[5])(int,int);//pfArr是一个函数指针的数组,(pfArr先和[]结合,是数组),去掉数组名和元素个数->int(*)(int,int) 所以元素是函数指针
    int(*(*ppfArr)[5])(int,int) = &pfArr;	//ppfArr先和*结合->指针  此时ppfArr就是一个指向函数指针数组的指针, *ppfArr == pfArr,,*ppfArr相当于拿到了pfArr数组的数组名,去掉数组名和元素个数->元素类型,-> int(*)(int,int) 所以ppfArr指向的数组的元素类型为函数指针类型
    
     函数指针->函数指针数组->指向函数指针数组的指针
    //技巧:在函数指针数组的数组名前加*号,然后用括号括起来,这样就是指针了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    16.回调函数的概念

    1.回调函数是什么

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


    • 回调函数就是一个通过函数指针调用的函数
    • 回调函数一般通过函数指针实现
    • 回调函数一般不是函数的实现方调用,而是在特定的场景下,由另外一方调用
    • 回调函数是调用函数指针指向的函数

    17.使用回调函数设计简易计算器


    基本框架

    void menu()
    {
    	printf("*********************\n");
    	printf("**1.add   2.sub******\n");
    	printf("**3.mul   4.div******\n");
    	printf("**0.exit       ******\n");
    	printf("*********************\n");
    }
    enum Option
    {
    	EXIT,	//0
    	ADD,	//1
    	SUB,	//2
    	MUL,	//3
    	DIV,	//4
    };
    int main()
    {
    	int input = 0;
    	do
    	{
    		menu();
    		printf("请输入你的选择->:");
    		scanf("%d", &input);
    		switch(input)
    		{
    		case ADD:
    			Calc(Add);
    			break;
    		case MUL:
    			Calc(Mul);
    			break;
    		case DIV:
    			Calc(Div);
    			break;
    		case SUB:
    			Calc(Sub);
    			break;
    		case EXIT:
    			printf("退出成功\n");
    			break;
    		default:
    			printf("选择错误,请重新选择\n");
    			break;
    		}
    		
    	} while (input);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    Calc函数的参数是加减乘除函数的地址,Calc函数参数使用函数指针接收,枚举的成员值从0开始,所以可以使用枚举对应用户的选项,这样编写程序的时候更直观


    加减乘除函数

    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)
    {
    	if (y == 0)
    	{
    		printf("被除数不能为0,运算出错,自动返回-1\n");
    		return -1;
    	}
    	return x / y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    回调函数

    //Calc函数的参数是一个函数指针
    void Calc(int(*pf)(int, int))
    {
    	int x = 0;
    	int y = 0;
    	int ret = 0;
    	printf("请输入两个操作数:>");
    	scanf("%d %d", &x, &y);
    	ret = pf(x, y);
    	//也可以写成: ret = (*pf)(x,y);
    	printf("运算结果为:%d\n", ret);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    通过Calc函数调用函数指针指向的函数 pf是函数指针 *pf == 函数 == pf 地址值: 函数名 == &函数名

    (pf有能力接收函数的地址-> 说明pf == 函数) ,(pf存放函数的地址-》*pf即为调用该函数)

    调用的时候,既可以直接使用函数指针调用,也可以通过函数指针所指向的值去调用, (*p)所代表的就是函数指针所指向的值,也就是函数本身,这样调用自然不会有问题,有兴趣的同学可以去试一试,


    18.使用指向函数指针的数组实现简易计算器

    加减乘除函数+菜单函数

    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)
    {
    	if (y == 0)
    	{
    		printf("被除数不能为0,运算出错,自动返回-1\n");
    		return -1;
    	}
    	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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    主函数

    int main()
    {
        int input = 0;
        int x = 0;
        int y = 0;
        int ret = 0;
        do
        {
            menu();
            printf("请选择:>");
            scanf("%d",&input);
            //转移表 - C和指针
            int(*pfArr[5])(int,int) = {0,Add,Sub,Mul,Div};//函数名和&函数名意义一样,所以可以直接写成函数名  为了和选项对应上,下标为0的位置放0
          if(input == 0)
          {
              printf("退出成功\n");
          }
          else if(input >= 1&& input <= 4)
          {
              printf("请输入两个操作数:>");
              scanf("%d %d",&x,&y);
              ret = pfArr[input](x,y);	//调用下标为input的函数,并传参
              printf("%d\n",ret);
          }
           else
           {
               //相当于default
               printf("选择错误\n");
           }
        }while(input);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

  • 相关阅读:
    数据库练习题
    BFC讲解
    『德不孤』Pytest框架 — 7、Pytest预期失败
    Python 批量提取pdf/word中的图片,并生成markdown文档
    数据挖掘之贝叶斯优化——前反馈特征的参数,估计特征的最佳数值
    Java项目:学生课堂考勤管理系统(java+SSM+JSP+layui+Mysql)
    代码源每日一题div1 平方计数
    水力和水文软件介绍
    go通过pprof定位groutine泄漏
    人工智能数学课高等数学线性微积分数学教程笔记(5. 线性代数高级)
  • 原文地址:https://blog.csdn.net/chuxinchangcun/article/details/127373653