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



时间已经过去很多了,我们上次谈到了函数调用了,,,前面也讲过指针是怎么回事了,现在我们再来回顾一下前面的知识。

首先,我们讲我们的数据都是在内存中的,内存是以字节为单位的,系统为我们分配物理内存,并通过MMU(内存管理单元)将物理内存地址映射到逻辑地址空间里,以后如果有机会,我们可以给大家演示一个实例 ,如何定位到我们的程序中的数据所在的物理内存,当然这个是有点小复杂的啦,反正不管怎么着,大家要知道映射这一概念啦。

然后我们讲了变量的类型,很多种类型,然后我们对地址进行分析,地址其实就是一个32位的数字,这个数字本身是没有什么意义的,问题在于我们怎么看待它,也就是我们要为它赋上意义,如 0X0012FF7C 这一个地址,我们把它看成 (int *)0X0012FF7C 那么现在它就是一个有意义的地址了,把它看成 (char *)0X0012FF7C 它也变得有意义了,关键这个意义是什么,需要我们用一个* 运算符来解释它,如 *(int *)0X0012FF7C 那么就会取到 以0X0012FF7C为起始地址的连续四个字节的数据,而且是按照整数的补码表示形式来进行解释的,也就是如下表

地址
0X0012FF7C0X41
0X0012FF7D0X42
0X0012FF7E0X43
0X0012FF7F0X44

*(int *)0X0012FF7C 取到的是 0X44434241 ,而*(char *)0X0012FF7C 取到的是 0X41,很好理解吧。

后来我们又讨论了 有意义的 地址 加1 与 减1 的意义,它偏移的是这个赋与它意义的类型的大小,也就是int * 会偏移4个字节,而char *会偏移1个字节。

然后我们就过度到函数调用了,函数是一个非常有意思的东东,我们写了一个交换函数,其实就是设计了一段二进制代码,如果这个你不理解,你应该看看前面的章节了,CPU 顺序执行 这段二进制,遇到各种指令进行各种处理。但是函数调用过程中,普通数据是存放在 栈区中的,我们在上一次的视频里已经分析到了,还看到函数调用过程的细节,如保存上一函数执行的环境,申请栈内存,初始化申请的空间,在栈中为局部变量申请空间,压参,调用其它函数,从其它函数返回,平衡栈,还原上一函数执行环境,返回到上一函数中继续执行, 大致就是这么一个流程了。如果你有哪个地方不理解,06篇你应该再多看几遍了哦。


好了,做了一下简要回顾,我们来看一下今天来分析一下神马东东。来谈一下数组吧。

int arr[3] = {0,1,2};

这就是定义一个整型数组,有3个整数,其实,如果大家前面理解的好,现在自己调试一下,在内存窗口地址框中输入arr就可以看到效果了。如下图

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

是不是感觉很容易理解呀,如果是,那么你已经步入C语言的大门啦。

其实我们讲数组其实很简单,就是可以将多个值存放在一块连续的内存空间里,那么我们其实都知道数组可以通过arr[index] 的方式来进行访问,那么它到底是什么东西呢,我来来看看。其实数组这个概念,我们可以当没有它呢,我们还记得地址吧,其实arr就是记录了整个数组占用的连续内存空间的起始位置,那么,我们只要有了这个地址,就可以通过指针的加1方式进行偏移来访问数据啦。当然这里要求你对前几节的知识了解的比较透彻呀,正所谓,基础不牢,地动山摇。,而arr就是这个地址,我们可以通过将一个地址赋给一个别的类型的指针来检测一个地址的类型,如下

C语言入门篇-07_C语言学习_02

我们可以看到数组名是有类型的,其类型是 int [3] 类型 ,是不是很难理解,是的,是就对啦,其实 我们在讲类型的时候 就讲过其实 类型是一个很复杂的东西呢,现在是不是已经有点感觉了呀,int [3] 也是类型,很好玩呀,很有意思,那么是不是意味着 short[2][3][4][5] 也是类型呢,其实是这样的,所有这些都是一种类型,但是它们到底记录的是什么呢?我来告诉大家,它们记录的是 地址,地址呀,熟悉吧,就像指针似的,记录的是地址,也就是arr记录的是地址,而且arr把它看成了int [3] 这种类型,其实就是连续的3个整数类型吧,这个意思是说,arr+1会偏移 12个字节(就是3个整数的长度),能理解了吧,所以说,我在讲类型的时候,说过类型是很重要的概念,我们在学习的时候,要注重概念性的东西,有许多人说谭浩强的书不好,为什么呢?因为他们根本没看懂谭浩强书里面强调的概念,书中对很多东西都有定义,但是却很少有人能描述清楚,比如逗号表达式,你现在能给个定义出来吗?好吧,先讨论到这里,既然我们知道数组名是记录了地址之后 ,那么我们有了这个地址就可以访问到里面的数据了呀,这里需要一个概念啦,就是前面提到过的 赋给一个地址具体的意义,现在arr记录的地址的意义是int[3],但是我们想访问数组里面的值,所以我们要强制转换它为int * ,代码如下

#include <stdio.h>
int main()
{
    int arr[3] = {0,1,2};
    for (int i=0;i<3;i++)
    {
        printf("%d\n",*((int *)arr + i));
    }
    return 0;
}
如果你看不懂的话,我再来做最后一次分析,以后的小节中不会再对指针的这种写法做其它解释了啊,这可是最后一次了。
(int *)arr  就是先将arr记录的地址转换成int *类型,方便我们对这个地址用*运算符取到里面的整数(为什么?因为我们存的就是整数呀),那么后面加i就是在数组的第一个地址上进行偏移,一次偏移四个字节呀。好了,如果再不理解,你只能从头再来了,一定要重头再来。
好吧,既然 (int *)arr 转换之后是一个 int * 类型的 地址,那么我们为什么不把它用一个int *的变量来表示呢?代码如下:
#include <stdio.h>
int main()
{
    int arr[3] = {0,1,2};
    int * p = (int *)arr;
    for (int i=0;i<3;i++)
    {
        printf("%d\n",*(p + i));
    }
    return 0;
}
其实非常简单的吧,好了,这就是我要讲的数组啦,一维数组已经讲完了。
那么p与arr 有什么区别吗? 答案是有的, 不过区别不是很大。而且我们一般也不会太过于关心这些问题。
p是int *类型的,arr是int [3] 类型的,所以它们的类型是不一样的,但是他们都是用来记录地址的,不过我们可以通过强制类型转换进行转换。
p是变量,是int *类型的变量,所以它的值是可以变化的,我们可以让p=p+1;让p指向数组中的下一个元素,但是arr=arr+1却是不可以的,因为arr是不能进行修改的,回忆一下上一次谈的函数调用,我们的数组其实是在栈上申请的,当这块内存已经申请好了,并将arr的值指向了首地址后,那么这个值就是固定的了,因此也就不能修改了,所以这也是一个区别哦。那么我们来总结一下访问数组元素的方法 吧。代码如下:
#include <stdio.h>
int main()
{
    int arr[3] = {0,1,2};
    //方法一
    for (int i=0;i<3;i++)
    {
        printf("%d\n",arr[i]);
    }
    //方法二
    for (i=0;i<3;i++)
    {
        printf("%d\n",*(  (int *)arr +i ) );
    }
    //方法三
    for (i=0;i<3;i++)
    {
        printf("%d\n",i[arr]);//这个跟方法一其实是一样的,都是arr+i
    }
    //方法四
    int * p = (int *)arr;
    for (i=0;i<3;i++)
    {
        printf("%d\n",*(p + i));
    }
    //方法五
    for (i=0;i<3;i++)
    {
        printf("%d\n",p[i]);    //跟arr[i]可以做一样的理解啦
    }
    //方法六
    for (i=0;i<3;i++)
    {
        printf("%d\n",*p++);    //通过偏移p来访问,p的值被修改了
    }
    return 0;
}

好了,这一次就介绍这么多了。。如果有不理解的,希望大家多看前面的介绍啦。