在第二次课学习了指针的定义和引用,那么指针有什么用那?主要是两个用途,一种是将指针作为子函数参数时,在子函数内部可以对外面的变量进行修改;二是函数只有一个返回值,当需要返回多个值的时候可以通过将指针作为函数参数来完成。
(1)指针作为函数参数
在学习函数调用时,函数参数的传递是值传递,即将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。比如下面的例子,如果再g函数中修改k的值i的值是否会改变?大家可以试验下:
1 #include <stdio.h> 2 void g(int k); 3 4 int main(void) { 5 int i=6; 6 printf(" i=%d ",i); 7 g(i); 8 return 0; 9 } 10 11 void g(int k){ 12 printf(" k=%d ",k); 13 }
结果肯定i的值不会改变。那么如果将参数换成指针那?参照g函数再写一个f函数(对照着g解释f):
1 #include <stdio.h> 2 void g(int k); 3 void f(int *p); 4 5 int main(void) { 6 int i=6; 7 g(i); 8 printf(" &i=%p ",&i); 9 f(&i); 10 return 0; 11 } 12 13 void f(int *p){ 14 printf(" p=%p ",p); 15 } 16 17 void g(int k){ 18 printf(" k=%d ",k); 19 }
从这个例子看出g和f函数很类似,只是传递的参数的类型不同,一个是整数类型,一个是指针类型,都是值传递,g传递的是整数,f传递的是一个地址。
我们知道在g函数里修改的k的值,i的值没有变化,那么如果在f里修改*p的值,main里i的值是否变化那?再上述程序上添加代码如下:
1 #include <stdio.h> 2 void f(int *p); 3 void g(int k); 4 5 int main(void) { 6 int i=6; 7 printf("&i=%p ",&i); 8 f(&i); 9 g(i); 10 printf("i=%d ",i); 11 return 0; 12 } 13 14 void f(int *p){ 15 printf(" p=%p ",p); 16 printf("*p=%d ",*p); 17 *p = 26; 18 } 19 20 void g(int k){ 21 printf(" k=%d ",k); 22 }
在调用f函数之后调用g函数和输出i的值,发现i的值和k的值都发生了变化。这就是g和f函数的不同点:c语言的函数在调用时发生的参数的转移是一种值的传递,我们把值传进了函数,所以函数和调用它的地方没用任何的联系,现在情况有点不一样了,但是我们仍然坚持说,现在这个传递依然是值的传递,地址值被传进了函数,但是因为传进来的是地址,所以通过这个地址在函数内部可以以*p的方式访问到外面的变量的值。
(2)函数通过指针返回多个值
那么在子函数里可以访问到main中的变量又有什么用那?再看swap例子(听课笔记:指针的应用场景),当传递到swap中的是整型数值时,交换只发生在swap函数内部。而当传递的是指针类型的地址时main里的两个值也实现了交换。为什么?如下图所示。
所以以指针作为函数参数时,可以在函数内部访问到外面变量,如何访问,可以读可以写。
在上面的例子中,swap函数在做交换后,返回来2个值,只返回a不够,还应该返回b。这种让子函数返回两个值的需求,通过函数的返回值是不能实现的,但是通过指针就可以实现,如何实现?通过指针带回,也就是说传入的参数实际上是需要保存带回的结果的变量。再看下例,(2.1)函数返回运算的状态,结果通过指针返回(以听课笔记:指针的应用场景中的divide为例进行说明)
以上2点就是指针的主要用途,那么这两点在使用指针操作数组时也很常见,并且利用指针可以替代通过数组下标所能完成的所有操作,并且利用指针编写的程序比数组下标编写的程序执行速度还要快,下面讨论下指针和数组的关系:
(1)数组中各元素的地址
使用int a[10]定义数组a,其实是在内存里申请了10个存储空间,每个单元都有相应的地址,那我们输出各个单元地址查看他们的地址有什么关系:
从结果可以看出:通过a[0]、a[1]、a[2]的地址可以发现数组各元素的地址是递增的关系,并且每个元素地址之间的差值是4(可以尝试将所有数组元素的地址都输出出来验证),注意和相邻变量的地址分配是不一样的,相邻变量中后定义的变量比先定义的地址要小。
(2)数组名是一个特殊的指针
另外从上述实验结果可以看出:&a=a=&a[0],在c语言中,数组的地址就是数组首元素a[0]的地址,数组名a本身就是地址,就是这个首地址,可以不使用&取地址符,所以要求数组地址时直接输出数组名就可以。在前面介绍过,指针变量的值就是地址,那么数组名a的值也是地址,可否当指针使用?能否对a使用*运算符,下面试一下:
从结果可以看出完全可以,对a进行*运算和[]运算结果是一样的,都是取该地址指向的变量的值。
既然a可以使用*运算符,那么将a赋值给指针变量p,变量p是否也可当数组首地址,对p进行[]运算符那?,比如输出数组中第5个元素的值,其实直接打印a[4]即可实现,那么p[4]结果是什么那?
从结果看出p也可以做[]运算来访问数组中的每个元素,那么是不是就说明指针p和数组名a就是一样的?或者数组名a就是一个普通指针那?答案是否定的,实际上数组名a是const常量指针,在之前讲数组时说到过,如果定义两个数组,int a[],int b[],使a=b是不可以的,数组之间是不能赋值的。而int *q=a,这个是可以的,一个不可以一个可以有什么区别那?实际上我们说的int a[]可以被看作int * const a,const意思是a是常量,不可以改变,它是这个数组就不可以是别的数组了,所以数组名在被当作值使用时,可理解为或者相当于一个常量指针,不可以赋值,不可以代表别的东西。
(3)数组名作为函数参数
我们知道如果通过函数参数传递一个普通变量,那么参数接收到的是值,如果传递一个指针变量,参数接收到的也是值,只不过这时的值是地址。那么数组是什么?将数组作为值传给一个函数,在函数的参数里有一个数组变量来接收这个数组,看下到底接收到数组变量的什么东西呢?
新建test函数,以数组作为参数,输出a在main和test中的值,结果地址是一样的,这说明什么,说明在test里的这个数组就是main里的这个数组,他们是同一个。再比如在test中修改a[2]的值,然后在main中调用完test函数后输出a[2]的值,发现确实结果确实改变了。
那么在test函数中能不能计算出数组a的个数?在test和main中分别添加printf函数:
从结果可以看出,在main里a的大小是64,而在test函数中数组a的大小为8,8是什么,在64位机器下,8刚好和一个指针的大小是一样的,和地址的大小是一样的。test参数中int a[]就是指针,那么将这个参数修改为int a[10],在test中sizeof没有办法得到这个数组元素的个数,原因就在于它其实就是个指针,它只是样子看上去像一个数组,那么既然它实际上是一个指针,我们把它写成像一个指针行不行,a[]写成*a,发现编辑和运行结果都没有变化。所以我们可以说数组和指针存在某种联系:函数参数中的数组实际上是指针,也就是说sizeof(a)= siziof(int*),但是对于这种指针可以使用数组的方括号[]运算符来进行运算。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
学生反应知识点有点多,前后关联性不强,指针作为函数参数应该和第二次课讲,而数组和指针是一次课。