玩转C语言——深入理解指针

简介: 玩转C语言——深入理解指针

一、指针概念

       1.1 内存和地址

    在开始学习指针前,我们先来讲一个例子,假如你身处一栋楼中,你点了一份外卖,那么,外卖员如何能找到你?有两种方法。法一:直接一间一间找,这样做不仅消耗时间长,而且效率低。法二:把房间编号,然后告诉外卖员房间号即可。⽣活中,每个房间有了房间号,就能提⾼效率,能快速的找到房间。

       生活中如此,计算机中亦如此。计算机为了实现对内存的高效管理,是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。其中,每个内存单元,相当于⼀个房间,⼀ 个⼈字节空间⾥⾯能放8个⽐特位,就好⽐一个房间住八个人,每个⼈是⼀个⽐特位。 每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的⻔牌号),有了这个内存单元的编 号,CPU就可以快速找到⼀个内存空间。

       ⽣活中我们把⻔牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。C语⾔中给地址起 了新的名字叫:指针

       所以我们可以理解为: 内存单元的编号 == 地址 == 指针

       1.2 指针变量和地址

       理解了以上概念,我们再回到C语⾔,在C语⾔中创建变量其实就是向内存申请空间。

       那我们怎么拿到地址呢?那就不得不拿出一个操作符了——&(取地址操作符),它的作用便是拿出地址。

1.  int a = 4;
2.  printf("%p", &a);

       这样便可打印出a的地址。

       1.3 指针的类型

       那么,指针都有哪些类型呢?

       int *ptr; // 声明一个指向整数的指针

        char *ptr; // 声明一个指向字符的指针

        double *ptr; // 声明一个指向双精度浮点数的指针

       总之,和我们以前学习变量类型时差不多,那我们该如何判断指针类型呢?

       从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看各个指针的类型:

int*ptr;//指针的类型是int*

char*ptr;//指针的类型是char*

int**ptr;//指针的类型是int**

int(*ptr)[3];//指针的类型是int(*)[3]

int*(*ptr)[4];//指针的类型是int*(*)[4]

怎么样?找出指针的类型的方法是不是很简单?

1.4 指针变量和解引⽤操作符(*)

       那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:0x006FFD70,这个数值有时候也是需要 存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。

1.  int a = 4;
2.  int* p = &a;

       指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

       我们把地址保存好以后,我们该如何使用呢?大家想象一下:我们如何把锁住门打开,答案很简单,使用钥匙即可。同理,我们如何使用地址,就要找出与之匹配的“钥匙”那就是叫解引⽤操作符(*)。

1.  int a = 4;
2.  int* p = &a;
3.  *p = 0;

       以上代码,使用了*操作符,什么意思呢?即*pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0。

1.5 指针的大小

       初步了解了使用后,我们肯定要了解一下指针的大小,那指针的大小和什么有关呢?大家可以先猜测一下,我们随后解密。

1. #include<stdio.h>
2. int main()
3. {
4.  printf("%zd\n", sizeof(char*));
5.  printf("%zd\n", sizeof(short*));
6.  printf("%zd\n", sizeof(int*));
7.  printf("%zd\n", sizeof(double*));
8.  return 0;
9. }

       答案如下:

x64环境下输出结果

       

x86环境下输出结果

       总结:

       32位平台下地址是32个bit位,指针变量⼤⼩是4个字节

        64位平台下地址是64个bit位,指针变量⼤⼩是8个字节

        注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

1.6 指针变量类型的意义

       调试以下代码,观察内存变化。

1. #include <stdio.h>
2. int main()
3. {
4. int n = 0x11223344;
5. int *pi = &n; 
6.  *pi = 0; 
7. return 0;
8. }//代码一
1. #include <stdio.h>
2. int main()
3. {
4. int n = 0x11223344;
5. char *pi = &n; 
6.  *pi = 0; 
7. return 0;
8. }//代码二

       调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。

       结论:

       指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。

1.7 野指针

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

       形成原因:

       1. 指针未初始化

1. #include <stdio.h>
2. int main()
3. { 
4. int *p;//局部变量指针未初始化,默认为随机值
5.  *p = 20;
6. return 0;
7. }//此代码在VS上会报错

       2.指针越界访问

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

       3.指针指向的空间释放

1. #include<stdio.h>
2. int* test()
3. {
4.  int n = 10;
5.  return &n;
6. }
7. int main()
8. {
9.  int* p = test();
10.   return 0;
11. }

       那该如何避免指针变成野指针呢?

       1.指针初始化,明确指针指向哪里。如若不知道可以赋值为NULL(NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。)

       2.⼩⼼指针越界,不要越界访问。

       3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性。

       4.避免返回局部变量的地址。

       1.8 const修饰

       变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作用。变量中可以这样使用,指针亦是如此。

1. #include<stdio.h>
2. void test1()
3. {
4.  int n = 10;
5.  int m = 20;
6.  int* p = &n;
7.  *p = 20;//ok?
8.  p = &m; //ok?
9. }
10. void test2()
11. {
12.   int n = 10;
13.   int m = 20;
14.   const int* p = &n;
15.   *p = 20;//ok?
16.   p = &m; //ok?
17. }
18. void test3()
19. {
20.   int n = 10;
21.   int m = 20;
22.   int* const p = &n;
23.   *p = 20;//ok?
24.   p = &m; //ok?
25. }
26. void test4()
27. {
28.   int n = 10;
29.   int m = 20;
30.   const int*const p = &n;
31.   *p = 20;//ok?
32.   p = &m; //ok?
33. }
34. int main()
35. {
36.   test1();//无const修饰
37.   test2();//const在*的左边
38.   test3();//const在*的右边
39.   test4();//const在*的两边
40.   return 0;
41. }

       大家可自行在VS上验证,观察是否会报错。

       这里直接说结论:

       const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。

       const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。

       大家可以这样理解:假如你带你女朋友去逛街,突然想吃凉皮,你要是想守住你的钱袋子,就在左边加const,这样你的钱袋子就保住了,但是你的女朋友可能会想,连个凉皮都不给我,我要换男朋友,然后你就没女朋友了。所幸刚刚是做梦,但是不幸的是,你女朋友现在就是要吃凉皮,为了避免上一次情况的出现,把*放入了const右边,给她了一碗凉皮,这样她就不会换男朋友了。就在你为余额清零心疼不已时,你又醒了,原来又是一个梦,你女朋友又要吃凉皮,你重生归来,顿时恼怒,结合前两世方法,在const两边都加*,这样你既不用花钱,有能保住女朋友,这才是重生文男主。(仅当帮助记忆,无其他不良引导)

       1.9 二级指针

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

       答案是:二级指针。

1. #include<stdio.h>
2. int main()
3. {
4.  int a = 10;
5.  int* p = &a;
6.  int** pa = p;
7.  return 0;
8. }

       对于⼆级指针的运算有:

       *pa 通过对pa中的地址进⾏解引⽤,这样找到的是 p , *pa 其实访问的就是 p 。

       **pa 先通过 *pa 找到 p ,然后对 p 进⾏解引⽤操作: *pa ,那找到的是 a 。

       我觉得可以把多级指针想象成多级导数或者剥洋葱,逐渐抽丝剥茧,最后寻到本质。

二、指针运算

       指针的基本运算有三种,分别是:

  •         指针+- 整数
  •         指针-指针
  •         指针的关系运算

2.1 指针+- 整数

       因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。

1. int main()
2. {
3.  int arr[10] = { 0 };
4.  int* p = &arr[0];
5.  int sz = sizeof(arr) / sizeof(arr[0]);
6.  for (int i = 0; i < sz; i++)
7.  {
8.    printf("%d ", *(p + i));
9.  }
10.   return 0;
11. }

2.2 指针-指针

1. int my_strlen(char* s)
2. {
3.  char* p = s;
4.  while (*p != '\0')
5.    p++;
6.  return p - s;
7. }
8. int main()
9. {
10.   printf("%d\n", my_strlen("abcd"));
11.   return 0;
12. }

2.3 指针的关系运算

1. int main()
2. {
3.  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
4.  int* p = &arr;
5.  int i = 0;
6.  int sz = sizeof(arr) / sizeof(arr[0]);
7.  while (p < arr + sz)
8.  {
9.    printf("%d ", *p);
10.     p++;
11.   }
12.   return 0;
13. }

三、指针与数组

       3.1 数组名理解

       在学习函数时,我们要传数组时,总是写的是数组名。当你看到这时,或许会好奇数组名是什么?用以下代码来演示:

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

       运行结果如下:

       我们发现它们的地址一样,所以我们得出如下结论:

       数组名是地址并且是数组首元素的地址。

       这里强调两个例外:

• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩, 单位是字节

• &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)

       那么,arr和&arr有什么区别呢?那可以试试如下代码:

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

        这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是 ⾸元素的地址,+1就是跳过⼀个元素。 但是&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。

       3.2使⽤指针访问数组

        有了前⾯知识的⽀持,再结合数组的特点,我们就可以很⽅便的使⽤指针访问数组了。

1. #include <stdio.h>
2. int main()
3. {
4.  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
5.  int* p = arr;
6.  int sz = sizeof(arr) / sizeof(arr[0]);
7.  for (int i = 0; i < sz; i++)
8.  {
9.    printf("%d ", (*p + i));
10.   }
11.   return 0;
12. }

       这个代码搞明⽩后,我们再试⼀下,如果我们再分析⼀下,数组名arr是数组⾸元素的地址,可以赋值 给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可 以访问数组呢?

1. #include <stdio.h>
2. int main()
3. {
4.  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
5.  int* p = arr;
6.  int sz = sizeof(arr) / sizeof(arr[0]);
7.  for (int i = 0; i < sz; i++)
8.  {
9.    printf("%d ", p[i]);
10.   }
11.   return 0;
12. }

        我们可以得出:arr[i]等价于*(arr+i)。

        3.3 一维数组传参本质

       我们在学习函数时,也传递过数组。那么,我们到底传过去的是什么呢?

1. #include <stdio.h>
2. void test(int arr[])
3. {
4.  int sz = sizeof(arr) / sizeof(arr[0]);
5.  printf("%d", sz);
6. }
7. int main()
8. {
9.  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
10.   test(arr);
11.   return 0;
12. }

       结果如下:

       这就要学习数组传参的本质了,说本质上数组传参本质上传递的是数组⾸元素的地址。

       所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写 sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函 数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。

       总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

       3.4 指针数组

       指针数组是指针还是数组? 我们类⽐⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。 那指针数组呢?是存放指针的数组。

       指针数组的每个元素都是⽤来存放地址(指针)的。

       指针数组的每个元素是地址,⼜可以指向⼀块区域。

      3.5 指针数组模拟⼆维数组

       

       我们知道 ,二维数组在内存里是连续存放的。

       pa[i]是访问pa数组的元素,pa[i]找到的数组元素指向了整型⼀维数组,pa[i][j]就是整型⼀维数 组中的元素。

1. #include <stdio.h>
2. int main()
3. {
4.  int arr1[] = { 1,2,3,4,5 };
5.  int arr2[] = { 6,7,8,9,10 };
6.  int arr3[] = { 11,12,13,14,15 };
7.  //数组名是数组⾸元素的地址,类型是int*的,就可以存放在pa数组中
8.  int* pa[3] = { arr1, arr2, arr3 };
9.  int i = 0;
10.   int j = 0;
11.   for (i = 0; i < 3; i++)
12.   {
13.     for (j = 0; j < 5; j++)
14.     {
15.       printf("%d ", pa[i][j]);
16.     }
17.     printf("\n");
18.   }
19.   return 0;
20. }

    3.6 数组指针变量

       前面我们已经学习过了指针数组,指针数组是把指针放到一个数组中,那么,数组指针变量是指针变量?还是数组?

       答案是:指针变量。

       我们已经熟悉:

        • 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。

        • 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

       那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

1. int* p1[10];
2. int (*p2)[10];

       大家可以先猜测一下p1和p2分别是什么?

       数组指针变量

int (*p)[10];

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

       那问题来了,那该这么初始化?数组指针本质是什么?答案是指针。指针是用来干什么的?答案是存放地址?那数组指针是不是可以理解为:存放数组的地址。所以:如果要存放个数组的地址,就得存放在数组指针变量中。

1. int arr[10] = {0};
2. int (*p)[10] = &arr;

       通过调试,我们发现&arr与p的类型一致,证明我们的结论是正确的。

       数组指针类型解析:

1. int(*p)[10] = &arr;
2. |   |   |
3. |   |   |
4. |   |   p指向数组的元素个数
5. |   p是数组指针变量名
6. p指向的数组的元素类型

3.7 二维数组传参本质

       学习到这里我们就可以解释二维数组传参本质。当时我们代码如下:

1. 
2. #include <stdio.h>
3. int main()
4. {
5.  int arr1[] = { 1,2,3,4,5 };
6.  int arr2[] = { 6,7,8,9,10 };
7.  int arr3[] = { 11,12,13,14,15 };
8.  //数组名是数组⾸元素的地址,类型是int*的,就可以存放在pa数组中
9.  int* pa[3] = { arr1, arr2, arr3 };
10.   int i = 0;
11.   int j = 0;
12.   for (i = 0; i < 3; i++)
13.   {
14.     for (j = 0; j < 5; j++)
15.     {
16.       printf("%d ", pa[i][j]);
17.     }
18.     printf("\n");
19.   }
20.   return 0;
21. }
22. 
23.

       这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?

        ⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。

       所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀ 维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类 型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀ ⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

1. #include<stdio.h>
2. void test(int *p[5],int a,int b)
3. {
4.  for (int i = 0; i < a; i++)
5.  {
6.    for (int j = 0; j < b; j++)
7.    {
8.      printf("%d", *((*p+i)+j));
9.    }
10.   }
11. }
12. int main()
13. {
14.   int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
15.   test(arr, 3, 5);
16.   return 0;
17. }

       总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

四、指针与函数

       4.1 函数指针变量的创建

       什么是函数指针呢?通过前面的学习我们不难知道。函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。

       那我们不禁要问?函数真的有地址吗?我们来代码验证一下:

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

       确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅ 式获得函数的地址。

        如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针 ⾮常类似。

1. #include<stdio.h>
2. void test()
3. {
4.  ;
5. }
6. int add(int x, int y)
7. {
8.  return x + y;
9. }
10. int main()
11. {
12.   int x = 3;
13.   int y = 4;
14.   test();
15.   int ret = add(x, y);
16.   void(*p)() = test;
17.   int(*p1)(int, int) = add;//里面有无x,y都一样
18.   return 0;
19. }

       函数指针类型解析:

1. int (*pf3) (int x, int y)
2. |    | ------------
3. |    |   |
4. |    |   pf3指向函数的参数类型和个数的交代
5. |    函数指针变量名
6. pf3指向函数的返回类型
7. int (*) (int x, int y) //pf3函数指针变量的类型

       4.2 函数指针变量的使⽤

       通过函数指针调⽤指针指向的函数。

1. #include<stdio.h>
2. int add(int x, int y)
3. {
4.  return x + y;
5. }
6. int main()
7. {
8.  int x = 2;
9.  int y = 3;
10.   int ret = add(x, y);
11.   int(*p1)(int, int) = add;
12.   printf("%d\n", p1(2,3));
13.   printf("%d\n", (*p1)(2, 3));
14. printf("%d\n",ret);
15.   return 0;
16. }

       4.3 typedef关键字

       typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。

       像unsigned int 你觉得写得麻烦,你就可以把它定义成unit。

typedef unsigned int unit;

       如果是指针类型,能否重命名呢?其实也是可以的。

typedef int* pt_t;

       但是对于数组指针和函数指针稍微有点区别: ⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5];//parr_t必须放入*右边

函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

1. 
2. typedef void(*pf_t)(int);//parr_t必须放入*右边

       4.4 函数指针数组

       数组是⼀个存放相同类型数据的存储空间,那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?如下:

int (*p[10])();

       p 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

五、转移表

       函数指针数组的⽤途:转移表。

       举例:计算器的⼀般实现:

       在没学习函数指针数组之前:

1. #include<stdio.h>
2. void menu()
3. {
4.  printf("*************************\n");
5.  printf(" *****1:add 2:sub *******\n");
6.  printf(" *****3:mul 4:div *******\n");
7.  printf(" *******0:exit **********\n");
8.  printf("*************************\n");
9.  printf("请选择:");
10. }
11. int add(int x, int y)
12. {
13.   return x + y;
14. }
15. int sub(int x, int y)
16. {
17.   return x - y;
18. }
19. int mul(int x, int y)
20. {
21.   return x * y;
22. }
23. int div(int x, int y)
24. {
25.   return x / y;
26. }
27. int main()
28. {
29.   int x = 0;
30.   int y = 0;
31.   int input = 0;
32.   int ret = 0;
33.   do
34.   {
35.     menu();
36.     scanf("%d", &input);
37.     switch (input)
38.     {
39.     case 1:
40.       printf("请输入要计算的两个数:");
41.       scanf("%d%d", &x, &y);
42.       ret =add(x,y);
43.       printf("ret = %d", ret);
44.       break;
45.     case 2:
46.       printf("请输入要计算的两个数:");
47.       scanf("%d%d", &x, &y);
48.       ret = sub(x, y);
49.       printf("ret = %d", ret);
50.       break;
51.     case 3:
52.       printf("请输入要计算的两个数:");
53.       scanf("%d%d", &x, &y);
54.       ret = mul(x, y);
55.       printf("ret =%d", ret);
56.       break;
57.     case 4:
58.       printf("请输入要计算的两个数:");
59.       scanf("%d%d", &x, &y);
60.       ret = div(x, y);
61.       printf("ret = %d", ret);
62.       break;
63.     default:
64.       printf("选择错误,重新选择");
65.       break;
66.     }
67.   } while (input);
68.   return 0;
69. }

       我们可以看出,不仅非常繁琐,还涉及许多不必要的重复,这是我们就可以来优化一下:

1. #include<stdio.h>
2. void menu()
3. {
4.  printf("*************************\n");
5.  printf(" *****1:add 2:sub *******\n");
6.  printf(" *****3:mul 4:div *******\n");
7.  printf(" *******0:exit **********\n");
8.  printf("*************************\n");
9.  printf("请选择:");
10. }
11. int add(int x, int y)
12. {
13.   return x + y;
14. }
15. int sub(int x, int y)
16. {
17.   return x - y;
18. }
19. int mul(int x, int y)
20. {
21.   return x * y;
22. }
23. int div(int x, int y)
24. {
25.   return x / y;
26. }
27. int main()
28. {
29.   int x = 0;
30.   int y = 0;
31.   int input = 0;
32.   int ret = 0;
33.   int (*p[5])(x, y) = { 0,add,sub,mul,div };
34.   do
35.   {
36.     menu();
37.     scanf("%d", &input);
38.     scanf("%d%d", &x, &y);
39.     if (input > 0 && input < 5)
40.     {
41.       printf("%d", (p[input])(x, y));
42.     }
43.   } while (input);
44.   return 0;
45. }

       我们可以看到更加的简洁,这就是函数指针数组的好处。

完!

相关文章
|
1月前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
36 1
|
25天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
45 0
|
24天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
24天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
24天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
1月前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
30天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
1月前
|
C语言
C语言指针(3)
C语言指针(3)
11 1
|
1月前
|
C语言
C语言指针(2)
C语言指针(2)
13 1