指针详解
目录:
- 字符指针
- 数组指针
- 数组传参和指针传参
- 函数指针
- 函数指针的几个例子
- 回调函数
- 指针小结
1,字符指针
字符指针即指向字符或字符串的指针,类型为char*。在c程序中,字符串是存在字符数组里的,我们想要引用字符串的内容有两种方式,一种是通过数组下标引用来引用其中的某个元素,另一种是通过指针来进行访问。
下面讲一个例子来帮助大家理解:
(分别用数组和指针的方式来打印字符串)
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdefg";
char *p = "abcdefg";
int sz = strlen(arr);
//直接打印
printf("%s\n", arr);
printf(p);
printf("\n");
//通过数组来引用
for (int i = 0; i<sz; i++)
{
printf("%c", arr[i]);
}
printf("\n");
//通过指针来打印
printf("%s\n", p);
for (int i = 0; i<sz; i++)
{
printf("%c", *(p+i));
}
}
对于以上代码大家可能不太理解这俩段代码:
char *p = "abcdefg";
printf(p);
第一个代码的意思是将字符串的首元素的地址,传给了p,大家可不敢理解成将字符串的内容放进了字符指针里。
第二个代码涉及到了printf函数的相关内容,printf函数称为可变格式输出函数,再打印字符串时只需要将字符串的首地址即可。
再举个例子帮助大家理解:
int main()
{
int a=1;int b=2;
printf("%d,%d",a,b);
char *p="%d,%d";
printf(p,a,b);
}
大家觉得以上代码有问题吗?其实没有问题,在这里我们也深入的了解了printf函数的打印机制,就需要” “里的首元素地址即可。
对于字符指针传参也是有两种方式,一种是传字符数组,另一种是传字符指针。这两种方式都行,本质上没有区别。
2,数组指针
数组指针即指向数组的指针,这里一定要和指针数组区别开,这完全是俩回事,数组指针是指针,指针数组是数组。
大家先区分一下面的代码:
int *p1[10];
int (*p2)[10];
//p1和p2谁是数组指针?
答案是p2,()括号可不敢省略,因为[]的优先级比*高,如果没有()就会先和[]结合,成为p1[10],这是数组,所以int *p1[10],是存了10个元素为整型指针的指针数组。而 *p2有()的加持 成为了指针,指向了一个有十个整型的数组。
现在举一个实用例子:
利用数组指针来打印数组:
void print(int(*p)[4], int rol, int row)
{
for (int i = 0; i < rol; i++)
{
for (int j = 0; j < row; j++)
{
printf("%d ", p[i][j]);
}
}
}
int main()
{
int arr[3][4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };
int(*p)[4] = arr;
print(p, 3, 4);
return 0;
}
大家应该知道,二维数组可以理解成由一维数组 组成的,所以我传的是第一行的地址,这个arr [3] [4]总共有三行四列,arr可以理解成arr+0,所以是第一行的地址,再深入讲讲,*(arr+0)=arr[0],这个可不是代表第一行的元素,这其实还是个地址,你可以将其理解成arr[0]+0= *(arr+0)+0,它是第0行第0列的元素,也就是首元素。
3,数组传参和指针传参
在函数传参中,有传值调用,和传址调用。在传址调用中需要用指针(地址)来接受。
数组传参,大家都知道数组名是数组首元素的地址,那么可以用什么样的参数来接受呢?
比如:
int arr[100]={0};//创建了一个数组
test(arr);//数组传参
大家可以思考一下,可以用怎样的参数来接受arr。
void test(int arr[]);
void test(int arr[100]);
void test(int *p);
以上三种都可以。
二维数组传参和一维数组类似,但又有一点需要强调。
int arr[3][5]={0};
test(arr);
void test(int arr[3][5]);
void test(int arr[][5]);
void test(int arr[3][]);
void test(int arr[][]);
void test(int *p);
void test(int (*p)[5]);
以上的有几组是不可行的。
不行的有:
void test(int arr[3][]);
void test(int arr[][]);
二维数组传参,函数形参的设计只能省略第一个[]的数字。也就是说必须知道二维数组的每行的列数。
指针传参有一级指针传参,二级指针传参等,再高级的就不讨论了,有点复杂。
一级指针就是传非指针变量的地址,二级指针就是传指针变量的地址。
简单举一举,二级指针传参的例子:
int a=0;
int *p=&a;
int **p=&p;
test(p);
int* p1[10]={&a};
test1(p1);
//等等
4,函数指针
函数指针即指向函数的指针,函数也是有地址的,函数名就是函数的地址。空说,大家可能体会不到,所以我编一个小程序。
int add(int x, int y)
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
int c=add(a, b);
printf("%p\n", add);
printf("%p\n", &add);
}
这是运行结果。
可以看到,函数名就是函数的起始地址。
函数指针的形式:返回类型 (*指针变量名)(函数参数)。
将上面的add函数存入一个函数指针p。
int add(int x, int y)
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
int(*p)(int ,int)=add;
int c=(*p)(a,b);
printf("%d",c);
}
结果正确。
函数指针的一个重要用途是把函数的入口地址作为参数传递到其他函数中,实现在函数中调用其他函数。这个在后文的回调函数中讲,这里先提一嘴。
5,函数指针的几个例子
这里主要介绍几个变了花样的函数指针,该如何去定义。
函数指针数组: 就是元素为函数指针的数组,一点一点的深入,先是函数指针,就比如是int(*)()类型,接下来是数组p[10],所以结合起来就是
int(*p[10])(),这就ok了。所以我们在定义函数指针数组时,可以先定义函数指针,然后在( * )里添上数组名以及元素个数(*p[10])。
指向函数指针数组的指针: 还是和上面一样,大家一上来就要定义可能有点困难,但是一步步的,就可以了,假如我就想 指向 上面的函数指针数组。所以我们就直接走下一步,int(* (* p ) [10])(),只需要让p成为指针就可以了。
6,回调函数
回调函数就是通过函数指针来调用的函数,也就是说可以在函数中嵌套调用函数,只需要将此函数的地址传进去就可以了。
我举一个计算器的例子,有了函数指针,可以极大的提高代码效率。
void fun(int x, int y, int(*p)(int, int))
{
printf("%d\n", (*p)(x, y));
}
int add(int x, int y)
{
return x + y;
}
int dea(int x, int y)
{
return x - y;
}
int main()
{
int input = 0;
int a = 0; int b = 0;
do
{
printf("请选择:1,进行加法。2,进行减法。0,退出\n");
scanf("%d", &input);
if (input == 1)
{
printf("请输入两个操作数:");
scanf("%d%d", &a, &b);
fun(a, b, add);
}
if (input == 2)
{
printf("请输入两个操作数:");
scanf("%d%d", &a, &b);
fun(a, b, dea);
}
if (input == 0)
{
printf("退出成功\n");
}
} while (input);
return 0;
}
以上的计算器只能加减法,有感兴趣的老铁还可以自己多加些功能。
代码运行一波:
运行结果正确。
指针小结
在本文的最后对指针做个小小的总结:
指针就是地址,指针变量是存放地址的变量,所以指针变量的值就是地址。数组名是一个地址,函数名也是一个地址。定义一个指针变量必须说明指针指向数据的类型,这会影响对于地址中数据的存取,以及指针的运算,指针++,指针–等运算,需要注意的是,指针加指针是没有意义的,如果要用指针-指针那么俩指针必须指向同一个数组的元素,差值为之间的元素个数。要注意野指针的出现,它是个大问题,可不敢访问野指针,更不敢给野指针赋值。