指针数组
- 首先是一个数组
- 其次每个元素都是指针类型
- 指针指向的类型在定义时也已经确定
指针数组的声明
首先是声明的格式:
(基本类型)*(指针数组名)[元素个数];
这里举个例子:
char* arr[10];
数组arr
就是一个指针数组,怎么看呢?来,跟着我的步伐:
- 首先
arr
左边是一个*
号,右边是[ ]
号,我们知道arr
是先和[ ]
号进行匹配,那么说明arr
就是一个数组 - 然后看它的每个元素的类型,去掉
arr
剩余的就是它的元素的格式了,去掉之后变为char*
,那么就说明这个数组的每一个元素都是一个指向字符的指针。
指针数组的初始化(赋值)
指针初始化完成之后那么要进行的就是赋值了,当然也可以通过定义的时候进行初始化:
char* arr[] = {0}; //将所有指针赋值为空
或者
char a = 'a';
char b = 'b';
char* arr[] = { &a,&b };//将a和b的地址作为arr的元素
等很多种初始化方式…..
赋值的话我就直接进行总结一下,指针数组都可以将哪些值作为自己的元素?
- 相同类型变量的地址
- 指向相同类型指针
- 空指针
指针数组的用法
指针数组在实际的用途中可以当成普通的数组进行提取元素来使用,但是要牢记,指针数组存的是地址,取出来之后只能当做地址来使用,一个地址的使用方法我就不在这提了。
数组指针
- 首先是一个指针
- 这个指针指向的一个数组,表示整个数组
- 数组的类型在定义时也已经确定
数组指针的声明
首先是数组指针的声明格式:
(数组元素的类型)(*指针的名称)[]
首先我举个例子:
char (*parr)[10];
parr
是一个指针,它指向的是个什么呢?,当我们去掉*parr
时剩余的部分char [10]
就是它指向的类型,不难看出它这是一个数组的定义方式,只是没有名字而已,其实,这就是一个数组的格式,代表一个有10个元素的数组,10个元素的类型都是char
型的。
这里我要重要说明一下,数组指针在定义时必须将数组元素的个数标明出来,必须是指定长度的数组
数组指针的初始化
数组指针类型都这么复杂,到底怎么初始化呢?我们来一步一步分析:
- 数组指针指向的是一个数组的地址,那我们就得找一个数组的地址来赋给它,那么应该找什么样的数组合适呢?
- 首先数组指针在定义的时候就已经说明了,这个指针可以指向什么类型的并且多长的数组
- 所以我们就很容易为他找到合适的驻足的地址来存放了。
- 其次是数组的地址,我们都是知道,
&
后面跟数组名代表数组的指针,指向的是整个数组的地址。
我们以parr
为例,首先它指向是一个字符数组,数组的元素有10个,那额我们就可以这样为它赋值:
char arr[10];
parr = &arr;
这样我们就把数组arr
的地址赋给了数组指针parr
。
数组指针的用法
数组指针指向的是整个数组的地址,如果直接进行sizeof(数组指针)
结果为一个指针的大小。如果(数组指针)+1
那么当前的指针将指向数组后面的地址。
如下面这段代码:
#include<stdio.h>
#include<stdlib.h>
int main()
{
char arr[10];
char(*parr)[10] = &arr;
printf("sizeof(parr) = %d\n",sizeof(parr));
printf("arr = %p\n",arr);
printf("parr = %p\n",parr);
printf("arr + 1 = %p\n", arr+1);
printf("parr + 1 = %p\n",parr+1);
system("pause");
return 0;
}
你都做对了吗?
最后可以总结一句话,数组指针的用法就相当于
&数组名
二维数组
为什么要将二维数组放在这里介绍,因为学完函数指针才能正确的理解二维数组,好了,下面开始正式介绍二维数组
首先定义一个二位数组
char arr[5][10];
我们知道其有5行,10列,共有50个元素,我们也知道arr[0]
代表的是第一行的地址,那第一行是什么呢,第一行不就是一个有10个元素的一维数组,而第一行的地址就是一个10元素的一维数组的地址,也就是一个数组指针,所以一个二维数组就可以当成一个存放数组指针的一维数组。
那么arr
就是一个拥有5个数组指针的数组,而这5个数组指针都是指向10个char
型的数组。
二维数组传参
既然可以将二维数组看成存放数组指针的一维数组,那么我们就需要传这个一维数组的数组名,回想一维数组传参,需要传送一维数组名,以及每个元素的格式,那么就可以写成如下方式:
char (*arr[])[10];
怎么分析呢?
- 首先是
arr
和[]
结合,说明是一个数组。 - 然后去掉
arr[]
剩下的就是每个元素的格式。 - 剩下的就是
char(*)[10]
这就是一个数组指针类型,而这个中的[10]
是类型,所以不可缺少。 - 而真正数组在传参时可以简化为
arr[][10]
,这下你终于知道了二位数组传参时列的数量为什么不可以省略了吧。
函数指针
函数地址
函数指针,字面意思是存放函数地址的指针,那么函数就肯定有地址喽。
我们试着打印一下函数的地址
int add(int a,int b)
{
return a+b;
}
main()
{
printf("%p",&add);
return 0;
}
结果是打印的出来的,但是如果将函数名以地址形式进行打印,会发现和刚才函数的地址的值是一样,那么证明了函数名其实也就是函数的地址,但是,会发现一个怪的现象,函数名和&函数名
打印出来的都是函数的地址,但是因为函数名就是函数地址,所以不管进行多少次&函数名
得到的值都应该一样,但是这就不对了,我们都知道给一个变量进行多次取地址时,编译器就会报错,因为当你取到这个地址时在进行取地址就是将这个变量的值当作地址去访问此位置的内存时,这就是非法的,所以,我们将函数的地址作为一个例外吧。
函数指针
那么函数指针应该用一个什么变量来存函数地址呢?
这里我先写出来:
int (*padd)(int,int);
下面我来分析一下:首先padd
和*
结合,那么说明padd
就是一个指针,然后,去掉*padd
之后剩余的就是此指针可以指向的函数的返回值类型和参数类型了,*padd
前面的int
就是返回值,后面的括号内容就是他的参数类型列表了,可以明显的看到是两个int
型的参数。
那么说明padd
这个指针可以指向返回值为int
参数为两个int
型变量的函数了,那么我们完全就可以将上面函数add
的地址赋给padd
了:
padd = &add; //经过上面函数地址的介绍,此处不加 & 符是可以的,但是为了意思明显一点,加上还是好的。
函数指针的用法
既然函数有地址,那么我们应该怎样去使用它呢?我们可以想像一下,函数的地址需不需要加或者减呢?
* 函数的地址如果加一,那么将会跳过多大空间呢?
* 其次如果跳过整个函数,那么跳过之后又指向的是什么呢?
总之,函数的地址是不能加和减的。
那么函数的地址应该怎么用呢?
首先是解引用,从一般的地址的属性来讲,一个地址解引用找到的是那块空间里的东西,那么函数的地址解引用就是调用函数喽:
int ret = *padd(1,2);
这样对不对呢? 结果是错的,因为padd
是先和括号结合的,所以应该这样:
int ret = (*padd)(1,2);
这样结果就很完美了吗?
那么这样呢?
int ret = padd(1,2);
既然函数的地址的地址还是函数的地址,那么函数的地址就可以当成是函数的地址的地址了,那么对函数的地址和对函数的地址的地址解引用又什么不同的,我们一般通过函数名来调用函数,那不就是通过函数的地址来调用吗,那通过函数的地址的地址调用也不是一样的么。
函数指针数组
既然有函数指针这个类型,那么我们如果把多个相同的函数指针类型的指针放在一块就形成一个函数指针数组。
函数指针数组类型
函数指针数组变量的类型到底怎么写呢?如下
int (*ArrPF1[10])(int,int);
int (*ArrPF2[])(int,int) = {0,0,0,0};
以上两种定义的方式我来解释一下:
第一种(未初始化):
- 首先是
ArrPF1
跟[10]
结合形成一个10元素的数组 - 然后去掉
ArrPF[10]
剩余的int (*)(int, int)
就是数组元素的类型,这个类型明显是函数指针类型 - 因此,这个定义可以认为是定义一个10元素的函数指针数组,每一个函数指针都指向的是返回值为
int
参数为两int
型变量的函数。
第二种(带初始化):
- 首先我们知道
ArrPF2
是一个函数指针指针数组。 - 那我们看后面的初始化为四个
0
,所以此数组也就只有4个元素 - 指针初始化为0就是将指着初始化为空指针
- 这个定义我们就可以认为是定义了四个为空的函数指针,每个函数指针指向的都是返回值为
int
参数为两int
型变量的函数
函数指针数组的使用
首先它是一个数组,我们就可以把它当作数组用 数组名[下标]
来访问其每个元素,进行使用,因为每个元素都是一个函数指针,所以访问到的每一个元素都是函数指针,又因为函数地址的特殊性,每一个元素都可以当作一个函数名,然后直接调用。
int ret = ArrPF1[0](1,2);
这里我就通过函数指针数组的形式调用了一个函数,这个函数正好就是函数指针数组第一个元素所指向的函数。
指向函数指针数组的指针
先来段代码:
int arr[10];
int (*parr)[10] = &arr;
我们都知道&arr
代表数组的地址,它的类型是一个数组类型,代表它指向的是一个数组,它的定义我们就用以上这种方式,那么 &ArrPF
算是一个什么类型呢,按道理说应该是一个函数指针数组类型,但是要怎么写呢?
想着确实有点复杂,首先我们会写函数指针数组,那么我们去掉数组名剩下的就是函数指针数组类型了:
int (*[10])(int, int);
因为我们要顶一个指向函数指针数组的指针,所以,首先我们得定义一个指针,指针指向的为函数指针数组类型,那么就得出了这样的写法:
int (*[10])(int, int) *PArrPF;
思路是没有任何问题,但是这样写着总感觉不对,那么就直接按照规定来,将 *PArrPF
写在 [10]
的前面, *
的后面,并用 ()
将 *PArrPF
括起来以表示 PArrPF
是一个指针,所以就形成了
int (*(*PArrPF)[10])(int, int);
这就是完美的解决办法。
我们也可以直接给它初始化:
int (*(*PArrPF)[10])(int, int) = &ArrPF;
将函数指针数组ArrPF
的地址赋给它。
指向函数指针数组的指针的用法特别的稀有,所以说,算了不解释了,其实小编并不知道这个东西什么时候能用到。
直到这里,关于指针的所有基础知识我都讲完了其实你在用指针的时候完全可以认为所有指针是同一个东西,不过考虑一下问题,就会很简单额将它们分隔开来。
- 此指针可以指向什么类型的变量,而我需要一个指向什么类型变量的指针。
- 此指针的加一或者减一可以跳过多大的空间,能跳到哪儿。
- 此指针能不能解引用,解引用之后的值能不能改,此指针的值能不能改。