再‘论’指针&数组

再‘论’指针&数组

刚刚接到的通知,下周要去MS面试了。作为一个真心对C++不感冒的人来说,不知道要和他们聊什么了,没准还是HTML 5呢!今天和大家分享一个小知识,关于C的数组和指针的一点学习。

     我曾经迷恋上了《C专家编程这本书》,断断续续的看了不到一个月,有一章叫做再论数组,突然想起了,里面曾经谈到的一个经典的话题,就是数组和指针转换的问题。

     我们首先定义一个数组,(数组就是开辟一段连续的内存保存数据)

      char a[10] = "abcde";

     补充一点背景知识:a保存的是数组的第一个元素a[0]的地址。

     我们知道我们可以通过a[n]来直接访问某一个位置的元素,同时也可以用指针来操作

     char *p = a;


 

     这时候,我们可以使用*(p+n)来实现同样的效果,当然也可以p[n]来操作,这个时候就有了一个说法,指针和数组是“一样的”,真的一样吗?

     但如果我们仔细思考C语言编译器的内存分配的时候,就会发现,指针操作实际上多了一步的操作:

         

     从图中可以看出,p保存的是一个地址,其实*p得到的是一个地址。这个地址就是a的地址,所以当我们通过指针操作元素的时候,首先是从指针中获得一个地址x,然后编译器再去访问x地址,而用数组操作元素的话,则直接访问本地址的内容。 这是一个区别。

     这个区别在什么时候可以出现,就是在定义和声明的时候,如果你定义了一个数组,而在另一个文件中用extern关键字声明成了指针就会出错。因为二者在内存上的分布是不同的。

     另一个经典的区别是,p是一个变量,可以对其本身进行改变,但是数组名是一个常量,只读不可写。


 

     为什么数组可以用指针偏移量来做呢?其实是下标引用也将会被编译器改为用指针偏移量来执行,原因是在底层硬件上,指针偏移更加的高效和容易实现。

     Peter 曾经写到多年来对于指针和数组可以相互转换的误区,就是大家一直没有看到完整的说法,指针和数组等价的前提是做函数参数的时候。。原因是无论函数参数是数组还是指针,最终编译器都会将其转化为指针。

     我按照老人家的要求写了一个demo,就是用来演示这一事实。          

         

     上面的两个方法用于返回参数的大小,我们将比较一个数组传入函数后,其大小是否还是原来的数组

       

       上面的两个函数,将分别打印参数的地址,我们将验证所谓的首地址还是不是数组的首地址  

      

      主程序,我们定义了一个全局的数组,并且打印了他的大小,可以预测数组的size是6.下面我们看一下程序的执行结果

       

      

      可以看出,原来数组的size是6,但是传入函数后,无论是以数组和指针的形式,大小都变成了4,没错就是一个指针所占的大小,而不再是6哥字符的空间了,说明了什么?

      没错,数组都被转换成了指针!

      接下来我们打印函数参数的地址,发现其地址并不是数组元素的首地址,根据我们的第一个图,内存分布,我想大家也可以看懂,现在的参数已经不是数组名了,而是一个新的指针。


 

     那么,编译器为什么这么做呢?就是因为传参的时候,如果我们传进一个整个的数组,效率会大大降低,转换成指针可以节省时间和空间。

      其实,总结一下,数组和指针也没有那么纠结,其实就是操作内存的不同形式,明白了内存的操作,以上的东西就都是形式上的东西了~

      

     本文完整源代码:https://github.com/octobershiner/Algorithm/tree/master/Pointer_Array


http://www.cnblogs.com/octobershiner/archive/2012/04/12/2444868.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
序列是软件系统中常见的一类数据结构,它由一系列的元素组成,其中每个元素都有一个唯一的位置。序列在软件系统中的应用非常广泛,比如字符串、数组、链表等都是序列的形式。在软件工程中,序列的正确性是非常重要的,因为序列的错误可能会导致程序的崩溃或者结果不正确。因此,形式化方法和语言在序列的处理中发挥着重要的作用。 本文将介绍一些关于序列的形式化方法和语言的研究,包括序列的表示和操作、序列的性质和规约、序列的验证和测试等方面。本文将分为三个部分,首先介绍序列的表示和操作,然后介绍序列的性质和规约,最后介绍序列的验证和测试。 一、序列的表示和操作 序列的表示和操作是序列处理中最基本的问题之一。为了有效地处理序列,需要选择一种合适的数据结构来表示序列,并实现一组有效的操作来对序列进行操作。 在序列的表示方面,有许多不同的方法,其中最常见的方法是使用数组和链表。数组是一种连续的内存块,用于存储相同类型的元素。数组的访问时间是常数时间,因此非常适合用于访问序列中的任意元素。链表是一种非连续的内存块,每个元素都包含指向下一个元素的指针。链表的访问时间是线性时间,但是插入和删除元素的时间是常数时间,因此非常适合用于在序列中插入和删除元素。 在序列的操作方面,有许多不同的操作可以用来操作序列,包括插入、删除、替换、反转、排序等。这些操作可以通过不同的方法实现,比如使用循环、递归、迭代等。例如,插入操作可以通过将序列分为两个部分并插入新元素来实现,删除操作可以通过将序列中的元素移动到正确的位置来实现,替换操作可以通过删除和插入操作的组合来实现,反转操作可以通过迭代或递归来实现,排序操作可以通过不同的排序算法来实现。 二、序列的性质和规约 序列的性质和规约是序列处理中另一个重要的问题。序列的性质是指序列的一些特定属性,比如长度、重复元素、有序性等。序列的规约是指对序列进行形式化描述的过程,可以使用各种形式化方法和语言来描述序列的规约。 序列的性质和规约有助于确保序列的正确性和一致性。例如,长度属性可以用来确保序列中的元素数量是正确的,重复元素属性可以用来确保序列中的元素不重复,有序性属性可以用来确保序列中的元素按照特定的顺序排列。规约可以用来描述序列的操作和约束条件,比如插入操作必须保留元素的顺序,删除操作必须确保序列中的元素数量减少等。 在序列的性质和规约方面,有许多不同的方法和语言可以使用。其中最常见的方法是使用谓词逻辑和类型论。谓词逻辑可以用来描述序列中的元素和操作,类型论可以用来描述序列中的类型和属性。例如,下面是一个使用谓词逻辑和类型论描述序列的规约的例子: ``` Seq = (E:Type, n:nat, f:nat->E) Elt(i:nat) = (j:nat | j < i) forall i:nat, j:nat, e:E, insert(Seq, i, e, Seq') -> Seq' = (E, n+1, f') /\ forall k:nat, (k < i -> f'(k) = f(k)) /\ (k = i -> f'(k) = e) /\ (k > i -> f'(k) = f(k-1)) ``` 上述规约描述了一个序列Seq,它由一个类型E、一个长度n和一个映射f组成。Elt(i)表示在位置i之前的元素的集合,insert(Seq,i,e,Seq')表示在位置i处插入元素e并得到新序列Seq'。规约中使用了forall、->、/\等符号,表示量词、逻辑蕴含和逻辑与等概念。 三、序列的验证和测试 序列的验证和测试是序列处理中的最终问题。验证是指通过形式化方法来证明序列的正确性和一致性,测试是指通过实验方法来检查序列的正确性和一致性。 在序列的验证方面,有许多不同的方法可以使用。其中最常见的方法是使用模型检测和定理证明。模型检测是一种自动化验证方法,可以通过生成状态空间来检查序列是否满足规约。定理证明是一种手动验证方法,可以通过形式化推理来证明序列是否满足规约。这些方法都需要使用形式化方法和语言来描述序列和规约,并且需要使用相应的工具来执行验证。 在序列的测试方面,有许多不同的方法可以使用。其中最常见的方法是使用单元测试和集成测试。单元测试是一种针对单个函数或模块的测试方法,可以通过输入输出对比来检查序列的正确性。集成测试是一种针对整个系统的测试方法,可以通过模拟用户使用情况来检查序列的正确性。这些方法都需要使用相应的测试框架和工具来执行测试。 结论 序列是软件系统中常见的一类数据结构,处理序列的正确性和一致性是非常重要的。形式化方法和语言在序列处理中发挥着重要的作用,可以用来表示和操作序列,描述序列的性质和规约,验证和测试序列的正确性和一致性。这些方法和语言可以提高序列处理的效率和可靠性,有助于确保序列的正确性和一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值