1月27日 C Primer Plus学习
9.7 指针简介
指针是一个值为内存地址的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址。
要创建指针变量,先要声明指针变量的类型。假设想把ptr声明为储存 int类型变量地址的指针,就要使用下面介绍的新运算符。
9.7.1 间接运算符:*
假设已知ptr指向bah,如下所示:
ptr = &bah;
然后使用间接运算符找出储存在bah中的值,该运算符有时也称为解引用运算符。不要把间接运算符和二元乘法运算符(*)混淆,虽然它们使用的符号相同,但语法功能不同。
val = *ptr; // 找出ptr指向的值
语句ptr = &bah;和val = *ptr;放在一起相当于下面的语句:
val = bah;
由此可见,使用地址和间接运算符可以间接完成上面这条语句的功能,这也是“间接运算符”名称的由来。
9.7.2 声明指针
因为声明指针变量时必须指定指针所指向变量的类型,因为不同的变量类型占用不同的存储空间,一些指针操作要求知道操作对象的大小。另外,程序必须知道储存在指定地址上的数据类型。long和 float可能占用相同的存储空间,但是它们储存数字却大相径庭。下面是一些指针的声明示例:
int * pi; // pi是指向int类型变量的指针
char * pc; // pc是指向char类型变量的指针
float * pf, * pg; // pf、pg都是指向float类型变量的指针
类型说明符表明了指针所指向对象的类型,星号()表明声明的变量是一个指针。int * pi;声明的意思是pi是一个指针,pi是int类型。

*和指针名之间的空格可有可无。通常,程序员在声明时使用空格,在解引用变量时省略空格。
pc指向的值(*pc)是char类型。,它的类型是“指向char类型的指针”。pc 的值是一个地址,在大部分系统内部,该地址由一个无符号整数表示。
不要把指针认为是整数类型。一些处理整数的操作不能用来处理指针,反之亦然。例如,可以把两个整数相乘,但是不能把两个指针相乘。所以,指针实际上是一个新类型,不是整数类型。因此,如前所述,ANSI C专门为指针提供了%p格式的转换说明。
9.7.3 使用指针在函数间通信
我们才刚刚接触指针,指针的世界丰富多彩。本节着重介绍如何使用指针解决函数间的通信问题。请看程序清单9.15,该程序在interchange()函数中使用了指针参数。稍后我们将对该程序做详细分析。
程序清单9.15 swap3.c程序
/* swap3.c -- 使用指针解决交换函数的问题 */
#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++程序员可能认为,既然C和C++都使用指针变量,那么C应该也有引用变量。让他们失望了,C没有引用变量。

图9.6 按字节寻址系统(如PC)中变量的名称、地址和值变量:名称、地址和值
通过前面的讨论发现,变量的名称、地址和变量的值之间关系密切。我们来进一步分析。
编写程序时,可以认为变量有两个属性:名称和值(还有其他性质,如类型,暂不讨论)。计算机编译和加载程序后,认为变量也有两个属性:地址和值。地址就是变量在计算机内部的名称。
在许多语言中,地址都归计算机管,对程序员隐藏。然而在 C 中,可以通过&运算符访问地址,通过运算符获得地址上的值。例如,&barn表示变量barn的地址,使用函数名即可获得变量的数值。例如,printf("%d\n", barn)打印barn的值,使用运算符即可获得储存在地址上的值。如果pbarn=&barn;,那么*pbarn表示的是储存在&barn地址上的值。
简而言之,普通变量把值作为基本量,把地址作为通过&运算符获得的派生量,而指针变量把地址作为基本量,把值作为通过*运算符获得的派生量。
虽然打印地址可以满足读者好奇心,但是这并不是&运算符的主要用途。更重要的是使用&、*和指针可以操纵地址和地址上的内容,如swap3.c 程序(程序清单9.15)所示。
10.3 指针和数组
指针提供一种以符号形式使用地址的方法。因为计算机的硬件指令非常依赖地址,指针在某种程度上把程序员想要传达的指令以更接近机器的方式表达。因此,使用指针的程序更有效率。尤其是,指针能有效地处理数组。我们很快就会学到,数组表示法其实是在变相地使用指针。
我们举一个变相使用指针的例子:数组名是数组首元素的地址。也就是说,如果flizny是一个数组,下面的语句成立:
flizny == &flizny[0]; // 数组名是该数组首元素的地址
flizny 和&flizny[0]都表示数组首元素的内存地址(&是地址运算符)。两者都是常量,在程序的运行过程中,不会改变。但是,可以把它们赋值给指针变量,然后可以修改指针变量的值,如程序清单10.8所示。注意指针加上一个数时,它的值发生了什么变化(转换说明%p通常以十六进制显示指针的值)。
程序清单10.8 pnt_add.c程序
//pnt_add.c -- 指针地址
#include <stdio.h>
#define SIZE 4
int main(void)
{
short dates[SIZE];
short * pti;
short index;
double bills[SIZE];
double * ptf;
pti = dates; // 把数组地址赋给指针
ptf = bills;
printf("%23s %15s\n", "short", "double");
for (index = 0; index < SIZE; index++)
printf("pointers + %d: %10p %10p\n", index, pti + index, ptf + index);
return 0;
}
在C中,指针加1指的是增加一个存储单元。对数组而言,这意味着把加1后的地址是下一个元素的地址,而不是下一个字节的地址。这是为什么必须声明指针所指向对象类型的原因之一。只知道地址不够,因为计算机要知道储存对象需要多少字节(即使指针指向的是标量变量,也要知道变量的类型,否则*pt 就无法正确地取回地址上的值)。

指针的值是它所指向对象的地址。地址的表示方式依赖于计算机内部的硬件。许多计算机是按字节编址,意思是内存中的每个字节都按顺序编号。这里,一个较大对象的地址(如double类型的变量)通常是该对象第一个字节的地址。
在指针前面使用*运算符可以得到该指针所指向对象的值。
指针加1,指针的值递增它所指向类型的大小(以字节为单位)。
下面的等式体现了C语言的灵活性:
dates + 2 == &date[2] // 相同的地址
*(dates + 2) == dates[2] // 相同的值
以上关系表明了数组和指针的关系十分密切,可以使用指针标识数组的元素和获得元素的值。从本质上看,同一个对象有两种表示法。实际上,C 语言标准在描述数组表示法时确实借助了指针。也就是说,定义ar[n]的意思是(ar + n)。可以认为(ar + n)的意思是“到内存的ar位置,然后移动n个单元,检索储存在那里的值”。
顺带一提,不要混淆 (dates+2)和dates+2。间接运算符(*)的优先级高于+,所以*dates+2相当于(*dates)+2:
(*和*之间在同一行之间有东西会变成斜体)。
*(dates + 2) // dates第3个元素的值
*dates + 2 // dates第1个元素的值加2
明白了数组和指针的关系,便可在编写程序时适时使用数组表示法或指针表示法。运行程序清单 10.9后输出的结果和程序清单10.1输出的结果相同。
程序清单10.9 day_mon3.c程序
/* day_mon3.c -- uses pointer notation */
#include <stdio.h>
#define MONTHS 12
int main(void)
{
int days[MONTHS] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int index;
for (index = 0; index < MONTHS; index++)
printf("Month %2d has %d days.\n", index + 1, *(days + index));//与 days[index]相同
return 0;
}
浙公网安备 33010602011771号