c指针详解 1

发布 2019-08-02 04:10:15 阅读 9776

9.1指针是什么

9.1.1 指针是一类数据类型的统称

对于c语言来说,计算机的内存由连续的字节(byte)构成。这些连续的字节同样被连续地编上了号码以相互区别,这个号码就是所谓的地址(address),如图9-1所示。

图9-1 内存单元与地址。

指针(pointer)是c语言中的一类数据类型的统称。这种类型的数据专门用来存储和表示内存单元的编号,以实现通过地址得以完成的各种运算。

这样看来指针似乎就是地址,然而,事实上却并非如此。后面将会看到,地址只是指针内涵中的一部分,甚至只是一小部分内容而远非其全部。片面地把地址理解为指针的全部,永远学不好指针。

为了使得语言具有广泛的适用性,c语言标准允许编译器自行选择指针类型数据的长度。在不同的编译环境下,指针数据类型的长度可能不同;甚至相同的编译环境中不同的指针数据类型,也可能有不同的大小。

为了叙述的方便,本书中的指针数据类型一律假设为具有32bit的长度。这样并不影响对指针本质的描述,但涉及指针数据类型长度的**(极少)在不同的编译环境中可能具有不同的结果,这点请读者加以注意。

c语言同样不规定地址这种内存单元的编号在内存中的存储格式,但在现实中目前这种编号多数是与二进制的unsigned int数据类型的存储格式一样,这是本章的另一个假定。这意味着程序可以访问的内存的大小最大为2的32次方(4gb)。但这绝对不意味着指针类型等同于 unsigned int 数据类型,因为它们的运算规则截然不同。

9.1.2 指针是派生数据类型

指针数据类型和数组、结构体、联合体等一样,也是一种派生数据类型(derived types)。也就是说,指针数据类型是一种借助其他数据类型构造出来的数据类型。对于任何类型(除了位段),都可以构造出与之相对应的指针数据类型。

因此指针数据类型实际上有无穷多种。

没有纯粹的指针,正如同没有纯粹的数组一样。数组是在其他数据类型的基础上构造出来的,指针也必须与其他数据类型一道才能构成自己。

指针让人感到比较复杂的原因之一在于,各种不同类型的指针都有自己的运算规则,尽管它们都被叫做指针。这一点请特别留意,不同类型的指针有不同的运算种类和不同的运算规则。

综上所述,每一种特定的指针类型都是一种派生数据类型,其值表示某个内存单元的地址,其用途是完成与地址有关的计算。

9.1.3 指针是一类数据的泛称

当某个数据的数据类型是指针时,通常也简称这个数据是一个指针。很显然,在这里“指针”具有“名词”的含义。而指针表示“数据类型”含义时,显然具有“形容词”的意味。

这种“一词多用”的现象,对于熟悉c语言特点的人来说并不值得大惊小怪,c语言本身也是这样的。比如,“[既可以作为类型说明符也可以作为运算符。

9.1.4 指针专用的类型说明符—“*

数组这种构造性的数据类型有自己特定的类型说明符—“[这种类型说明符用于定义数组或描述数组名的类型。

结构体和联合体数据类型特定的类型说明符分别是关键字“struct”和“union”。

指针也有自己的特定的类型说明符—“*

和仅靠“无法完成数组的描述一样,指针也需要“*”与其他的类型说明符一道才能完成对指针类型的完整描述。由于“其他的类型说明符”有无限多种,所以指针的类型也有无限种可能。可以构造出“int *”类型的指针、“char *”类型的指针、“double *”类型的指针、“void *”类型的指针……。

指针的一个重要特点是,它总是和另外一种数据类型联系在一起的。

9.1.5 指针的分类

尽管有无穷多种指针类型,但从指针所关联的数据类型方面看,指针可以分为3类:指向数据对象的指针(object pointer)、指向函数的指针(function pointer)、指向虚无的指针(“void *”类型)。前两者都与内存中的实体(数据和一段函数的执行**)有关,而“void *”类型的指针则仅仅是一个值,是纯粹的地址。

“指针就是地址”这样的说法对于“void *”这种类型的指针是成立的。但对于与一段具体内存实体相关联的指针类型来说,这种说法是极其片面的,甚至片面到了几乎完全忽略了指针的本质而只剩下了指针的皮毛的地步。正确的说法是,指针的值(右值)是地址,这与“指针就是地址”是完全不同的概念。

学习指针最重要的内容通常是关心指针的值以外的东西,而指针的值——下面将会看到,那几乎倒是无关紧要的(对于有些应用领域,如嵌入式开发等,有时需要特别关心指针的值。但对于初学者来说,这个值基本没有关心的必要。)。

从所具有的运算方面看,这3类指针各自拥有不同的运算种类的集合。有的运算种类多些,有的少些。

9.2 指向数据对象的指针

9.2.1 什么是“数据对象”

所谓“数据对象”(object),含义如下。

(1)是内存中一段定长的、以byte为基本单位的连续区域。

(2)这段内存区域中的内容表示具有某种类型的一个数据。

数据对象的类型不一定是简单数据类型(int、long、double等),也可以是派生类型,比如数组,甚至指针等。

而所谓的“指向”(pointer to)的含义是指针与这块具有类型含义的整体的关联。例如,对于。

1. int i;

“i”可以表示它所占据的内存块,当说到某个指针指向“i”时,其确切的含义是指向“i” 所占据内存的整体。显然这里提到的“i”是左值意义上的“i”。

函数类型不属于数据对象。

9.2.2 一元“&”运算

尽管前面各章从来没有提到指针,但实际上在前面编程的过程中已经和指针打过无数次交道了。这可能令人感到吃惊,但却是事实。

比如,在调用scanf()函数输入变量值的时候,在实参中经常可以看到的“&”实际上就是在求一个指向某个数据对象的指针。

对于下面的变量定义。

double d;

表达式“&d” 就是一个指针类型的数据,类型是“double *”这种类型的指针被称为是指向“double”类型数据的指针。

前面讲过,作为二元运算符,“&是按位与运算。当“&”作为一个一元运算符时,要求它的运算对象是一个左值表达式(一块内存),得到的是指向这块内存(类型)的指针。而一个变量的名字的含义之一就是这个变量所占据的内存。

大多数人在多数情况下关心的只是变量名的另一个含义—值,这可能是学不好指针以及c语言的一个主要原因。在此,简要地复习一下c语言的一些最基本的内容。假如有如下定义:

double d=3.0;

那么,应该如何理解表达式“d=d+5.0”呢?

这是一个赋值表达式,表示的确切含义是“取出变量‘d’的值与常量‘5.0’相加,然后把结果放到变量‘d’所在的内存中去”。请特别注意在赋值号“=”的左边和右边,“d”这个标识符的含义是不同的:

在赋值号“=”右边的“d”表示的是“d”的值,计算机的动作是取出这个值(本质上是在运算器中建立“d”的副本),并不关心“d”存放在内存中的什么地方;而在赋值号“=”左边的“d”表示的是“d”所在的内存空间,是把一个值放入这块内存中去,后一个动作与“d”中的值没有什么关系(只是把原来的值擦除),“d”中原来有什么值都不妨碍把一个新的值放入其中,也对新的值没有任何影响。

由此可见,同一个变量名确实有两种含义。针对两种不同的含义,计算机能进行的操作也不同。换句话说,对于某些运算,变量名的含义是其右值;而对于另一些运算,变量名的含义是其左值。

编译器根据上下文来分辨变量名究竟是哪种含义。对于用c语言编程的人来说,不分辨清楚这两种含义就不可能透彻地理解c语言。

再举个例子,在“sizeof d”这个表达式中,“d”的含义也是“d”占据的内存而不是“d”的值—无论“d”的值是多少,表达式“sizeof d”的值都为8。

在表达式“&d”中,“d”的含义也是“d”所在的内存而不是“d”的值,“d”的值是多少都对“&”的运算结果没有任何影响。

有一种说法称一元“&”运算是求地址运算,这种说法既是片面的,也是不严格的,同时对于学习指针有很大的负面作用。理由如下。

在c语言中根本没有“地址”这种数据类型,只有“指针”数据类型,而指针的值才是一个地址。用地址即指针的值的概念偷换指针的概念,显然是以偏概全。更为严重的是,这种说法使得许多人根本就不知道“&d” 是个指针,也掩盖了“&d”指向一块内存的事实,因为“&d”的值仅仅是“d” 所占据的那块内存单元中第一个byte的编号。

那么“&d”的值是多少呢?实际上多数情况下,尤其是对于初学者来说,根本没必要关心这个值是多少,也不可能事先知道这个值。因为为变量“d”安排存储空间是编译器的工作,编译器是根据程序运行时内存中的实际情况“随机”为变量“d”安排内存的。

源程序的作者是永远不可能为变量“指定”一块特定的存储空间,同样也不可能改变“d”在内存中的存储位置。

这样,“&d”就是一个既不可能通过**被赋值也不可能通过**被改变的值,因而是个常量,叫做指针常量(这种常量的含义是指不可能通过**在程序中改变。),类型是“double *”这样的常量不可以被赋值也不可以进行类似“++之类的运算,因为改变“&d”的值就相当于改变了变量“d”的存储空间的位置,然而这是根本不可能的。

当然,在程序运行之后,具体来说是“d”的存储空间确定之后(也就是定义了变量“d”之后,因为这时“d”才开始存在),“d”的值是确实可以知道的(其实知道了也没什么用)。如果想查看一下,可以通过调用printf()函数用“%p”格式输出(指针类型数据的输出格式是“%p”)。如下面所示。

程序**9-1

#include <>

#include <>

int main( void )

这段**的程序运行结果并不能事先确定,这和程序运行的具体环境有关。在作者的计算机上,其运行结果如图9-2所示。

图9-2 一元“&”运算。

这个运行结果表示的含义如图9-3所示。

图9-3 指针与地址。

应该注意到“d”没有被赋值,但程序没有任何问题。这再次说明了“&d”与“d”的值没有任何关系,在表达式“&d”中的“d”表示的仅仅是变量所在的内存而不是这块内存的值。

第十三讲指向结构体的指针与链表 C语言

本讲首先介绍了c语言中指向结构体变量和结构体数组的指针变量的使用,接着介绍了结构体数据作为函数参数的使用,最后介绍了链表的概念和基本操作。要求大家掌握指向结构体变量的指针变量和指向结构体数组及数组元素的指针变量的定义和引用,理解指向结构体变量的指针变量 结构体变量及其成员作为函数参数的使用方法,掌握...

c语言51单片机实例程序详解大全

实例1 使用累加器进行简单加法运算 mov a,02h a 2 add a,06h a a 06h 实例2 使用b寄存器进行简单乘法运算 mov a,02h a 2 mov b,06h b 6 mul abba a b 6 2 实例3 通过设置rs1,rs0选择工作寄存器区1 clr psw.4 p...

结构体和指针

一 结构体。结构体声明 结构体声明包含结构体关键字 类型名和数据成员。例 struct student char name char addr char age float height int weight char grade 7 结构体变量 定义包含直接声明时定义 单独定义。例 struct ...

谈CC 指针精髓

指针是c和c 语言编程中最重要的概念之一,也是最容易产生困惑并导致程序出错的问题之一。利用指针编程可以表示各种数据结构,通过指针可使用主调函数和被调函数之间共享变量或数据结构,便于实现双向数据通讯 并能像汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了 和c 语言的功能。在本文中...

人员密集场所能力验收流程详解 1

人员密集场所 四个能力 建设验收流程及演示项目设置。目录。一 人员密集场所 四个能力 验收重点模块示意图。二 通用验收流程。1 前厅模块。2 沿途讲解模块。3 楼层检查模块。4 灭火应急疏散演练模块。5 四个能力 具体内容抽查问答模块。三 消防安全重点部位演示项目。1 消防控制室检查。2 消防水泵房...

接地材料详解

铜包钢与普通镀锌钢的简单比较。3米长17.2mm直径的镀锌钢棒11年后开挖 在地下埋设11年的铜镀钢接地棒开挖 利用道路边缘施工。利用人行道施工。利用绿化带施工。焊接点在相同土壤10年后的效果。备注 以上 均为实践中所拍摄。中国目前接地的三大问题。从全国各地所做的接地调查揭示接地三大问题 1 接地网...

油封知识详解

一 什么是油封。油封是用于密封机械设备中旋转轴的封油用密封元件,而腔体基本上是静止的 见下图 所以油封又称旋转轴唇形密封圈。机械的摩擦部分由于在机械运转时有油进入,为防止这些油从机械的间隙中泄漏而使用油封,并且除了油以外还需要防止水与化学药液的泄漏以及尘埃及土砂从外部侵入,这时候也要用到油封。油封的...

C中as用法总结

在程序中,进行类型转换时常见的事,c 支持基本的强制类型转换方法,例如 object obj1 new newtype newtype newvalue newtype obj1 这样强制转换的时候,这个过程是不安全的,因此需要用try catch语句进行保护,这样一来,比较安全的 方式应如下所示 ...