1. 字符指针变量
2. 数组指针变量
3. ⼆维数组传参的本质
4. 函数指针变量
5. 函数指针数组
6. 转移表
一、字符指针变量
指针的类型有很多类型,我们常见的是int * ,那么接下来我们来介绍另一种,char *类型的指针
#include<stdio.h>
int main()
{
char c = 'a';
char* pr = &c; //存储字符a的地址
printf("%c\n", *pr);
*pr = 'b'; //通过解引用来找到该地址对应的值,然后进行修改
printf("%c\n", *pr);
return 0;
}
这里我们储存的是一个字符,得到了该字符的地址,通过地址就可以对该字符来进行操作。
那么我们存储一个字符串呢?它是如何储存的呢?
int main()
{
const char *pr= "abcdef";
printf("%s", pr);
return 0;
}
这个代码我们初学的时候很容易就会理解把“abcdef”的地址存放在地址中,但是实际上只是把首字符(a)的地址放到了pr中,再打印的时候编译器会跟根据首地址来进行访问打印。
如图所示:
我们来举个例子说明一下
int main()
{
char arr1[] = "hello";
char arr2[] = "hello"; //创建两个字符数组,让他们储存的数据相同,观察其储存的位置是否一样
const char* pr1 = "hello";
const char* pr2 = "hello"; //创建两个字符指针变量,让他们储存的数据相同,观察其储存的位置是否一样
if (arr1 == arr2)
{
printf("储存位置相同\n");
}
else
{
printf("储存位置不同\n");
}
if (pr1 == pr2)
{
printf("储存位置相同\n");
}
else
{
printf("储存位置不同\n");
}
return 0;
}
这里我们可以发现
1.arr1和arr2所储存的位置是不同的,即⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块
2.pr1和pr2的位置是相同的,这是因为C/C++会把常量字符串存储到单独的⼀个内存区域,当⼏个指针指向同⼀个字符串的时候,它们会指向同一块内存。
二、数组指针变量
我们前边介绍了指针数组:*储存指针的数组
格式是: int pr[] = { 0}
那么数组指针是什么呢?
我们也来类比一下
*整形指针变量 int pr :是能够储存整形变量的地址,能够指向整形
数据的指针变量
那么数组指针:就是能储存数组的地址,能够指向数组的指针变量
我们来猜一下
int main()
{
int *pr[5] = {0}; //指针数组
int (*pr)[5] = { 0 }; //数组指针
return 0;
}
我们来对int (pr)[5] 来解读一下
解读:pr先与 * 结合说明pr是一个指针变量,然后指向的是一个大小为5的整形数组。说明pr是一个指针,指向一个数组,叫数组指针。
这⾥要注意:[]的优先级要⾼于号的,所以必须加上括号来保证p先和结合
2.2 数组指针变量怎么初始化
数组指针变量是用来储存数组的地址的
int main()
{
int arr[10] = { 0 };
&arr; //我们通过取地址加数组名可以得到整个数组的地址
int (*pr)[10] = { &arr }; //这里就可以通过数组指针来存储数组的地址
return 0;
}
这里我们发现&arr和pr地址是一样的,说明我们已经用数组指针正常储存了&arr的地址了
三、二维数组传参的本质
我们先复习一下我们原来的写法。
void print(int arr[3][5], int r, int z)
{
for (int i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < z; 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;
}
我们原来在写函数对数组传参的时候,形参也可以用数组接收。
我们是否可以用其它方法来接受呢?当然接下来我们来介绍另一种。
这里我们来回忆一下,我们在介绍一维数组传参的本质的时候证明了传的是数组首元素的地址。
那么对于二维数组来说,其本质是由一维数组组成的,同理我们可以猜测,二维数组传递的是第一行的一维数组。
可以理解为:⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
总结:根据数组名是数组首元素这个地址可以知道,二维数组的数组名表示的就是第一行一维数组的地址,是一维数组的地址。
接下来我们演示一下用数组指针来接收二维数组的地址
void print(int (*pr) [5], int r, int z) //接收一位数组的地址,我们要用的是数组指针
{
for (int i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < z; j++)
{
printf("%d ", *(*(pr + i) + j)); // arr[i][j] == *(*(pr+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); //传递的是二维的数组名,即第一位数组的地址,类型是int [5]
return 0;
}
结论:综上我们可以发现二维数组传参的时候,形参的既可以写成数组形式,也可以写成指针形式。
四、函数指针变量
前面我们可以发现不管是整形指针,数组指针,我们都可以通过其指针来拿到其中的值。
那么这里我们猜测我们是否把函数的指针拿出来,然后通过指针来
调用函数呢?
我们来先检测一下函数是否有地址呢?
void text()
{
printf("练习两年半\n");
}
int main()
{
text();
printf("%p\n", text);
printf("%p\n", &text);
return 0;
}
这里我们发现函数名和&函数名的地址是一样的。
结论:函数名就是函数的地址
那么我们如何把函数的地址存放起来呢?
当然是运用函数指针呢,其形式和数组指针的形式非常相像。
如下:
void text() //这里的函数类型是空
{
printf("练习两年半\n");
}
int add(int x, int y) //这里的函数类型是int
{
return x + y;
}
int main()
{
text();
void (*pr)() = text; //所以我们这里的类型也需要符合为空
void (*pr2)() = &text;
int a = 0;
int b = 5;
add(a, b);
int (*pr3)(int x, int y) = add; //所以我们这里的类型也需要符合int
int (*pr3)(int x, int y) = &add;
return 0;
}
我们来解析一下函数指针类型的解析
4.2 函数指针变量的使用
通过函数指针来调用函数
int add(int a, int b)
{
return a + b;
}
int main() //函数指针的使用
{
int (*pr)(int, int) = add;
printf("%d\n", (*pr)(3, 5));//函数名是函数地址,通过解引用找到函数名
printf("%d", pr(6, 5)); //pr拿到是地址
//因为函数名是函数的地址,所以这两种是一样的,都可以实现
}
五、函数指针数组
我们知道指针数组是用来储存地址的数组的
那么函数指针数组,就是用来储存函数地址的数组的
我们来介绍一下函数指针数组是怎样定义的吧
int mian()
{
int* pr[5] = { 0 }; //这是指针数组
// int (*pr)[5] = 0; //这个是函数指针
int (*pr[5])() = { 0 }; // pr先与[5]结合说明其是数组指针 ,数组的内容是int(*)() 类型的函数指针
}
六、转移表
函数指针数组的作用是什么呢?
转移表
举例:计算器的一般实现
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 num1 = 0;
int num2 = 0;
do
{
printf("**********************\n");
printf("***1.add 2.sub*******\n");
printf("***3.mui 4.div*******\n");
printf("**********************\n");
scanf("%d", &input);
switch (input)
{
int ret = 0;
case 1:
scanf("%d%d", &num1, &num2);
ret = add(num1, num2);
printf("%d\n", ret);
break;
case 2:
scanf("%d%d", &num1, &num2);
ret = sub(num1, num2);
printf("%d\n", ret);
break;
case 3:
scanf("%d%d", &num1, &num2);
ret = mul(num1, num2);
printf("%d\n", ret);
break;
case 4:
scanf("%d%d", &num1, &num2);
ret = div(num1, num2);
printf("%d\n", ret);
break;
case 0:
printf("结束计算");
break;
default:
printf("输入错误,请输入数字1,2,3,4\n");
break;
}
} while (input);
return 0;
}
通过该代码我们可以实现两个整数的加减乘除,但是太繁琐,我们是否有其它的方法来精简该代码呢?
这里就可以运用到我们的函数指针数组。
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 num1 = 0;
int num2 = 0;
int input = 0;
//函数名就是函数的地址,所以我们创建一个函数指针数组,直接储存函数名就可以
int (*pr[5])(num1, num2) = { 0,add,sub,mul,div }; //因为这里的下标是从0开始的,我们牺牲一个空间来使其对应1到4分别代表+ - * /
do
{
printf("**********************\n");
printf("***1.add 2.sub*******\n");
printf("***3.mui 4.div*******\n");
printf("**********************\n");
printf("请输入,1,2,3,4,分别代表+ - * /\n");
printf("输入0退出程序\n");
printf("输入其它数字重新选择\n");
scanf("%d", &input); //这里我们用intut 实现了2种操作
int ret = 0;
if (input >= 1 && input <= 4) // 1到4分别代表+ - * /
{
printf("输入2个操作数\n");
scanf("%d%d", &num1, &num2);
ret = (*pr[input])(num1, num2); //1.通过input当作下标访问函数指针数组的函数 //通过下标来访问每个函数,然后传参进行使用
printf("%d\n", ret);
}
else if (0 == input)
{
printf("结束计算\n");
}
else
{
printf("输入错误请重新输入,1,2,3,4\n");
}
}
while (input); // 2.给循环赋条件
return 0;
}
这里写的多是为了方便同志们理解,大家如果理解许多的printf函数就可以省略
总结:我们可以看出运用函数指针数字可以很好的使我们的代码变得简洁。
年轻就要多学