文章目录
🚩🚩前言
🎉✨欢迎来到指针汇总章节!相信各位都听说各种指针名字吧!比如:一级指针、二级指针、数组指针、函数指针。以及名字反过来又不一样的指针数组、函数指针数组等。看到这些感觉头脑都晕了😵😵,接下来慢慢梳理一下这些各种指针的定义和用法。👇👇👇👇
🔑🔑一级指针
🎈🎈定义
一级指针,也叫做普通指针。它是用来存放各种数据类型变量的地址的。但是一级指针和普通类型变量的定义也有些许区别,并且含义不一样。
指针嘛,在前面章节讲到指针即地址,指针变量就是专门用来存储地址。接下来看看一级指针:
int a=10;
int* ptr1=&a;//这就是一级指针,*就是代表ptr1是指针变量,是存储的整型变量a的地址的。
- 我们可以存储变量的地址,从而改变原来变量的值。
#include<stdio.h>
int main()
{
int a = 10;
int* ptr1 = &a;//此处*号表示ptr1是指针变量
*ptr1 = 50;//此处的*号是解引用操作
printf("a=%d\n",a);
return 0;
}
- 结果被改变:
其改变a的值,原理是下面图所展示的:
🔑🔑二级指针
🎈🎈定义
上面说到一级指针是存储变量的地址的,而一级指针变量在存储其他变量地址的时候,自己也在计算机中有对应的自己内存空间。既然把变量的地址放在指针变量里面叫做一级指针,那么我们把一级指针自身的地址存储到另外一个的指针变量中去,就把另外存储一级指针变量地址的指针叫做二级指针。
就是说二级指针是存储一级指针变量地址的指针变量。
- 二级指针定义格式:
int a=50;
int* ptr1=&a;
int** ptr2=&ptr;//取出指针变量的地址
上面的 ** 号就表示ptr2是二级指针变量,存放的是一级指针ptr1变量的地址。
#include<stdio.h>
int main()
{
int a = 10;
int* ptr1 = &a;//此处*号表示ptr1是指针变量
//*ptr1 = 50;//此处的*号是解引用操作
int** ptr2 = &ptr1;
**ptr2 = 50;
printf("a=%d\n",a);
return 0;
}
- 一样可以改a的值:
🔑🔑数组指针
🎈🎈定义
在说之前,看一下这问题,数组指针变量是指针变量还是数组呢?
答案当然是:指针变量。
- 我们在前面说到
int* ptr;
//存整型变量的地址,能够指向整型数据的指针,叫做整型指针变量。float* ptr1;
//存放浮点类型的变量的地址,指向浮点类型数据的指针,叫做浮点型指针变量。- 那么数组指针,就是存放数组的地址,能够指向数组类型的指针变量了,叫做数组指针变量。
🎈🎈数组指针变量的格式
int (*ptr)[10];
//ptr是指向数组的,数组的每个元素为整型。
我们来看,ptr 首先和 * 号结合,那么ptr就是指针变量。然后,在后面跟了 [10] 。说明该指针变量指向的是一个大小为10的整型数组。因此,ptr是指针变量,而指针指向的是数组,叫数组指针变量。
-
注意点:‘[ ]’ 的优先级高于 ‘*’ 号的,因此必须加上 ‘( )’ ,保证ptr是指针变量。
🎈🎈数组指针初始化
在说到数组指针初始化的时候,不得不先说一个重要的知识点:
这有一个整形数组:
int arr[10];
//该数组可以放10个整形元素。
那么我们用到的下面两个有什么区别呢:
arr
:
表示数组首元素的地址,一个元素的地址用指针变量是这样的int* ptr1=arr;
arr
==&arr[0];
//它们的类型是int*
&arr
:
表示取出的是整个数组的地址。
它们两个在数值上是一样的,但是意义不一样。
取出数组的地址,数组指针存储的是这样的int (*ptr2)[10]=&arr;
//这就是数组指针的初始化。
ptr
的类型是int (*)[10]
//去掉名字,剩下的就是类型。
&arr
的类型也是int (*)[10]
🎈🎈数组指针的应用
👉👉数组指针在二维数组中的应用
二维数组传参的本质:
首先,二维数组与一维数组很相似,其实可以这样理解,二维数组的每一行是一个一维数组,那么相当于二维数组的每个元素是一维数组,首元素就是第一行的一维数组。
因此,我们在传二维数组名的时候,就是传的二维数组第一行的地址。第一行为一维数组,所以形参要写成数组指针形式
比如:
int arr[3][4];
//3行4列的二维数组。
arr
:表述二维数组的首元素地址,相当于传的是第一行的地址,而第一行是一维数组,所以,在接收这个二维数组首元素地址的时候,形参就应该这样写:
int (*ptr)[4];
//这就是数组指针,指向的是二维数组第一行的地址,一行有4个元素,每个元素的类型是int
。
接下来用两种方式把二维数组地址传给打印函数,不同的两种形参写法:
①二维数组形式
#include<stdio.h>
//传过来的是二维数组地址,我形参用二维数组接收,一一对应,好理解。
void print_array(int arr[3][5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("arr[%d][%d]=%d ",i,j,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_array(arr,3,5);//把二维数组的地址传过去,行和列也要传过去
return 0;
}
结果输出:
②数组指针形式:
#include<stdio.h>
void print_array(int(*ptr)[5], int r, int c)
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", *(*(ptr + i) + j));
//ptr指向行,+i 找到第i行,解引用拿到第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_array(arr,3,5);//把二维数组的地址传过去,行和列也要传过去
return 0;
}
结果打印:
🔑🔑函数指针
在了解函数指针的是什么之前,我们先来了解函数的地址:
🎈🎈函数的地址
- 学了C语言基础知识了,我们知道各种数据类型,在创建变量的时候,内存会分配地址给这个变量,也就是说变量是有地址的;学到数组的时候,数组在内存空间中是连续存储的,也有地址。那么,我们想一想函数到底有没有地址呢?别慌,我们通过代码来验证一下猜想:👇👇👇👇
//2、函数地址
#include<stdio.h>
//写了一个加法函数
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
printf("%d+%d=%d\n\n",a,b,Add(a,b));
//我把函数的地址取出来
printf("%p\n", &Add);//用取地址符
printf("%p\n", Add);//直接打印函数名
return 0;
}
- 结果如下:
从上面代码中可以看出,函数也是有地址的,并且函数名等价于&函数名,都是代表函数地址。
在这总结一下函数和数组的注意事项: ①首先函数是有地址的,并且 函数名 和 &函数名 ,拿到的都是函数的地址,两者没有任何差别。②但是,数组就不一样了, 比如:int arr[10]; arr 和 &arr 就有区别了。 &arr:是数组的地址,arr:是数组首元素地址,单从数值上看是一样的,但是意义完全不相同。 |
🎈🎈函数指针变量
我们通过类比看:
整型指针变量:是存放指针变量的地址。
字符指针变量:是存放字符变量的地址。
数组指针变量:是存放数组的地址。那么,我们从上面知道,函数也是有地址的,要把函数地址存起来,那就要用到函数指针变量。
函数指针变量的定义
我们想啊,函数指针,见名知意。肯定得是指针,所以在写的时候,必须满足是指针,那么是指针,就用到 * 号,有 * 才表示是指针变量,如:
(*ptr)
。写成指针后,那么这个指针变量是指向函数的,而函数是用 ( )小括号括起来的,所以,这个指针变量也得用 (),如:(*ptr)( )
。括号里里面的参数是根据原函数的参数多少和类型决定的。返回值也是看原函数的返回值,如:原函数是无参无返回值,那么函数指针就是这样:void (*ptr)( )
。
//函数指针变量
#include<stdio.h>
int Add(int x,int y)
{
return x+y;
}
int main()
{
printf("请输入两个整数:\n");
int a=0,b=0;
scanf("%d%d",&a,&b);
//利用函数指针变量来调�?,先创建函数指�?
int (*ptr)(int,int)=Add;//函数指针变量的格式,在参数里面x,y可以省略,只需要交代形参个数就可以。
int num=(*ptr)(a,b);
printf("结果为:%d\n",num);
return 0;
}
- 函数指针类型解释:
✨✨指针数组
在这把指针数组拿来说,是因为在上面解释了数组指针,说数组指针是一个指针,且指向数组地址的指针变量。那么,对比来看,这指针数组又是什么呢?它是指针还是数组?
我们可以和之前的整型数组:是存放整型数据的数组吧,字符数组是存放字符的数组吧。那么好,指针数组就是存放指针的数组了,也就是数组里面放的都是地址。
👉👉指针数组模拟二维数组的应用
先用二维数组来存储打印:
//二维数组打印
#include<stdio.h>
int main()
{
int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};//3行5列的二维数组
//打印
for(int i=0;i<3;i++)
{
for(int j=0;j<5;j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
return 0;
}
用指针数组模拟二维数组
#include<stdio.h>
int main()
{
//三个一维数组,用指针数组统一管理打印
int arr1[5]={1,2,3,4,5};
int arr2[5]={2,3,4,5,6};
int arr3[5]={3,4,5,6,7};
//首先创建指针数组
int* ptr[]={arr1,arr2,arr3};
for(int i=0;i<3;i++)
{
for(int j=0;j<5;j++)
{
printf("%d ",ptr[i][j]);
}
printf("\n");
}
return 0;
}
上面指指针数组模拟二维数组用下图来解释:
✨✨函数指针数组
函数指针数组?也是一样对比看,我们说指针数组是存放指针的数组,就是说这个数组里面都是地址,可以是整型类型的、字符型的。那么函数指针数组就是存放函数地址的数组,这里面存放了很多个函数的地址(有不同的函数名)。
👉👉函数指针数组在计算器(转移表)中的应用
最初代码展示:
//转移表(计算器)
#include<stdio.h>
void menu()
{
printf("*****************************\n");
printf("**** 1、Add 2、Sub ******\n");
printf("**** 3、Mul 4、Div ******\n");
printf("********** 0、eixt *********\n");
printf("*****************************\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;
}
int main()
{
int input=0;
int x=0,y=0;
do
{
menu();
printf("请选择对应的计算:\n");
scanf("%d",&input);
switch(input)
{
case 1:
//加法
printf("请输入两个数数据:\n");
scanf("%d%d",&x,&y);
printf("%d+%d=%d\n",x,y,Add(x,y));
break;
case 2:
//减法
printf("请输入两个数数据:\n");
scanf("%d%d",&x,&y);
printf("%d-%d=%d\n",x,y,Sub(x,y));
break;
case 3:
//乘法
printf("请输入两个数数据:\n");
scanf("%d%d",&x,&y);
printf("%d*%d=%d\n",x,y,Mul(x,y));
break;
case 4:
//除法
printf("请输入两个数数据:\n");
scanf("%d%d",&x,&y);
printf("%d/%d=%d\n",x,y,Div(x,y));
break;
case 0:
printf("退出计算器,欢迎下次使用!\n");
break;
default:
printf("选择错误,请重新选择:\n");
break;
}
} while (input);
return 0;
}
结果展示:
代码优化:我从代码中看到,特别是在
switch
结构里面,代码有些类似,冗余度太高,如果继续增加运算,会添加很多相似的代码,代码灵活度太低。我们看到除了每个调用的函数不同,其他格式都差不多。这里我们就用一个函数指针数组来存放这几个函数的地址,通过函数指针变量来调用,会很方便。我们可以这样写,4个函数不变,只是在main函数里面变化:
int main()
{
int input=0;
int x=0,y=0;
//函数指针数组的创建
int (*ptr[5])(int,int)={NULL,Add,Sub,Mul,Div};
//用NULL来占下标0的位置,这样就可以直接用input来使用下标,对应输入的功能值。
do
{
menu();
printf("请选择对应的计算:\n");
scanf("%d",&input);
if(input>=1&&input<=4)
{
//函数指针数组
printf("请输入两个操作数:\n");
scanf("%d%d",&x,&y);
printf("%d\n",ptr[input](x,y));//调用
}
else if(input==0)
{
printf("退出计算!\n");
break;
}
else
{
printf("输入错误请重新输入:\n");
break;
}
} while (input);
return 0;
}
结果:
在这些,我们还可以改写一下代码,我们学了存储函数地址的指针,叫做函数指针,那么我们把调用部分封装成函数,在新的函数里面通过传过来的地址来调用:
代码如下:
函数指针变量的应用
- 封装一个函数:
void Cal(int (*ptr)(int,int))
{
int x=0,y=0;
printf("请输入两个数数据:\n");
scanf("%d%d",&x,&y);
printf("%d\n", ptr(x,y));
}
那4个函数不变,在主函数里面直接调用就欧克:
int main()
{
int input=0;
int x=0,y=0;
switch(input)
{
case 1:
//加法
Cal(Add);
break;
case 2:
//减法
Cal(Sub);
break;
case 3:
//乘法
Cal(Mul);
break;
case 4:
//除法
Cal(Div);
break;
case 0:
printf("退出计算器,欢迎下次使用!\n");
break;
default:
printf("选择错误,请重新选择:\n");
break;
}
} while (input);
return 0;
}
- 结果如下:
各种指针就介绍到这,谢谢!🤞🤞