数组的某个成员可以用数组的基地址加上一个偏移量来表示。我们可以声明一个指针double *p;,把它作为基地址,然后就可以像数组一样在这个基地址上使用偏移量。在基地址上,我们可以找到第1个成员p[0]的内容,在基地址上前进一步可以找到第2个成员p[1]的内容,接下来以此类推。因此,只要提供一个指针以及两个相邻成员之间的距离,就可以把它作为数组使用了。

我们可以直接采用基地址加偏移量的书面形式,类似(p+1)。正如教科书所描述的那样,p[1]等同于 *(p+1),这就解释了为什么数组的第1个成员是p[0] == *(p+0)。

这个理论提示了一些规则,用于在实际应用中表述数组和它们的成员。

  • 可以通过显式的指针形式double *p,或静态/自动形式double p[100]来声明数组。
  • 不管是哪种情况,第n + 1个数组成员都是p[n]。不要忘了第一项是0而不是1,这样就可以采用特殊形式p[0] == *p。
  • 如果需要第n个成员的地址(而不是实际值),使用&符号:&p[n]。当然,第1个成员的地址就是&p[0] == p。

例1展示了这些规则的一些实际应用。

例1 一些简单的指针运算(arithmetic.c)

❶ 使用特殊形式*evens写入到evens[0]。

❷ 成员1的地址,赋值给一个新指针。

❸ 引用数组第1个成员的通常方式。

下面我再送你一个很好的技巧,这个技巧建立在指针运算规则“p+1表示数组中下一个成员的地址(&p[1])”的基础上。根据这个规则,我们不需要在遍历数组的循环中使用下标。在例2中我们就使用了一个备用指针来指向list的头部,然后用p++在数组中向前遍历,直到数组尾部的NULL标记,从而获得了整个数组值。如果你查看了接下来的指针声明的提示,会更容易理解这种用法。

例2我们可以利用p++表示“前进到下一个指针”实现循环的流水化

自己动手

如果不了解p++,你打算怎样实现这个目标?

如果目标是为了实现简洁的语法表示形式,基地址加偏移量这个技巧并不能提供太多的帮助,但它确实解释了C的许多工作原理。事实上,我们可以考虑一下使用结构,例如:

作为一种智力模型来分析,我们可以把list看成是基地址,list[0].b与基地址的距离正好用来表示b。也就是说,假设list的位置是整数(size_t)&list,b位于(size_t)&list + sizeof(int);,这样list[2].d的位置将是(size_t)&list + 6*sizeof(int) + 5*sizeof(double)。根据这种思路,结构就与数组非常相似了,区别是结构的成员是用名称而不是序号表示的,并且它们具有不同的类型和长度。

这个思路并不是非常正确,因为存在对齐这个因素,系统可能会决定数据需要位于某个特定长度的内存块中,因此字段尾部可能会填充一些额外的空间,使下一个字符从正确的位置开始,并且结构的尾部可能也会进行填充,使结构列表中的每个结构能够大致对齐[C99和C11,§6.7.2.1(15)和(17)]。stddef.h头文件定义了offsetof宏,它精确地描述了基地址加领偏移量的思路:list[2].d的实际地址是(size_t)&list + 2*sizeof(abcd_s) + offsetof(abcd_s, d)。

顺便说一下,在结构的起始处不可能出现填充,因此list[2].a肯定等于(size_t)&list+ 2*sizeof(abcd_s)。

下面是个笨拙的函数,它以递归的方式对列表中的成员进行计数,直到遇到值为0的成员。假设我们想把这个函数用于零值为合理数据的任何类型的列表,因此我们让它接受一个void指针(当然这不是一种好的思路)。

基地址加偏移量的规则解释了为什么这种做法是不行的。为了表示a_list[1],编译器需要知道a_list[0]的准确长度,这样才能知道应该从基地址偏移多少。但是,由于没有与之相关联的类型,它无法计算这个长度。

typedef作为一种教学工具

任何时候当我们遇到一种复杂的类型时,类似于指向某种类型的指针的指针的指针等情况,可以考虑用typedef进行简化。

例如,下面这个常见的定义:

有效地减少了字符串数组的视觉混乱,使它们的意图变得清晰。

在前面的指针运算p++例子中,char *list[]这样的声明是否很清楚地告诉你它表示一个字符串列表而*p是一个字符串?

例3对例2的for循环进行了重写,用string替换了char *。

例3 添加一个typedef声明使笨拙的代码稍稍变得清晰

list的声明行现在变得简单,很清晰地表示它是个字符串列表,并且string *p也很清晰地表示p是个指向字符串的指针。因此,*p表示一个字符串。

最后,我们仍然需要记住字符串是个指向字符的指针。例如,NULL是个合法的字符串值。

我们甚至可以更进一步,例如使用上面的typedef加上typedef stringlist string*,声明一个字符串的二维数组。这种方法有时候非常实用,但有时候只会增加记忆的负担。

从概念上讲,函数类型的语法实际上是指向一个特定类型的函数的指针。如果我们有一个头部类似下面这样的函数:

然后只要添加一个星号(并加上括号以保证优先级),就可以描述一个指向这种类型的函数的指针:

然后在前面加上typedef来定义一种类型:

现在我们可以把它当作一种类型使用,例如声明一个接受另一个函数作为其输入参数的函数,可以这样:

通过对函数指针类型的重新定义,那些接受其他函数作为输入的函数的表达—其中连环星号的书写曾是令人生畏的考验变得不再可怕。

最后需要说明的是,指针实际上要比教科书所描述的简单得多,因为它实际上只是一个位置或别名,根本不需要涉及不同类型的内存管理。像指向字符串的指针的指针这样的复杂构造总是会让人感到迷惑,但这只不过是因为我们以狩猎为生的祖先从来没有见到过这玩意而已。至少,C提供了typedef这个工具来处理它们。


本文节选自《C程序设计新思维》

内容简介

C语言已经有40年的历史了。经过长时间的发展和普及,C语言的应用场景有了很大的变化,一些旧观念应该被淡化或者不再被推荐。

本书展现了传统C语言教科书所不具有的最新的相关技术。全书分为开发环境和语言两个部分,从编译、调试、测试、打包、版本控制等角度,以及指针、语法、文本、结构、面向对象编程、库等方面,对C程序设计的核心知识进行查缺补漏和反思。本书鼓励读者放弃那些对大型机才有意义的旧习惯,拿起新的工具来使用这门与时俱进的简洁语言。

本书适合有一定基础的C程序员或C语言学习者阅读,也适合想要深入理解C语言特性的读者参考。

c++ 对象起始地址 指针靠齐_你需要知道的各种指针运算相关推荐

  1. c++ 对象起始地址 指针靠齐_Go的内存对齐和指针运算详解和实践

    uintptr 和 unsafe普及 uintptr 在Go的源码中uintptr的定义如下: /* uintptr is an integer type that is large enough t ...

  2. 智能指针的释放_堆栈里的悄悄话——智能指针

    来自公众号:编程技术宇宙 1 栈里的对象 "构造完毕,请睁开眼吧!",迷迷糊糊中,听着这个声音,我睁开了眼睛,一位小哥映入眼帘."你是谁?我是谁?这又是什么地方?&quo ...

  3. 智能指针的释放_手把手教你实现智能指针

    一. 为什么使用智能指针 C++的内存管理是让很多人头疼的事,因为C++语言中没有垃圾回收机制,当我们通过new语句动态申请堆空间时,用完这个堆空间时就得用delete语句释放申请的堆空间,不然就会导 ...

  4. 指针数组 c ++_了解C ++中的数组指针

    指针数组 c ++ 介绍 (Introduction) Today in this tutorial, we are going to understand the concept of the Po ...

  5. 明显调用的表达式前的括号必须具有指针函数类型_基于指针对象的方法

    " 本文来源于<The Go Programming Language>" 6.2. 基于指针对象的方法 当调用一个函数时,会对其每一个参数值进行拷贝,如果一个函数需要 ...

  6. void指针(void*):void*存放任意对象的地址、通过(int*)a转换指针类型

    0.void*简介 void*是一种特殊的指针类型,可用于存放任意对象的地址. void *pv =&obj; // obj 可以是任意类型的对象 void指针pv只保存了对象obj的首地址, ...

  7. 2021-08-23 FM24C04写入数据时,跨页会导致指针指向本页的起始地址

    FM24C04写入数据时,跨页会导致指针指向本页的起始地址

  8. java 对象地址_如何获取Java对象的地址?

    有没有办法获取Java对象的地址? 问题来自哪里?:首先,我读取属性文件,并将文件中的所有数据放入表中.属性文件可以更新.所以,我想听那个文件.我使用PropertyChangeSupport和Pro ...

  9. 结构体内指针数组调用_指针的这些技巧你都掌握了吗

    点击上方蓝字"杜明c"一起玩耍 摘要 为什么需要用指针? 一些概念 数组指针 指针数组 指针数组和数组指针在内存中的关系 函数指针 函数指针例子 指针作为参数的传递 通过函数修改指 ...

最新文章

  1. python统计元素个数_python怎么统计列表中元素的个数
  2. 赢了!赢了!赢了!反抗违法裁员,小米以绩效低为由辞退员工败诉!这里单独给大家一些应对参考!...
  3. java开心农场安卓_开心农场之田园日记
  4. java with关键字_[Java教程]javascript 里面 with 关键字
  5. java script 教程_Java Script入门
  6. Python语言学习之字母L开头函数使用集锦:logging日志用法之详细攻略
  7. C++中transform的用法
  8. 90后中国程序员“黑吃黑”博彩网站,半年获利256万,判刑11年半
  9. php如何把图片存入oracle,在PHP中将图片存放ORACLE中
  10. 访问母版页控件、属性、方法及母版页中调用内容页的方法
  11. python中confIgparser模块学习
  12. 从caffe2 开源的代码中抽取 用于加载已训练神经网络参数,使用CPU进行预测的 部分代码,并运行成功一个预测模型...
  13. word根据标题自动生成目录
  14. nginx HLS m3u8播放视频跨域问题
  15. 上交大计算机科学与技术,上海交通大学计算机科学与工程系(CSE)
  16. android学习笔记之GoogleMap
  17. 规范升级 NPM 包
  18. 2.9寸墨水屏制作互联网时钟填坑手记
  19. J-Link JTAG/SWD接口
  20. PHP第四方支付 - 彩虹易支付2021新版

热门文章

  1. 开源C# Winform控件库《SunnyUI》强力推荐
  2. Dotnet Core下的Channel, 你用了吗?
  3. Azure 静态 web 应用集成 Azure 函数 API
  4. 使用 GB28181.Solution + ZLMediaKit + MediaServerUI 进行摄像头推流和播放
  5. async,await执行流看不懂?看完这篇以后再也不会了
  6. 【要闻】Kubernetes安全问题严峻、Linux v5.4安全性浅谈
  7. Magicodes.IE 2.0发布
  8. 一文了解Nuget的使用
  9. 正反案例介绍SOLID原则
  10. 微软一顿操作猛如虎,PowerShell 排名直线上升