文章开头,先梳理一下指针的基本知识
1 .指针是一个变量,用来存放地址。
2 .指针的大小是固定的4/8个字节(32位平台/64位平台)。
3 .指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
字符指针
字符指针char*是指针类型的一种,存放字符变量的地址,一般形式:
char c = 'a';
char *p1 = &c;
还有一种特殊的情况就是与字符串相关的时候
char* p2 = "abcdef";
这里其实是将字符串“abcdef”的首字符a的地址赋给了字符指针p,即通过字符指针我们可以找到一整个字符串,当我们尝试打印这一个字符串时,可以通过指向这串字符串的字符指针
指针数组与数组指针
指针数组顾名思义就是存放指针的数组。他本质上是一个数组,符合数组的所有性质,只不过里面的元素是指针类型
而数组指针,本质上是指针,指向数组的指针
从定义上分清了这两个概念,我们从代码角度来区别一下
所以p1为指针数组,而p2是数组指针
从以上两段代码我们了解到,当我们想定义一个数组指针时,由于[ ]的优先级高于 * 号的,所以必须加上()来保证p先和 * 结合。
提起指针,我们已经不陌生,指针就是地址,而在数组中,我们已知道的数组名代表数组首元素的地址,而&符号是取出某个元素的地址,那我们看一段代码来了解一下&数组名和数组名的区别:
当我们打印&数组名的地址和数组名的地址时,发现他们的结果是一样的,那是不是代表二者等价呢
我们已经知道指针±整数的意义,当我们对上面的代码进行±整数时:
当我们对两者进行+1的操作时,结果有了偏差,这就明白了&a和a的值虽然一样,但是所表示的意义并不同,从他们+1所差的字节数,我们可以大致总结一下
&arr其实表示的是整个数组的地址,并不是数组首元素的地址,这样数组地址+1,相差40,也就是跳过了整个数组的地址
arr代表数组首元素的地址,+1自然跳过一个元素(int型4个字节),打印出来的地址是跳过的下一个元素的地址
数组名是数组首元素的地址我们已经知道了,但是这种情况也有两个例外
1.sizeof(数组名):数组名单独放在sizeof()内部时,这里的数组名表示整个数组,计算的是整个数组的大小
2.&数组名,这里的数组名也表示整个数组,这里取出的是整个数组的地址
数组传参和指针传参
当在一个函数中需要运用到数组时,就需要把“数组”传递给函数,在数组传参过程中,会有很多种情况
首先看一下一维数组传参,下面几段代码是一维数组传参的实例
void test(int a[])//将数组作为参数存过去,函数形参拿数组来接收,因为形参本身不创造数组,所以可以省略数组大小
{}
void test(int a[10])//与第一种情况相同,数组大小标明10
{}
void test(int *a)//传参过来的是arr是数组首元素地址,地址拿指针接收
{}
void test2(int *a[20])//虽然a2是指针数组,但也是数组,形参也用指针数组接收
{}
void test2(int **a)//实参传过去一级指针的地址(a2),用二级指针来接收
{}
int main()
{
int a[10] = {0};
int *a2[20] = {0};
test(a);
test2(a2);
}
最后一个例子可能有些特殊,这里画图作解释:
既然有数组传参,那有一维数组就一定会有二维数组,我们看几个二维数组传参的实例:
void test(int a[3][5])//二维数组传参,二维数组接收
{}
void test(int a[][])//这个传参形式是错误的,形参部分根据数组的知识可知行可以省略但列不能省略
{}
void test(int a[][5])//二维数组传参可以省略行有几个元素
{}
void test(int *a)//这个传参形式是错误的,数组名是首元素地址,是一行的地址,不是一个整型的地址所以不能传给一级指针
{}
void test(int* a[5])//这个传参形式是错误的,传过去的是数组名,是地址,但是形参是指针数组,所以不行
{}
void test(int (*a)[5])//形参是数组指针,可以指向一行,每个元素是整型
{}
void test(int **a)//这个传参形式是错误的,实参传过去是地址,是第一行的地址不可以用二级指针接收。
{}
int main()
{
int a[3][5] = {0};
test(a);
}
ps:二级指针是用来接收一级指针的地址的
----->通过一级指针与二级指针传参的几个实例,我们大概也了解了传参的规则,那总结以上,当一个函数部分为一级指针的时候,函数能接收什么参数呢?当函数的参数为二级指针的时候,又可以接收什么参数?
对于函数参数为一级指针来说,可以传一个变量的地址,一个指向变量的指针,数组名....总而言之,只要传过去的类型与形参类型匹配,就可以
对于函数参数为二级指针时,可以传一个一级指针的地址,一个二级指针,指针数组,
函数指针
函数指针就是指向函数的指针变量
我们已经知道,数组名就是数组当地址,那么函数名是不是函数的地址呢?我们尝试打印函数名,发现二者结果一样,这说明函数名也作函数的地址
接下来看两个特殊的代码
1、(*(void (* )( ))0)();
2、void (*signal(int , void(*)(int)))(int);
通过分析我们发现这两段代码理解起来很复杂,对于这种情况,我们就可以利用typedef来对指针重命名,让这段代码看起来更容易理解
typedef int* p 将指针重新命名为p
typedef int(*p)[10] 重新对数组指针类型重命名为p
typedef int(*p)(int,int) 定义一个函数指针变量
综上可知,上面第2个代码例子就可以利用typedef转化成:
typedef void(*pf_t)(int)
pf_t signal(int,pf_t);
这样所表示的含义就一清二楚了
函数指针数组
通过上面的介绍,我们了解了整型指针数组,字符指针数组,那既然数组是可以存放一组相同的元素,指针可以指向函数,必然就可以有函数指针数组。
函数指针数组就是数组的每个元素是函数指针类型:
//例如在我们自定义了一系列Fun函数之后,就可以将他们设置为函数指针数组
int (*p[4])[int, int]= {Fun1,Fun2,Fun3....... };
既然可以有函数指针数组,那就随之也可以有指向函数指针数组的指针等等等,一层一层地“嵌套”。
看到这里本章就结束了伟大的程序员辛苦了