点击链接加入群【C语言】:http://jq.qq.com/?_wv=1027&k=2H9sgjG


    

上一节,我们分析了数组,其实我们不关心有关数组的意义,我们只需要关心数组的首地址,有了这个地址,我们为它赋以某种意义,那么我们就可以随意获取数据啦。所以前面分析的地址、变量类型、指针 这些概念都是非常重要的哦。

我们还知道一维数组中数组的名字就是记录了数组的首地址,有了这个地址,基本上就等于你可以访问这个地址周边的数据了。数组名默认是有类型的,我们上一节中也分析到了,不过这不重要,不管你是什么类型,记录的值只要是地址,我们就可以通过强制类型转换来改变这个地址的意义。

还知道数组名与普通变量的区别 ,数组名是不能更改的,而变量的值是可以更改的,这与我们之后要讲的const 是有一定的关系的哦。不过要在后面的章节中分析啦。

今天我们来分析一下二维数组,其实就是X*Y的一个方阵啦,韩信点兵的故事你应该听说过吧,就是这东西啦。不管你用什么样的阵,你的兵的数量是固定的,也就是你定义一个二维数组,那么它所存放的元素的个数是固定的,那么这个方阵就可以随意由你怎么看待啦,我们先来看一下二维数组的类型

C语言入门篇-08_C语言学习

我们看到我们定义的是 int arr[3][4] ,而arr却是int [3][4] 类型的,还是那句话,不管它是什么类型,它只要记录地址,我们就可以将它转换成我们需要的类型,一共12个元素,可以看成是1*12,2*6,3*4,4*3,6*2,12*1 的方阵,关键问题是我们怎么将它转换成其它类型呢,以及这个类型怎么定义,我们现在讨论的问题并不是很重要,但是它能再次让你对变量类型有个深刻认识,先来说一说吧,我们如果把它看成6*2的方阵,怎么定义这样的指针变量p呢?首先要确定的是类型,如果看是6*2的方阵,也就是六列两行,那么

p+1的意义就是偏移6个整数空间所占的位置,那么就对应两行,所以必须得是一个数组形式,如 int [6],但我们在上一节已经知道这是一个一维数组的类型,如何将它表示为指针呢,其实很简单,最起码得有个 * 号,OK,加在哪里呢,int 前面?我感觉你是秀逗了,哈哈,放在[6]里面?那你秀的更狠,放在后面?其实都不是,答案就是 int * [6] 。对嘛?答案是不正确的,因为很简单,回想类型的类型, 如果我写个 int * parr[6] ={.....}; 这是定义一个数组 parr,存放的元素类型是int *,parr的类型是 int * [6],就是上面的分析结果啊,很显然它是不正确的,那么为了区分这两种类型,为前面的*加上括号,以示区别 int (*)[6],这样子看的话,这个类型已经有点小复杂啦,是不是呢?我说类型那一节是非常重要的没有骗你吧。那么我们来定义一下这种类型的变量,变量写在*号的后面,如int * p ; int(*p)[6] ;

#include <stdio.h>

int main()

{

int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };

int (* p)[6] = ( int(*)[6] )arr;

printf("%d\n",p[1][1]);

return 0;

}

上面的代码,将arr记录的地址赋给变量p,但是p的类型是int(*)[6] ,这个应该算是从开始到现在遇到的最复杂的一个类型的例子了吧,但是我相信你已经可以理解它啦。

p[1][1] 就是2行6列方阵中的第2行中的第2个数据,如下表,取到的值就是8啦

123456
789101112

,当然大家有兴趣也可以将这12个数据组织成3维的,我在这里只给大家一个例子,只要大家能把上面的代码理解好了,就行了。下面属于扩展,不过其实前面讲的东西都真正理解了,下面的自然而然的也就会啦。

#include <stdio.h>

int main()

{

int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };

int (* p)[2][2] = ( int(*)[2][2] )arr;

printf("%d\n",p[1][1][1]);

return 0;

}

这段代码依旧是访问的数字8,不过是通过三维数组的方式啦。


好啦,我们来回顾一下今天讲了什么,今天讲的东西本身并不是很重要,因为其应用性较差,换言之,一般人是不会这么写代码滴,这是主要是想让大家对类型有一个扩展性的认识,不要以为就那么几个基础类型,指针类型是可以千变万化的哦,以后我们还有一个东西呢,就是函数指针啦,我们先来大概了解一下函数指针的概念,我们写的函数经过编译和链接后最终形成的是2进制机器码,它们被保存在.EXE文件中,当应用程序启动的时候,操作系统会将我们的字节码加载到相应的内存分区中,也就是说我们的机器代码也是被加载到内存中的,也就是我们的代码段喽,所以一个函数一般是有一个起始内存位置,这个内存位置也就是我们所以讨论的函数指针喽,它是一个地址,当我们为它赋以函数指针的意义,那么就可以把它当函数来使用啦。

举个简单的例子

#include <stdio.h>
int add(int a,int b)
{
    return a+b;
}
int main()
{
    int (*pfun)(int,int) = add;
    int c =pfun(1,2);
    return 0;
}

这个例子中,我们定义了一个求和函数,如果这个你看不懂,那你只能找本基础点的书看看喽,定义函数指针是怎么定义的呢?其实我们要从函数是怎么定义的来看,如下表

int add(int a,int b)
int (*pfun)(int,int)

其实应该可以发现,把形参去掉,只留下形参类型,将函数名换成(*函数指针名),还记得*一个地址的意义吧,就是可以访问到原数据,可以理解为*pfun = add,至于为什么要加括号,我们可以先把括号去掉,int * pfun (int,int) ,你能看出来有什么不对劲吗,前面 int * 成了返回值类型了,pfun成函数名了,这就成了函数声明啦,所以跟数组一样,为了做区别,我们使用了括号。

可能大家会发现一个问题,什么问题呢?就是如果我们要定义这种类型的变量,这个类型( int (*pfun)(int,int); )过于复杂了,书写不方便 ,如果我们可以给它重新起个名字的话就好了,C语言提供了这个方法呢,叫 typedef

typedef 源类型 新类型;

如:typedef unsigned int UINT 就是把unsigned int 定义为 UINT 以后我们就可以直接使用UINT a; 就相当于 unsigned int a;但是要注意这里不是等价替换,而是定义新类型,也就是这个新类型是一个新类型,不能直接使用替换功能进行扩展,估计我没说清楚呢。。。。你要自己好好理解。

那么对于int (*)(int,int) 这种类型 怎么进行重定义呢?

猜想一下:typedef int(*)(int,int) PFUN ; 这个应该可以吧,大家可以试试,结果是不行的,至于为什么,大家自己猜想为什么吧。

其实定义类型和定义变量是一样的,

#include <stdio.h>
int add(int a,int b)
{
    return a+b;
}
typedef  int (*PFUN)(int,int);
int main()
{
    PFUN pfun = add;
    int c =pfun(1,2);
    return 0;
}


<br><br>