• 【C语言】指针的入门详细介绍



    前言

    指针是c语言的重中之重


    (一)指针初阶:


    一 : 什么是指针

    其实指针有两层含义:

    1.指针是地址是一层含义

    2.当地址或者指针需要存起来的时候,我们需要一个指针变量这个指针变量是存放地址的,我们经常说的“指针“,”指针“,其实是指针变量,指针变量也被称为指针


    二 : 什么是野指针

    概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

    简单的说:你在大街上看见一条狗,那个狗没有栓链子,没有主人,我们称它为野狗,指针也是一样,不知道指向内存的什么地址,就被称为野指针。

    什么情况下会造成野指针呢?

    1 . 指针未初始化

    #include 
    int main()
    {
    	int* p;//局部变量指针未初始化,默认为随机值
    	*p = 20;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面的代码意思可以理解为:你看见了一个酒店,你进去了,随便找了个房间就睡觉了,并没有去前台取得自己的一个房卡。

    2 . 指针越界访问

    #include 
    int main()
    {
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
    {
    				//当指针指向的范围超出数组arr的范围时,p就是野指针
    	*(p++) = i;
    }
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这里画一个图表示一下:
    在这里插入图片描述
    arr数组就给了10个空间,只到下标九,但是上面的for循环循环了11次,可以得出已经访问超过我们的arr数组大小了所以会出现指针越界访问的情况。
    在这里插入图片描述
    3 . 指针指向的空间释放

    int* test()
    {
    	int a = 10;  //(1)第一次进来创建a   出去test函数 空间给操作系统
    	return &a;
    }
    
    int main()
    {
        int *p=	test();  
    	*p = 20;   //(2)还是第一次的地址 ,但是出来了第一次的地址已经还回去了,
    				//(3)这时候进行赋值,会造成非法访问  p野指针
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    来个例子:你和你女朋友分手了,但是你还有女朋友的电话,你就三番两次打电话给她,但是她已经不是你女朋友了,你这样就属于非法骚扰了…


    如何避免野指针

    1.指针初始化

    要养成好的编程习惯,当变量不知道赋什么的话,可以先赋个0,或者相对应类型的值 int a = 0;

    指针不知道应该初始化成什么值的时候,建议直接初始化为NULL。

    #include 
    int main() 
    {
    	int* p = NULL; //NULL要引用头文件
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.小心越界

    C语言本身不会检查数组的越界,要我们自己保存头脑清晰


    3.指针指向的空间释放及时置NULL

    要使用的代码被释放了,及时置NULL

    4.指针使用之前检查有效性

    先来看看下面的代码可以运行起来不?

    int main() 
    {
    	int* p = NULL;
    	*p = 10;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    答案是:不可以的,为什么呢?
    NULL其实是一个地址位置是在0处的一个地址,这个地址在操作系统里,没有分配给用户,所以不可以把10放进去赋值。
    在这里插入图片描述
    那么怎么规避呢?可以在使用之前先判断一下

    int main() 
    {
    	int* p = NULL;
    	if (p!= NULL)  //不是空才继续使用,增加代码的健壮性
    	{
    		*p = 10;
    
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上面的方法讲完就不会遇见野指针了吗?其实不然,你要写BUG谁也难不住,教的是怎么避免少写,养成好习惯!!!


    三 : 指针的运算

    • 指针± 整数
    • 指针-指针
    • 指针的关系运算
    1.指针的加减整数运算

    代码包含指针加减整数和指针的关系运算

    #define N_VALUES 5
    float values[N_VALUES];
    float *vp;
    //指针+-整数;指针的关系运算
    for (vp = &values[0]; vp < &values[N_VALUES];)
    {
         *vp++ = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面的代码大家可以试着看一下,下面有解析:

    解析:
    在这里插入图片描述
    定义了一个值为5


    在这里插入图片描述
    定义了一个数组长度为5,下面是名为values的数组
    在这里插入图片描述
    在这里插入图片描述
    指针变量 vp


    for (vp = &values[0]; vp < &values[N_VALUES] ; )
    {
    *vp++ = 0;
    }
    在这里插入图片描述


    vp < &values[N_VALUES] 小于第5个元素
    在这里插入图片描述


    *vp++ = 0;

    vp指向的是第一个数组下标为0的,条件是小于数组第5个元素就停止,所以最后代码全部运行起来,就是把数组0-4的下标全部赋为0

    在这里插入图片描述


    随着数组下标的增长, 地址其实是由低到高变化的
    在这里插入图片描述


    2.指针 - (减)指针

    问:下面代码显示的是什么?

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

    在这里插入图片描述

    答案:9

    解析:

    指针减指针 得到的两个指针之间的元素个数
    指针和指针相减的前提是:两个指针指向的同一个空间

    &arr[9] 和&arr[0] 分别指向的是,他们之间有9个元素,所以是9
    在这里插入图片描述


    指针和指针相减的前提是:两个指针指向的同一个空间

    int main()
    {
    	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    	char c[5]; 
    	printf("%d\n",&arr[9] -&c[0]);//e
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面代码块是错误的。


    3.使用指针减指针模仿写一个strlen

    我们都知道指针减指针就可以得到元素个数,那么来看一下接下来的模仿strlen函数的思路吧!
    在这里插入图片描述
    找到a的地址,和 \0的地址(每一个字符串后面都有一个隐藏的\0),让它们相减就可以求出字符串的长度了

    下面放代码:

    int  my_strlen(char* str)
    {
    	char* start = str;  //保存一开始的地址   start 里面是 a的地址
    
    	while (*str != '\0') //  如果str不会最后一个\0就不会停下来
    	{
    		str++;         //一直加到\0
    	}
    	 
    	return str - start;   //  '\0' - 'a'    指针减指针
    }
    int main() 
    {
    	int a = my_strlen("abc");
    	printf("%d\n",a); // 打印3
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    四 : 指针和数组

    想学好指针,你必须先知道数组名是什么!!
    数组名是数组首元素地址

    代码来验证一下:

    int main()
    {
    	int arr[10] = { 0 };
    	printf("%p\n",arr);   //数组名是首元素地址
    	printf("%p\n",&arr[0]);  //取第一个元素的地址
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    果然地址是一样的!
    在这里插入图片描述


    那么数组名是一个地址,是不是也可以拿指针来接收

    int arr[10] = { 0 };
    int* p = arr;
    
    • 1
    • 2

    看下面的代码:

    既然arr是首元素地址 ,但是可以拿p指针来接收, 是不是 (arr == p)呢?

    跑一下下面的代码:

    int main()
    {
    	int arr[10] = { 0 };
    	int* p = arr;
    	int i = 0;
    
    	for ( i = 0; i < 10; i++)
    	{
    		printf("%p <==> %p <==> %p \n",&arr[i] , p+i ,arr+i);  
    		//p+i 访问第i个元素地址
    	}
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    结果:好家伙3个地址一模一样,所以上面的问题是成立的
    在这里插入图片描述
    有了上面的例子,我们可以推导出来一个更加有趣的代码例子:
    (加法支持加法交换律)

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int* p = arr;
    
    arr[2] <==> *(arr+2) <==> *(2+arr) <==> 2[arr] 
    *(p+2) <==> *(2+P) <==> 2[p] <==> p[2]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    以上都是可以支持打印出来正确的数字,但是为什么呢?

    [ ]是一个操作符 arr[ 2] , arr 和 2 是两个操作数

    其实 arr [ 2 ] 这种写法编译器最终会把它转换为 *(arr+2) ,arr表首元素地址 加上2 就是跳过2个元素 解引用就得到最终的结果了


    五 : 二级指针

    指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?

    看看下面代码:

    int main()
    {
    	int a = 10;   
    	//为什么要int*呢
    	int *pa = &a;  //a是int类型 *pa表示pa是个指针变量 所以是int*
    	int* *ppa = &pa;  //int* 是因为pa是int*类型  *ppa 表示ppa是个指针
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    有个a变量,在内存开辟一个内存空间,地址是0x11223344

    int a = 10;
    在这里插入图片描述

    int* pa = &a;
    把a的地址放在pa的内存空间里面。 pa的地址是0x44332211
    在这里插入图片描述

    int** ppa = &pa;

    把pa的地址放在ppa的内存空间里面。 ppa的地址是0x22334411在这里插入图片描述

    为什么叫二级指针呢,因为指向的有两层关系,比如我现在要修改 a 的值要怎么修改呢?

    **ppa == *(pa) == 把pa解引用 自然可以修改到值

    在这里插入图片描述


    六 : 指针数组

    指针数组是指针还是数组?
    答案:是数组。是存放指针的数组。

    int arr1[5]: 整形数组 -存放整形的数组就是整形数组
    在这里插入图片描述

    char arr2[6]: 字符数组 - 存放的是字符
    在这里插入图片描述

    int* arr3[5]:整形指针数组: arr3是一个数组,有五个元素,每个元素是一个整形指针。

    在这里插入图片描述


    (二)指针进阶:

    一 : 字符指针

    在指针的类型中我们知道有一种指针类型为字符指针 char*, 一般是这样使用的:

    int main()
    {
        char ch = 'w';
        char *pc = &ch;
        *pc = 'w';
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    还有一种使用方式如下: 把字符串放在字符指针变量中。

    int main()
    {
        char* pstr = "hello world";//这里是把一个字符串放到pstr指针变量里了吗?
        printf("%s\n", pstr);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    那么出现一个问题:char* 就算是在64位的机器是最多也是存8个字节啊,把hello world放进去可以放的下吗?

    其实:关于上面的问题,解释是:本质上是把字符串的首字符地址存储在了pstr中,可以代码验证一下:如果是第一个字符那打印出来的是 h

    在这里插入图片描述
    果然一模一样!!


    来一个练题:

    #include 
    int main()
    {
    	char str1[] = "hello bit.";
    	char str2[] = "hello bit.";
    	char* str3 = "hello bit.";
    	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\n");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    输出什么?

    答案:在这里插入图片描述

    为什么呢?来看下面的解析

    首先我们可以看到可以有4个变量,先看一下str1 和str2 .
    在这里插入图片描述

    它们是两个不相同的数组,所以分别指向不同的地址

    来看看str3和str4,因为str3 和str4存放的是常量字符,常量字符是不可以被改变的,所以在内存存放的是同一个内容,2个变量同时指向同一个地址一起使用,所以应该是下图所示:
    在这里插入图片描述
    所以最后就是上面的那个结果,对于这道题目的最大争议就是 str3 和str4存的变量是不是常量变量不可以被修改,我们可以去编译器看一下:
    在这里插入图片描述
    可以看见这里是报错的,所以说我们上面所述的如实。


    二: 指针数组

    指针数组本质上是数组,数组中存放的是指针(地址)

    应用场景:

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

    三 : 数组指针

    数组指针是指针?还是数组?
    答案是:指针。

    下面代码哪个是数组指针?

    int *p1[10];
    int (*p2)[10];
    //p1, p2分别是什么?
    
    • 1
    • 2
    • 3

    解释:

    p1是指针数组
    p2是数组指针,
    解释:p2先和结合,说明p2是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
    指针,指向一个数组,叫数组指针。
    这里要注意:[]的优先级要高于
    号的,所以必须加上()来保证p先和*结合


    &数组名VS数组名

    对于下面的数组:

    int arr[10];

    arr 和 &arr 分别是啥?
    我们知道arr是数组名,数组名表示数组首元素的地址。
    那&arr数组名到底是啥?
    我们看一段代码:

    #include 
    int main()
    {
    int arr[10] = {0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    可见数组名和&数组名打印的地址是一样的。
    难道两个是一样的吗?
    我们再看一段代码:

    #include 
    int main()
    {
    int arr[10] = { 0 };
    printf("arr = %p\n", arr);
    printf("&arr= %p\n", &arr);
    printf("arr+1 = %p\n", arr+1);
    printf("&arr+1= %p\n", &arr+1);
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
    实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
    数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

    数组名是数组首元素的地址
    但是有2个例外:
    1.sizeof(数组名) - 数组名表示整个数组,计算的是整个数组的大小,单位是字节

    2.&数组名 - 数组名表示整个数组 ,取出的是整个数组的地址


    四 : 一维数组传参、二维数组传参

    数组的传参,使用以下形式都可以

    (1):一维数组传参
    #include 
    void test(int arr[])//ok
    {}
    //不在[]里面写10,是因为传过来的是首元素地址,并不会真的开辟一个10个数组的大小,可省,写了也不影响
    void test(int arr[10])//ok
    {}
    //同上
    void test(int *arr)//ok
    {}
    //传过来的是首元素地址,所以可以拿1级指针。
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这里解释一下为什么 void test2(int **arr) 也可以:
    在这里插入图片描述
    arr里面的类型都是1级指针,所以放在2级指针存放没有什么不合适。


    (2):二维数组传参

    二维数组,首元素是第一行地址

    void test(int arr[3][5])//ok  
    {}
    //实参是2维数组,
    void test(int arr[][])//no
    {}
    //可以省略行,不可以省略列
    void test(int arr[][5])//ok
    {}
    //只省略了行,没有省略列
    //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
    //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
    //这样才方便运算。
    void test(int *arr)//no
    {}
    //这个是传过来第一行的地址
    void test(int* arr[5])//ok?
    {}
    void test(int (*arr)[5])//ok?
    {}
    //arr是一个指针,指向的是5个int类型,实参传过来的是首元素地址,也就是第一行地址,第一行地址是int类型,所以可以
    void test(int **arr)//ok
    {}
    //因为传过来的压根不是首元素地址,是第一行地址,是一维数组的地址,所以不可以拿二维指针接收。
    int main()
    {
    int arr[3][5] = {0};
    test(arr);
    }
    
    • 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
    (3):一级指针传参
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    思考:

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

    比如:

    void test1(int *p)
    {}
    int a = 10;
    int* pa = &a;
    //test1函数能接收什么参数?
    //答: test1(&a);    test1(a);
    void test2(char* p)
    {}
    char a='w' ;
    char* pa = &a;
    //test2函数能接收什么参数?
    //答: test2(&a);    test2(a);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (4):二级指针传参

    列:

    #include 
    void test(int** ptr)
    {
    printf("num = %d\n", **ptr);
    }
    int main()
    {
    int n = 10;
    int*p = &n;
    int **pp = &p;
    int * arr[10] = {0};
    test(pp); //传二级指针变量  可以
    test(&p);//传一级指针变量的地址  可以
    test(arr);//传存放一级指针的数组 可以
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    五 : 函数指针

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

    先看一段代码: 打印test函数的地址,和取地址打印test函数的地址,它们一样吗?

    #include 
    void test()
    {
    printf("hehe\n");
    }
    int main()
    {
    printf("%p\n", test);
    printf("%p\n", &test);
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    看结果:发现他们的地址都是一样的。
    在这里插入图片描述
    现在出来一个结论:

    数组 arr != &arr
    一个是数组首元素的地址 和整个数组的地址
    函数 test == &test
    函数不存在什么首元素地址,函数名即是取函数地址

    问:那我们的函数的地址要想保存起来,怎么保存?来个列子:

    int add(int x, int y)
    {
    	return x + y;
    }
    
    int main()
    {
    	int ret = add(3, 5);
    	int (*pret) (int, int) = &add;  //存放函数的指针
    	//返回类型 (*变量名) (函数类型)
    	int prt1 = (*pret)(3, 5);
    	int prt2 = pret(3,5);
    	int pt = add(3, 5); //ok  通过函数名
    	printf("%d\n", prt1); // ok
    	printf("%d\n", prt2); //ok
    	printf("%d\n", pt); //ok
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上面代码输出结果:
    在这里插入图片描述

    上面那么多代码:其实发现prt1prt2 一个加了*,一个没有加*,但是结果都是一样的,其实那颗* 是一个摆设,只是让我们更加好理解,你前面也可以加5颗* ,最后的结果是一样的 。

    在这里插入图片描述

    函数指针语法:
    返回类型 ([*]变量名) (函数类型)

    自己尝试写写下面列子:

    void test(char* str)
    {
    
    }
    int main() 
    {
    	pt = &test;  //pt该怎么写?
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果:void (*pt) (char*)


    阅读两段有趣的代码:请读懂这两段代码的意思?

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

    解析代码1:(整个代码的意思是:调用了0地址处的函数该参数返回类型是void) ,一步一步来看:

    (1)void (*)() 是一个函数指针类型:
    (2)( void (*)() ) 0 :把0强制转换类型,被解释是一个函数地址
    (3)(* void (*)() ) 0 :对0地址处进行了解引用操作
    (4)(* void (*)() ) 0() :调用了0地址处的函数

    本题出自:《C陷阱和缺陷》
    在这里插入图片描述

    解析代码2:整个代码是 函数参数的声明

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

    把这个代码拆分开,好理解点
    在这里插入图片描述

    (1)signal先和( )结合,说明signal是函数名
    (2)signal函数的第一个参数是int类型,第二个参数是函数指针,
    该函数指针,指向一个参数为int,返回类型是void
    (3)signal函数的返回类型也是一个函数指针
    该函数指针,指向一个参数为int,返回类型是void
    singal是一个函数声明

    代码2太复杂,如何简化:其实我们可以把 void(*)(int) 这个类型,使用typedef

    typedef void(*pfun_t)(int);
    简化前: void (signal(int , void()(int)))(int);
    简化后:pfun_t signal(int, pfun_t);


    六 : 函数指针数组

    我们都知道数组是存放同类型数据的存储空间,那我们已经学习了指针数组, 比如:int arr[10]; //数组的每个元素是int

    那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

    int (*parr1[10])()
    
    • 1

    怎么理解这个代码:首先parr1先和[ ]结合说明是一个数组

    int (*)()
    
    • 1

    去掉中间的内容,就是int (*)()类型的函数指针。

    问:那么函数指针数组有什么用呢? 函数指针数组的用途:转移表

    使用案列:未使用函数指针数组,代码冗余

    #include 
    int add(int a, int b)
    {
    	return a + b;
    }
    int sub(int a, int b)
    {
    	return a - b;
    }
    int mul(int a, int b)
    {
    	return a * b;
    }
    int div(int a, int b)
    {
    	return a / b;
    }
    
    int main()
    {
    	int x, y;
    	int input = 1;
    	int ret = 0;
    	do
    	{
    		printf("*************************\n");
    		printf(" 1:add 2:sub \n");
    		printf(" 3:mul 4:div \n");
    		printf("*************************\n");
    		printf("请选择:");
    		scanf("%d", &input);
    		switch (input)
    		{
    		case 1:
    			printf("输入操作数:");
    			scanf("%d %d", &x, &y);
    			ret = add(x, y);
    			printf("ret = %d\n", ret);
    			break;
    		case 2:
    			printf("输入操作数:");
    			scanf("%d %d", &x, &y);
    			ret = sub(x, y);
    			printf("ret = %d\n", ret);
    			break;
    		case 3:
    			printf("输入操作数:");
    			scanf("%d %d", &x, &y);
    			ret = mul(x, y);
    			printf("ret = %d\n", ret);
    			break;
    		case 4:
    			printf("输入操作数:");
    			scanf("%d %d", &x, &y);
    			ret = div(x, y);
    			printf("ret = %d\n", ret);
    			break;
    		case 0:
    			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
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    使用案列:使用函数指针数组,代码简洁

    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("************");
    	printf("***1.ADD***\n");
    	printf("***2.SUB***\n");
    	printf("***3.MUL***\n");
    	printf("***4.DIV***\n");
    	printf("***0.exit***\n");
    }
    int main() 
    {
    	int input = 0;
    	int ret = 0;
    	int x, y;
    	int(*pfArr[5])(int , int ) = { NULL, add,sub,mul,div};
    	do
    	{
    		menu();
    		printf("请选择菜单");
    		scanf("%d", &input);
    		if (input>=1 && input<= 4)
    		{
    			printf("请输入2个数字");
    			scanf("%d %d",&x , &y);
    			ret = (*pfArr[input])( x ,  y);   //找到执行函数的地址,必须要参数一样的才可以这样使用。
    			printf("ret = %d\n", ret);
    		}
    		else
    		{
    			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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    七 : 指向函数指针数组的指针

    指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针

    int(*P) (int, int)  //函数指针
    int(*P2[5]) (int, int) //函数指针数组
    int(*p(*P3)[5])(int ,int ) =&p2 ;//指向函数指针数组的指针
    
    • 1
    • 2
    • 3
    void test(const char* str)
    {
    	printf("%s\n", str);
    }
    int main()
    {
    	//函数指针pfun
    	void (*pfun)(const char*) = test;
    	//函数指针的数组pfunArr
    	void (*pfunArr[5])(const char* str);
    	pfunArr[0] = test;
    	//指向函数指针数组pfunArr的指针ppfunArr
    	void (*(*ppfunArr)[10])(const char*) = &pfunArr;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这部分做个了解,不必太深入。


    七 : 回调函数

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

    还是上面的 计算器代码:

    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 calu(int(*(*pf)(int,int)))     //把函数地址当参数
    {
    	int x, y;
    	printf("输入操作数:");
    	scanf("%d %d", &x, &y);
    	return (*pf)(x, y);
    }
    
    int main()
    {
    	int input = 1;
    	int ret = 0;
    	do
    	{
    		printf("*************************\n");
    		printf(" 1:add 2:sub \n");
    		printf(" 3:mul 4:div \n");
    		printf("*************************\n");
    		printf("请选择:");
    		scanf("%d", &input);
    		switch (input)
    		{
    		case 1:
    			ret = calu(add);  //把函数地址传过去
    			printf("ret = %d\n", ret);
    			break;
    		case 2:
    			ret = calu(sub);
    			printf("ret = %d\n", ret);
    			break;
    		case 3:
    			ret = calu(mul);
    			printf("ret = %d\n", ret);
    			break;
    		case 4:
    			ret = calu(div);
    			printf("ret = %d\n", ret);
    			break;
    		case 0:
    			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
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

  • 相关阅读:
    MySQL数据库常用命令大全(完整)(表格形式)
    springboot+教学工作量管理系统 毕业设计-附源码221541
    Linux调试器-gdb使用
    C语言只推荐这1本宝藏书,你读过吗?
    【Spring】之初识
    蓝桥杯入门(三)卡片
    面试官:来说说vue3是怎么处理内置的v-for、v-model等指令?
    开源论道 源聚一堂@COSCon
    Servlet入门接口、类和配置学习
    C++模板(函数模板/类模板)
  • 原文地址:https://blog.csdn.net/qq_46874327/article/details/117308452