C语言学习栏目目录
目录
源码
指针?什么是指针?从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址。在C语言中,指针有许多用法。本章将介绍如何把指针作为函数参数使用,以及为何要这样用。
假设一个指针变量名是ptr,可以编写如下语句:
ptr = &pooh; // 把pooh的地址赋给ptr
对于这条语句,我们说ptr“指向”pooh。ptr和&pooh的区别是ptr是变量,而&pooh是常量。或者,ptr是可修改的左值,而&pooh是右值。还可以把ptr指向别处:
ptr = &bah; // 把ptr指向bah,而不是pooh
现在ptr的值是bah的地址。要创建指针变量,先要声明指针变量的类型。假设想把ptr声明为储存int类型变量地址的指针,就要使用下面介绍的新运算符。
1 间接运算符:
假设已知ptr指向bah,如下所示:
ptr = &bah;
然后使用间接运算符*(indirection operator)找出储存在bah中的值,该运算符有时也称为解引用运算符(dereferencing operator)。不要把间接运算符和二元乘法运算符(*)混淆,虽然它们使用的符号相同,但语法功能不同。
val = *ptr; // 找出ptr指向的值
语句ptr = &bah;和val = *ptr;放在一起相当于下面的语句:
val = bah;
由此可见,使用地址和间接运算符可以间接完成上面这条语句的功能,这也是“间接运算符”名称的由来。
小结:与指针相关的运算符
地址运算符:&
一般注解:
后跟一个变量名时,&给出该变量的地址。
示例:
&nurse表示变量nurse的地址。
地址运算符:*
一般注解:
后跟一个指针名或地址时,*给出储存在指针指向地址上的值。
示例:
nurse = 22;
ptr = &nurse; // 指向nurse的指针
val = *ptr; // 把ptr指向的地址上的值赋给val
执行以上3条语句的最终结果是把22赋给val。
2 声明指针
相信读者已经很熟悉如何声明int类型和其他基本类型的变量,那么如何声明指针变量?你也许认为是这样声明:
pointer ptr; // 不能这样声明指针
为什么不能这样声明?因为声明指针变量时必须指定指针所指向变量的类型,因为不同的变量类型占用不同的存储空间,一些指针操作要求知道操作对象的大小。另外,程序必须知道储存在指定地址上的数据类型。long和float可能占用相同的存储空间,但是它们储存数字却大相径庭。下面是一些指针的声明示例:
int * pi; // pi是指向int类型变量的指针
char * pc; // pc是指向char类型变量的指针
float * pf, * pg; // pf、pg都是指向float类型变量的指针
类型说明符表明了指针所指向对象的类型,星号(*)表明声明的变量是一个指针。int * pi;声明的意思是pi是一个指针,*pi是int类型(见下图)。
*和指针名之间的空格可有可无。通常,程序员在声明时使用空格,在解引用变量时省略空格。
pc指向的值(*pc)是char类型。pc本身是什么类型?我们描述它的类型是“指向char类型的指针”。pc 的值是一个地址,在大部分系统内部,该地址由一个无符号整数表示。但是,不要把指针认为是整数类型。一些处理整数的操作不能用来处理指针,反之亦然。例如,可以把两个整数相乘,但是不能把两个指针相乘。所以,指针实际上是一个新类型,不是整数类型。因此,如前所述,ANSI C专门为指针提供了%p格式的转换说明。
3 使用指针在函数间通信
我们才刚刚接触指针,指针的世界丰富多彩。本节着重介绍如何使用指针解决函数间的通信问题。请看下程序清单,该程序在interchange()函数中使用了指针参数。稍后我们将对该程序做详细分析。
/************************************************************************
功能: 使用指针解决交换函数的问题
/************************************************************************/
#include<stdio.h>
void interchange(int* u, int* v);
int main(void)
{
int x = 5, y = 10;
printf("Originally x = %d and y = %d.\n", x, y);
interchange(&x, &y); // 把地址发送给函数
printf("Now x = %d and y = %d.\n", x, y);
return 0;
}
void interchange(int* u, int* v)
{
int temp;
temp = *u; // temp获得u所指向对象的值
*u = *v;
*v = temp;
}
该程序是否能正常运行?下面是程序的输出:
Originally x = 5 and y = 10.
Now x = 10 and y = 5.
没问题,一切正常。接下来,我们分析程序清单9.15的运行情况。首先看函数调用:
interchange(&x, &y);
该函数传递的不是x和y的值,而是它们的地址。这意味着出现在interchange()原型和定义中的形式参数u和v将把地址作为它们的值。因此,应把它们声明为指针。由于x和y是整数,所以u和v是指向整数的指针,其声明如下:
void interchange (int * u, int * v)
接下来,在函数体中声明了一个交换值时必需的临时变量:
int temp;
通过下面的语句把x的值储存在temp中:
temp = *u;
记住,u的值是&x,所以u指向x。这意味着用*u即可表示x的值,这正是我们需要的。不要写成这样:
temp = u; /* 不要这样做 */
因为这条语句赋给temp的是x的地址(u的值就是x的地址),而不是x的值。函数要交换的是x和y的值,而不是它们的地址。与此类似,把y的值赋给x,要使用下面的语句:
*u = *v;
这条语句相当于:
x = y;
我们总结一下该程序示例做了什么。我们需要一个函数交换x和y的值。把x和y的地址传递给函数,我们让interchange()访问这两个函数。使用指针和*运算符,该函数可以访问储存在这些位置的值并改变它们。可以省略ANSI C风格的函数原型中的形参名,如下所示:
void interchange(int *, int *);
一般而言,可以把变量相关的两类信息传递给函数。如果这种形式的函数调用,那么传递的是x的值
function1(x);
如果下面形式的函数调用,那么传递的是x的地址:
function2(&x);
第1种形式要求函数定义中的形式参数必须是一个与x的类型相同的变量:
int function1(int num)
第2种形式要求函数定义中的形式参数必须是一个指向正确类型的指针:
int function2(int * ptr)
如果要计算或处理值,那么使用第 1 种形式的函数调用;如果要在被调函数中改变主调函数的变量,则使用第2种形式的函数调用。我们用过的scanf()函数就是这样。当程序要把一个值读入变量时(如本例中的num),调用的是scanf("%d", &num)。scanf()读取一个值,然后把该值储存到指定的地址上。
对本例而言,指针让interchange()函数通过自己的局部变量改变main()中变量的值。
变量:名称、地址和值
通过前面的讨论发现,变量的名称、地址和变量的值之间关系密切。我们来进一步分析。编写程序时,可以认为变量有两个属性:名称和值(还有其他性质,如类型,暂不讨论)。计算机编译和加载程序后,认为变量也有两个属性:地址和值。地址就是变量在计算机内部的名称。在许多语言中,地址都归计算机管,对程序员隐藏。然而在 C 中,可以通过&运算符访问地址,通过*运算符获得地址上的值。例如,&barn表示变量barn的地址,使用函数名即可获得变量的数值。例如,printf("%d\n",barn)打印barn的值,使用*运算符即可获得储存在地址上的值。如果pbarn=&barn;,那么*pbarn表示的是储存在&barn地址上的值。
简而言之,普通变量把值作为基本量,把地址作为通过&运算符获得的派生量,而指针变量把地址作为基本量,把值作为通过*运算符获得的派生量。
虽然打印地址可以满足读者好奇心,但是这并不是&运算符的主要用途。更重要的是使用&、*和指针可以操纵地址和地址上的内容,
小结:函数
形式:
典型的ANSI C函数的定义形式为:
返回类型 名称(形参声明列表)
函数体
形参声明列表是用逗号分隔的一系列变量声明。除形参变量外,函数的
其他变量均在函数体的花括号之内声明。
示例:
int diff(int x, int y) // ANSI C
{
// 函数体开始
int z; // 声明局部变量
z = x - y;
return z; // 返回一个值
} // 函数体结束
传递值:
实参用于把值从主调函数传递给被调函数。如果变量a和b的值分别是5和2,那么调用:
c = diff(a,b);
把5和2分别传递给变量x和y。5和2称为实际参数(简称实参),diff()函数定义中的变量x和y称为形式参数(简称形参)。使用关键字return把被调函数中的一个值传回主调函数。本例中, c接受z的值3。被调函数一般不会改变主调函数中的变量,如果要改变,应使用指针作为参数。如果希望把更多的值传回主调函数,必须这么做。
函数的返回类型:
函数的返回类型指的是函数返回值的类型。如果返回值的类型与声明的返回类型不匹配,返回值将被转换成函数声明的返回类型。
函数签名:函数的返回类型和形参列表构成了函数签名。因此,函数签名指定了传入函数的值的类型和函数返回值的类型。
示例:
double duff(double, int); // 函数原型
int main(void)
{
double q, x;
int n;
...
q = duff(x,n); //函数调用
...
}
double duff(double u, int k) //函数定义
{
double tor;
...
return tor; //返回double类型的值
}