9.3 指针的应用与误用

9.3.1 指针有什么用

在了解了指针的一些基本概念之后,自然而然会想到的一个问题就是指针究竟有什么用处。如果对于变量定义

9.3 指针的应用与误用 - 图1

既然“i=2”与“*&i=2”是完全等价的操作,那么两个完全等价的操作中难道不是必然会有一个是多余的吗?

想到这些问题非常自然。实际上指针非常有用,指针是C语言的精华。下面将逐步介绍如何应用指针。

指针的用途之一是通过函数改变函数调用处本地局部变量的值。如果没有指针的话,改变本地局部变量的值,只能通过把函数返回值赋值给这个本地局部变量的办法。但是由于函数只能返回一个值,所以这种办法有很大的局限性。

首先看一个简单的例子。

程序代码9-6

9.3 指针的应用与误用 - 图2

这段程序的输出是:

9.3 指针的应用与误用 - 图3

可以看到在f()函数中,形参“n”的值的改变对main()函数中的1没有影响。这是因为在C语言中,实参与形参之间是“传值”的关系,形参“n”是把“i”的值(右值)而不是“i”本身作为自己的初始值。在计算实参时求出的“i”的值可能被放在运算器中,也可能被放在内存中的另一个地方,这样无论“n”如何变化都不会使得“i”发生改变。这个过程如图9-6所示。

9.3 指针的应用与误用 - 图4

图9-6 n与i是两个不同作用区域的变量

也就是说,尽管在f()函数中,可以获得main()中当地变量“i”的值(右值),然而由于“i”是main()中的局部变量,f()函数并不能直接使用这个变量的左值。

如果在main()中希望通过函数调用改变本地局部变量的值,也就是说在f()函数中改变main()中的局部变量“i”的值,应该如何实现呢?答案是通过指针和间接引用运算。

程序代码9-7

9.3 指针的应用与误用 - 图5

这段程序的输出是:

9.3 指针的应用与误用 - 图6

在这段程序中,函数调用以指向“i”的指针“&i”作为实参,可以实现“p”指向变量“i”。这样在f()函数中对“*p”的操作,也就是对main()中局部变量“i”的操作,因而实现了通过对f()函数的调用改变函数调用处,即main()中的局部变量“i”的值的目的,如图9-7所示。理解了这个道理,就不难明白为什么调用scanf()时经常需要写“&”这个运算符了。

9.3 指针的应用与误用 - 图7

图9-7 f()中的(*p)++表示的是对main()中的i的运算

此外要注意在f()函数中(p)++不可以写成p++,原因在于++比优先级高,p++的含义是(p++),也就是说是对指针p做“++”运算而不是对“p”做“++”运算。当然对于上个例子来说,把“(p)++”写成“++p”最后的执行效果是一样的。

9.3.2 C代码中的“XXX到此一游”

“XXX到此一游”,这种不分场合胡写乱画的事情在C代码中也常常出现。

比如

9.3 指针的应用与误用 - 图8

这是个典型的误用指针错误。这个错误在于,定义了指针变量“p”之后并没有给“p”赋值。由于“p”是个auto类别的局部变量,所以定义之后“p”的值是个“垃圾值”,说不清楚“p”指向哪块内存,这样“p=10”就会导致把数据写在内存中一个未知的、不当的、错误的位置。这会使应用程序发生错误甚至是灾难性的后果(更坏的后果是你可能根本无法马上察觉)。这种对“”运算的误用的后果通常会比对变量的误用严重得多。

为了尽量避免这种情况,在定义指针变量时直接将其赋值为“0”被普遍认为是一种良好的编程习惯。例如:

程序代码9-8(片段)

9.3 指针的应用与误用 - 图9

其中NULL是文本文件“stdio.h”中定义的一个符号常量,其值为“0”,指针被赋值为“0”值时,这个“0”一般是不用进行类型转换的。“0”这个地址的写入操作是被禁止的,这样可以很大程度地防止应用程序在内存中错误地“随处乱写”。

9.3.3 分桔子问题

例题:父亲将2520个桔子分给6个儿子。分完后父亲说:“老大将分给你的桔子的1/8分给老二;老二拿到后连同原先的桔子分1/7给老三;老三拿到后连同原先的桔子分1/6给老四;老四拿到后连同原先的桔子分1/5给老五;老五拿到后连同原先的桔子分1/4给老六;老六拿到后连同原先的桔子分1/3给老大”。在分桔子的过程中并不存在分得分数个桔子的情形,结果大家手中的桔子正好一样多。问六兄弟原来手中各有多少桔子。

每次分桔子都有两个人的桔子数目发生改变。由于函数只能返回一个值,所以无法通过函数一次求得两个人在分之前的数目,但是利用指针可以完成这样的功能。

问题由6个相同的小问题组成,其中的任一个小问题的提法都可以描述如下。

甲把自己的桔子分给乙“1/n”之后,甲和乙各有桔子若干,求甲把自己的桔子分给乙之前两人桔子的数目。若通过函数完成这个任务,显然需要知道甲分给乙之后两人桔子的数目和“1/n”。由于要求函数改变两个数据的值,所以函数原型可以描述为:

void求甲分给乙之前各自的数目(int pointerto甲的数目,int Pointerto乙的数目,const int n);

由于这样的函数的前两个参数是指针,所以在函数中不但可以知道“甲的数目”和“乙的数目”(“ pointerto甲的数目”和“ pointerto乙的数目”),也可以通过这一次函数调用同时改变“甲的数目”和“乙的数目”值,即同时求出甲把自己的桔子分给乙之前两人桔子的数目。

程序代码9-9

9.3 指针的应用与误用 - 图10

9.3 指针的应用与误用 - 图11

运行结果如图9-8所示。

9.3 指针的应用与误用 - 图12

图9-8 分桔子问题

练习

1.写一个能实现交换两个“int”变量的值的函数,并通过程序验证函数的功能。

2.改写求调和级数例题,约分部分用一个函数实现。