学过c语言的人都知道,指针是c语言中比较复杂难理解的一个知识点,想要理解透彻有些难度,所以在此辨析一些容易混淆的概念,整理了有关指针的知识点。
1,什么是指针
首先,先来看几个概念。
- 指针:就是地址。
- 指针变量存放地址的变量。
- 变量指针:变量的地址。
所以,我们常常听到定义一个指针,实际上是定义一个指针变量,而不是定义一个指针,指针既然是地址,又怎么能被定义呢?只不过我们口语中习惯与说定义一个指针了而已。但是我们自己心里一定要清楚定义的是指针变量。
那么如何定义一个指针呢?
int *p;
int a=10;
p=&a;
不难理解,有一个变量a的值为10,有一个指针变量叫p,p里放着a的地址。通常我们也会说,p指向a。通过下图可以清楚的看到。
我们说,指针变量是一个能存放地址的变量,那么它就是变量。
既然是变量,就需要考虑两个东西:1,开辟空间 。2,如何看待里面的内容。
- 一个指针变量的大小在32位平台下是4个字节,在64位平台下是8个字节。原因是因为计算机在内存管理时一个最小单位是字节,每一个字节包含一个地址,有多少地址,是由cpu地址总线位数来决定,比如32位平台下,有32根地址线,即可以编址4G的空间,则有2^32的地址,所以一个指针变量得大小就是4个字节。
- 指针变量用来存放地址,所以它里面的内容都是地址,不管里面是什么,都会被当成地址来处理。
- 指针是变量,就有类型:type + *。
char* pc=NULL;//char*类型的指针是为了存放char类型变量的地址
int* pi=NULL;//int*类型的指针是为了存放int类型变量的地址
short* ps=NULL;//short*类型的指针是为了存放short类型变量的地址
long* pl=NULL;
float* pf=NULL;
double* pd=NULL;
指针的类型决定了对指针解引用时有多大的权限。比如:char*的指针解引用就只能访问一个字节,int*的指针解引用可以访问4个字节。
如何使用指针?
一般我们使用*即解引用。
int *p;
int a=10;
p=&a;
*p=20;
此时,我们就通过对指针解引用找到了a,并且修改了a的内容。这个*好像就是一把钥匙一样,你拿着这把钥匙去匹配一个指针变量,就能通过这个钥匙和指针变量找到内存的内容。
2,数组
之前学过了数组,我们知道,数组是一些相同元素类型的集合。
int arr[]={1,2,3,4,5};
画图示意:
编译器会根据元素个数和元素类型来开辟空间的,比如上例会开辟20个字节的大小。
- 辨析&arr和&arr[0]:
&arr是对整个数组取地址
&arr[0]是对首元素取地址
这两个值都相同,实际意义却不同,就好比一个是陕西省政府,一个是西安市政府,虽然都在一个地方,却不是同一个,意义也不同。 - 辨析&arr+1和&arr[0]+1
&arr+1:跳过这个数组,下一个数组的地址。
&arr[0]+1:指向第二个元素,第二个元素的地址。
3,数组与指针的关系
答案是:没有关系
我们常常会看到一些书上写数组就是指针,这种说法是不合理的。
实际上,数组是数组,指针是指针,他们之间是没有关系的。
那么为什么会将他们混淆,原因是因为我们在使用时,数组和指针可以达到同样的目的。以两个例子来说明。
char *p="abcdef";
经过分析,p是一个指针变量,里面保存了a的地址,这里需要注意一点,这里的指针p在栈上,而abcdef字符串在字符常量区。
我们使用下标和指针两种方式访问,比如我们要访问字符‘c’
- 指针形式访问:*(p+2):取出p中保存的a的地址,按照字符类型偏移两次,找到了c的地址,解引用就是c。
- 下标形式访问:p[2]:这种形式实际上编译器也会把他当成指针形式来操作,取出p中保存的地址,即就是a的地址,然后加上括号里2个元素的偏移,得到一个新的地址,就是c的地址,此时就可以访问c。
char arr[]="abcdef";
与上例不同的是,此时是一个字符数组,每一个字符都被保存在栈上,连续存放。
- 指针形式访问:与上例相同,*(arr+2):通过首字符地址偏移两个元素类型的大小,找到c的地址然后解引用。
- 下标形式访问:这里编译器会得到a的地址,然后加上括号里两个元素类型大小的偏移得到新的地址,再取出内容。
综上:数组与指针是两个不同的东西,只是在使用时他们能达成同样的目的。
4,指针数组,数组指针
指针数组:强调数组,是一个存放指针的数组,即数组里的元素都是指针。
数组指针:强调指针,是能够指向数组的指针,具体是什么类型需要参考数组元素的类型。
例:
int *arr[10];//指针数组
char *arr[4];//字符指针数组
char**arr[5];//二级指针数组
int (*p)[10];//数组指针
如何区分?
优先级 ,通俗来说,p先和谁结合谁结合就是什么。
这里要注意:【】的优先级要高于*号,如果是指针就要加上()来保证p先和星号*结合。可以通过上面的例子看到,arr先和【】结合,所以是数组,arr先和星号结合,所以是指针。
画图说明,指针数组与数组指针的区别。
5,函数指针,函数指针数组,函数指针数组的指针
之前我们学过了函数调堆栈用时知道在函数调用时,函数也是有入口地址的。既然是地址,那么我们就可以用指针来保存。
函数指针
- 什么是函数指针:顾名思义,是能存放函数地址的指针。本质还是指针。
- 如何判断:
void (*pfun1)();//函数指针
void *pfun2();//函数
同样考虑优先级,先和星号结合就是指针。
(*(void (*)())0)();
那么这个如何判断?
分析:1,void(*)(),这是一个void类型的函数指针,返回值为空,参数也为空。
2,(void(*)())0,对0进行强制类型转换成函数指针的类型,即将函数保存在首地址为0的某段区域。
3,(* (void(*)())0),对地址解引用,取得首地址为0的区域的内容,即保存在那段区域的函数。
4,(* (void (*)())0)(),对函数进行调用,参数为空。
函数指针数组
- 函数指针数组:是数组,数组中的每个元素都是函数指针。
- 如何判断:
int (*parr[10])();//函数指针数组
同样是用优先级判断,先于[]结合,所以是数组。
char *(* p[3])();
这是一个函数指针数组,首先它是有3个元素的一个数组,里面存放的是指向函数的指针。
函数指针数组的指针
- 函数指针数组的指针:是一个指针,指针指向一个数组,数组的元素都是函数指针
- 如何判断:
char *(*(*p)[3])(char *);//函数指针数组的指针
同样你可以用优先级判断,先于星号结合,所以它是指针,指针指向一个有3个元素的数组,每个元素都是一个函数指针。