目录
一、???????了解指针
二、二级指针
三、字符指针
四、野指针
1.未初始化
2.指针越界访问
3.动态内存释放
4.规避野指针
五、指针数组
六、数组指针
七、函数指针
八、函数指针数组
九、指向函数指针数组的指针
十、函数的回调
C语言之所以难,就是因为有指针这个东西,那么首先我们需要了解指针,这里基本概念我会简单略过,主要讲解指针的各种运用。
一、???????了解指针
概念:指针=地址(本篇指针和地址会换着使用,但请读者了解这两个是一个东西),而我们平常习惯说的指针,实质上是指针变量,这两个是不同的概念,千万别搞混了!
我们用指针来指向地址,例如:
#include<stdio.h>
int main()
{
int a=10;
int* p=&a;
return 0;
}
这里我要提一下,*号告诉p是指针,p前面的int是告诉我们p指向的对象的类型是int类型
我们调试便可以知道p和a的地址是相同的
细心的小伙伴肯定发现p本身也是有地址的,所以有了二级指针。
二、二级指针
#include<stdio.h>
int main()
{
int a=10;
int* p=&a;
int** pp=&p;
return 0;
}
有了指针以后,我们可以用*号去解引用,可以进行修改等一系列操作,例如:
? 那么我们要如何理解这里的操作呢?请看下面的图例
那么我们为什么要加*号呢,我的理解是a的类型是int,p的类型是int *,我们想要类型匹配,就需要在加一个*号,这样我们才能去使用a保存的值,而不是地址。
三、字符指针
概念:指向字符的指针
#include<stdio.h>
int main()
{
//这里保存的并不是整个字符常量,而是字符常量的首地址,即w的地址
char* c="work hard";
printf("%s",c);
return 0;
}
四、野指针
什么叫野指针?即:指针指向的空间是未知的。
导致这个问题有几个原因,我们来一一分析。
1.未初始化
int main()
{
int *p;//p指向一个随机的位置
*p=20;
printf("%d",*p);
return 0;
}
我们调试的时候便可以看到问题:
2.指针越界访问
int main()
{
int arr[5]={1,2,3,4,5};
int* p=arr;
//数组最大是5,我们却访问了后面的元素,不仅指针不能这么用,数组也不能这么用
for(int i=0;i<9;i++)
printf("%d",*(p+i));
return 0;
}
3.动态内存释放
int main()
{
int* a=(int*)malloc(sizeof(int));
free(a);
//free只是释放这个空间,单这个空间还可以被使用,为了不被使用,我们需要置空
a=NULL;
return 0;
}
4.规避野指针
? ? (1)?指针初始化
? ? (2)不越界访问
? ? (3)指针指向的空间要释放
? ? (4)避免返回局部变量的地址(因为局部变量出了作用域会被销毁)
? ? (5)指针使用前检查有效性
五、指针数组
概念:本质上是数组,里面存放了指针(地址),定义方法为int* p[]
p是一个数组,数组里存放了3个元素,每个元素的类型是int*
这里p[3]存放的是数组名,而数组名就是数组首元素的地址,这个用法是帮助我们理解指针数组,实际上指针数组还有一个常用的方法:
#include<stdio.h>
int main()
{
int i=0;
char* p[3]={"I","love","China"};
for(i=0;i<3;i++)
printf("%s ",*(p+i));
return 0;
}
输出结果为:I love China
注意!很多同学在这里认为指针数组保存的是一整个字符串,其实不是,这里保存的是常量字符串的首地址。
六、数组指针
概念:指向数组的指针,存放的是数组地址(重点是数组地址),本质上是一个指针,那么这里我们这样定义int* p[]是对的吗?答案是:完全错误!因为p首先和方括号结合,最后就成了数组了,这里我们需要加括号来指名他是数组指针,例如:int (*p)[],这样p就会首先和*号结合,牢牢记住!
p的类型是int(*)[],数组指针的基本用法为:
#include<stdio.h>
int main()
{
int i=0;
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int (*p)[10]=&arr;
for(i=0;i<10;i++)
{
//*p=arr,因为p相当于&arr,*p就等于*&arr,这样就抵消了,相当于获得了数组名
printf("%d ",(*p)[i]);
}
return 0;
}
输出结果为:1 2 3 4 5 6 7 8 9 10
这里一定要注意我的注释,非常重要,这里注意是&arr,指针数组用的是arr,这两个的区别是arr是数组首元素的地址,而&arr是整个数组的地址,实际上我们不会这么用,我只是想用这个方法告诉你数组指针的基本概念,我们一般这样用:
#include<stdio.h>
void print(int (*arr)[4],int row,int col)
{
int i=0;
int j=0;
for(i=0;i<row;i++)
{
for(j=0;j<col;j++)
//printf("%d ",arr[i][j]);
printf("%d ",*(*(arr+i)+j));
printf("\n");
}
}
int main()
{
int arr[3][4]={{1,2,3,4},{2,3,4,5},{3,4,5,6}};
print(arr,3,4);
return 0;
}
输出结果为:1 2 3 4
? ? ? ? ? ? ? ? ? ? 2 3 4 5
? ? ? ? ? ? ? ? ? ? 3 4 5 6
二维数组的数组名是首元素的地址,而二维数组的首元素就是第一行的地址!所以arr+i就是第i行的地址*(arr+i)相当于拿到了第i行,这又相当于数组名,因为*(p+i)==p[i],在二维数组中,这就是第i行的数组名,数组名又相当于数组首元素的地址,所以我们可以写成*(*(arr+i)+j)
七、函数指针
函数指针:指向函数的指针变量,存放函数地址
下面我们来看一个图:
我们发现&Add和Add的地址一样的,?那么我们可以用指针保存起来,void?(*pf)(),以下是代码
#include<stdio.h>
int Add(int x,int y)
{
return x+y;
}
int main()
{
//这两个都可以用,因为他们指向的地址是一样的
//数组&和没有&是有区别的,但是函数没有,因为函数没有首元素取地址的说法
//int (*pf)(int x,int y)=&Add;
int (*pf)(int x,int y)=Add;
//这里可以没有*号的原因是Add=pf
int ret=pf(2,3);
//int ret=(*pf)(2,3);
printf("%d \n",ret);
return 0;
}
八、函数指针数组
概念:把函数地址存放到数组中成为函数指针数组,它的本质是数组,所以我们定义为void(*pf[])()
在我们看函数指针数组实际运用之前,我们先做一个加减乘除的计算器,代码如下:
#include<stdio.h>
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("**** 0.exit 1.add ****\n");
printf("**** 2.sub 3.mul ****\n");
printf("**** 4.div ****\n");
}
int main()
{
int input=0;
int a=0;
int b=0;
int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
do
{
menu();
printf("请输入->\n");
scanf("%d",&input);
switch (input)
{
case 0:
printf("exit\n");
break;
case 1:
printf("请输入两个数字\n");
scanf("%d %d",&a,&b);
printf("%d\n",Add(a,b));
break;
case 2:
printf("请输入两个数字\n");
scanf("%d %d",&a,&b);
printf("%d\n",Sub(a,b));
break;
case 3:
printf("请输入两个数字\n");
scanf("%d %d",&a,&b);
printf("%d\n",Mul(a,b));
break;
case 4:
printf("请输入两个数字\n");
scanf("%d %d",&a,&b);
printf("%d\n",Div(a,b));
break;
default:
break;
}
}while(input);
return 0;
}
这段代码冗余度很高对吧?这时函数指针数组的优势就体现出来了,我们可以用函数把main函数的代码改为:
int main()
{
int input=0;
int a=0;
int b=0;
int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
do
{
menu();
printf("请输入->\n");
scanf("%d",&input);
if(input==0)
{
printf("exit\n");
}
else if(input>=1&&input<=4)
{
printf("输入两个数字\n");
scanf("%d %d",&a,&b);
int ret=pfArr[input](a,b);
printf("%d\n",ret);
}
else
{
printf("重新输入\n");
}
}
while(input);
return 0;
}
九、指向函数指针数组的指针
概念:简单说就是存放函数指针数组的地址,重心在于指针!
int main()
{
int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
//重心在于指针,所以我们需要先写成这样(*ppfArr)
int (*(*ppfArr)[5])(int x,int y)={&pfArr};
}
十、函数的回调
概念:函数的(指针)地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们称为函数的回调。
可能看这个文字描述会让我们云里雾里的,所以我们直接用代码去解释,就用之前的计算器的代码:
//既然我们传过来了函数的地址,那么我们里面的参数就可以用函数指针来接受
void calc(int (*pf)(int x,int y))
{
printf("请输入两个数字->");
int a=0;
int b=0;
scanf("%d %d",&a,&b);
int ret=pf(a,b);
printf("%d\n",ret);
}
int main()
{
int input=0;
do
{
menu();
printf("请输入->\n");
scanf("%d",&input);
switch (input)
{
case 0:
printf("exit");
break;
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
default:
break;
}
}
while(input);
return 0;
}
这里我们在回过头来想想是不是很好理解了?我们把函数的地址作为参数,把这个参数给另一个函数,这个指针被用来调用了我们定义的加减乘除函数。
指针的所有用法基本都在这里介绍了,如果有错误希望指正!
|