文档章节

第一章 数组与指针概念剖析

北极心
 北极心
发布于 2016/08/11 09:52
字数 3322
阅读 4
收藏 0
点赞 0
评论 0

 

数组与指针生来就是双胞胎,多数人就是从数组的学习开始指针的旅程的。在学习的过程中,很自然就会经常听到或见到关于数组与指针的各种各样的看法,下面我节选一些在各种论坛和文章里经常见到的文字:

“一维数组是一级指针”

“二维数组是二级指针”

“数组名是一个常量指针”

“数组名是一个指针常量”

........................

这些文字看起来非常熟悉吧?类似的文字还有许多。不过非常遗憾,这些文字都是错误的,实际上数组名永远都不是指针!这个结论也许会让你震惊,但它的确是事实。但是,在论述这个问题之前,首先需要解决两个问题:什么是指针?什么是数组?这是本章的主要内容,数组名是否指针这个问题留在第二章进行讨论。看到这里,也许有人心里就会嘀咕了,这么简单的问题还需要说吗?int *p, a[10];不就是指针和数组吗?但是,笔者在过往的讨论过程中,还真的发现有不少人对这两个概念远非清晰,这会妨碍对后面内容的理解,所以还是有必要先讨论一下。

什么是指针?一种普遍存在的理解是,把指针变量理解成就是指针,这种理解是片面的,指针变量只是指针的其中一种形态,但指针并不仅仅只有指针变量。一个指针,包含了两方面的涵义:实体(entity)和类型。标准是这样描述指针类型的:

6.2.5 Types

A pointer type may be derived from a function type, an object type, or an incomplete type, called the referenced type. A pointer type describes an object whose value provides a reference to an entity of the referenced type. A pointer type derived from the referenced type T is sometimes called ‘‘pointer to T’’. The construction of a pointer type from a referenced type is called ‘‘pointer type derivation’’.

请留意第二句所说的内容:指针类型描述了这样一个对象,其值为对某种类型实体的引用。标准在这里所用的措词是指针类型描述了一个对象。

再来看看标准关于取址运算符&的规定:

6.5.3.2 Address and indirection operators

Semantics

The unary & operator returns the address of its operand. If the operand has type “type”, the result has type “pointer to type”....... Otherwise, the result is a pointer to the object or function designated by its operand.

这个条款规定,&运算符的结果是一个指针。但问题是,&表达式的结果不是对象!标准自相矛盾了吗?当然不是,这说明的是,指针的实体有对象与非对象两种形态。

我们常说的指针变量只是指针实体的对象形态,但对象与非对象两种形态合起来,才是指针的完整涵义,就是说,无论是否对象,只要是一个具有指针类型的实体,都可以称之为指针,换言之,指针不一定是对象,也不一定是变量。后一种情况,指的是当需要产生一个指针类型的临时对象时,例如函数的传值返回或者表达式计算产生的中间结果,由于是一个无名临时对象,因此不是变量。

在C++中,由于引入了OOP,增加了一种也称为“指针”的实体:类非静态成员指针,虽然也叫指针,但它却不是一般意义上的指针。C++标准是这样说的:

3.9.2 Compound types

....... Except for pointers to static members, text referring to “pointers” does not apply to pointers to members..........

接下来,该谈谈数组了。数组是一种对象,其对象类型就叫数组类型。但笔者发现有个现象很奇怪,有些人根本没有数组类型的意识,不过也的确有些书并没有将数组作为一个类型去阐述,也许原因就在于此吧。数组类型跟指针类型都属于派生类型,标准的条款:

6.2.5 Types

An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T, the array type is sometimes called “array of T”. The construction of an array type from an element type is called “array type derivation”.

数组类型描述了某种对象的非空集合,不允许0个元素,我们有时候看见某个结构定义内部定义了一个大小为0的数组成员,这是柔性数组成员的非标准形式,这个留在第八章讲述。数组类型的语法(注意不是数组对象的声明语法)是element type[interger constant],例如对于

int a[10];

a的数组类型描述就是int[10]。

数组名作为数组对象的标识符,是一个经过“隐式特例化”处理的特殊标识符。整数对象的标识符、浮点数的标识符等等虽然也是标识符,但数组名与之相比却有重大的区别。计算机语言存在的目的,是为了将人类的自然语言翻译为计算机能够理解的机器语言,让人类更加容易地利用和管理各种计算机资源,易用是思想,抽象是方法,语言将计算机资源抽象成各色各样的语言符号和语言规则,数组、指针、整数、浮点数等等这些东西本质上就是对内存操作的不同抽象。作为抽象的方法,可以归纳为两种实现,一是名字代表一段有限空间,其内容称为值;二是名字是一段有限空间的引用,同时规定空间的长度。第一种方法被各种计算机语言普遍使用,在C/C++中称为从左值到右值的转换。但数组不同于一般的整数、浮点数对象,它是一个聚集,无法将一个聚集看作一个值,从一个聚集中取值,在C/C++的对象模型看来缺乏合理性,是没有意义的。在表达式计算的大多数情况中,第一种方法并不适合数组,使用第二种方法将数组名转换为某段内存空间的引用更适合。

因此,与一般标识符相比,数组名既有一般性,也有特殊性。一般性表现在其对象性质与一般标识符是一样的,这种情况下的数组名,代表数组对象,同时由于符合C/C++的左值模型,它是一个左值,只不过是不可修改的,不可修改的原因与上一段中叙述的内容相同,通过一个名字试图修改整个聚集是没有意义的;而特殊性则反映在表达式的计算中,也就是C/C++标准中所描述的数组与指针转换条款,在这个条款中,数组名不被转换为对象的值,而是一个符号地址。

现在来看看标准是如何规定数组与指针的转换的:

C89/90的内容:

6.2.2.1 Lvalues and function designators

Except when it is the operand of the sizeof operator or the unary & operator, or is a character string literal used to initialize an array of character type. or is a wide string literal used to initialize an array with element type compatible with wchar-t, an lvalue that has type “array of type” is converted to an expression that has type “pointer to type” that points to the initial element of the array object and is not an lvalue.

C99的内容:

6.3.2.1 Lvalues, arrays, and function designators

Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type “array of type” is converted to an expression with type “pointer to type” that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

数组类型到指针类型转换的结果,是一个指向数组首元素的类型为pointer to type的指针,并且从一个左值转换成一个右值。经过转换后,数组名不再代表数组对象,而是一个代表数组首地址的符号地址(这一句应为数组首元素地址的符号地址,2011年4月),并且不是对象。特别指出的是,数组到指针的转换规则只适用于表达式,只在这种条件下数组名才作为转换的结果代表数组的首地址(应为数组首元素的地址,2011年4月),而当数组名作为数组对象定义的标识符、初始化器及作为sizeof、&的操作数时,它才代表数组对象本身,并不是地址。

这种转换带来一个好处,对于数组内部的指针运算非常有利。我们可以用a + 1这种精炼的形式表示a[1]的地址,无须用&a[1]这种丑陋的代码,实际上,&a[1]是一种代码冗余,是对代码的浪费,因为&a[1]等价于&*( a + 1 ),&与*由于作用相反被抵消,实际上就是a + 1,既然这样我们何不直接使用a + 1呢?撇开为了照顾人类阅读习惯而产生的可读性而言,&a[1]就是垃圾。

但是,另一方面,这种异于一般标识符左值转换的特例化大大增加了数组与指针的复杂性,困扰初学者无数个日日夜夜的思维风暴从此拉开了帷幕!

在两个版本的转换条款中,有一点需要留意的是,两个版本关于具有数组类型的表达式有不同的描述。

C89/90规定:

 

an lvalue that has type “array of type” is......

但C99却规定:

an expression that has type “array of tye” is.......

C99中去掉了lvalue的词藻,为什么?我们知道,数组名是一个不可修改的左值,但实际上,也存在右值数组。在C中,一个左值是具有对象类型或非void不完整类型的表达式,C的左值表达式排除了函数和函数调用,而C++因为增加了引用类型,因此返回引用的函数调用也属于左值表达式,就是说,非引用返回的函数调用都是右值,如果函数非引用返回中包含数组,情况会怎样?考虑下面的代码:

#include <stdio.h>
struct Test
{
    int a[10];
};
struct Test fun( struct Test* );

int main( void )
{
    struct Test T;
    int *p = fun( &T ).a;                   /* A */

    int (*q)[10] = &fun( &T ).a;            /* B */

    printf( "%d", sizeof( fun( &T ).a ) );  /* C*/
    return 0;
}

struct Test fun( struct Test *T )
{
    return *T;
}

在这个例子里,fun( &T )的返回值是一个右值,fun( &T ).a就是一个右值数组,是一个右值表达式,但a本身是一个左值表达式,要注意这个区别。在C89/90中,由于规定左值数组才能进行数组到指针的转换,因此A中的fun( &T ).a不能在表达式中进行从数组类型到指针类型的转换,A中的fun( &T ).a是非法的,但C99在上述条款中不再限定左值表达式,即对这个转换不再区分左值还是右值数组,因此都是合法的;C中的fun( &T ).a是sizeof运算符的操作数,这种情况下fun( &T ).a并不进行数组到指针的转换,因此C在所有C/C++标准中都是合法的;B初看上去仍然有点诡异,&运算符不是已经作为例外排除了数组与指针的转换吗?为什么还是非法?其实B违反了另一条规定,&的操作数要求是左值,而fun( &T ).a是右值。C++继承了C99的观点,也允许右值数组的转换,其条款非常简单:

An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to an rvalue of type “pointer to T.” The result is a pointer to the first element of the array.

数组类型到指针类型的转换与左值到右值的转换、函数类型到指针类型的转换一起是C/C++三条非常重要的转换规则。C++由于重载解析的需要,把这三条规则概念化了,统称为左值转换,但C由于无此需要,只提出了规则。符号是语言对计算机的高级抽象,但计算机并不认识符号,它只认识数值,因此一个符号要参加表达式计算必须先对其进行数值化,三条转换规则就是为了这个目的而存在的。

看到这里,大概有些初学者已经被上述那些左值右值、对象非对象搞得稀里糊涂了。的确,数组与指针的复杂性让人望而生畏,不是一朝一夕就能完全掌握的,需要一段较长的时间慢慢消化。因此笔者才将数组与指针称为一门艺术,是的,它就是艺术!

本文转载自:http://blog.csdn.net/code_crash/article/details/4855027

共有 人打赏支持
北极心
粉丝 34
博文 39
码字总数 16464
作品 0
深圳
后端工程师
剖析 “‘最好的模板引擎’Beetl剖析及与Tiny模板引擎对比”

http://blog.csdn.net/cndes/art ... 88771 这有一篇文章,说是剖析beetl模板引擎,并与同为国内的tiny模板引擎做对比(以下简称“剖析beetl”),其剖析过程公正,但结论却不正确(文中暗示的...

闲大赋
2016/07/14
651
9
关于二维指针强制转换及传递的简单剖析

关于二维指针强制转换及传递的简单剖析。 C语言中的两大利器:强制转换、指针。很多小伙伴在学习C语言的过程中都遇到过一支拦路虎,那就是指针。 C很自由,但是自由同样暗示着你需要付出更多...

so_foolish
2017/04/01
0
0
做游戏,学编程(C语言) 网易云课堂MOOC视频

应一些同学的要求,把这学期上C语言编程课的讲课视频录制剪辑,上传到网易云课堂,感兴趣的朋友可以在线观看,欢迎多提宝贵意见。 MOOC视频链接:http://study.163.com/course/introduction....

童晶
2017/11/07
0
0
C/C++程序员应聘常见面试题深入剖析

C/C++程序员应聘常见面试题深入剖析 1.引言   本文的写作目的并不在于提供C/C++程序员求职面试指导,而旨在从技术上分析面试题的内涵。文中的大多数面试题来自各大论坛,部分试题解答也参考...

庸人谷
2012/12/14
0
0
Linux字符设备驱动剖析

以下内容转载于博客http://blog.csdn.net/yueqian_scut/article/details/45938557。有删改和格式调整,如有侵权,请告知删除 。 一、应用层的程序 很简单,open设备文件,read、write、ioctl...

oqqHuTu12345678
2017/12/29
0
0
【原创翻译】深度剖析Go数据结构

当向一个新程序员解释Go语言时,我发现如果解释Go的数据是如何在内存中表示的,将有助于建立编写高效程序的良好直觉。 基础类型 让我们从一些简单的例子开始: 变量i是int类型,在内存中占用...

zingscript
2014/01/21
579
3
Clang array of pointer and pointer of arrayC语言数...

在C语言中有数组指针与指针数组这两个概念,相同的字与相同字符数,前后反转后意思就不一样了 数组指针,本质即是一个指针,这个指针指向一个数组(是整个数组),指针类型与元素个数要与指向的...

wape-yang
2013/09/21
0
0
深入浅出Dubbo剖析出视频教程了!!!

深入浅出Dubbo剖析出视频教程了,目前出了Dubbo剖析-基础教程,本课程作为深入浅出Dubbo课程系列的基础篇,内容如下: 第一章 初始Dubbo(免费) 第二章 使用 ZooKeeper 搭建服务治理中心 第...

阿里加多
05/04
0
0
C++中引用和匿名对象的理解和本质剖析

大家对C++的引用应该都不陌生吧,抱着既要知其然,也要知其所以然的态度。 下面将按照是什么?怎么用?为什么需要?本质剖析的流程来想大家一一描述。 引用时什么? 引用其实就是给变量起的一...

沙米笔记
2016/04/10
2.5K
24
C语言学习之 数组,指针,字符串. (二)

前篇内容介绍了简单的 数组与指针 的简单形式. 都是简单的 *p 或者 a[5] 这样的简单形式. 顺便引出了 typedef . 内容相当简单没有深度. 概念也相当简单. 一切皆为数字, 计算机操作的都是数字...

泡不烂的凉粉
2012/11/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

CDH的坑之Sqoop导出数据到MySQL

CDH的坑之Sqoop导出数据到MySQL 最近使用Sqoop从Hive导出数据到MySQL中,出现了一系列的问题,下面将这个问题记录一下,避免再度踩坑! 导出语句 sqoop export --connect jdbc:mysql://192....

星汉
9分钟前
0
0
Hyperledger Fabric 客户端开发三

前面两篇文章介绍了Hyperledger Fabric SDK并使用一个实例介绍如何通过SDK和Hyperledger Fabric Blockchain交互, 现在详细分析相关的过程。 首先看 enroll (登录) admin 过程。 'use stric...

十一月不远
10分钟前
0
0
PowerDesigner连接MySQL和逆向工程图

最近想梳理公司项目的表间关系,从项目后台管理系统的操作入手,以及代码的hibernate注解入手,都不算特别尽人意,于是最后还是鼓捣了一下PowerDesigner的逆向工程图,这样更直观一些。 想着...

Oo若离oO
10分钟前
0
0
威胁web应用安全的错误

一般绝大部分的web应用攻击都是没特定目标的大范围漏洞扫描,只有少数攻击确实是为入侵特定目标而进行的针对性尝试。这两种攻击都非常频繁,难以准确检测出来,许多网站的web应用防火墙都无法...

上树的熊
12分钟前
2
0
pypy2 install crypto error

install pycryptodome instead pip install pycryptodome

coord
16分钟前
0
0
Service Mesh所应对的8项挑战

Lori Macvittie 微服务架构是把双刃剑,我们享受它带来的开发速度(development velocity),却也不得不面对服务间通讯带来的复杂性问题。 目前大多数扩展容器化微服务的架构多是基于proxy-b...

好雨云帮
25分钟前
0
0
时间复杂度

1. 维基上的定义 在计算机科学中,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低...

liuyan_lc
31分钟前
0
0
js中的~符

~是js里的按位取反操作符,~~就是执行两次按位取反,其实就是保持原值,但是注意虽然是原值,但是对布尔型变量执行这个操作,会转化成相应的数值型变量,也就是 ~~true === 1,~~false === 0...

JamesView
32分钟前
0
0
webpack安装

npm install --save-dev webpack-cli

Vincent-Duan
34分钟前
0
0
实时监听EditText内容变化

主要是addTextChangedListener方法的使用 aswerEdittext.addTextChangedListener(new TextWatcher() { //编辑框的内容发生改变之前的回调方法 @Override public void before...

王先森oO
38分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部