c语言笔记(鹏哥)上课板书+课件汇总(深入指针4)
函数定义:将函数所有都交代了的过程,返回类型,函数名,函数参数,参数名,函数体函数声明:返回值类型,函数名,参数类型(强调类型)函数调用:函数名,参数注·:上述图片有举例子阅读这种代码,一般需要从里到外慢慢剖析,找里面认识的东西,然后一步一步慢慢的向外探索。
深入指针4
一、数组指针和指针数组的回顾

验证: * 号 和&符号是存在抵消关系的
- [ i ]--------i 数组元素的下标,通过方括号可以进行下标访问,访问下标为i的数组元素
- arr[i][j]------是访问行i,j列的元素
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("%d\n", *p);//实际上就是p里面存放的就是&a,*p=*(&a)=a
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*parr)[10] = &arr;//数组指针(存放数组的地址)
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", (*parr)[i]);//实际上是*(&arr)[i]=arr[i]=*(arr+i)
}
return 0;
}
结果是:
二、维数组传参的本质
二维数组在内存中存储的图例:
- 二维数组在内存中是连续存储的,行下标代表了他在哪一组(行),列下标代表的是他在这一组的第几个元素

- 二维数组的数组名代表的也是首元素地址,但是它的首元素是第一行的一维数组(下标为0),所以二维数组数组名是第一行一维数组的地址

将二维数组传参打印
//将二维数组传参打印
void print1(int arr[3][5],int r,int c)//写成数组接收也可以
//void printf1(int (*arr)[5],int r,int c)本质上传过来的是一维数组的指针,二维数组的首元素就是第一行一维数组的地址。
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
//以下三种写法均可以
//printf("%d ",(*(p+i))[j]);
//printf("%d ", p[i][j]);
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
void print2(int(*p)[5],int r,int c)//利用数组指针接收,p是指针名,*p代表是指针
//指针p指向5个元素的数组,每个元素类型是int
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
printf("%d ",(*(p+i))[j]);//p[i]
}
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);//直接传进二维数组的数组名,也就是首个数组的地址也就是第一行的一维数组{1,2,3,4,5}的地址
return 0;
}
三、函数指针变量
1、变量有地址,函数也有地址,可以存储在函数指针变量中
2、&函数名=函数名,都是函数的地址
3、当没有获得函数名时,通过函数地址也可以去调用函数
4、看一个变量的类型,就将变量名去掉,剩余部分就是它的类型
int Arr(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int* pa = &a;//整形指针变量,存放整型变量地址
int arr[5] = { 0 };
int(*parr)[5] = &arr;//数组指针变量,存放数组的地址
//arr &arr不一样
int x = 0;
int y = 0;
Arr(x, y);
printf("Arr= %p\n", Arr);
printf("&Arr= %p\n", &Arr);
//Arr 和 &Arr一样
int (*pf)(int, int) = Arr;//函数指针变量,用来存放函数的地址
//int (*pf)(int, int) = &Arr;
//当没有获得函数名时,通过函数地址也可以去调用函数
int ret1 = (*pf)(4, 5);//pf里面存着函数地址&Arr,解引用得到函数名再传参可以调用
int ret2 = pf(4, 5);//pf里面存的就是函数名Arr(函数地址)
int ret3 = Arr(4, 5);//直接通过函数地址(函数名),函数传参调用函数
printf("ret1 = %d\n", ret1);
printf("ret2 = %d\n", ret2);
printf("ret3 = %d\n", ret3);
return 0;
}

四、两段有趣的代码(提高代码的阅读能力)
一、一段有趣的函数调用

(类型)常量----------------强制类型转换
将0强制转化为函数指针类型,所以0就是一个函数指针变量,所以0就是函数的地址,*代表解引用,也就是找到0地址处的那个函数了,加上小括号就是传参的意思,也就是没有参数,无返回值void,所以是一次函数调用,调用的是0地址的那个函数

模拟开机时会调用0地址函数,其实0地址处是不可以调用的
二、一段有趣的函数声明

函数声明,定义,调用有什么区别
函数定义:将函数所有都交代了的过程,返回类型,函数名,函数参数,参数名,函数体
函数声明:返回值类型,函数名,参数类型(强调类型)
函数调用:函数名,参数
注·:上述图片有举例子
阅读这种代码,一般需要从里到外慢慢剖析,找里面认识的东西,然后一步一步慢慢的向外探索
五、typedef关键字
像上述代码一样,其中类型特别乱,代码的可读性也不是很高,那么我们是否可以简化类型呢,使用typedef关键字方可解决(type(类型)+ define(定义)将类型重新定义为新名字)
typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
//返回类型我们一般是什么_t,size_t
//⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:
typedef unsigned int uint;//整形
//如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写:
typedef int* ptr_t;//整形指针
//但是对于数组指针和函数指针稍微有点区别:
//⽐如我们有数组指针类型 int(*)[6] ,需要重命名为 parr_t ,那可以这样写:
typedef int(*parr_t)[6];////新的类型名必须在*的右边
//函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef int (*pf_t)(int, int);//函数指针类型,重定义要把名字放在*后面
//
int Add(int x, int y)
{
return x + y;
}
int main()
{
//unit = unsigned int
unsigned int n1 = 10;
uint n2 = 10;
//int* = pint_t
int* p = &n1;
ptr_t p1= &n1;
//int(*)[6] = parr_t
int arr[6] = { 0 };
int(*parr1)[6] = &arr;
parr_t parr2 = &arr;
//int (*)(int, int) = pf_t
Add(3, 4);
int (*PAdd)(int, int) = &Add;
pf_t padd1 = &Add;
return 0;
}
有了以上的基础:我们可以将这段代码给简化了
void (* signal ( int , void (*) (int) ) ) (int);
typedef void(*pf_t) (int)
pf_t signal(int,pf_t);
六、 函数指针数组
数组是⼀个存放相同类型数据的存储空间,可以将函数指针类型的数据集合到一个数组内,形成了函数指针数组
运算符优先级 * <[ ]
int * parr[5]:parr先与[5]结合变为数组,意思是parr是数组,它有5个元素,每个元素的类型是int*
int (*parr)[5] : parr先与 * 结合变为指针,指向一个数组,数组有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* parr2[5] = { 0 };//整形指针数组存放整形地址
char* parr1[6] = { 0 };//字符指针数组存放字符地址
//
//以下是用函数指针存放函数的地址
int (*pf1)(int, int) = Add;
int (*pf2)(int, int) = Sub;
int (*pf3)(int, int) = Mul;
int (*pf4)(int, int) = Div;
//由于类型一致的元素,现在我要用函数指针数组来存放这些函数地址,使用起来更加方便
// 在函数指针的基础上写函数指针数组更加方便
int (* pf[4])(int, int) = { Add,Sub,Mul,Div };
// 0 1 2 3
//代表pf是一个数组,他有4个元素,每个元素的类型是int(*)(int,int)函数指针类型
//遍历函数调用
for (int i = 0; i < 4; i++)
{
printf("%d ", pf[i](4, 6));
//printf("%d ",(*pf)[i](4, 6));这块不可以这样写,因为*只能解引用指针,不能解引用数组
}
}
这里给大家介绍*只能解引用指针:
int a, b, c, d, e;
int* p1 = &a;
int* p2 = &b;
int* p3 = &c;
int* p4 = &d;
int* p5 = &e;
int* parr[5] = { p1,p2,p3,p4,p5 };//整形指针数组存放整形地址
int* (*pint)[5] = &parr;//意思是pint是指针,指向数组为5个元素,每个元素类型是int*
for (int i = 0; i < 5; i++)
{
printf("%p ", (*pint)[i]);//这里是解引用数组指针了,将每一个地址给打印
}
return 0;
七、计算器
1、首先我们先写一个普通的就算器
//计算器
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 menu()
{
printf("*********************************\n");
printf("**********1.Add 2.Sub***********\n");
printf("**********3.Mul 4.Div***********\n");
printf("**********0.exit ***********\n");
printf("*********************************\n");
}
int main()
{
int x = 0;
int y = 0;
int input = 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", ret);
break;
case 2:
printf("请输入两个整数: ");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("结果是:%d", ret);
break;
case 3:
printf("请输入两个整数: ");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("结果是:%d", ret);
break;
case 4:
printf("请输入两个整数: ");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("结果是:%d", ret);
break;
case 0:
printf("正在退出计算器....");
break;
default:
printf("请重新选择: ");
break;
}
printf("\n");
} while (input);
return 0;
}
二、计算器转移表实现(函数指针数组实现函数调用)
由于考虑计算器可能会添加其他的计算,到时候switch语句会更长,增添了许多不必要的代码,然而实现计算器运算的部分,基本逻辑是一样的,只是函数调用不一样,所以可以直接通过函数指针数组来通过下标调用函数
转移表------------------------------------其实就是通过函数指针转移到函数调用在把结果返回的过程
//计算器
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 And(int x, int y)
{
return x & y;
}
int Or(int x, int y)
{
return x | y;
}
int Nor(int x, int y)
{
return x ^ y;
}
int contray(int x)
{
return ~x;
}
void menu()
{
printf("*********************************\n");
printf("**********1.Add 2.Sub***********\n");
printf("**********3.Mul 4.Div***********\n");
printf("**********5.And 6.Or ***********\n");
printf("**********7.Nor 8.contray*******\n");
printf("**********0.exit ***********\n");
printf("*********************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int (*parr[8])(int, int) = { 0,Add,Sub,Mul,Div,And,Or,Nor };
do {
menu();
printf("请选择: ");
scanf("%d", &input);
if (input >= 1 && input <= 7)
{
printf("请输入两个数: ");
scanf("%d %d", &x, &y);
int ret = parr[input](x, y);
printf("结果是:%d\n", ret);
}
else if (input == 0)
{
printf("退出计算器");
}
else if (input == 8)
{
printf("请输入一个数:");
scanf("%d", &x);
int ret = contray(x);
printf("结果是:%d\n", ret);
}
else
{
printf("请重新输入: \n");
}
} while (input);
return 0;
}
预知回调函数是什么请看下一节笔记;
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)