目录
字符指针变量
数组指针变量
二维数组传参的本质
函数指针变量
函数指针数组
转移表
正文开始
字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针char*
;
一般使用:
1 2 3 4 5 6 7 8 9 #include <stdio.h> int main () { char ch = 'w' ; char * pc = &ch; *pc = 'b' ; printf ("%c\n" , *pc); return 0 ; }
还有一种使用方式如下:
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main () { const char * p = "abcdef" ; printf ("%c\n" , *p); printf ("%s\n" , p); printf ("%s\n" , arr); return 0 ; }
画图理解:
代码 const char* p = "abcdef"; 特别容易让同学以为是把字符串
abcdef 放到字符指针
p ⾥了,但是本质是把字符串
abcdef ⾸字符的地址放到了
p 中。
《剑指offer》中收录了一道和字符串相关的笔试题,我们一起来学习一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> 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\n" ); return 0 ; }
输出结果:
画图理解:
这里str 3 和str 4 指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str 1 和str 2 不同,str 3 和str 4 相同。
数组指针变量 数组指针变量是什么? 指针数组是一种数组,数组中存放的是地址(指针)。
数组指针变量是指针变量?还是数组?
答案是:**指针变量
**。
类比:
字符指针 -char* -指向字符的指针 - 字符指针变量中存放字符变量的地址。
1 2 char ch = 'w' ;char * p = &ch;
整型指针 -int* -指向整型的指针 - 整型指针变量中存放整型变量的地址。
1 2 int a = 10 ;int * p = &a;
数组指针 -char* -指向数组的指针 - 数组指针变量中存放
数组的地址 `。 &数组名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> int main () { char arr[10 ] = {1 , 2 , 3 , 4 , 5 }; char (*p)[10 ] = &arr; return 0 ; }
运行结果:
我们已经熟悉:
整形指针变量 :int * pint
;存放整形变量的地址
,能够指向整形数据的指针
。
浮点型指针变量 :float * pf
;存放浮点型变量的地址
,能够指向浮点型数据的指针
。
数组指针变量 :存放的应该是数组的地址,能够指向数组的指针变量
。
练习:
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> int main () { char * ch[5 ] = {0 }; char * (*p)[5 ] = &ch; return 0 ; }
运行结果:
下面代码哪个是数组指针变量 ?
1 2 int *p1[10 ];int (*p2)[10 ];
思考一下:p 1 ,p 2 分别是什么 ?
数组指针变量
解释 :p先和*结合,说明p是一个指针变量,然后指针指向的是一个大小为 10 个整型的数组。所以p是一个指针,指向一个数组,叫 **数组指针**
。
注意 :[]的优先级要高于*号的,所以必须加上()来保证p先和*结合
。
数组指针变量怎么初始化
数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的&数组名。
1 2 int arr[10 ] = { 0 };&arr;
如果要存放个数组的地址,就得存放在 数组指针变量 中,如下:
我们调试也能看到&arr和p的类型是完全一致的
。
数组指针类型解析 :
1 2 3 4 5 6 int (*p) [10 ] = &arr; | | | | | | | | p指向数组的元素个数 | p是数组指针变量名 p指向的数组的元素类型
二维数组传参的本质
有了数组指针的理解,我们就能够讲一下二维数组传参的本质了。
过去我们有一个二维数组的需要传参给一个函数的时候,我们是这样写的
:
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 void Pirnt (int a[ 3 ][ 5 ], int r, int c) { for (i= 0 ; i<r; i++) { for (j= 0 ; j<c; j++) { printf ("%d " , a[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 }}; Pirnt(arr, 3 , 5 ); return 0 ; }
运行结果:
1 2 3 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7
类比
:
一维数组传参
:
数组名是首元素地址
一维数组在传参的时候,传递的是数组的地址,也就是数组的首元素的地址 。
函数的参数可以写成数组,也可以写成指针形式 。
二维数组传参
:
数组名是首元素地址
二维数组可以理解为一维数组的数组,也就是每个元素是一个一维数组 。
二维数组的每一行可以看做是一个一维数组,所以二维数组其实是一个一维数组,二维数组的首元素就是它的第一行 。
所以二维数组的数组名表示的就是第一行的地址,是一维数组的地址 。
如下图:
根据上面的例子,第一行的一维数组的类型就是int [5],所以第一行的地址的类型就是数组指针类型int(*)[5]。那就意味着
二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址 ,那么形参也是可以写成指针形式的
。如下:
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 void Print (int (*arr)[5 ], int r, int c) { 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 }}; Print(arr, 3 , 5 ); return 0 ; }
运行结果
:
1 2 3 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7
总结: 二维数组传参,形参的部分可以写成数组,也可以写成指针形式
。
补充解释
:
第9行代码 :*(*(arr+i)+j)
函数指针变量 函数指针变量的创建
什么是函数指针变量呢?
根据前面学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论
:
函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的 。
那么函数是否有地址呢?
做个测试
:
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 #include <stdio.h> int Add (int x, int y) { return x + y; } int main () { int (* pf)(int , int ) = Add; int ret2 = (*pf)(4 , 5 ); printf ("%d\n" , ret2); int ret = Add(4 , 5 ); printf ("%d\n" , ret); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> void test () { printf ("hehe\n" ); } int main () { printf ("test: %p\n" , test); printf ("&test: %p\n" , &test); return 0 ; }
输出结果如下
:
1 2 test: 005913 CA &test: 005913 CA
确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过&函数名的方 式获得函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针 非常类似
。如下 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void test () { printf ("hehe\n" ); } void (*pf1)() = &test;void (*pf2)()= test;int Add (int x, int y) { return x+y; } int (*pf3)(int , int ) = Add;int (*pf3)(int x, int y) = &Add;
函数指针类型解析
:
1 2 3 4 5 6 7 int (*pf3) (int x, int y)| | ------------ | | | | | pf3指向函数的参数类型和个数的交代 | 函数指针变量名 pf3指向函数的返回类型 int (*) (int x, int y)
函数指针变量的使用
通过函数指针调用指针指向的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <stdio.h> int Add (int x, int y) { return x + y; } int main () { int (*pf3)(int , int ) = Add; printf ("%d\n" , (*pf3)(2 , 3 )); printf ("%d\n" , pf3(3 , 5 )); return 0 ; }
输出结果
:
两段有趣的代码(了解一下
) 代码 1
代码 2
1 void (*signal(int , void (*)(int )))(int );
两段代码均出自:《C陷阱和缺陷》这本书
typedef关键字(了解一下
) typedef是用来类型重命名的,可以将复杂的类型,简单化。
比如,你觉得unsigned int
写起来不方便,如果能写成uint
就方便多了,那么我们可以使用:
1 2 typedef unsigned int uint;
如果是指针类型,能否重命名呢?其实也是可以的,比如,将int*
重命名为ptr_t
,这样写:
但是对于数组指针和函数指针稍微有点区别:
比如我们有数组指针类型int(*)[5]
,需要重命名为parr_t
,那可以这样写:
1 1 typedef int (*parr_t ) [5];
函数指针类型的重命名也是一样的,比如,将void(*)(int)
类型重命名为pf_t
,就可以这样写:
1 1 typedef void (*pfun_t ) (int ) ;
那么要简化代码 2 ,可以这样写:
1 2 typedef void (*pfun_t ) (int ) ;pfun_t signal (int , pfun_t ) ;
函数指针数组 数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组,
比如:
那要把函数的地址存到一个数组中,那这个数组就叫 函数指针数组 ,那函数指针的数组如何定义呢?
1 2 3 int (*parr1[ 3 ])();int *parr2[ 3 ]();int (*)() parr3[ 3 ];
答案是:parr 1
parr1
先和[]
结合,说明parr 1 是数组,数组的内容是什么呢?
是int (*)()
类型的函数指针。
举例
:
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 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 (* pf[4 ])(int , int ) = {Add, Sub, Mul, Div}; int i = 0 ; for (i = 0 ; i < 4 ; i++) { int ret = pf[i](6 , 2 ); printf ("%d\n" , ret); } return 0 ; }
运行结果
:
转移表 函数指针数组
的用途: 转移表
举例:计算器的一般实现
:
(1)
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include <stdio.h> 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 (" 0:exit \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 ; }
(2)
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <stdio.h> void menu () { printf ("********************************\n" ); printf ("****** 1. add 2. sub *****\n" ); printf ("****** 3. mul 4. div *****\n" ); printf ("****** 0. exit *****\n" ); printf ("********************************\n" ); } 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 input = 0 ; int x = 0 ; int y = 0 ; int ret = 0 ; do { menu(); printf ("请选择:" ); scanf ("%d" , &input); switch (input) { case 1 : printf ("请输入两个操作数:" ); scanf ("%d %d" , &x, &y); ret = Add(x, y); printf ("%d\n" , ret); break ; case 2 : printf ("请输入两个操作数:" ); scanf ("%d %d" , &x, &y); ret = Sub(x, y); printf ("%d\n" , ret); break ; case 3 : printf ("请输入两个操作数:" ); scanf ("%d %d" , &x, &y); ret = Mul(x, y); printf ("%d\n" , ret); break ; case 4 : printf ("请输入两个操作数:" ); scanf ("%d %d" , &x, &y); ret = Div(x, y); printf ("%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 ******************************** ****** 1. add 2. sub ***** ****** 3. mul 4. div ***** ****** 0. exit ***** ******************************** 请选择:1 请输入两个操作数:10 20 30 请选择:2 请输入两个操作数:30 10 20 请选择:3 请输入两个操作数:2 3 6 请选择:4 请输入两个操作数:10 2 5 请选择:0 退出计算器
特殊情况
:
(1)(2)缺点
:
代码冗余:代码中有很多重复的函数定义,增加了代码的复杂度。
效率低:每一次用户输入都需要遍历数组,效率低。
(3)
在(2)的基础上,使用函数指针数组
来实现转移表。
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 68 69 70 71 #include <stdio.h> void menu () { printf ("********************************\n" ); printf ("****** 1. add 2. sub *****\n" ); printf ("****** 3. mul 4. div *****\n" ); printf ("****** 0. exit *****\n" ); printf ("********************************\n" ); } 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 input = 0 ; int x = 0 ; int y = 0 ; int ret = 0 ; int (*pfArr[5 ])(int , int ) = {0 , Add, Sub, Mul, Div}; do { menu(); printf ("请选择:" ); scanf ("%d" , &input); if (input >= 1 && input <= 4 ) { printf ("请输入两个操作数:" ); scanf ("%d %d" , &x, &y); ret = pfArr[input](x, y); printf ("%d\n" , ret); } else if (input == 0 ) { printf ("退出计算器\n" ); } 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 ******************************** ****** 1. add 2. sub ***** ****** 3. mul 4. div ***** ****** 0. exit ***** ******************************** 请选择:1 请输入两个操作数:10 20 30 请选择:2 请输入两个操作数:30 10 20 请选择:3 请输入两个操作数:2 3 6 请选择:4 请输入两个操作数:10 2 5 请选择:0 退出计算器
(4)
再次优化,Calc()
函数可以接收一个函数指针作为参数,并调用该函数。
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <stdio.h> void menu () { printf ("********************************\n" ); printf ("****** 1. add 2. sub *****\n" ); printf ("****** 3. mul 4. div *****\n" ); printf ("****** 0. exit *****\n" ); printf ("********************************\n" ); } 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 Calc (int (*pf)(int , int )) { int x = 0 ; int y = 0 ; int ret = 0 ; printf ("请输入两个操作数:" ); scanf ("%d %d" , &x, &y); ret = pf(x, y); printf ("%d\n" , ret); } int main () { int input = 0 ; do { menu(); printf ("请选择:" ); scanf ("%d" , &input); switch (input) { case 1 : Calc(Add); break ; case 2 : Calc(Sub); break ; case 3 : Calc(Mul); break ; case 4 : Calc(Div); 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 ******************************** ****** 1. add 2. sub ***** ****** 3. mul 4. div ***** ****** 0. exit ***** ******************************** 请选择:1 请输入两个操作数:10 20 30 请选择:2 请输入两个操作数:30 10 20 请选择:3 请输入两个操作数:2 3 6 请选择:4 请输入两个操作数:10 2 5 请选择:0 退出计算器
总结
指针变量:指向内存中其他变量的变量,可以用来存放地址、函数指针、数组指针等。
指针数组:存放指针变量的数组,可以用来存放不同类型的指针变量。
函数指针数组:存放函数指针的数组,可以用来实现转移表。
转移表:根据用户输入的选项,调用相应的函数。
完