目录
一些常见指针的用法和意义
指针数组
数组指针
函数指针
函数指针数组
常见的指针有以下几种
char*,int*,float*,double*
指针的初阶用法就是认识并能熟练使用像char*,int*等一些用法,
比如说实现一个交换两个整数的函数,你要知道实参和形参的区别,也就是为什么要传指针。
这些就是一些初阶用法。
而指针的意义是什么呢?
其实指针类型的意义就是:决定了每一次访问内存的的大小
我们经常在定义指针的时候是不是都是按照它的类型进行定义的,比如是int类型,就定义一个int*,char类型就定义一个char*,其实你可以想象一下,地址全都是占4或8个字节的空间,哪它为什么要存在指针类型呢?
我把int*类型的指针放在char*行不行?你是否有这样的疑问?*
来我给你举个例子
//下面输出什么
#include
int main()
{
int a = 0x00001234;
int b = 0x00001234;
int* pa = &a;
*pa = 1;
char* pb = (char*)&b;
*pb = 1;
printf("a=%x,b=%x\n", a, b);
return 0;
}
看懂这个答案的前提你得了解数据在内存中的存放方式,不然可以会有点懵懵懂懂的感觉,我在上一个文章就有详细的介绍过!
OK,你再回想一下,地址全都是占4或8个字节的空间,哪它为什么要存在指针类型呢?当你这样想之后,再回想一下上面的例子,就会更近一步想到,哦!原来是每次访问内存的大小!
那可能会有人问我把上面int类型的b的指针放在char*之后,能不能把它也改成1?
能呀,就一个一个字节的改就行了呀,只不过你觉不觉得这样有点多此一举呢,我完全可以用一个int*,直接进行4个字节的改动,要char*的话还有一个一个字节的去改动。所以这就是存在指针类型的意义!
接下来给大家提升一点难度:字符char*指针的另一种用法!
上面的字符串是存在p里面吗??
首先我们知道char*类型是指针吧,那指针的大小是4或8个字节吧,所以上面的字符串是肯定放不下的,那可能有人学过或了解过数据截断的讲法,但是这里是不可能截断的,因为在后面已经完完整整的把字符串的内存打印出来了。
我给你举个和上面这个类似的例子
#include
int main()
{
int arr[10] = { 1,2,3,4,5,7,8,9,10 };
int* pa = arr;
return 0;
}
上面这个代码pa是存放整个数组的元素吗? 不是了吧,大家都知道是存放数组首元素地址。
所以我们上面的字符串是存在p里面吗??
显然不是,其实它也是存放首字符的地址,然后通过首字符的地址找到后面的字符!大家可以跟数组这一块类别一下就明白了!
而且呢!它指向的内容是无法被修改的,(不是因为我在char*p前加了const才不被修改的,是它本身就无法被修改,如果你修改了,程序会崩溃,加const就是为防止自己去修改,这样如果不小心修改了,编译器会报错提醒我们)
这样的字符也称“常量字符串”
下面有一个这样的题目
//下面输出什么?
#include
int main()
{
char arr1[] = "hello,My friend!";
char arr2[] = "hello,My friend!";
const char* arr3 = "hello,My friend!";
const char* arr4 = "hello,My friend!";
if (arr1 == arr2)
printf("arr1 =arr2\n");
else
printf("arr1 != arr2 \n");
if (arr3 == arr4)
printf("arr3 = arr4 \n");
else
printf("arr3 != arr4 \n");
return 0;
}
输出结果如下:
为什么会是这样的结果呢,其实呢!数组arr1和arr2它们在开辟空间的时候是分别开不一样的空间,所以它们两的地址是不可能相同的,而arr3和arr4,就是我们上面讲的,它们是不可被修改的,也就是说它们在开辟空间的时候,没必要开两个不一样的空间了,反正它们的内容也不会被修改,那为什么还要浪费空间开新的呢,这时候编译器就很聪明了,它就把两个指针都指向同一块内存空间。因此arr3=arr4! |
指针数组和数组指针其实很多人都容易混淆,怎么区分呢?
很简单,捉住主语!
比如:好孩子,那他的主语是不是“孩子”,”好“这个词是修饰孩子的。
同样:指针数组,它是个数组,指针是修饰它的类型
我们常用的有整型数组,字符数组等,整型数组就是存放整型的数组,而字符数组就是存放字符的数组,
所以指针数组,就是存放指针的数组!!
那它是如何在代码中定义的呢?
数组指针 主语:是指针!
我们知道int*是指向int型的指针,char*是指向char型的指针
所以数组指针是指向数组的指针!
p1, p2分别是什么?
int *p1[10];
int (*p2)[10];
p1上面我们讲过是指针数组
而p2就是我们的数组指针了!
为什么呢?
int (*p)[10]; //解释:p先和*结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
写出下面的数组指针
我们知道定义一个指针是要根据它的类型,然后在类型后面+*就可以定义一个指针了,比如int *a,所以说我们在定义数组指针的时候也要知道数组的类型,那么数组的类型是什么呢?
举个例子,int a=0;去掉变量名后就是它的类型,比如a的类型就是int,所以数组也是同样的方式,int a[5],去掉变量名后
就是int [5],这个就是它的类型吗?
是的,别怀疑,这个就是它是类型,然后呢,我们就像普通类型一样在加个*和指针名就可以
只不过它的指针名跟int那些不一样,数组指针的名字是放在int 和[]之间的,而且必须要用括号括起来,不然就变成这样了
int *pa [5],它是一个指针数组,因为[]优先级高与*,所以呢数组a的指针最终的结果就是int (*pa)[5],b的也是同样的分析方法。
那数组指针怎么用呢?它有什么作用呢?
我给大家写一个数组指针错误的使用方式!
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
//但是我们一般很少这样写代码 一个数组指针的比较笨的使用方法
int i = 0;
for (i; i < 10; i++)
{
printf("%d ", *((*p) + i) );
}
printf("\n");
//上面的写法我们完全可以用一个整型指针来完成
int* parr = arr;
for (i = 0; i < 10; i++)
{
printf("%d ", *(parr + i));
}
return 0;
}
上面这个列子就是数组指针错误的使用方式,为什么说它错误呢!因为它是的用一个数组指针来实现数组打印,而这个功能完完全全可以用整型指针来实现,可它偏偏用了数组指针,这就将它复杂化,简直就是脱裤子放屁,多此一举!所以我们学数组指针可不是用来实现这样的功能的。
相对正确的使用方式是用于二维数组传参的时候使用的
举个例子
#include
//以二维数组的形式接受地址
void print_arr1(int arr[3][5], 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("\n");
}
}
//以数组指针的形式接受地址
void print_arr2(int(*arr)[5], int row, int col)
{
int j = 0;
int i = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
//思考一下上面的代码是表示数组呢,还是数组指针呢?还是其他什么呢?
int arr[5];//一个整型数组,数组有5个元素,每个元素的类型是整型
int *parr1[10];//一个指针数组,数组里有10个元素,每个元素的类型是一个整型指针(int*)
int (*parr2)[10];//数组指针,指向的对象是一个拥有10个元素的数组,每个元素的类型是整型(int)
int (*parr3[10])[5];//一个数组指针数组,数组有10个元素,每个元素的类型是数组指针(int(*)[5])
第四个再解释一下,因为[]的优先级比*要高,所以parr3先和[]结合,所以它是个数组,又因为我们把parr3[]去掉之后,就剩下了int(*)[5],这不就是数组指针类型吗,其实跟第三个对比一下,parr2去掉之后,就是int(*)[10],这就是数组指针。所以第四个的类型是数组指针,而它是一个数组,所以最后其实就是一个数组指针数组
“函数指针”我想大家首先得知道它是一个指针吧!
而且是一个指向函数的指针
可能有人会问—>什么鬼?函数也有地址吗?
有没有呢我给你测试一下就知道了,看下面一段代码
#include
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
你看到了吗!显然在这里,这个函数test()的地址为004E13BB,而且用不用&符号都能把该函数地址取出来!
那问题来了,整型数,字符,数组等的地址我们都已经学会如何保存在相对应的指针中了。
那我们的函数的地址要想保存起来,怎么保存?
我有个选项想让大家选出正确的函数指针:
看下面代码
void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
其实我想大家应该都能选出来,答案就是第一个pfun1,因为类似上面的数组指针嘛!*符号一般都要括号括起来的,因为它的优先级比较低
那怎么解释这个(void (*pfun1)())呢?
其实是这样的,pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。!
举个例子
//写出下面函数的函数指针
int add(int x, int y)
{
return x + y;
}
答案应该是:int (*pf)(int,int)=&add,&符号可加可不加
解释:因为add函数的返回值是int ,参数类型为int x,int y,所以函数指针的类似为int(*)(int x,int y),但是x,y可以省略不写
接下来给大家阅读两段有趣的代码:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
知道上面的代码是什么意思吗?这可不是我胡编乱造出来的,上面的代码来自于《C陷阱和缺陷》这本书。
解释一下这两行代码的意思
第一行代码其实就是一个函数调用,为什么呢?
别急,首先我们把括号拆分一下,然后把外壳(*)(),先去掉,先留下(void(*)() )0,然后这时候就要擦亮你的眼睛了,void(*)(),不就是函数指针吗?,它是一个类型,然后放在0的前面代表什么呢?不就是强制类型转换吗,比如(char)0,就将0强制类型转换成char类型,同样的(void(*)())0,就是将0强制类型转换成函数指针类型,而这个函数的返回值是void,参数为空。
那么现在0就相当于这样的一个:函数的返回值是void,参数为空,的函数指针,然后再把外壳装回来就是(*0)(),就是将0进行解引用操作,然后就相当于调用函数的操作。比如我把0换成pf,最后的形式就是(*pf)(),不就是类似于test(),这样的函数调用吗!
第二行代码其实就是一个函数声明,为什么呢?
首先还是先把括号拆分开,看红色框中的两个进行类比,下面add我相信大家都知道是一个函数声明吧,它的返回类型是int,参数有两个,一个是int,另一个也为int,add为它的函数名
同样的signal其实也是一个函数名,它的参数也有两个,一个是int,另一个是void(*)(int),不知道大家看到这里了,能不能看得出这是一个函数指针呢,这个函数指针的返回值为void,参数为int,其实就是一个传指针的操作,没有什么高大上的,只不过它的形式有点复杂。
而最后的外壳的void(* )(int),我们跟add类比,add的前面是它的返回类型int,所以呢,signal这个函数的返回类型就是一个函数指针,void(*)(int).
有没有什么函数指针的使用场景呢?
有,比如c语言自带的标准库中有一个可以排序任何类型的排序算法qsort,它就涉及了函数指针的参数和使用!
而像qsort这样通过调用函数指针的函数就被称为回调函数
回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
感兴趣的可以翻一下我之前的文章有模拟实现过qsort函数,和qsort函数的介绍!
为了防止大家学懵了,给大家回忆一下什么是数组!
数组是一个存放相同类型数据的存储空间
int *arr[10];//数组的每个元素是int*
int arr1[5];//数组每个元素是int
char* arr[6];//数组每个元素是char*
那我们已经学习了指针数组,现在学这个函数指针数组我相信是不难理解的!
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组
那函数指针的数组如何定义呢?
还是给几个选项吧!
int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。*
教一下大家如何正确的写好一个函数指针数组
#include
int add(int x, int y)
{
return x + y;
}
int sum(int x, int y)
{
return x - y;
}
int main()
{
//先写函数指针
int (*pf)(int, int) = &add;
//再把函数指针copy过来,在变量名后加个[]
int (*pf1[5])(int, int) = { &add,&sum };
printf("pf=%p\npf1[0]=%p\npf1[1]=%p \n", pf, pf1[0], pf1[1]);
return 0;
}