指针
字符指针变量
字符串变量的一般使用
int main()
{char ch = 'w';char* p = &ch;*p = 'h';printf("%c", ch);return 0;
}
上面就是通过指针的解引用改变了ch的值
int main()
{char* p = "ni hao a";//这个是常量字符串printf("%s\n", p);//打印的是一个字符串printf("%c\n",*p);//打印的是?return 0;
}
这个代码p打印的是什么呢?
可以发现p打印的是n,也就是字符串的第一个元素,也就是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量p。
常量字符串的值是不可以改变的。也就是不可以通过解引用的操作改变。常量字符串的值。
打印字符串的三种方法
#include<stdio.h>
#include<string.h>
int main()
{//方法一:char* arr = { "hello world" };printf("%s\n", arr);//利用指针打印//方法二printf("%s\n", "hello world");//方法三int len = strlen(arr);//计算字符串 长度for (int i = 0;i < len;i++){printf("%c", *(arr + i));}return 0;
}
数组指针变量
数组指针变量的初步理解
数组指针是一种指针,是存放数组的地址的一种指针变量。
怎幺样创建一个数组指针变量,并且初始化?
int main()
{int arr[10] = { 0 };int (*p) [10] = &arr;//&arr取出的是整个数组的地址return 0;
}
为什么p要加括号?
因为[]操作符的计算优先级是大于操作符的,如果不加括号p会与[]先结合,那就不是数组指针了,变成了指针数组
p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫 数组指针。
[]里面的数字不能省略
用图片来理解数组指针变量
数组指针的类型
int a;//当创建一个整形类型时,去掉变量名a,剩下的int就是变量类型
char ch;//去掉变量名ch剩下的char就是变量类型
char (*p)[10];//p是数组指针的变量名,去掉p剩下的char *[10]就是数组指针的类型。
所以[]里面的数字一定不能省略。它代表数组指针指向了几个元素。
二维数组传参
使用数组的形式进行二维数组的传参
void array(int arr[4][4], int x, int y)//用数组来接收
{for (int i = 0;i < x;i++){for (int j = 0;j < y;j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[4][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7} };array(arr, 4, 4);//把数组,行数,列数传过去return 0;
}
二维数组传地址
既然数组名是数组首元素的地址,那么二维数组的数组名地址是谁呢
因为在理解二维数组时,就是把二维数组看成了多个一维数组,二维数组的一行就是一个一维数组,所以二维数组的数组名对应的就是二维数组的第一行,每一行都可以看成一个元素。
所以二维数组的数组名就是第一行的地址
这就可以得到二维数组数组指针的初始化方式
int (*p) [4] = arr;//p是数组指针存放的是数组首元素的地址[4]代表首元素(第一行)有4个元素
```c
void array(int(*arr)[4], int x, int y)//用数组指针来接收数组首元素的地址
{for (int i = 0;i < x;i++){for (int j = 0;j < y;j++){printf("%d ", (*(arr + i))[j]);//arr+i会找到数组的每一行的地址,通过解引用会得到这个值,在通过下标引用操作打印每行的每个元素}printf("\n");}
}int main()
{int arr[4][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7} };array(arr, 4, 4);//把数组,行数,列数传过去return 0;
}
函数指针变量
如何创建函数指针变量
函数指针变量是存放函数地址的变量,以后可以通过函数指针来调用这个函数
&函数名和函数名的关系
int Add(int a, int b)
{return a + b;
}
int main()
{printf("%p\n",Add);printf("%p\n",&Add);return 0;
}
可以发现&函数名和函数名的地址是一样的,所以&函数名和函数名都可以表示一个函数的地址
存放函数地址
上面已经知道了函数也是有地址的,并且函数名和&函数名都可以表示函数的地址。
那怎样吧这个地址存起来呢?
int Add(int a, int b)
{return a + b;
}
int main()
{int(*pf)(int, int) = &Add;return 0;
}
pf是这个函数指针的名字,用括号让pf和*结合证明他是一个指针变量,后面的括号里面的类型是函数对应的形参的类型,前面的类型对应的是函数的返回值类型
这时有以下一段代码,要将他存到一个名为pa的函数指针中要怎样表示?
void* text(char* p, int* a)
{return NULL;
}
int main()
{= &text;return 0;
}
text函数的形参类型是char *和int *,所以后面的括号就是char *和int ,函数的返回值是void所以前面的括号就是void *。
void *(*pa)(char *,int *)=&text;
这就将text函数的地址存放到了pa函数变量中了。
函数指针类型
函数指针类型和数组指针类型一样去掉名字就是类型,void *(*pa)(char *,int *)的类型就是void ()(char *,int *)
函数指针变量的使用
int Add(int a, int b)
{return a + b;
}
int main()
{int(*p1)(int, int) = &Add;int(*p2)(int, int) = Add;int i = (*p1)(2, 7);//通过解引用p1,找到Add函数的地址printf("%d ", i);return 0;
}
在我们平时调用函数的时候是函数名(实参),函数名的本质也是一个地址,也是通过地址的方式调用函数,这时我们将函数的地址存到了p1,p2中那能不能直接通过p1,p2来调用函数?
int add(int a, int b)
{return a + b;
}
int main()
{int(*p1)(int, int) = &add;int(*p2)(int, int) = add;int i = p1(2, 7);int j = p1(2, 7);printf("%d ", i);printf("%d ", j);return 0;
发现是可以的所以以后调用函数时可以通过*函数名的方式也可以直接通过函数名的方式.
尝试解析两段代码
(*(void (*)())0)();
viod ()()是一个函数指针类型,(void ()()) 0类型+括号+0就是对0进行强制类型转换,将0转换为地址,前面的*是对0所对应的地址进行解引用操作,后面的括号是值函数在调用时不传任何参数。
void (*signal(int , void(*)(int)))(int);
这是一次函数声明,void()(int)是一个函数指针,signal是函数名,signal的参数是int,void()(int),剩下的void(*)(int)是函数的返回值。
typedef
typedef 是⽤来类型重命名的,可以将复杂的类型简单化。
typedef重命名无符号整形
在我们写代码时有时候会用到无符号整形变量unsigned int发现这样写太麻烦了,这时就可以通过typedef关键字来对类型进行简单化
//格式:
typedef unsigned int uint;
通过typedef关键字将unsigned int简写成了uint,uint的功能与unsigned int时完全相同的。
typedef unsigned int uint;
int main()
{uint a= 20;printf("%u ", a);return 0;
}
用uint程序也可以正常运行输出
用typedef 重命名指针类型
typedef int* in;
将整形指针类型重命名成了in
用typedef 重命名数组指针类型
将 int(*)[5] 类型重命名为 pf
typedef int(*pf)[5];
要让想重命名成的名字放在*号右边
用typedef关键字重命名函数指针
将 void(*)(int) 类型重命名为 pf_t
void(* pf_t )(int)
和数组指针重命名的规则一样将名字放在*右边
//那这个时候就可以对void (*signal(int , void(*)(int)))(int);进行简化
//通过上面的分析可以知道该代码的函数参数和返回值类型都是void(*)(int)
//这时可以将void(*)(int)重命名成pr_t
typedef void(* pf)(int);
pf signal(int,pf)//化解后的代码
define和typedef的区别
typedef int* pf;
#define PD int*
int main()
{pf p1, p2;PD p3, p4;return 0;
}
define是将int 的内容变成了PD ,typedef是将int重写成了pf虽然看起来是一样的,但通过调试发现两者有区别。
发现p1,p2,p3的类型都是int *,p4的类型是int
所以用define重命名只是作用于第一个变量,typedef是作用与全部变量
函数指针数组
函数指针数组的定义
前面学过指针数组
char * arr[5];//是字符指针数组,存放的是字符地址
int *arr[3];//是整形指针数组,存放的是整形地址
那么函数指针数组就是存放函数地址的数组喽。
函数指针数组的初始化
int (*pf[3])();
pf与[]结合证明pf是数组,数组有3个元素,每个元素类型是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()
{return 0;
}
可以看出以上几个函数的函数指针类型是一样的都是int (*)(int , int ),这时就可以将他们的地址存放到一个函数指针数组中
int (*arr[4])(int int)={Add,Sub,Mul,Div};
arr先和[]结合证明他是一个数组,数组有4个元素,前面的证明这个数组指向的是一个指针,int ()(int , int )是这个数组的类型,里面存放的是Add,Sub,Mul,Div的地址
函数指针数组的使用
当把函数的地址存放到一个函数指针数组中时可以对其中的元素进行调用
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 (*arr[4])(int, int) = { Add,Sub,Mul,Div };int i = 0;for (i = 0;i < 4;i++){int ret = arr[i](2, 4);//调用数组中的每个元素,将至=值返回到retprintf("%d ", ret);}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;
}
void menu()
{printf("**1.Add**********2.Sub********\n");printf("**2.Mul**********3.Div********\n");printf("**0.exit**********************\n");printf("*****************************\n");
}
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("退出游戏");break;default :printf("选择错误,请重新选择");}} while (input);}
这段代码确实可以完成要求计算两个数的加减乘除的是有问题
图中括起来的部分操作过程都是一样的,相当于重复写了四段代码造成了代码的冗余,
解决方法1:将4个函数存放在一个函数指针数组中,然后对数组的元素进行调用、
int (*arr[4])(int, int) = { Add,Sub,Mul,Div };//创建一个函数指针数组,里面存放四个函数的地址
创建完一个函数指针数组后还有一个问题,当输入1的时候访问的是Sub函数,但我们希望输入1时访问Add函数,这时可以时首元素的值设为NULL
int (*arr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
这样就可以解决那个问题了
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("**1.Add**********2.Sub********\n");printf("**2.Mul**********3.Div********\n");printf("**0.exit**********************\n");printf("*****************************\n");
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择");scanf("%d", &input);printf("输入两个整数");scanf("%d%d", &x, &y);int (*arr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//创建一个函数指针数组,里面存放四个函数的地址ret=arr[input](x, y);//当input=1是访问Add函数完成加法操作,当input=2时,访问Sub函数完成减法操作printf("结果为:%d\n",ret);} while (input);
}
这样也可以完成要求,但照样有问题
当输入的数不符合条件时系统不会有任何的提示,希望的效果是当输入0时程序会停止运行,输入<0或者>4的整数时系统提醒重新输入,
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择\n");scanf("%d", &input);if (input >= 1 && input <= 4){printf("输入两个整数\n");scanf("%d%d", &x, &y);int (*arr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//创建一个函数指针数组,里面存放四个函数的地址ret = arr[input](x, y);printf("结果为:%d\n", ret);}else if(input == 0){printf("退出程序\n");}else{printf("输入错误重新选择\n");}} while (input);
}
这时这个代码就完整了