目录
1. 字符指针变量
2. 数组指针变量
2.1 数组指针变量是什么
2.2 数组指针变量怎么初始化
3. 二维数组传参的本质
4. 函数指针变量
4.1 函数指针变量的创建
4.2 函数指针变量的使用
4.3 两段代码
4.3.1 typedef 关键字
5. 函数指针数组
在指针的类型中我们知道有一种指针类型为字符指针 char*
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'w';
return 0;
}
(1)可以把字符串想象为一个字符数组,但是这个字符串是不能修改的(最好加const);
(2) 当常量字符串出现在表达式中的时候,它的值是第一个字符的地址。
int main()
{
const char* p = "abcdef";//不是把字符串abcdef\0存放在p中,而是把第一个字符的地址存放在p中
printf("%c\n", p);
return 0;
}
这里有一个例子:
#include
int main()
{
char str1[] = "abcdef";
char str2[] = "abcdef";
const char* str3 = "abcdef";
const char* str4 = "abcdef";
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;
}
结果如下:
这里的 str1 和 str2 是两个不同的数组,只不过是它们存放的常量字符串一样而已。用相同的常量字符串去初始化不同的数组的时候会开辟出不同的内存块。所以 ,str1 和str2 是不一样的。
而 str3 和 str4 指向的是同一个常量字符串。对于常量字符串,我们只能拿它的内容,不能对其进行修改,这样的存储一份就够了。C\C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际指向的是同一块内存。所以,str3 和str4 是一样的。
指针数组是一种数组,数组中存放的是地址(指针)
数组指针变量是一种指针变量。
整型指针变量:存放整型变量的地址,能够指向整型数据的指针。
浮点型指针变量:存放浮点型变量的地址,能过指向浮点型数据的指针。
所以,数组指针变量存放的应该是数组的地址,能够指向狮子的指针变量。
int main()
{
int n = 10;
int* pa = &n;
char ch = 'w';
char* pc = &ch;
float f = 3.14f;
float* pf = &f;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*parr)[10] = &arr;
return 0;
}
注意:
int *p1[10]
p1是数组,数组有10个元素,每个元素的类型是 int* ,所以p1是指针数组。
int (*p2)[10]
p2是指针,指针指向的是数组,数组有10个元素,每个元素的类型是 int 。
(因为 [ ] 的优先级高于 * ,* 会和 [ ] 先结合,所以用()将 * 和 p2 放在一起)
做个比较:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p1 = arr; //首元素的地址
// int* int*
int* p2 = &arr[0]; //首元素的地址
// int* int*
int(*p3)[10] = &arr; //数组的地址
//int(*)[10] int(*)[10]
return 0;
}
数组指针变量是用来存放数组地址的,那么怎么获得数组地址呢?
int arr[10] = {0};
&arr;//得到的是整个数组的地址
如果要存放整个数组的地址,就要存放在数组指针变量里。
int (*p)[10] = &arr;
对其解析为:
int (*p)[10] = &arr;
| | |
| | |
| | p指向的数组的元素个数
| p是数组指针变量名
p指向的数组的元素的类型
int (*)[10] //p数组指针变量的类型
可以看到, &arr 和 p 的类型是一样的。
之前,我们有一个二维数组需要传给一个函数的时候,写法如下:
这里实参是二维数组,形参也写成二维数组的形式。
那还有其他的写法吗?
一维数组传参,形参可以是数组,也可以是指针。
写成数组更加直观,为了方便理解;写成指针,因为数组传参传递的是数组第一个元素的地址。
二维数组的起始点可以看作是每一个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组,那么二维数组的首元素就是第一行,是一个一维数组。 首元素的地址就是第一行的地址。
这样就得到了新的写法:
总结:二维数组传参,可以写成数组,也可以写成指针的形式。
什么是函数指针变量?
根据类比可知:函数指针变量是用于存放函数地址的,后续通过地址能够调用函数。
函数是否有地址呢?
数组名——数组的首元素的地址
&数组名——整个数组的地址
函数名——函数的地址
&函数名——函数的地址
函数指针变量的写法和数组指针变量的写法很相似。
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 (*pf4)(int x, int y) = &Add;//x和y可以省略
对其解析为:
int (*pf) (int x, int y) = &Add;
| | |
| | |
| | pf指向的函数的参数类型和个数的交代
| pf是函数指针变量名
pf指向的函数的返回类型
int (*)(int x, int y) //pf函数指针变量的类型
#include
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(2, 6));
return 0;
}
int main()
{
(*(void(*)())0)();
return 0;
}
这段代码是一次函数调用:
(1)把0这个整数值,强制类型转换为一个函数的地址,这个函数没有参数,返回类型是void
(2)调用0地址处的函数
void (*signal(int, void(*)(int)))(int);
int main()
{
return 0;
}
这段代码是函数声明:
(1)signal是一个函数,有两个参数,第一个是int类型
(2)第二个是函数指针类型,该指针指向的函数参数是int,返回类型是void
(3)signal函数的返回类型是 void (*)(int) 类型的函数指针
(4)该指针指向的函数参数是int,返回类型是void
typedef 是用于类型重命名的,可以将复杂的类型简单化。
比如:
typedef unsigned int uint;
//将unsigned int 重命名为 uint
typedef int* ptr_t;
//将 int*重命名为 ptr_t
但是,对于数组指针和函数指针有一些区别:
typedef int(*parr_t)[5];
//将 int(*)[5] 重命名为 parr_t
//新的类型名必须在*的右边
typedef void (*pfun_t)(int);
//将 void(*)(int) 重命名为 pfun_t
//新的类型名必须在*的右边
所以上述的第二个代码可以简化为:
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);
数组是一个存放相同类型的数据的存储空间,比如指针数组:
int* arr[10];
//数组的每个元素是int* ,整型指针数组
char* arr[6];
//数组的每个元素是char* ,字符指针数组
那把函数的地址存到一个数组中,那这个数组就叫做函数指针数组,函数指针数组的定义如下:
int (*parr[])();
举个例子:
#include
int Fun1(int x, int y)
{
return x + y;
}
int Fun2(int x, int y)
{
return x - y;
}
int Fun3(int x, int y)
{
return x * y;
}
int Fun4(int x, int y)
{
return x / y;
}
int main()
{
//int (*pf)(int, int) = Fun1;//pf是函数指针
int (*pfArr[4])(int, int) = { Fun1, Fun2, Fun3, Fun4 };
// 0 1 2 3
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = pfArr[i](6, 2);
printf("%d\n", ret);
}
}
制作一个计算器
首先是最直接的一种写法:
#include
void menu()
{
printf("******************************\n");
printf("**** 1. + 2. - ****\n");
printf("**** 3. * 4. / ****\n");
printf("**** 0. exit ****\n");
printf("******************************\n");
}
int Fun1(int x, int y)
{
return x + y;
}
int Fun2(int x, int y)
{
return x - y;
}
int Fun3(int x, int y)
{
return x * y;
}
int Fun4(int x, int y)
{
return x / y;
}
int main()
{
int x = 0;
int y = 0;
int ret = 0;
int input = 0;
do {
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Fun1(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Fun2(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Fun3(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Fun4(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("输入错误,重新选择\n");
break;
}
} while (input);
return 0;
}
这种写法虽然直接,便于理解,但是这只是4种基本运算,如果加上&&、||等等运算, 代码中的switch语句会越来越长,这时就需要用到函数指针数组的方式解决。
#include
void menu()
{
printf("******************************\n");
printf("**** 1. + 2. - ****\n");
printf("**** 3. * 4. / ****\n");
printf("**** 0. exit ****\n");
printf("******************************\n");
}
int Fun1(int x, int y)
{
return x + y;
}
int Fun2(int x, int y)
{
return x - y;
}
int Fun3(int x, int y)
{
return x * y;
}
int Fun4(int x, int y)
{
return x / y;
}
int main()
{
int x = 0;
int y = 0;
int ret = 0;
int input = 0;
do {
menu();
int(*pfArr[])(int, int) = {NULL, Fun1, Fun2, Fun3, Fun4 };
// 0 1 2 3 4
printf("请选择:");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
pfArr[input](x, y);
ret = pfArr[input](x, y);
printf("%d\n", ret);
}
else
{
printf("选择错误,请重新输入\n");
}
} while (input);
return 0;
}
通过函数指针数组的方式大大地简化了代码。而且,未来想增加其他的运算,只要添加运算,把函数名不断地往菜单和数组里加,调整input的范围就行。