1.字符指针
2.指针数组
3.数组指针
4.数组传参和指针传参
5.函数指针
6.函数指针数组
7.指向函数指针数组的指针
8.回调函数
一.字符指针
int main()
{
char* p = "abcdef"; //这是一个常量字符串,意思并不是把"abcdef"传给指针p,因为在32位平台指针只占4个字节,而这个都有6个
//字节了,所以他的意思是把首字符的地址传给了a
//printf("%c\n", *p); a
printf("%s\n", p); // "abcdef"
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdef";
char* p1 = "abcdef";
char* p2 = "abcdef";
if (p1 == p2)
{
printf("hehe\n");
}
else
{
printf("haha\n"); //结果会是hehe 因为他们都是常量字符串,所以两个指针值向的是同一个空间
}
//if (arr1 == arr2)
//{
// printf("hehe\n");
//}
//else
//{
// printf("haha\n");
//} 结果会是haha 因为arr1 和 arr2分别指向了两个不同地址空间
return 0;
}
2.指针数组 — 核心是数组,用来存放指针的
int main()
{
int arr[10] = {
0 }; // 整形数组
char ch[5] = {
0 };//字符数组
int* parr[4]; //存放整形指针的数组 -- 指针数组
char* pch[5];//存放字符指针的数组 -- 指针数组
}
指针数组的使用
int main()
{
int arr1[] = {
1, 2, 3, 4, 5 };
int arr2[] = {
2, 3, 4, 5, 6 };
int arr3[] = {
3, 4, 5, 6, 7 };
int* parr[] = {
arr1, arr2, arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(parr[i] + j));
}
printf("\n");
}
return 0;
}
3.数组指针— 本质是指针
int main()
{
int* p = NULL; //p是整形指针--指向整形的指针--可以存放整形的地址
char* pc = NULL;//pc是字符指针--指向字符的指针--可以存放字符的地址
//数组指针--指向数组的指针--存放数组的地址
//arr -- 首元素地址
//&arr[0] --首元素的地址
//&arr --数组的地址
int arr[10] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int(*p)[10] = &arr; //数组的地址要存起来
//如果不加()p会先和[]进行结合就会变成指针数组
}
对于二维数组来说,你需要把二维数组想象成为一维数组,第一行是第一个元素,第二行是第二个元素 …来思考
//数组传参
void print1(int arr[3][5], int x, int y)
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
//指针传参
void print2(int(*p)[5], int x, int y)
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
printf("%d ", *(*(p + 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 } };
print1(arr, 3, 5); // 这里有两种设计方案,对于这个可以认为传过去的是一个二维数组
print2(arr, 3, 5); // 这里可以认为arr代表的是第一行的地址,也就是数组指针
return 0;
}
arr[i] == *(arr+i) 以arr为起始地址访问下标为i的元素
二维数组传参的时候可以省略行,但绝不可以省略列,不然就会报错。
四.数组传参和函数传参
对于第四模块数组传参和函数传参,已经在上面的部分进行了融合,所以这一块在这里省略。
五.函数指针
函数指针–是指向函数的指针–存放函数地址的指针
在函数里面 &函数名 和 函数名 都是函数的地址
int Add(int x,int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;//对于传参部分类型一定要表达清楚,但是可以省略参数
int(*pa)(int, int) = Add; //
printf("%d\n", (*pa)(a, b));
return 0;
}
//void (*)(char*) 就是这个函数指针的类型
void Print(char* str)
{
printf("%s\n", str);
}
int main()
{
void(*p)(char*) = Print;
(*p)("hello world");
return 0;
}
思考题:
1.(* (void(*)() ) 0) ()
把0地址强制类型转换为void ( * )( )的函数指针类型,然后解引用,在调用这个函数。
2.void ( * signal(int,void( * )(int)))(int)
signal是一个函数名,里面的参数一个是int,一个是void( * ) (int)的函数指针,然后你去掉函数名和参数,你会发现返回类型也是
void( * )(int) 的函数指针类型。
typedef void(*pfun_t)(int);
typedef unsigned int uint;
你认为我们一般重命名应该是把pfun_t放在最后面,但是对于函数指针来重命名是不一样的,他需要放在函数名的位置
所以我们可以把上面那个晦涩的代码修改为
pfun_t signed(int, pfun_t);
思考二.
你会发现对于这么一段代码来说,你前面不管加多少个*来解引用最后输出的结果都是30,所以可以说 * 的这个解引用对于函数指针来说是没有作用的。
int Add(int x,int y)
{
return x+y;
}
int main()
{
int a = 10;
int b = 20;
int(*pa)(int, int) = Add;
printf("%d\n", pa(a, b));
printf("%d\n", (*pa)(a, b)); //这个方法更容易理解一些
printf("%d\n", (**pa)(a, b));
printf("%d\n", (***pa)(a, b));
}
六.函数指针数组
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()
{
//需要一个数组,这个数组可以存放4个函数的地址 --- 函数指针的数组
int(*pa[4])(int, int) = {
Add, Sub, Mul, Div }; // 这里[]的优先级更高所以pa回西安和它结合形成一个数组,剩下的类型就是
//函数指针类型
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n", pa[i](2, 3));
}
return 0;
}
训练:
char* my_strcpy(char* dest,const char* src)
//写一个函数指针pf,能够指向my_strcpy
char* (*pf)(char*, const char*)
//写一个函数指针数组pfArr,能够存放4个my_strcpy函数的地址
char* (*pfArr[4])(char* ,const char*)
函数指针数组使用例子:计算器
#include<stdio.h>
void menu()
{
printf("**************************\n");
printf("****1.add 2.sub ***\n");
printf("****3.mul 4.div ***\n");
printf("*** 0.exit ***\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;
}
//如果我们需要在这里添加很多种不一样的求法比如按位或,按位与等等,你会发现我们缩写的case语句将会变得十分的长和繁杂
int main()
{
int input = 0;
int x = 0;
int y = 0;
int(*pfArr[5])(int, int) = {
0, Add, Sub, Mul, Div };//这里前面添加一个0,就可以很好地和我们menu里面的数值所对应上
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf_s("%d%d", &x, &y);
int ret = pfArr[input](x, y);
printf("%\n", ret);
}
else if (input == 0)
{
printf("退出\n");
}
else
{
printf("选择错误\n");
}
} while (input);
/* switch (input)
{
case 1:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", Add(x, y));
break;
case 2:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", Sub(x, y));
break;
case 3:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", Mul(x, y));
break;
case 4:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", Div(x, y));
break;
case 0:
printf("退出\n");
break;
default:
printf("输入错误,请重新选择!\n");
break;
}
} while (input);*/
return 0;
}
8.回调函数
回调函数:就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这事回调函数你会发现上面的switch-case语句中出现了多次一样的代码行,为了避免冗余,我们可以在写一个函数,来封装这几行代码
void menu()
{
printf("**************************\n");
printf("****1.add 2.sub ***\n");
printf("****3.mul 4.div ***\n");
printf("*** 0.exit ***\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;
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", pf(x, y));
}
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;
}
9.指向函数指针数组的指针
int(*pf)(int, int);//pf 是一个函数指针
int(*pfArr[4])(int, int);//pfArr是一个函数指针的数组
int(*(*ppfArr)[4])(int, int) = &pfArr; //是一个指向函数指针数组的指针