好,那么讨论完这些基本的概念之后啊,我们就可以学学怎么来用指针了。 其实指针在使用的过程中啊,一个很大的用途,是跟另外一个东西一起使用。 这个东西就是数组。啊,所以说接下来我们讨论一下, 指针与数组。看看当我们利用指针来操作数组的话, 啊,应该怎么样去处理。首先我们来看一个最简单的, 当我们用一个指针指向数组中的某个元素的时候, 会是怎样的一种情况。我们来看这个程序,在这个程序里头啊, 我定义了一个数组,a[5]。然后呢,我对它进行了初始化。 赋值为1,2,3,4,5。 然后呢我定义了一个普通的指针,*p 。给这个指针赋值的时候呢, 注意我给它赋值,赋了这个结果。赋的是 a[3] 这个数组元素的 地址, &a[3], 表示的就是,a[3] 这个数组元素的地址。 那么赋完这个值之后我们可以说什么?我们是不是可以说,指针 p 指向了 a[3] ,啊,可以这样说了。 然后呢我接下来我打印 *p 的值。 那么如果是指向的是一个普通变量的话,*p 的值应该是什么? 应该就是相当于打印的那个普通变量。对不对?啊, 然后呢,我对 *p 的值进行重新的赋值,赋值为 100。 然后呢,做完这个赋值以后呢,我再去打印 a[3] 的值。 在我们去看这个程序的运行结果之前啊,我就想先告诉大家, 其实,当我们利用一个指针变量去指向一个数组元素的时候, 这个时候啊,指向一个数组元素跟指向一个普通的变量 没有任何区别。也就是说,既然它指向 a[3] , 跟指向普通变量都没有区别,那么当我打印 *p 的时候,其实是不是 就相当于我去打印 a[3] 吗?在这个地方。那么当我利用 *p 进行重新赋值的时候,是不是这个地方就相当于对 a[3] 进行重新赋值啊? 那如果我去打印 a[3] 的话,当然就会打印出来这个 * 的值100。 对吧?这是我们对这个程序的一个推理。我们看了它的运行结果,是不是这样。 看这个结果。当 p 指向 a[3] 的时候,我直接去打印 a[3] 的值。a[3] 的值是 4, 所以说就打印 4 。那么当我对 *p 进行重新赋值,把它赋值为100以后, 再去打印 a[3] 的值,变成了新的值,100。 所以说啊,当我们把一个指针变量指向数组元素的时候, 啊,其实跟指向普通的变量,是没有任何区别的。 ok, 这是我们得到的第一个结论。 那下面呢,我们再来看一个例子。 看这个例子。在这个例子里头啊,我也定义了一个 数组, a[5] 。赋初值呢,10,11,12,13,14。 然后,接下来为了搞清楚这个数组名字,也就是这个 a 到底是什么含义,我做了这么几个输出。第一个,我打印输出 a。 啊,我们已经知道了,刚才的程序里头已经打印了,肯定会输出 一个地址。然后呢,我打印输出 *a。 *a 。根据指针运算符的含义, 那么打印 *a 的话,将打印出 a 这个地址所指向的哪片内存单元中的那个内容,结果它打印出来。 这个内容是什么呢?我不知道。那么待会儿打印完了我就知道了。 然后呢,我再去打印一下 a[0] , a[0] 也就是数组的第一个元素。 它的地址,我把它打印一下,看看是什么。然后呢,我最后再打印一下 a[0] , 啊这里一般,毫无疑问我们知道结果的,是打印 a[0] , 啊,我们毫无疑问打印 a[0] 的话,这个地方一定会输出 10 。对不对? 那么在第一个呢,刚才我们就看过这个地方会输出一个地址。 这是我们已经知道的。那么中间这两个数,我们所不知道的。我们来看一下, 运行的结果。首先打印 a 的时候啊,打印出来这个地址。 0017F754 。我不知道这个地址是什么,但是我确信 a 是一个地址。然后呢,我打印 *a。 *a , 打印出来的是什么呢? 是 10 。这个 10 是什么呢?10 在这儿。 是数组中第一个元素,也就是 a[0] 它的值。 也就是说啊,在这个地方,你用 cout打印输出 *a。 其实就相当于你打印输出了 a[0]。 是这个意思。然后接下来我们再去打印 a[0] 的地址。 a[0] 的数组元素 a[0] 的地址。结果,我们来看这个结果。 结果是这样的一个值。这个值跟上面的这个值一模一样。也就是说, 当我们打印数组中第一个元素的地址的时候, 跟我们使用数组名直接打印出来的这个地址是一模一样的。 而且,当我们使用 * 指针运算符去打印 数组的名字,也就是这个地址所指向那片内存空间的那个 内容的时候,跟我们使用 a[0] 打印出来的结果是一模一样的。 那么通过这个例子,我们可以看得出来, 我们能够啊,得到这样的一个结论。关于数组的地址的这么一个结论。 什么结论呢?第一个,数组名啊,代表数组首元素的地址。 从而呢,我们就可以得到一个这样的结论: 数组名相当于指向数组第一个元素的指针。原来当我们拿到一个数组的名字的时候,就相当于拿到了 指向数组第一个元素的指针。那么在这儿呢,我多解释一句。之所以要, 举个例子,比方说对于数组 a[10] ,数组名呢, a 其实就相当于它的第一个元素,也就是 a[0] ,它的指针。 也就是说,如果拿到 a 的话,就相当于我们拿到了& 的 a[0] 的值。 这就是数组名的含义。当然,在这儿我们还是要强调一点。 数组名啊,不是变量。所以说我们在任何时候都不能给 a 去赋值。ok, 那么在这儿呢, 我稍微多说明一句,之所以把原来的 ppt 里面, '是'指向数组第一个元素的指针,改成了'相当于'指向数组元素的指针。 我希望啊,这个修改能让它更准确一点。 当然,原来之所以使用'是'这个词,是为了简单,啊,我实在不想引入 太多的概念。因为我们是一个入门的课程。我实在不想引入太多的概念,给大家增加一些麻烦。 比方说,如果我用相当于的话,那有的同学可能就要问了,为什么叫相当于? 好吧,在这儿呢,我考虑再三,在这儿呢,我还是增加这样的 一个解释。其实啊,在 C语言的规范中, 那么在这儿呢,我从 C语言的规范的 2011 版中, 截起了一些片段,把它放在这儿。这个规范呢,我们以前跟大家提到过, 大家呢,都可以直接从 ISO 的网站上去查询到这个规范。到 internet 上去搜,啊。 都可以搜得到。因为有些人会把它放到 internet 的上面,啊献让出来。 那么,在这个规范的第6.2.5 节,Types 这一节中, 对数组的类型也就是 array type ,有这样一段描述。啊,我们看这里。 An array type is said to be derived from its element type. If its element type is T, the array type is sometimes called array of T. 啊,有这样的一段描述。 是什么意思呢?是说啊,一个数组的类型是由它元素的类型延伸出来的。啊,那么如果 比方说我们定义一个数组,啊,比方说, int a[10] ,定义这样一个数组, 啊,它每一个元素的类型呢,是 int 型。那这个数组的类型应该是什么类型呢? 应该是 array of T, 啊应该是这样的一个类型。啊因为在这个规范里面说, If its element type is T, the array type is sometimes called array of T. Sometimes ,啊,看到这个词的时候,是不是大家对这个规范有点失望啊。哈。 一个规范怎么可以定义得这么不严谨。啊, sometimes, 用这样的词。 那么在这儿呢,需要说明一下,其实, 这个规范这个东西啊,本身就是很多人坐在一起商讨的一个结果, 也就是很多人在做妥协的一个结果。 在这样一个场景中,势必会产生一些模棱两可的东西出来。 啊,比方说在这儿,用了一个 sometimes 的词,实在无奈之极。所以说, 在这个规范里面说一个数组的类型是什么类型呢? sometimes array of T, 它是这样一个类型。那我们为什么又说数组名相当于, 指向数组首元素的指针呢? 看这里,在这个规范的第6.3.2.1节里面,关于 Lvaules, arrays, and function designators 这样一段描述里头啊,有这样一段描述, 说 except when it is the operand of the size of operator. the Align of 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. 也就是说,如果一个表达式, 它不是size of 或Align of 或者是& 这些操作符的操作术, 也就是说它不出现在这些操作符的右边, 而且呢,它也不是string literal, 如果不是这些情况的话,那么一个数组类型 array of type, 一个数组类型的expression表达式, 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. 什么意思啊?指向数组的第一个起始元素的指针 那这句话说的很长啦,也就是说如果一个数组名不是被 用在size of 或者& 后面的话, 那么这个数组名将会被转换成指针类型。那指针类型指向哪里呢? 指向数组的第一个元素,ok. 通过这两段话我们就可以知道, 如果从严格意义上说的话,在C语言的规范里头数组的类型是什么类型啊? sometimes called array of T. 有时被称作T的数组的类型。那么这种类型的表达式如果 不出现在size of, Align of, or & operator, 这些操作符的后面的话, 它会被转换成指针类型,这个指针类型是指向哪里的呢? that points to the initial element of the array object 指向数组的第一个元素。 那么这就是C语言的规范中关于数组名的一个彻底的解释。 那有的同学会说,那老师你为什么说相当于呢? ok,很简单,看这里在C语言的6.5.2.1节,关于, 关于Array subscripting 这一节描述里头有这样一段话, because of conversion rules that apply to the binary +operator, 这个我们不用管,在这种情况下, if E1 is an array object ,比方说有一个数组叫E1【E2】, 那么在这描述的E1就是这个a,E2呢就是这个10, 两个表达式。那么在这个时候呢, 它关于这个E1有一个注释,它说: equivalently,a pointer to the initial element of an array object, 其实这样的描述在C语言中啊,比较多, 这段描述是这样的,在一个句子的描述里,它提到了E1,E1也就是一个数组的名, 在提到数组名的时候,它直接加了一个注释,在这个注释里面,它怎么去解释数组名呢? 他说equivalently a pointer to the initial element of an array object. 什么意思啊?如果翻译过来这句话就可以翻译成等价的,或者是相当于, a pointer to the initial element of an array object.相当于指向数组首元素的指针。 所以说我们在这里沿用了C语言规范里的这样一个说法, 数组名相当于指向数组第一个元素 的指针。 那这个呢,就关于整个这句话的一个解释。 那么虽然只有短短的几个字,但是确实背后包含了太多的这种信息。 虽然课前发挥以后也没有同学,也没有其他的人跟我反应 过这个问题,但是思之再三,我觉得 还是呢希望把它写的更准确一些。所以说在这呢我就用了相当于 这个词。ok,这是关于数组名问题的解释。 我希望大家能够很好的去理解这句话,因为这句话, 非常非常的有用,它不单是在我们现在是有用的, 等到我们讲到多维数组的时候,它照样是非常非常有作用的。 ok,这个部分我们就先讲到这。接下来那我们看, 如何利用指针来引用数组中的元素。