• C语言-入门-语法-指针(十二)


    指针的概念

    指针是C语言的一个重要概念,也是C语言的一个重要特色,正确而灵活地运用它,可以使程序简洁、紧凑、高效。不掌握指针就是没有掌握C的精华。在介绍指针之前,有必要了解内存地址的相关知识。

    程序中,我们声明一个变量(int a = 1),将数据1存到变量a中,计算机内部会将这个数据存到内存中,那么数据存到某个地方就会涉及地址。就像你买的快递,快递到了就要存到某个驿站里面放着,你的快递就是一个数据,驿站就是一个变量,这个驿站就要有地址,不然全国这么多驿站你怎么知道你的快递在哪个驿站。

    现在想想地址(比如0x0000 0001)不也是一个数据吗,那么不也可以用一个变量存地址这个数据?是的,可以,这个变量就是指针,指针它就是存储另一个变量的内存地址的一种数据类型,即指针的内容就是另一个变量的内存地址。

    创建变量本质就是在内存中占用一块合适的空闲内存, 而这个内存的地址指向了这个变量,赋值的时候就是将值放入这个地址对应的内存中 , 而指针本身也是一个变量,所以指针变量也有自己的地址,只是它有点特殊,它只能存放另一个变量的地址,理解这句话就行。

    间接访问

    你的一个朋友搬家了,你来到她住的那栋大厦,你怎么样才能够找到她呢?当然是知道房号啦!那么你怎么知道房号呢? 她之前没和你说房号你就需要向楼道保安咨询她所在的房号了

    通过指针指向变量,先获得变量地址,然后根据地址去访问对应的值

    直接访问

    她搬家前和你说了她的房号,那么你就可以直接去找她的房间了

    通过地址直接访问对应的值 (变量名本身存储的就是地址)

    使用指针

    关键符号

    在这里插入图片描述
    &: 取地址符 作用:取变量占据的内存地址

    int a = 1;
    printf("%d\n", &a);//取出变量a的地址
    int *b=&a; //取a的地址给b指针
    
    
    • 1
    • 2
    • 3
    • 4

    *: 指针运算符 作用: 定义指针或者取出指针指向的地址存储的内容也被称为解引用

    int *p; //表示声明一个整形的指针变量
    
    • 1
    int a = 1;
    int *b=&a; //取a的地址给b指针
    int c = *b; //取b地址内部的实际数据给c
    
    • 1
    • 2
    • 3

    指针变量的初始化方法

    定义的同时进行初始化

    int a = 5; 
    int *p = &a;
    
    • 1
    • 2

    先定义后初始化

    int a = 5; 
    int *p; 
    p=&a;
    
    • 1
    • 2
    • 3

    把指针初始化为NULL

    int *p=NULL; 
    int *q=0;
    
    • 1
    • 2

    多级指针

    如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为“二级指针”
    在这里插入图片描述
    多级指针定义

        char c = 'a';
        char *cp;//定义一级指针使用一个*
        cp = &c;//取出c的地址给一级指针
        char **cp2;  //定义二级指针,使用2个*
        cp2 = &cp; //将一级指针的地址给二级指针
    
    • 1
    • 2
    • 3
    • 4
    • 5

    多级指针的取值规则
    简单来说就是定义的时候使用了几个*那么取值的时候就使用几个*

        char c = 'a';
        char *cp;//定义一级指针使用一个*
        cp = &c;//取出c的地址给一级指针
        char **cp2;  //定义二级指针,使用2个*
        cp2 = &cp; //将一级指针的地址给二级指针
        //取出二级指针内部地址对应的实际值 ,第一个*取出的值是一级指针的地址第二个*取出一级指针地址对应的实际的值
        printf("c = %c", **cp2); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    任意多层的指针的玩法都是如此, 而且多级指针可以用在数组,结构体,函数等多个地方,并且还可以实现无类型存储

    在这里插入图片描述

    NULL 指针

    在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。

       int  *ptr = NULL;
       printf("ptr 的地址是 %p\n", ptr  ); //0x0
    
    • 1
    • 2

    野指针

    “野”指针。因为“野”指针随机指向一块空间,该空间中存储的可能是其他程序的数据甚至是系统数据,故不能对“野”指针所指向的空间进行存取操作,否则轻者会引起程序崩溃,严重的可能导致整个系统崩溃。

    int *pi,a; //pi未初始化,无合法指向,为“野”指针
    *pi=3; //运行时错误!不能对”野”指针指向的空间做存入操作。该语句试图把 3 存入“野”指针pi所指的随机空间中,会产生运行时错误。
    a=*pi; //运行时错误!不能对”野”指针指向的空间取操作。该语句试图从“野”指针pi所指的空间中取出数据,然后赋给变量a同样会产生运行时错误。
    
    • 1
    • 2
    • 3

    正确用法

    pi=&a;//让pi有合法的指向,pi指向a变量对应的空间
    *pi=3;//把3间接存入pi所指向的变量a对应的空间
    
    • 1
    • 2

    万能的void指针

    void指针是一种不明确类型的指针,任何指针都可转换为void指针。

    void指针定义方式: void* p; 具体使用方式如下:

    int n = 500; //定义一个int变量
     
    int * p = &n; //定义int类型指针
     
    void * pv = p; //定义void指针,只保存了p的值(即n的内存首地址)
     
    //错误的写法
    printf("%d\n", *pv); //这里会报错,因pv指针没有明确数据类型,因此也不知道需要取多少字节的数据
     
    //正确写法
    printf("%d\n", *( (int*)pv ) ); //先把pv指针转为int类型指针,再对其解引
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    某些情况下,不知道指针的类型,先用void*来代替,根据需要再强制转换成需要的指针类型使用。 比如malloc函数。

    注意点:

    • void指针只保存了 指针的值 并没有记录指针指向对象的类型。因此在用到对void指针解引时,需要先把void指针转换成原本的数据类型。
    • ANSI C 标准规定,进行算法操作的指针必须确定知道其指向数据类型大小,也就是说必须知道内存目的地址的确切值。
    • void指针不能直接进行的加减法操作(因为不知道类型,无法确定偏移的大小)。

    const指针

    cosnt 表示常量,对const修饰的变量,我们是不能去修改他的值的 , int const 和 const int没有什么区别, 但是const和指针关联起来就有区别了

    第一种情况: const int* p1 = # const在 * 的左边 修饰的是 * (*p1 只读的,p1可读可写) 称为常量指针(常量的指针)

    第二种情况: int* const p2 = # cosnt 在 * 的右边,修饰的是p2,( *p2可读可写,p2只读) 称为指针常量 (指针类型的常量)

    第三种情况:const int* const p3 = # const 在 * 的左右两边,既修饰 * (指针类型, * 代表地址)也修饰指针变量(即 * p3只读,p3只读) 称为常量

    指针数组

    在C语言语言中,数组元素全为指针的数组称为指针数组 , 其本质为数组。,使用方式与普通数组类似。指针数组常适用于指向若干字符串,这样使字符串处理更加灵活方便。

    int main ()
    {
       const char *names[] = {
                       "Zara Ali",
                       "Hina Ali",
                       "Nuha Ali",
                       "Sara Ali",
       };
       int i = 0;
     
       for ( i = 0; i < MAX; i++)
       {
          printf("Value of names[%d] = %s\n", i, names[i] );
       }
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    地址算术运算

    C语言支持3种格式的指针算术运算,包括指针加上整数,指针减去整数和两个指针相减。同时还可以用关系运算符进行指针比较。

    • 指针p加上整数j表示指针向后移动j个单位(每个单位大小为指针p的类型所占的字节数),指向p原先指向的元素后的第j个元素。若p指向数组a[i],则p+j指向a[i+j]。
    • 指针p减去整数j表示指针向前移动j个单位,指向p原先指向的元素前的第j个元素。若p指向数组a[i],则p-j指向a[i-j]。
    • 两个指针相减,得到的是指针之间元素的个数。若指针p指向a[i],指针q指向a[j],则p-q等于i-j
    • 指针的比较依赖于指针所指向的两个元素的相对位置。若指针p指向a[i],指针q指向a[j],p和q的比较结果由i与j的大小决定

    因此适用于指针运算的运算符包括算术运算符+、-,赋值运算符和复合赋值运算符(=,+=,-=,++,–)和所有的关系运算符。

    C语言的间接寻址运算符*常和++或–运算符一起使用,具有以下四种不同的形式:
    在这里插入图片描述
    注意: 前缀递增递减和*计算右到左, 后缀递增递减和*计算从左到右。

    #define N 10
    
    int main()
    {
        int a[10] = {0,1,3,5,7,10,12,15,17,19};
        int temp = 0;
    
        int *p = a;
        printf("p的初始值为%p, *p值为%d\n", p, *p);
    
        temp = *p++;  //等同 temp=a[0]; p=&a[0+1];
        printf("*p++的值为%d, 运算后p的值为%p, *p的值为%d\n\n", temp, p, *p);
    
        printf("p的初始值为%p, *p值为%d\n", p, *p);
        temp = *(p++);//等同  p=&a[1+1]; temp=a[1];
        printf("*(p++)的值为%d, 运算后p的值为%p, *p的值为%d\n\n", temp, p, *p);
    
        printf("p的初始值为%p, *p值为%d\n", p, *p);
        temp = (*p)++;//等同 temp=a[2]; a[2]=a[2]+1;
        printf("(*p)++的值为%d, 运算后p的值为%p, *p的值为%d\n\n", temp, p, *p);
    
        printf("p的初始值为%p, *p值为%d\n", p, *p);
        temp = *++p;//等同 p=&a[2+1];  temp=a[3];
        printf("*++p的值为%d, 运算后p的值为%p, *p的值为%d\n\n", temp, p, *p);
    
        printf("p的初始值为%p, *p值为%d\n", p, *p);
        temp = *(++p);//等同 p=&a[3+1];  temp=a[4];
        printf("*(++p)的值为%d, 运算后p的值为%p, *p的值为%d\n\n", temp, p, *p);
    
        printf("p的初始值为%p, *p值为%d\n", p, *p);
        temp = ++*p; //等同 a[4]=a[4]+1; temp=a[4];
        printf("++*p的值为%d, 运算后p的值为%p, *p的值为%d\n\n", temp, p, *p);
    
        printf("p的初始值为%p, *p值为%d\n", p, *p);
        temp = ++(*p);//等同 a[4]=a[4]+1; temp=a[4];
        printf("++(*p)的值为%d, 运算后p的值为%p, *p的值为%d\n\n", temp, p, *p);
    
        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

    因为int是4字节那么每次 ++或者-- 地址都是以16进制加4或者减4进行运算的,而数组[i+4]就是下一个坐标的位置 , 建议打断点仔细看数组和指针的变化
    在这里插入图片描述
    总结: 其实用的最多的是 p++ 指针+1 和 *(++p) 指针+1后返回当前指针的值 以及 *(p++) 返回当前指针的值然后指针+1

    看个人爱好吧.物极必反,适合自己才重要,不要啥都使用,那么把自己搞得乱的一逼

    数组指针

    数组指针是指向数组首元素的地址的指针,其本质为指针,指针存放的是数组首地址的地址,相当于2级指针,这个指针不可移动。通过数组下标所能完成的任何操作通过指针都可以实现。用指针编写的程序要比用数组下标编写的程序执行速度快。

    int a[10];一个长度为10的数组a,即由10个对象组成的集合,这10个对象存储在相邻的内在区域中,名字分别为a[0]、a[1]…a[9]。a[i]可表示为该数组的第i个元素。

    int *pa;此处声明了一个指向整型类型的指针,pa = &a[0];则表示pa指向数组a的第0个元素的地址,也就是说pa的值为数组a[0]的地址。

    int x = *pa;将数组a[0]中的内容复制到变量x中。

    如果pa指向数组中某个特定元素pa = &a[i],那么,pa+1将指向下一个元素,pa+i将指向数组当前之后的第i个元素,而pa-i将指向当前数组元素之前的第i个元素。

    因此,如果pa指向a[0],那么*(pa+i)引用的是数组a[i]的内容,pa+i存储的是数组元素a[i]的地址。

    所以pa = &a[0] , pa = a 所以而对数组元素a[i]的引用也可以写成*(a+i)。

    实际上计算a[i]的值时,首先是将其转换成*(a+i)的形式,然后再求值。如果对这两种等价的表示形式分别加地址运算符&,可以知道:&a[i]和a+i的含义是相同的。

    int main() {
        int a[5] = {2, 4, 6, 8, 22};
        int *p;
        p = a;  // p = &(a[0]);
        printf("%d %d\n",a[0],*p); // 输出结果: 2, 2
        int len=sizeof(a)/sizeof(int);
        for (int i = 0; i < len ; ++i) {
            printf("a[i]=%d 等价 *(p+1)=%d\n",a[i],*(p+i));
        }
    
        return(0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    指针数组

    如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:
    dataType *arrayName[length]; 因为[ ]的优先级高于*,该定义形式应该理解为: dataType *(arrayName[length]); 括号里面说明arrayName是一个数组,包含了length个元素,括号外面说明每个元素的类型为dataType * 指针类型。 除了每个元素的数据类型不同,指针数组和普通数组在其他方面都是一样的,下面是一个简单的例子:

    int main() {
        int a = 16, b = 932, c = 100;
        //定义一个指针数组
        int *arr[3] = {&a, &b, &c};//也可以不指定长度,直接写作 int *arr[]
        //定义一个指向指针数组的指针
        int **parr = arr;
        printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]);
        printf("%d, %d, %d\n", **(parr+0), **(parr+1), **(parr+2));
    
        return(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    arr 是一个指针数组,它包含了 3 个元素,每个元素都是一个指针,在定义 arr 的同时,我们使用变量 a、b、c 的地址对它进行了初始化,这和普通数组是多么地类似。

    parr 是指向数组 arr 的指针,确切地说是指向 arr 第 0 个元素的指针,它的定义形式应该理解为int *(*parr),括号中的*表示 parr 是一个指针,括号外面的int *表示 parr 指向的数据的类型。arr 第 0 个元素的类型为 int *,所以在定义 parr 时要加两个 *。

    第一个 printf() 语句中,arr[i] 表示获取第 i 个元素的值,该元素是一个指针,还需要在前面增加一个 * 才能取得它指向的数据,也即 *arr[i] 的形式。

    第二个 printf() 语句中,parr+i 表示第 i 个元素的地址,*(parr+i) 表示获取第 i 个元素的值(该元素是一个指针),**(parr+i) 表示获取第 i 个元素指向的数据。

    指针和字符串

    注意:

    • 使用字符数组来保存的字符串是保存栈里的,保存栈里面东西是可读可写,所以可以修改字符串中的的字符
    • 使用字符指针来保存字符串,它保存的是字符串常量地址,常量区是只读的,所以我们不可以修改字符串中的字符
    • 不能够直接接收键盘输入,必须先给指针声明才行,不然他就是野指针

    字符串指针:

    int main() {
        //数组创建字符串
        char string[]="I love lnj!";
        printf("%s\n",string);
    
        //指针创建字符串
        char *str = "abc";
        printf("%s\n",str);
    
        return(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    指针数组还可以和字符串数组结合使用,请看下面的例子:

    int main() {
    
        //使用数组创建字符串数组
        char str[][100] = {
                "c.net",
                "C语言",
                "C Language"
        };
        printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
    
    
        //使用指针创建字符串数组
        char *str1[3] = {
                "c.net",
                "C语言",
                "C Language"
        };
        printf("%s\n%s\n%s\n", str1[0], str1[1], str1[2]);
    
        return(0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    也只有当指针数组中每个元素的类型都是char *时,才能给指针数组赋值,其他类型不行。为了便于理解,可以将上面的字符串数组改成下面的形式,它们都是等价的。

    int main(){
        char *str0 = "c.net";
        char *str1 = "C语言";
        char *str2 = "C Language";
        char *str[3] = {str0, str1, str2};
        printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    函数指针

    定义

    首先,函数名代表函数的起始地址,调用函数时,程序会从函数名获取到函数起始地址,并从该地址起执行函数中的代码,函数名就是函数的指针,所以我们可以定义一个指向函数的指针变量,用来存放函数的起始地址,这样一来,就可以通过该变量来调用其所指向的函数。

    定义指向函数的指针变量: 返回值类型 (*指针变量名)(形参1, 形参2, ...);

    例如: int(*p)(int, int);这行代码定义了一个可以指向返回值为整型且有两个整型形参函数的指针变量p,符合返回值为整型且有两个整型形参的函数都可以将其地址(即其函数名)赋给p。

    //定义一个函数
    int sum(int a,int b) { return a + b; }
    int main() {
        //定义一个指针函数
        int (*p)(int,int);
        p = sum;//将函数的地址给指针函数
        int result= p(1,2);
        printf("%d",result);//3
        return(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注意点:

    • 由于这类指针变量存储的是一个函数的入口地址,所以对它们作加减运算(比如p++)是无意义的
    • 定义指针函数(*指针变量名)括号是不可少的,我们不应该理解为求值运算,在此处它只是一种表示符号

    函数指针应用场景

    一个人C语言功底强不强,不是看他写的代码注释够不够全,代码逻辑够不够清晰,看下他对函数指针的理解和使用就可以了。函数指针的概念上很简单,无非也就是指针,专门指向函数而已。但是什么时候能用到它,用它可以带来什么好处,这就是考察个人的能力了,涉及到多方面。 在哪些应用场合可以用到函数指针呢?

    第一,软件分层设计:如果个人代码没有软件分层设计,而是一味堆砌,那么代码的扩展性、可读性、通用性必然比较差。我们上下层讲究的是"纵向依赖、横向独立",上层可以直接调用下层函数接口,而下层是不能直接调用上层接口的,如何解决下层实现对上层接口的代用呢,通过函数指针实现回调函数,实现下层对上层接口的调用,这样原则上也遵循上层实现对下层处于屏蔽。像各种嵌入式操作系统比如ucos、freertos钩子函数,均通过回调函数实现对未知用户的具体实现的调用。简而言之,便于软件分层设计,降低耦合度,使得接口与实现分开。

    第二,写库:自己写一些基于数据结构的库函数,设计好了一个函数框架,但是设计初期并不知道自己的函数会被如何使用,所以库作为管理员是不知道用户具体要执行什么操作的。比如我们写一个基于链表的库,要实现fetch功能实现用户查找信息,用户通过搜索关键字,但是管理员不知道用户是通过输入名字呢还是输入编号或者是身份证号来找用户的,那么这时用管理员只负责提供一个搜索关键字的函数指针,具体的关键字功能由用户提供,就达到了一个接口对多功能的支持。简而言之,就是利于系统抽象。

    第三,引用不在代码段中的函数
    此功能在嵌入式系统中经常使用。我们知道,我们写的用户程序的code是存放在代码段中的,在嵌入式系统中,一般情况下是存放在flash中的。什么叫不在代码段中的函数?很多微控制器在出厂前会将一些功能函数(系统函数)固化在rom中(类似于PC机中的BIOS),如Flash擦写功能,Flash Copy功能。而我们写的代码是不认识这些函数的,不能直接使用函数名调用。所以,当我们想在用户程序中调用这些系统函数时,就只能使用函数指针的方式,通过将系统函数的入口地址传给函数指针,来达到调用rom中程序的目的。这些系统函数一般都会在官方手册中给出功能,返回值类型和参数列表

    第四,函数指针与指针数据的结合可以实现典型的表驱动:
    通过将相同类型的函数以指针形式存储在数组中,我们在调用过程中就可以直接通过索引执行相应的函数了,这样的执行效率比起if else的嵌套实现会大大提高效率。

    回调函数

    函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。

    简单讲:回调函数是由别人的函数执行时调用你实现的函数。

    你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。

    实例

    #include 
    #include 
    #include 
    #include 
    
    // 回调函数
    // populate_array 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。
    void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
    {
        for (size_t i=0; i<arraySize; i++)
            array[i] = getNextValue();
    }
    
    // 获取随机值
    int getNextRandomValue(void)
    {
        return rand(); //获取随机数
    }
    
    int main()
    {
        int myarray[10];
        /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于调用这个函数了*/
        populate_array(myarray, 10, getNextRandomValue);
        for(int i = 0; i < 10; i++) {
            printf("%d ", myarray[i]);
        }
        printf("\n");
        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

    函数返回函数指针

    循序渐进地举几个相对复杂一些的例子
    给函数指针赋值的时候,对应的函数签名(返回值和参数列表)必须是和他的相匹配的。如果对应的函数原型比较复杂,相对应的函数指针的写法也会复杂一些。

    反人类的写法

    int *(*(*fp)(int(*)(int, int), int(*)(int)))(int, int, int(*)(int, double * (p1)(const double * , int m)));
    
    • 1

    在这里插入图片描述

    好在一般这种写法,只会经常出现在大学的期末试卷里生产实践中谁也不会把函数写成这个鬼样子。不过这也奠定了我们内心深处对于函数指针深深的抵触和恐惧。下面我们就慢慢循序渐进地将学习复杂的指针

    // 最简单的函数及其对应的函数指针:
    void f();
    void (*f_ptr)();
    
    • 1
    • 2
    • 3
    // 复杂点的,带返回值和参数列表,但都是基本类型
    int f(double b, int i);
    int (*f_ptr)(double b, int i);
    
    • 1
    • 2
    • 3
    // 返回值和参数带上指针,再加上几个const混淆一下
    const double * f(const double * b2, int m);
    const double * (*f_ptr)(const double * b2, int m);
    
    • 1
    • 2
    • 3
    // 再复杂一点点,参数里加个函数指针 也不是很复杂,基本只要把函数名换成(*函数名) 就可以了
    int f(int (*fp)(),int a );
    int (*f_ptr)(int (*fp)(),int a ); 
    
    • 1
    • 2
    • 3

    稍微再复杂一点点,函数返回值是一个函数指针 ,这就非常复杂了,而且各种嵌套要命了
    在这里插入图片描述

    返回函数指针的函数
    指针的最大用处个人认为就是加强的函数的返回能力,就像c语言的函数是不能返回数组的,但是如果返回的是一个指向数组的指针就是被允许的。 那么就可以考虑一个问题,返回一个函数指针是被允许的吗?巧了,这是完全可以的。

    int (*fun(int i))(int, int){
        ;
    }
    
    • 1
    • 2
    • 3

    这里的fun(int i)的优先级高于 * 所以fun首先是一个函数。返回的是一个int (*fun)(int, int)的指针。 但是这么复杂的一个函数定义,到底有什么用。 这里可以举例一个实例

    int sum(int a, int b) {
        return a + b;
    }
    
    int sub(int a, int b) {
        return a - b;
    }
    
    int (*cal(char c))(int, int) {
        if ('+' == c) {
            return sum;
        } else if ('-' == c) {
            return sub;
        }
    }
    
    int main() {
        int a, b, result;
        char c;
        int (*fp)(int, int);
        while (1) {
            scanf("%d%c%d", &a, &c, &b);
            fp = cal(c);
            result = fp(a, b);
            printf("%d%c%d=%d\n", a, c, b, result);
        }
        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

    在这里插入图片描述
    通过返回函数指针的函数提高的其他代码的复用性。但是这种定义十分复杂,可以通过typedef来简化定义。

    int main() {
        int a, b, result;
        char c;
        typedef int (*FP)(int,int);
        FP cal(char c);
        while (1) {
            scanf("%d%c%d", &a, &c, &b);
            result =cal(c)(1,2);
            printf("%d%c%d=%d\n", a, c, b, result);
        }
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    typedef 这个后续会详细学习的这里就先了解这一点就行了

    指针函数

    指针函数是指函数的返回值是指针类型的函数。一个函数的返回值可以是整数,实型和字符类型,也可以是指针类型。指针类型的定义形式举例如下:float* fun(int a,int b);

    其中,fun是函数名,前面的“*”说明返回值的类型是指针类型,因为前面的类型标识是float,所以返回的指针指向浮点型。该函数有两个参数,参数类型是整型。

    假设若干个学生的成绩在二维数组中存放,要求输入学生的编号,用指针函数实现其成绩的输出。 学生成绩放在二维数组中,一行存放一个学生的成绩,通过输入学生编号,返回该学生存放成绩的地址,然后利用指针访问学生的每一门课程成绩,并输出。

    
    #include
    int *FindAddress(int (*ptr)[4],int n);//声明查找成绩行地址函数
    void Display(int a[][4],int n,int *p);//声明输出成绩函数
    int main()
    {
    	int row,n=4;
    	int *p;
    	int score[3][4]={{76,87,85,81},{67,61,71,60},{81,89,82,78}};
    	printf("请输入学生的编号(1或2或3).输入0退出程序.\n");
    	scanf("%d",&row);//输入要输出学生成绩的编号
    	while(row)
    	{
    		if(row==1||row==2||row==3)
    		{
    			printf("第%d个学生的成绩4门课的成绩是:\n",row);
    			p=FindAddress(score,row-1);//调用指针函数
    			Display(score,n,p);//调用输出成绩函数
    			printf("请输入学生的编号(1或2或3).输入0退出程序");
    	        scanf("%d",&row);
    		}
    		else
    		{
                printf("输入不合法,重新输入(1或2或3).输入0退出程序");
    	        scanf("%d",&row);
    		}
    	}
    	return 0;
    }
    int* FindAddress(int (*ptrScore)[4],int n)//查找学生成绩行地址函数的实现
    //通过传递的行地址找到要查找学生成绩的地址,并返回行地址
    {
    	int *ptr;
    	ptr=*(ptrScore+n);//修改行地址,即找到学生的第一门课成绩的地址
    	return ptr;
    }
    void Display(int a[][4],int n,int *p)
    //输出学生成绩的实现函数。利用传递过来的指针输出每门课的成绩
    {
    	int col;
    	for(col=0;col<n;col++)
    	     printf("%5d",*(p+col));//输出查找学生的每门课成绩
    	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
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    注意事项

    1. 指针变量只能存储地址, 不能存储其它类型
    2. 给指针变量赋值时,指针变量前不能再加* *p=&a; //错误写法
    3. 多个指针变量可以指向同一个地址
    4. 指针的指向是可以改变的 ,重新给指针一个新地址就行
    5. 指针没有初始化里面是一个垃圾值,我们叫这个指针为一个野指针 而野指针可能会导致程序崩溃,野指针访问你不该访问数据,所以指针必须初始化才可以访问其所指向存储区域

    在这里插入图片描述

    点赞 -收藏-关注-便于以后复习和收到最新内容
    有其他问题在评论区讨论-或者私信我-收到会在第一时间回复
    在本博客学习的技术不得以任何方式直接或者间接的从事违反中华人民共和国法律,内容仅供学习、交流与参考
    免责声明:本文部分素材来源于网络,版权归原创者所有,如存在文章/图片/音视频等使用不当的情况,请随时私信联系我、以迅速采取适当措施,避免给双方造成不必要的经济损失。
    感谢,配合,希望我的努力对你有帮助^_^
  • 相关阅读:
    力扣hot100——第5天:22括号生成、23合并K个升序链表、31下一个排列
    MySQL理解-下载-安装
    操作系统-进程与线程(线程的状态与转化,线程的组织与控制,处理机调度的概念与层次,进程调度的过程与方式)
    【电工基础】电路的基本概念与基本定律
    IT网络监控系统的主要功能有哪些
    Linux线程:线程分离
    @Scope 注解失效了?咋回事
    linux单机部署kafka
    使用 snappyjs 压缩数据并解压
    酒类商城小程序怎么做
  • 原文地址:https://blog.csdn.net/weixin_45203607/article/details/125999330