各位同学大家好,那么接下来呢我们开始今天的课程。 那么今天我们来讲指针的最后一个部分,也就是指针的第三个部分。 那么在这一讲里头啊,我们主要来关注指针与函数的关系。 那么指针与函数呢主要是两部分的内容。第一个就是指针用做函数参数的时候, 会怎样?第二部分呢,就是指针用做函数返回值的时候, 会怎样? 那么下面呢,我们先来了解下,如何把指针 用作函数的参数。 那么下面呢,我们先来看一个程序。看这个程序: 在这个程序里头呢,我定义了一个函数叫做Rank,那么Rank函数呢有两个参数, 都是指针,分别是去q1和q2。 那么在主函数里呢,我定义了两个变量a 和b, 然后读入a 和b。 然后定义了两个指针p1和p2, 让p1呢指向a,让p2呢指向b。 那么如果我们画一个图来表示一下现在的状况的话,就是这样的一个状况, 一个main 函数,一个Rank函数,然后在主函数里呢定义了两个变量 a 和b, 然后呢读入这两个变量。比方说我给a赋值为3,b赋值为5, 然后呢,我定义了两个指针,分别指向这两个变量p1和p2。 那么在接下来的操作里头呢, 我把p1 和p2 当作实际参数传递给函数Rank, 这相当于把指向了变量a 和变量b的两个指针, 传递给了Rank。 我们以前曾经特别强调过, 函数在传递的时候是怎么样去传递的。 是把这个要传递的参数的值, 把他们两个的值直接copy给函数, 是把他们两个的值直接copy过去。也就是说, 当我们把p1和p2的值传给Rank的时候, 实际上就相当于让Rank里面的 q1和q2指向了main函数里头的a和b,对不对, 因为我们把地址传过去了。这就象以前我打的一个比方一样, 我呢在北大的未名湖边上,埋藏了一些宝藏, 然后我把地址给了你,你呢,就可以直接去那, 随意的处置那些宝藏了。那么在这呢, 因为p1和p2是指向a和b的指针, 也就是说p1和p2里面存放的是a和b的地址, 所以说当我们把p1和p2当作实际 参数传递给函数Rank的时候, 也就相当于让Rank里面的q1和q2这两个形式参数 直接指向了main函数里面的a和b。 那么接下来,通过q1和q2, 对a和b的任何操作其实也就是对 变量a和b的操作。我们来看它进行了一个什么操作? 那么Rank函数里面,说当q1所指向的 变量的值小于q2所指向变量值的时候, 我就交换, q1所指向的变量的值和q2所指向的变量的值。 所以说在这个程序里头当我们所入3和5给a和b的时候, 在Rank函数里头就会把3和5进行交换,变成5和3, 毫无疑问,这个程序的输出结果是5和3。 这就是指针用做函数参数的情况。 那么基于前面我们所学习到的知识呢,相信大家是不难理解这个过程的。 这是最典型的一种把指针用作函数参数的情况。 那么除了这种情况以外呢,其实我们在以前例子里 还接触过另一种类型的把指针用作函数参数的情况。 我们来看这个例子,那么首先需要说明的是,这是同一个程序, 这是写在一个文件里的,我是为了让大家看的更加清楚,我把它 分别写到两个框里头,这是主程序,这是可以调用的一个函数。 当然如果你自己呢想要运行这个程序的话,你需要首先把这个主程序先copy 到你的编译环境中, 然后呢把这个函数也copy到你的编译环境中。 放在哪里呀,那么这个函数的位置呢应该放在主函数之前, 预定义部分之后,放在这个位置。 那这个程序就可以去运行了。我是为了让大家看的更清楚, 我把主函数和被调用的函数分开来写了。 ok,下面我们就来看一下这个程序,那么在这个例子里头,我想实现这么一种情况, 定义一个函数,这个函数有一个参数是一个指针。 我想把一个数组的名字当做 函数的实际参数传递给这个函数。 说简单一点就是把数组名当作实参,付给指针类型的形参。 这样可以吗?我们曾经讲过这样当然是可以的。 毫无疑问,为什么可以啊?因为数组名就相当于指向数组第一个元素的指针。 比方说对这个数组而言,数组名a就相当于 指向一个整形元素的指针。而在这个函数里头呢, 指针p它的涵义也是指向整形元素的指针。所以说, 这个形参p的涵义,跟实参a的涵义是完全等价的。 所以我们完全可以把一个数组的名字当作实参去赋给一个指针。 当然前提是你所定义的这个指针跟数组名字它的涵义必须是 等价的。那有的同学可能立刻就想问,如果是多维数组可以吗? 我们来看这样一道题目,在这个题目里头啊,主函数里头啊定义了一个三行四列的二维数组, 并且呢对这个二维数组进行了初始化。 然后呢我把这个二维数组的名字a 当作函数的实参传递给一个函数maxvalue, 让maxvalue这个函数啊,从这个3乘4的矩阵里头 找到所有元素的最大值, 这就是这个程序的基本涵义。 那么现在呢,二维数组的参数空出来,让我们填一个参数的定义进去。 我们来看一下,应该怎么去填这个定义。 首先在主函数里头传进来的实际参数是二维数组的名字a, 所以说在函数的定义里面,我们一定会定义一个函数的参数来接纳这个a, 这个参数的名字呢,应该是p。因为这个程序里面除了p之外,其他都是被定义过的。 而且呢我们所要定义的这个变量p呀, 它必须能够通过pij, 这种方式来访问二维数组里的元素。 也就是说p它的类型必须与二维数组的名字a类型是一样的。 那么说到这可能有的同学就已经反应过来了,既然 它与二维数组的名字a具有相同的类型,那a的类型是什么呢? 我们上次课刚刚讲过。a呢是指向二维数组中第一个小数组的指针。 那怎么去定义一个指针,使得它成为一个指向 二维数组中第一个小数组的指针呢? 我们上次课刚刚讲过,在这可以这样来定义这个p, int( *p)[4],那么通过这样一种方式我们就定义了一个变量p, 于是呢p和a的涵义就等价了。 那么,在这呢,我们就可以用这样一个p来接纳这个a了。 ok,这是多维数组的数组名做函数参数的情况。 那有的同学可能问了,哎呀,这个定义也太复杂了点啦。 每次要定义这样一个形参还真是挺费劲的。 我能不能在这直接去定义一个数组呢? 也就是说我能不能直接在这用一个数组的名字当作形参来接纳一个 数组名类型的实参呢?有同学可能还没听明白是怎么回事,我们来 看一个程序。这个程序就是这种情况,在主函数里我们定义了一个数组名字为a。 然后呢,我把这个数组名啊,当做实参传递给了一个函数 sum,然而呢,在这个函数里头,接纳这个数组的名字的时候,我没有去定义一个指针 而是啊,直接通过这种方式,定义了一个数组,int array[] 也就是说啊,把一个数组名,当做形参来 处理,而且呢,在这个函数里头啊,我直接把 这个array当做变量来使用,那么甚至呢,我还可以对这个array进行加 加减减的操作,那当我明年看到这样一个程序的时候,有的同学觉得可能就很诧异了,array不是数组名嘛? 那么数组名是不能当做变量来使用的,你在这还对它进行加加减减, 这是以前我们讲过,完全不可以的, 那么这样一个程序,能正确的运行吗? 其实啊,这样的一个程序是可以正确 运行的,为什么呢?因为C++的编译器啊,会把 形参定义在形式参数里面的这个数组 名,当做指针变量 来处理,它就相当于我们定义了一个,指向int类型的指针 变量,所以说,当我们把数组名a当做实参来传递给 这个形参array的时候,完全没有问题, 那么这个呢,是需要我们特别注意的一种情况,啊,这种情况是完全合理的,它是可以运行的 那我们来看一下,这个程序做了一个什么 事情,在主函数里头呢,定义了一个 数组a,然后呢,给它赋了10个值,1,2,3,4,5,6,7,8,9 10,然后呢,把数组的名字,当做实参传递给sum这个函数, 在这个函数里头啊,进行了这么一系列的操作, 啊,for从i=0到i<10-1 啊,<10-1它是有目的的,我们来看一下,它什么目的 *(array+1) 就等于*array+*(array+1) 这是什么 意思啊,是说,从数组的第二个元素开始, 每个数都等于当前的这个元素和前一个元素的和 而且呢,我一直累加到数组的最后一 个元素,也就是说sum这个函数做了这么一件事情,它 把数组里所有元素的和求了出来, 并且呢,把这个最终的和存放在数组的最后一个元素里头,并且呢,把这个和 返回出来,啊,这就是sum这个函数的作用, 那么通过这种方式求和,可以吗? 啊,也可以,但是,在这个程序里头啊,sum这个 函数,除了完成求和这个功能以外 啊,它还做了一些多余的 事情,什么多余的事情啊,它其实在不断的修改着这数组 里从第2个元素开始,往后所有元素的值, 那这样一个函数啊,可能用起来,我们就有顾虑 了,对不对,啊,我本来把一个,在主函数里面呢,我定义了一个数组,我本来把这个 数组交给你啊,是想让你,读一读数组里面的这些元素,并且呢,把这些元素的, 和求出来,本来是这样一个 意图,结果呢,你在求和的过程中,却把我 每个元素的值都给我修改了,这是我所 不希望的,所以说啊,当我们在程序里头,把一个变量或者是 数组的名字,传递给一个函数的时候,还是挺危险的, 对不对?因为这个函数拿到的,是这个变量或者是数组的地址, 它拿到了地址,它就可以直接对这个地址里面所埋藏的这些宝藏,也就是 地址里面存放的这些值,进行肆意的修改,这就是当我们把指针用作函数参 数的时候,所带来的一个很大的问题,对不对,那有没有一种办法,可以呢 使我既能够把这个地址传递给某个函数,又 能够确保不让这个函数随意的修改这片地址里面的值呢? 有没有一个这样的办法呢?啊,是有这样一个办法的 那么接下来呢,我们就来看一下,如何,来 限制指针的功能。