各类指针的详细介绍

news2025/1/25 9:18:08

🏖️作者:@malloc不出对象
⛺专栏:《初识C语言》
👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈
在这里插入图片描述

目录

    • 前言
    • 一、基本数据类型指针
      • 1.1 指针的解引用
      • 1.2 指针的基本运算
        • 1.2.1 指针+-整数
        • 1.2.2 指针 - 指针
      • 1.3 字符指针
      • 1.4 字符串与字符数组的联系
    • 二、指针数组
    • 三、数组指针
    • 四、函数指针
      • 4.1 函数名就是地址吗
    • 五、函数指针数组
    • 六、指向函数指针的数组


前言

之前的一篇文章也是带大家初步的认识了一下指针,那么这篇文章呢主要就是介绍各种指针类型的基本用法,让你一次性搞清各种指针类型的用法。

一、基本数据类型指针

我们知道,变量有不同的类型,整型、浮点型等,那指针有没有类型呢?准确的说:有的。那大家还记得我们上篇讲的指针变量与指针的区别嘛?这里的"指针"到底是什么呢?好了,相信只要看过上篇文章就一定知道此时的指针其实是为指针变量,变量才有类型,地址就是一个数值你能看的出它是代表什么类型吗?这里就稍微的带大家回顾一下上篇的知识点,我们平常还是口头上说指针比较顺口吧。

之前我们提到过指针的大小在32位平台下占4个字节,64位平台下占8个字节,那么指针类型的意义到底在哪呢?它们的大小不都是一样大的嘛?
我们来看下图:

在这里插入图片描述

从这幅图我们看到也看到不同类型的指针类型它们所指向的地址也是一样的,这里其实是会有一个警告的,int*和char*的类型不兼容,但这并不影响他们的地址是一样的,pa和pc两者都是指针变量,能够存放的字节大小都为4or8.
既然不同指针类型的大小和所指地址都一样那我们要分成这么多指针类型呢?用一个不就好了吗?下面我们继续进行分析.

1.1 指针的解引用

在上一篇文章指针的初步认识部分我只是简单的讲了一下解引用,下面我将对这部分进行着重讲解。

下面我们来看一个例子,对指针进行解引用会得到什么呢?

在这里插入图片描述

通过调试我们来观察一下整型a在下内存的变化,a的内容在内存中是倒着存的,VS是小端模式,低地址在低位,高地址在高位:

在这里插入图片描述

下面我们进行下一步:

在这里插入图片描述

这时我们发现通过整型指针pa的解引用,a的四个字节全都置为0了,大家还记得我在上一篇文章解引用那块说了一个什么结论吗?
在同类型情况下对指针解引用,代表指针所指向的目标。 那么对于整型指针pa来说就非常好理解了,*pa = 0 ==> a = 0。

下面我们一起来看看字符指针pc进行解引用会对a产生什么效果:

在这里插入图片描述

这里我们发现整型a只有一个字节(首字节)变为0,其他三个字节不变,那么这说明了什么问题呢?

结论:指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)。

1.2 指针的基本运算

1.2.1 指针±整数

我们来看一个例子,大家想想会得到什么结果:

#include<stdio.h>

int main()
{
    char* c = NULL;
    short* s = NULL;
    int* i = NULL;
    double* d = NULL;

    printf("%d\n", c);
    printf("%d\n\n", c + 1);

    printf("%d\n", s);
    printf("%d\n\n", s + 1);

    printf("%d\n", i);
    printf("%d\n\n", i + 1);

    printf("%d\n", d);
    printf("%d\n\n", d + 1);
    
    return 0;
}

下面给出结果:

在这里插入图片描述

由此我们得出结论:指针的类型决定了指针 ± 向前或者向后走一步有多大(距离)。

下面接着我们来看一个例子,这段代码得到的又是什么呢(x86环境下)?

#include<stdio.h>

int main()
{
    char* c = NULL;
    char** q = NULL;
    char*** d = NULL;
    
    printf("%d\n", c);
    printf("%d\n\n", c + 1);

    printf("%d\n", q);
    printf("%d\n\n", q + 1);

    printf("%d\n", d);
    printf("%d\n\n", d + 1);
    
    return 0;
}

下面给出答案:

在这里插入图片描述

根据上面得出来的结论:我们知道指针+1本质上是加上指针所指向的对象类型的大小,第一个我就不多说了,第二个指针所指向的类型为char*,那么它占多大呢?它所指向的对象为指针变量,在x86环境下是4个字节大小,x64下是8个字节大小;第三个指针所指向的类型为char**,它所指的对象是不是也为一个指针变量呢?所以它也是占4字节。

由此我们又可以得到一个小结论:一级指针+1是会随着所指类型改变而改变的,而二级及以上的指针+1是不会发生变化的,在x86环境下+1移动4个字节的单位,在x64环境下+1移动8个字节的单位。

1.2.2 指针 - 指针

看着我的小标题大家也许会想为什么不会出现指针+指针呢?下面我给大家解释一下:

C/C++中禁止两个指针相减,只能两个指针相加。这样做的原因是两个指针相减给出了一个逻辑上可以解释的结果,两个指针相减得到的是两者之间的内存偏移量。同样,你也可以在指针中±一个整数,这意味着“向上或向下移动指针”。而指针加指针是很难解释的,得到的新指针表示什么?如果你明确需要一个指向内存中某个位置的指针,该位置的地址是其他两个地址的总和,则可以将这两个指针强制转换为某种数据类型,将其相加后的结果强制转换为指针。但请记住此解决方案需要非常注意指针算法,并且真的不应该这样做。

更通俗的其实可以这样来理解:指针是用来代表内存地址的,指针的数值是该地址相对于最低位地址也就是0位地址的偏移量,也可称之为坐标。坐标相加得到的新值是没什么意义的,坐标相减则是两者距离,这是有意义的。

接下来我们来看一个实例,指针减指针得到的是什么呢?

#include<stdio.h>
int main()
{
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    printf("%d\n",&arr[9] - &arr[0]);
    return 0;
}

让我们看向结果:

在这里插入图片描述

结果为9说明什么,得到的是元素的个数,我们通过上面的结论也可以理解此时&arr[9]和&arr[0]相当于两个坐标,坐标减坐标自然得到的是它们两者相差的距离,也就是本题当中的元素个数。最简单的理解方式也可以是这样,还记得我们上篇文章提到过要想访问一个数组元素是怎样来进行访问的吗?我们说过要找到某一个目标必须得根据它的起始地址来进行偏移寻找,&arr[9] == arr + 9,&arr[0] == arr,俩者相减是不是就为它们之间的元素个数了呢🙈🙈

当然一定会有人这样来想,&arr[9] - &arr[0]为什么得到的是元素个数啊?它们得到不是一个地址差值嘛,也就是36呢? 首先说明一下这是C语言标准规定的:在同一块空间内同等类型的指针相减得到的就是之间的元素个数。
至于为什么不是36而是9?我们可以这样来想,36得到的是字节之差,此时单位为字节,,而此时得到的是9,也就是36 / 4 = 9,此时的单位是数组长度。这里你可以理解为C语言对它进行了细节处理,此时指针-指针的单位是数组长度而不是字节,所以最后我们看到的答案就是元素个数了。

下面是一幅简单的图来表示关系:

在这里插入图片描述

1.3 字符指针

关于基本数据类型指针我最想讲的是字符指针,这其中牵扯到字符串与字符数组之间的联系,我想好好给大家讲一讲这其中的一些重要知识点。其他的基本数据类型指针就不做过多的阐述了…

首先我们先来看看最简单的用法:

#include<stdio.h>

int main()
{
	char ch = 'a';
	char* pch = &ch;		//字符指针 -->  是一个指针,保存的是字符ch的地址,它所指向的对象的类型为char型
	printf("%d\n", *pch);
	
	return 0;
}

下面我们再来看一段代码,当字符指针变量接收字符串时:

#include<stdio.h>
int main()
{
    char* pch = "abcdefg";
    printf("%s\n",pch);
    return 0;
}

此时字符指针变量pch接收到的是整个字符串的地址还是字符串的首字符的地址?我们不妨来检测一下:

在这里插入图片描述

从图中我们可以看出以当对pch进行解引用时,我们用%c打印出来的是首字符,所以此时证明字符指针变量pch接收到的是首字符的地址,本质上字符串其实就是首字符的地址。

1.4 字符串与字符数组的联系

字符串是什么?

用" "双引号括起来的就是一个字符串,字符串是以’\0’结尾的,它的末尾会自动加上一个’\0’,‘\0’(转义字符)为字符串结束的标志。

C语言中是没有字符串这种数据类型,但可以通过字符数组或字符指针来表示,那么字符数组就一定是字符串吗?

不是的,其实字符数组和字符串是两个完全不一样的概念,从存储位置来看字符串在静态存储区中,所以它是不能被修改的,而字符数组是在堆/栈区上开辟的一块连续的空间它的元素是一个个的字符元素,它是内容是能够被修改的,并且字符数组不一定表示的就是字符串。

关于这些性质,下面我们下面就来一一证实一下。
我们来看看下面一段代码,大家觉得能修改成功吗?

#include<stdio.h>
int main()
{
    char* pch = "abcdefg";
    *pch = 'W';
    printf("%c\n",*pch);
    
    return 0;
}

我们一起来看看结果:

在这里插入图片描述

从结果上来看程序已经崩溃了,因为字符串常量它是不可被修改的,它是放在静态区的。
下面我们来看看用字符数组来表示字符串是否能够修改字符数组中的内容:

在这里插入图片描述

从图中我们可以看到字符数组中的内容它是能被修改的,因为此时字符数组ch是在栈上开辟的一段空间,保存的是一个个字符元素,并且由于右值是一个字符串它的末尾也会自动添加一个\0,此时数组ch的元素个数其实是为8个,我们通过监视窗口一起来看看:

在这里插入图片描述

下面我来通过一张图让大家彻底明白为什么用字符指针变量表示的字符串它的内容是不可修改的,而用字符数组来表示它却能修改其中的内容。

在这里插入图片描述

*pch代表的其实就是字符串的首字符a,它位于字符串常量区只能作为右值只提供内容,而不能提供空间,因此它是不能被修改的,,而字符数组是在栈上开辟的一块连续的空间,它的内容是能被修改的。

那么如果我就是想改变指针变量所保存的内容呢?我们可以改变指针的指向,这样得到的字符串内容就不一样了:

在这里插入图片描述

我们继续看下一个问题,为什么我前面说过字符数组不一定表示的就是一个字符串呢?下面我们一起来看这个例子,它会打印出什么东西呢?

#include<stdio.h>
int main()
{
    char ch[] = { 'a', 'b', 'c', 'd' };
    printf("%s\n", ch);

    return 0;
}

我们来看一下结果:

在这里插入图片描述

结果打印出乱码来了这是为什么呢?

%s是将字符串格式化输出的,那么此时你看这个字符数组能表示成一个字符串吗?答案是不能的,因为此时它的末尾没有\0表示字符串结束的标志,而%s是根据地址来进行一个个寻找的,直到遇到\0就停止寻找输出结果。要想表示成一个字符串我们需要在最后一个位置放上一个\0(0)表示字符串结束的标志,这样就没有任何问题了。

在这里插入图片描述

此时我们也可以得出一个小结论:以\0(0)结尾的字符数组可以看做一个字符串,如果没有以数字0结尾那就不是一个字符串。还需要注意此"字符串"非真正意义上的字符串。

接下来我们来做一道题,如果你听懂了上述我的一系列讲解那么这道题一定没问题:

#include<stdio.h>
int main()
{
    char str1[] = "hello hhncu.";
    char str2[] = "hello hhncu.";
    char* str3 = "hello hhncu.";
    char* str4 = "hello hhncu.";

    if (str1 == str2)
        printf("str1 and str2 are the same\n");
    else
        printf("str1 and str2 are not same\n");

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

    return 0;
}

我们给出结果:

在这里插入图片描述

下面我还是带大家分析一遍,只需要下面一幅图大家就能理解了:

在这里插入图片描述

根据这幅图来分析其实很简单,str1[]和str2[]是两个不同的数组,我们在栈上开辟的是两块不同的空间,虽然它们存放的内容是相同的,但它们不属于同一块空间,所以str1和str2肯定是不一样的。
我们再来分析一下str3和str4,它们存放的都是一个常量字符串的地址,常量字符串里面的内容又不能被修改,所以没必要存两份一样的字符串,但是str3和str4还是两个变量,只是它们指向同一块空间。

关于基本数据类型指针就给大家介绍到这里了。

二、指针数组

int arr[10];   //整型数组,存放整型的数组
char ch;       //字符数组,存放字符的数组

那指针数组又是什么呢?
类比推理,它是一个存放指针(地址)的数组。

下面我们来看一下它的基本应用:

#include<stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int c = 30;
    int* arr[3]={&a, &b, &c};    //这里[]的优先级比*高,所以arr与[]结合变为了一个数组,数组元素的类型为int*型,也就是数组元素为指针(地址)
    for(int i = 0; i < 3; i++)
    {
        printf("%d ",*(arr[i]));  //arr[i]表示的是地址,再进行解引用得到的就是相应的abc的值
    }
    return 0;
}

在这里插入图片描述

下面来看看它适用的环境:

#include<stdio.h>
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[3] = { arr1,arr2,arr3 };//这里面存的是三个数组名,也就是三个数组的首地址

    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 5; j++)
        {
        	printf("%d ", *(parr[i] + j));//parr[i]是三个一维数组的首地址+j表示这个往后移动几个单位,这样就能够依次打印出三个数组中的所有元素
        }
        putchar('\n');//打印换行符
    }

    return 0;
}

我们来看下效果:

在这里插入图片描述

三、数组指针

介绍完指针数组我们来看一看数组指针,数组指针是数组还是指针呢?同样的我们使用类比的方法进行推理。

int* p = NULL;				//p是整型指针     -指向整型的指针  - 可以存放整型的地址
char* pc = NULL; 			//pc是字符指针 	 -指向字符的指针  - 可以存放字符的地址

数组指针就是指向数组的指针,可以存放数组的地址。

如何快速的知道它是数组还是一个指针呢?
这里我们有个小技巧:看最后一个名词即可判断出它是指针还是数组,前面的都是语文中的形容词,都是用来修饰最后一个名词的。例如:数组指针是一个指针、指针数组是一个数组、函数指针是一个指针、函数指针数组是一个数组…

下面来看一看数组指针的基本用法:

#include<stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int(*pa)[10] = &arr;      //由于[]的优先级高于*,所以我们要想表示它为一个指针就应该将它括起来,表示pa是一个指针,它所指向的数组大小为10,数组元素的类型为int型 
    //&arr -- > pa = &arr,*pa = *&arr,*和&一抵消就是arr数组名了

    for (int i = 0; i < 10; i++)
    {		//以下的两种表达方式都可
            printf("%d ", *(*pa + i));//pa存放的是整个数组的地址,对它进行解引用其实得到的是arr数组名(数组首地址),arr + i得到的是i的地址,最后进行解引用得到的就是数组元素
            //printf("%d ", (*pa)[i]);//我们可以用这样来表示数组的元素arr[i],既然这里*pa=arr,那么也就可以这样来表示(*pa)[i])
    }    

    return 0;
}

其实打印一维数组用数组指针来做还有点麻烦,我们采用int* p = arr;这种方式不更简单嘛,循环里面直接用*(p + i)或者p[i]就能实现了。

下面我们来看数组指针的实际应用场景,打印二维数组中的元素:

#include<stdio.h>

int main()
{
    int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
    int(*p)[5] = arr;		//数组指针p它所指向的数组大小为5,数组元素的类型为int型,数组名arr表示的是首地址
   //将二维数组看成一维数组的话,那么它有三个元素(一维数组)分别为arr[0]、arr[1]、arr[2],它们都为数组名都为一维数组的首地址。
   //那么二维数组的首地址就为&arr[0],arr[0]为一个一维数组,&arr[0]就为第一行(第一个一维数组)的地址

    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 5; j++)
        {
        	//一下任选一种都可
            //printf("%d ", p[i][j]); //p = arr --> arr[i][j] ==> p[i][j]
            //printf("%d ", (*(p + i))[j]);//这里可以类比一维数组,*(p+i)得到的是第i个元素,在二维数组中第i个元素就为第i个数组-->p[i][j] == arr[i][j]
            //printf("%d ", *((p[i] + j)));//p[i]表示第i行,+j就表示第i行第j列元素的地址,最后进行解引用得到第i行第j列元素的值
            printf("%d ", *(*(p + i) + j));//*(p+i) == p[i],同上
        }
        putchar('\n');
    }

    return 0;
}

关于数组指针的使用我们再来看一个例子:

char* arr[5];
&arr做为右值的话左边该如何去写?

我们来分析一下:我们要存放数组的地址是不是要一个指针变量来存,所以我们写成(*p)表示它为一个指针,接着看我们的p指向的数组是不是有5个元素,所以我们写成(*p)[5],再看着char* arr[5]它为一个指针数组,数组中的每个元素的类型是char*,所以我们p指向的数组的元素类型应该为char*型,最后我们就写成这样来表示:char* (*p)[5] = &arr;

下面看一段代码再来加深一下印象,你能准确说出它们的类型吗?

int arr[10];       //这是一个整型数组,arr数组有10个元素,每个元素的类型为int
int* parr1[10];    //parr1为一个指针数组,parr1与[10]结合说明他为一个数组,该数组的元素个数有10个,每个元素的类型为int*,它是一个存放指针的数组。
int(*parr2)[10];   //parr2为一个数组指针,parr2先与*结合说明它是一个指针,它所指向数组的元素有10个,每个元素的类型为int,它可以用来存放数组的地址
int(*parr3[10])[5];//parr3先与[10]结合说明它是一个数组,这里有个小技巧parr3与[10]结合成为数组之后,剩下的部分就是它的类型了int(*)[5],它又是一个数组指针。
                   //parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素的类型为int

我们通过画图来展示一下parr3:

在这里插入图片描述

四、函数指针

大家先思考一下,整型、字符型、数组都有地址,那么函数也有地址吗?

答案是有的,函数是什么呢?函数是由一些运行的语句组成的,一个函数被编译后就成为一系列的指令,当程序被调用时,函数指令代码在内存中占据一片存储单元。当函数被调用时,流程跳转到该函数内继续运行,调用该函数时执行的第一条指令的地址称为函数的入口地址,通过这个地址可以找到该函数。函数代码在内存中开始的那个内存空间的地址就是函数的地址!!
函数是存储在代码区的,至于函数中用的变量的地址并不是放在代码区的,一般都放在另外的两个地方即栈区与堆区,所以在地址上是有很大的差值的。

4.1 函数名就是地址吗

我们通过一个例子来检测一下,同时我们想一下Add和&Add两者是否是一样的呢?arr表示的是数组名(首地址),&arr表示数组的地址,那么函数名也有这一说法嘛?

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在数值上是相等的,那么是否可以认为函数名 == &函数名,函数名就是地址呢?

切不可把函数名认为就是地址,函数名并不是函数地址的代表,这种误解与数组名就是指针一样犯了相同的错误。函数名是函数实体的代表,不是地址的代表。当然,你马上就会有疑问,平时我们不都是把函数名作为函数的地址吗?是的,函数名可以作为函数的地址,但是绝大多数人都忽略了一个条件,从函数到指针的隐式转换是函数名在表达式中的行为,就是说这个转换仅在表达式中才会发生,这仅是函数名众多性质中的一个而非本质,函数名的本质就是函数实体的代表。
到了C++,由于C++规定,非静态成员函数的左值不可获得,因此非静态成员函数不存在隐式左值转换,即不存在像常规函数那样的从函数到指针的隐式转换,所以必须在非静态成员函数前使用&操作符才能获得地址。

关于数组与指针之间的牵扯我后面会跟大家进行详细的讲解,接下来我们就来看看函数指针的用法:

#include<stdio.h>

int Add(int x,int y)
{
    int z = 0;
    z = x + y;
    return z;
}
int main()
{
    int a = 10;
    int b = 20;
    int(*p)(int,int) = Add;  //这里写成Add和&Add都可,因为它们在数值上相等
    //函数指针的写法类比数组指针,首先它是一个指针,所以写成(*p)表示p是一个指针变量
    //指针所指向的是一个函数,函数的两个形参为int型,所以我们写成这样(*p)(int, int),,最后Add函数的返回类型为int型,所以最终我们写成int (*p)(int, int) = &Add.
    printf("%d\n", (*p)(a, b));
    return 0;
}

我们来验证一下结果,答案是没有问题的:

在这里插入图片描述

接下来我们继续来看一个例子,这是我在调试窗口中看到一个的奇怪现象:

#include<stdio.h>

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

int main()
{
    void (*p)(char*) = &Print;
    (*p)("hello hncu!");
    printf("%p\n", p);
    printf("%p\n", &Print);
    
    printf("%p\n", *p);
    printf("%p\n", Print);

    return 0;
}

首先我们来看看结果,结果是没有什么问题的,它们四个在数值上是相等的

在这里插入图片描述

接下来我们再监视窗口看到的奇异现象:

在这里插入图片描述

我们发现在监视窗口的地址跟打印出来的地址不一样?这是为什么呢?
这是因为此时函数指针变量p保存的是代码区的函数地址,而在监视窗口看见的Print的地址是栈上的,p在存储Print的入口地址时并没有在栈上开辟空间,因此保存的是函数代码区的地址,这是为了方便寻找函数。

关于函数指针还有两个例子下面我们一起来看看,我们该如何理解下面的代码?

//代码1
(*(void(*)())0)();
//代码2
void(*signal(int,void(*)(int)))(int);

以上例子出自《C陷阱与缺陷》,感兴趣的读者可以详细读一下这本书,也是非常的经典。
下面我们来分析一下这道题:

首先看到代码一,我们先从最里面开始入手,红色划线部分void(*)()它是一个函数指针,接着我们看到它是用括号括起来的,我们就想到了强制类型转换,这里是将0强制类型转化为函数指针型,此时0就是一个函数的地址,再进行解引用找到某个函数,调用该函数,该函数的类型为void,并且无参。
这段代码的作用就是调用0地址处的该函数。

在这里插入图片描述

再来看下第二段代码,void(* signal(int , void(*)(int) ) ) (int);首先我们观察一下发现signal是一个函数名,它的第一个参数为int型,第二个参数为 void(*)(int)–>函数指针。这里我们举个例子int Add(int,int),Add是函数名,它的两个参数为int型,它的返回类型为int,这里还有个小技巧我们把Add(int,int)这部分去掉就得到了它的返回类型int了。类似的我们去掉signal后面的部分得到的void(* ) (int)就是它的返回类型了,它的返回类型也为一个函数指针。
最终我们这么来解释这段代码:signal是一个函数声明,它的函数参数有两个第一个是int,第二个是函数指针,该函数指针的参数类型为int,返回类型为void;signal函数返回类型也为一个函数指针,该函数指向的函数参数为int,返回类型为void。

大家有没有发现有个问题,为什么int Add的返回类型写在前面,而这个代码却写成void(*signal(int,void(*)(int)))(int)这个样子,为什么不写成void(*)(int) signal(int,void(*)(int))这样的形式呢?
这里跟我们的指针有关,*表示他为一个指针,变量和函数名要贴近*先与他结合在一起才表示为一个指针,就如int(*p)();*先与p结合才表示它为一个指针。

这段代码看起来确实有些复杂我们能不能进行简化一下呢?

提到简化你会想到什么,#define还是typedef,这里我们采用typedef来进行简化,而不采用#define进行文本替换,我们在之前也讲过#define在预处理阶段就已经将文本替换好了,它不进行任何语法语意以及类型的检查,一旦我们的类型出现任何问题我们很难迅速的找到问题所在之处,所以这里采用typedef进行类型重命名替换。

按我们平常就应该是这么typedef void(*)() pfun_t;写的了,而对于我们的函数指针来说不是这样的,它有自己的规定,我们应该这么来写:typedef void(*pfun_t)();最终我们就简化写成这种形式:pfun_t signal(int , pfun_t);

五、函数指针数组

我们知道数组可以连续存放相同类型的元素,指针数组是一个可以连续存放指针的数组,那么我们推断函数指针数组就是能连续存放函数指针的一个数组。在运用多种函数的情况下非常适用。我们来举个例子:

#include<stdio.h>

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

int Sub(int x, int y)
{
    int z = 0;
    z = x - y;
    return z;
}

int Mul(int x, int y)
{
    int z = 0;
    z = x * y;
    return z;
}

int Div(int x, int y)
{
    int z = 0;
    z = x / y;
    return z;
}

int main()
{
    int (*parr[4])(int, int) = { Add, Sub, Mul, Div };//函数指针数组,parr先与[4]结合表示它为数组,接着我们不看这一部分剩下的就是该数组中每个元素的类型了,int(*)(int,int)-->函数指针,表示该函数所指的参数类型为int,int型,该函数的返回类型为int型。

    for (int i = 0; i < 4; i++)
    {
        printf("%d\n", parr[i](2, 3));//parr[i]代表函数名
    }

    return 0;
}

此部分就是简单的实现了一下计算器的功能,利用函数指针数组调用不同的函数去实现对应的功能

在这里插入图片描述

六、指向函数指针的数组

	int arr[10] = { 0 };
    int(*p)[10] = &arr;//取出数组的地址
    int(*pf)(int,int);//函数指针
    int(*pfArr[4])(int,int);//pfArr是一个数组 - 函数指针的数组
    int(*(*ppfArr)[4])(int,int) = &pfArr;//pfArr是一个指向函数指针数组的指针,(*pfArr)说明它是一个指针,(*pfAff)[4]说明该指针指向的数组有4个元素,每个元素的返回类型为int(*)(int,int)

关于指向函数指针数组的指针其实这部分用的并不多,稍微了解一下即可。

今天的文章就到这了吧,由于篇幅问题我把回调函数这块的内容放到下一篇文章中进行详细讲解,最经典qsort函数就是用回调函数来实现的。关于文章如果有任何疑问或者错处欢迎大家评论区进行交流哦orz~🙈🙈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/104989.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

漏洞丨cve2017-11882

作者&#xff1a;黑蛋 一、漏洞简介 本次漏洞还是一个office溢出漏洞&#xff0c;漏洞编号cve-2017-11882。该漏洞是office一个组件EQNEDT32.EXE引起的栈溢出&#xff0c;通杀office版本2007-2016。 二、复现环境 系统版本 目标程序 调试工具 辅助工具 win7 sp1 x86 off…

【MySQL基础教程】DDL语句详细介绍

前言 本文为 【MySQL基础教程】DDL语句 相关相关内容进行详尽介绍&#xff0c;下边将对数据库操作&#xff08;包括&#xff1a;查询所有数据库、查询当前数据库、创建数据库、删除数据库、切换数据库等&#xff09;&#xff0c;表操作&#xff08;包括&#xff1a;查询创建、数…

微信小程序分包及案例

文章目录5. 分包6. 独立分包7. 分包预下载8. 案例-自定义tabbar5. 分包 分包指的是把一个完整的小程序项目&#xff0c;按照需求划分为不同的子包&#xff0c;在构建时打包成不同的分包&#xff0c;用户在使用 时按需进行加载。 可以优化小程序首次启动的下载时间在多团队共同…

微信公众号开发—扫描二维码实现登录方案

&#x1f60a; 作者&#xff1a; 一恍过去&#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390&#x1f38a; 社区&#xff1a; Java技术栈交流&#x1f389; 主题&#xff1a; 微信公众号开发—扫描二维码实现登录方案⏱️ 创作时间&#xff1a; 2022…

非零基础自学Golang 第13章 并发与通道 13.1 概述

非零基础自学Golang 文章目录非零基础自学Golang第13章 并发与通道13.1 概述13.1.1 并行与并发13.1.2 Go并发优势第13章 并发与通道 并发是指在同一段时间内&#xff0c;程序可以执行多个任务。 随着社会需求的发展&#xff0c;光靠硬件的提升是无法满足高并发的需求的&#…

[前端攻坚]:数组去重的几种方法

总结一些日常需要用到的一些api&#xff0c;也是在一些面试中会经常出现的题目&#xff0c;今天分享的是数组去重的几个不同的方法&#xff0c; 同时文章也被收录到我的《JS基础》专栏中&#xff0c;欢迎大家点击收藏加关注。 数组去重的方法 1.set去重 2.map去重 3.for循环in…

Python安装Pycrypto

前言 安装 使用以下命令安装 pip install pycrypto2.6.1报错 如果在安装过程中出现如下错误 则说明系统缺乏相应python开发包&#xff0c;需要进行安装对应的python开发包 解决 在CentOS下&#xff0c;如果是python2.7则使用如下命令安装 yum install python-devel是pyt…

Pytest用例运行及规范

温馨提示 本篇约1600字&#xff0c;看完需3-5分钟&#xff0c;学习学半小时&#xff0c;加油&#xff01; 先看普通函数运行顺序 import pytestdef test_one():print("我是清安")def test_02():print("--02--")def test_a():print("--a--")de…

BP神经网络的最简Python实现

文章目录神经元BP原理及实现测试BP&#xff0c;就是后向传播(back propagation)&#xff0c;说明BP网络要向后传递一个什么东西&#xff0c;这个东西就是误差。 而神经网络&#xff0c;就是由神经元组成的网络&#xff0c;所以在考虑BP之前&#xff0c;还不得不弄清楚神经元是…

endata 电影票房响应数据破解

本文仅供参考学习&#xff0c;如有侵权可联系本人 目标网站 aHR0cHM6Ly93d3cuZW5kYXRhLmNvbS5jbi9Cb3hPZmZpY2UvQk8vWWVhci9pbmRleC5odG1s加密入口分析 在异步请求那里可以看到请求接口&#xff0c;请求参数并未加密只是响应内容进行了加密&#xff0c;暂时也无法判断加密方…

JavaWeb的Servlet学习之Request03

目录 1.Request 1.1Request执行流程 1.2request对象和response对象的原理 1.3 request对象继承体系结构 1.4request功能&#xff1a; 1.3.1获取请求消息数据 1.获取请求行数据 2.获取请求头 3.获取请求体数据 4.其他功能 4.1获取请求参数通用方式&#xff1a;不论get…

开源CA搭建-基于openssl实现数字证书的生成与分发

目录 一、前言 二、openssl介绍 三、openssl的常用用法 &#xff08;一&#xff09;单向加密 &#xff08;二&#xff09;生成随机数 &#xff08;三&#xff09;生成公钥&#xff0c;私钥 1.生成私钥 2.提取公钥 四、搭建CA &#xff08;一&#xff09;创建根CA私钥…

Linux的camera驱动 摄像头调试方法

CameraInfo类用来描述相机信息&#xff0c;通过Camera类中getCameraInfo(int cameraId, CameraInfo cameraInfo)方法获得&#xff0c; 主要包括以下两个成员变量facing&#xff0c;facing 代表相机的方向&#xff0c; 它的值只能是CAMERA_FACING_BACK&#xff08;后置摄像头&am…

Golang 【basic_leaming】1 基本语法

阅读目录Go 语言变量Go 语言 - 声明变量1. 标准格式2. 批量格式Go 语言 - 初始化与定义变量1. 标准格式2. 编译器推导类型格式3. 短变量声明与初始化Go语言 - 多变量同时赋值Go 语言 - 匿名变量参考资料Go 语言整型&#xff08;整数类型&#xff09;1 自动匹配平台的 int 和 un…

新项目为什么决定用 JDK 17了

大家好&#xff0c;我是风筝。公众号「古时的风筝」&#xff0c;专注于后端技术&#xff0c;尤其是 Java 及周边生态。文章会收录在 JavaNewBee 中&#xff0c;更有 Java 后端知识图谱&#xff0c;从小白到大牛要走的路都在里面。 最近在调研 JDK 17&#xff0c;并且试着将之前…

阴差阳错,阴阳之变

北京的第一批“杨康”们已经返回到工作岗位&#xff0c;这其中就包括我。简单总结一下我的感染和康复过程&#xff0c;给大家做个样本吧。我属于北京放开的第一波感染者&#xff0c;12.9日当天感觉嗓子干&#xff0c;毫不犹豫&#xff0c;果然是中招了&#xff1b;周末开始发烧…

特朗普发行NFT惹群嘲,上线售罄现“真香定律”

文/章鱼哥出品/陀螺财经特朗普14日在其创建的社交平台truth social上发帖称&#xff0c;“美国需要一个超级英雄”。他还预告自己将于当地时间15日宣布“重大消息”。据《新闻周刊》报道&#xff0c;特朗普当日在其社交平台上发了一段十几秒的视频&#xff0c;里面有一个他站在…

Windows实时运动控制软核(三):LOCAL高速接口测试之C++

今天&#xff0c;正运动小助手给大家分享一下MotionRT7的安装和使用&#xff0c;以及使用C对MotionRT7开发的前期准备。 01 MotionRT7简介 MotionRT7是深圳市正运动技术推出的跨平台运动控制实时内核&#xff0c;也是国内首家完全自主自研&#xff0c;自主可控的Windows运动控…

Linux搭建测试环境详细步骤

本文讲解如何在Linux CentOS下部署Java Web项目的步骤 环境准备 &#xff08;1&#xff09;Linux系统&#xff08;2&#xff09;JDK&#xff08;3&#xff09;Tomcat &#xff08;4&#xff09;MySQL工具下载 一、Linux系统 本文主要是Linux CentOS7为例 自己在家练习小项…

[拆轮子] PaddleDetection 中的 COCODataSet 是怎么写的

今日&#xff0c;辗转反侧&#xff0c;该&#x1f4a9;的代码就是跑不成功&#xff0c;来看看 COCODataSet 到底是怎么写的&#xff0c;本文只参考当前版本的代码&#xff0c;当前版本 PaddleDetection2.5 COCODataSet 源码见附录 COCODataSet 类内部就三个函数&#xff1a; …