8.2 指针变量
从上节已知:存放地址的变量是指针变量,它用来指向另一个对象(如变量、数组、函数等)。那么,怎样定义和使用指针变量呢?
8.2.1 使用指针变量的例子
先分析一个例子。
例8.1 通过指针变量访问整型变量。
解题思路:先定义两个整型变量,再定义两个指针变量,分别指向这两个整型变量,通过访问指针变量,可以找到它们所指向的变量,从而得到这些变量的值。
编写程序:
#include <stdio.h>
int main()
{
int a = 100, b = 10; // 定义整型变量a和b,并初始化
int *pointer_1, *pointer_2; // 定义指向整型数据的指针变量pointer_1和pointer_2
pointer_1 = &a; // 把变量a的地址赋给指针变量pointer_1
pointer_2 = &b; // 把变量b的地址赋给指针变量pointer_2
printf("a=%d, b=%d\n", a, b); // 输出变量a和b的值
printf("*pointer_1=%d, *pointer_2=%d\n", *pointer_1, *pointer_2); // 输出指针变量所指向的变量值
return 0;
}
运行结果:
a=100, b=10
*pointer_1=100, *pointer_2=10
程序分析
-
定义指针变量:
- 在开头处定义了两个指针变量
pointer_1
和pointer_2
。此时它们并未指向任何一个变量,只是提供两个指针变量,规定它们可以指向整型变量,至于指向哪一个整型变量要在程序语句中指定。 - 程序第5、6行的作用就是使
pointer_1
指向a
,pointer_2
指向b
。此时pointer_1
的值为&a
(即a
的地址),pointer_2
的值为&b
,见图8.3。
- 在开头处定义了两个指针变量
-
输出变量的值:
- 第7行输出变量
a
和b
的值:100和10。 - 第8行输出
*pointer_1
和*pointer_2
的值。其中的“*”表示“指向”。*pointer_1
表示“指针变量pointer_1
所指向的变量”,也就是变量a
。*pointer_2
表示“指针变量pointer_2
所指向的变量”,也就是变量b
。从运行结果看到,它们的值也是100和10。
- 第7行输出变量
-
区分定义与使用:
- 程序中有两处出现
*pointer_1
和*pointer_2
,二者的含义不同。程序第4行的*pointer_1
和*pointer_2
表示定义两个指针变量pointer_1
和pointer_2
。它们前面的“*”只是表示该变量是指针变量。 - 程序最后一行
printf
函数中的*pointer_1
和*pointer_2
则代表指针变量pointer_1
和pointer_2
所指向的变量。
- 程序中有两处出现
注意:定义指针变量时,左侧应有类型名,否则就不是定义指针变量。例如:
*pointer_1;
// 企图定义pointer_1
为指针变量。出错int *pointer_1;
// 正确,必须指定指针变量的基类型
图示
通过这个例子,可以清晰地看到指针变量的定义和使用过程。指针变量是C语言中强大而灵活的工具,掌握它能使程序更加高效和灵活。
8.2.2 怎样定义指针变量
在例8.1中,我们已经看到怎样定义指针变量。定义指针变量的一般形式为:
类型名 *指针变量名;
例如:
int *pointer_1, *pointer_2;
左端的int
是在定义指针变量时必须指定的“基类型”。指针变量的基类型用来指定此指针变量可以指向的变量的类型。例如,上面定义的基类型为int
的指针变量pointer_1
和pointer_2
,可以用来指向整型的变量i
和j
,但不能指向浮点型变量a
和b
。
基本数据类型和指针
前面介绍过基本的数据类型(如int
, char
, float
等)。既然有这些类型的变量,就可以有指向这些类型变量的指针,因此,指针变量是基本数据类型衍生出来的类型。它不能离开基本类型而独立存在。下面是一些合法的定义:
float *pointer_3; // pointer_3是指向float型变量的指针变量,简称float指针
char *pointer_4; // pointer_4是指向字符型变量的指针变量,简称char指针
可以在定义指针变量时,同时对它初始化,如:
int *pointer_1 = &a, *pointer_2 = &b; // 定义指针变量pointer_1和pointer_2,并分别指向a和b
定义指针变量的注意事项
-
指针变量前的“*”表示该变量为指针型变量:
- 指针变量名是
pointer_1
和pointer_2
,而不是*pointer_1
和*pointer_2
。这是与定义整型或实型变量的形式不同的。 - 程序中的语句不应写成
*pointer_1 = &a;
和*pointer_2 = &b;
,因为a
的地址是赋给指针变量pointer_1
,而不是赋给*pointer_1
(即变量a)。
- 指针变量名是
-
必须指定基类型:
- 有的读者认为既然指针变量是存放地址的,那么只须指定其为“指针型变量”即可,为什么还要指定基类型呢?要知道不同类型的数据在内存中所占的字节数和存放方式是不同的。
- 指向一个整型变量和指向一个实型变量,其物理上的含义是不同的。指针变量是用来存放地址的,C的地址信息包括存储单元的位置(内存编号)和类型信息。指针变量的属性应与之匹配。例如:
int a, *p; p = &a;
&a
不仅包含变量a
的位置(如编号为2000的存储单元),还包括“存储的数据是整型”的信息。现在定义指针变量p
的基类型为int
,即它所指向的只能是整型数据。这时p
能接受&a
的信息。如果改为:
float *p; p = &a;
&a
是“整型变量a的地址”。在用Visual C++编译时就会出现一个警告(warning):把一个int*
型数据转换为float*
数据。在赋值时,系统会把&a
的基类型自动改换为float
型,然后赋给p
。但是p
不能用这个地址指向整型变量。- 从以上可以知道指针或地址是包含有类型信息的。应该使赋值号两侧的类型一致,以避免出现意外结果。
-
指针类型的表示:
- 指向整型数据的指针类型表示为
int*
,读作“指向int的指针”或简称“int指针”。可以有int*
,char*
,float*
等指针类型,如上面定义的指针变量pointer_3
的类型是float*
,pointer_4
的类型是char*
。int*
,float*
,char*
是三种不同的类型,不能混淆。
- 指向整型数据的指针类型表示为
-
指针变量中只能存放地址:
- 不要将一个整数赋给一个指针变量。例如:
*pointer_1 = 100; // 错误,pointer_1是指针变量,100是整数,不合法
- 原意是想将地址100赋给指针变量
pointer_1
,但是系统无法辨别它是地址。从形式上看,100是整常数,而整常数只能赋给整型变量,而不能赋给指针变量,判为非法。在程序中不能用一个数值代表地址,地址只能用地址符&
得到并赋给一个指针变量,如:
p = &a;
通过以上内容,可以清晰地理解如何定义和使用指针变量。掌握指针变量的定义和使用,是深入理解C语言及其高效编程的基础。
8.2.3 怎样引用指针变量
在引用指针变量时,可能有以下三种情况:
-
给指针变量赋值:
- 例如:
p = &a; // 把a的地址赋给指针变量p
- 指针变量p的值是变量a的地址,p指向a。
-
引用指针变量指向的变量:
- 如果已经执行
p = &a;
,即指针变量p指向了整型变量a,则:
printf("%d", *p); // 以整数形式输出指针变量p所指向的变量的值,即变量a的值
- 如果有以下赋值语句:
*p = 1;
- 表示将整数1赋给p当前所指向的变量,如果p指向变量a,则相当于把1赋给a,即
a = 1;
。
- 如果已经执行
-
引用指针变量的值:
- 例如:
printf("%o", p); // 以八进制数形式输出指针变量p的值
- 如果p指向了a,就是输出了a的地址,即
&a
。
注意:要熟练掌握两个有关的运算符:
- & 取地址运算符:
&a
是变量a的地址。 - * 指针运算符(或称“间接访问”运算符):
*p
代表指针变量p指向的对象。
下面是一个指针变量应用的例子:
例8.2 输入a和b两个整数,按先大后小的顺序输出a和b。
解题思路:用指针方法来处理这个问题,不交换整型变量的值,而是交换两个指针变量的值。
编写程序:
#include <stdio.h>
int main()
{
int *p1, *p2, *p, a, b; // 定义指针变量p1、p2和整型变量a、b
printf("please enter two integer numbers:");
scanf("%d, %d", &a, &b); // 输入两个整数
p1 = &a; // 使p1指向变量a
p2 = &b; // 使p2指向变量b
if (a < b) // 如果a < b
{
p = p1; p1 = p2; p2 = p; // 使p1与p2的值互换
}
printf("a=%d, b=%d\n", a, b); // 输出a, b
printf("max=%d, min=%d\n", *p1, *p2); // 输出p1和p2所指向的变量的值
return 0;
}
运行结果:
please enter two integer numbers: 5, 9
a=5, b=9
max=9, min=5
程序分析
- 输入a=5, b=9。由于a < b,将p1和p2交换。交换前的情况见图8.4(a),交换后的情况见图8.4(b)。
- 注意,a和b的值并未交换,它们仍保持原值,但p1和p2的值改变了。p1的值原为
&a
,后来变成&b
;p2原值为&b
,后来变成&a
。这样在输出*p1
和*p2
时,实际上是输出变量b和a的值,所以先输出9,然后输出5。
p1
a
p1
a
&
&
5
p
&
&
b
p
b
&
&
9
图8.4
(a) 交换前的情况
(b) 交换后的情况
注意:程序第9行采用的是以前介绍过的方法,即两个变量的值交换要利用第三个变量。实际上,第9行可以改为:
p1 = &b; p2 = &a;
即直接对p1和p2赋以新值,这样可以不必定义中间变量p,使程序更加简练。这个问题的算法是不交换整型变量的值,而是交换两个指针变量的值(即a和b的地址)。
通过这个例子,可以更好地理解如何引用和操作指针变量。这是掌握C语言中指针操作的基础。
8.2.4 指针变量作为函数参数
函数的参数不仅可以是整型、浮点型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。下面通过一个例子来说明。
例8.3 题目要求同例8.2,即对输入的两个整数按大小顺序输出。现用函数处理而且用指针类型的数据作函数参数。
解题思路:例8.2直接在主函数内交换指针变量的值,本题是定义一个函数swap,将指向两个整型变量的指针变量(内放两个变量的地址)作为实参传递给swap函数的形参指针变量,在函数中通过指针实现交换两个变量的值。
编写程序:
#include <stdio.h>
// 函数声明
void swap(int *p1, int *p2);
int main() {
int a, b;
int *pointer_1, *pointer_2; // 定义两个指向整型数据的指针变量
printf("please enter two integer numbers: ");
scanf("%d %d", &a, &b); // 输入两个整数
pointer_1 = &a; // 使pointer_1指向变量a
pointer_2 = &b; // 使pointer_2指向变量b
if (a < b) {
swap(pointer_1, pointer_2); // 如果a < b,调用swap函数
}
printf("max=%d, min=%d\n", a, b); // 输出a和b的值
return 0;
}
// 定义swap函数
void swap(int *p1, int *p2) {
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp; // 使*p1和*p2互换
}
运行结果:
please enter two integer numbers: 5 9
max=9, min=5
程序分析
swap
是用户自定义函数,它的作用是交换两个变量(a和b)的值。swap
函数的两个形参p1
和p2
是指针变量。程序运行时,先执行main函数,输入a和b的值(现输入5和9)。然后将a和b的地址分别赋给int变量pointer_1和pointer_2,使pointer_1指向a,pointer_2指向b,见图8.5(a)。接着执行if语句,由于a < b,因此执行swap函数。注意实参pointer_1和pointer_2是指针变量,在函数调用时,将实参变量的值传送给形参变量。采取的依然是“值传递”方式。因此实参结合后形参p1的值为&a,p2的值为&b,见图8.5(b)。这时p1和pointer_1都指向变量a,p2和pointer_2都指向变量b。接着执行swap函数的函数体,使p1和*p2的值互换,也就是使a和b的值互换。互换后的情况见图8.5(c)。函数调用结束后,形参p1和p2不复存在(已释放),情况如图8.5(d)所示。最后在main函数中输出的a和b的值已是经过交换的值(a=9,b=5)。
请注意交换*p1
和*p2
的值是如何实现的。如果写成以下这样就有问题了:
pointer_1
↓
+----+ +----+
| a | | b |
+----+ +----+
| 5 | | 9 |
+----+ +----+
pointer_2
↓
p1就是a,是整型变量。而temp是指针变量temp所指向的变量。但由于未给temp赋值,因此temp中并无确定的值(它的值是不可预见的),所以temp所指向的单元也是不可预见的。所以,对temp赋值就是向一个未知的存储单元赋值,而这个未知的存储单元中可能存储着一个有用的数据,这样就有可能破坏系统的正常工作状况。应该将p1的值赋给与p1相同类型的变量,在本例中用整型变量temp作为临时辅助变量实现p1和*p2的交换。
注意:本例采取的方法是交换a和b的值,而p1和p2的值不变。这与例8.2相反。可以看到,在执行swap函数后,变量a和b的值改变了。请仔细分析,这个改变是怎么实现的。这个改变不是通过将形参值传回实参来实现的。
请读者考虑一下能否通过下面的函数实现a和b的互换:
pointer_1 pointer_2
↓ ↓
+----+ +----+
| a | | b |
+----+ +----+
| 9 | | 5 |
+----+ +----+
如果在main函数中调用swap函数:
swap(a, b);
会有什么结果呢?如图8.6所示。在函数调用时,a的值传送给x,b的值传送给y,见图8.6(a)。执行完swap函数后,x和y的值是互换了,但并未影响到a和b的值。在函数结束时变量x和y释放了,main函数中的a和b并未互换,见图8.6(b)。也就是说,由于“单向传送”的“值传递”方式,形参值的改变不能使实参的值随之改变。
为了使在函数中改变了的变量值能被主调函数main所用,不能采取上述把要改变值的变量作为参数的办法,而应该用指针变量作为函数参数。在函数执行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量值的变化依然保留下来,这样就实现了“通过调用函数使变量的值发生变化,在主调函数(如main函数)中可以使用这些改变了的值”的目的。
如果想通过函数调用得到n个要改变的值,可以这样做:
- 在主调函数中设n个变量,用n个指针变量指向它们;
- 设计一个函数,有n个指针形参。在这个函数中改变这n个形参的值;
- 在主调函数中调用这个函数,在调用时将这n个指针变量作实参,将它们的值,也就是相关变量的地址传给该函数的形参;
- 在执行该函数的过程中,通过形参指针变量,改变它们所指向的n个变量的值;
- 主调函数中就可以使用这些改变了值的变量。
请读者按此思路仔细理解例8.3程序。
注意:不能企图通过改变指针形参的值而使指针实参的值改变。请看下面的程序。
例8.4 对输入的两个整数按大小顺序输出。
解题思路:尝试调用swap函数来实现题目要求。在函数中改变形参(指针变量)的值希望能由此改变实参(指针变量)的值。
编写程序:
#include <stdio.h>
void swap(int *p1, int *p2); // 函数声明
int main()
{
int a, b;
int *pointer_1, *pointer_2; // pointer_1, pointer_2是int*型变量
printf("please enter two integer numbers:");
scanf("%d, %d", &a, &b);
pointer_1 = &a;
pointer_2 = &b;
if (a < b) swap(pointer_1, pointer_2); // 调用swap函数,用指针变量作实参
printf("max=%d, min=%d\n", *pointer_1, *pointer_2);
return 0;
}
void swap(int *p1, int *p2) // 形参是指针变量
{
int *p;
p = p1; // 下面3行交换p1和p2的指向
p1 = p2;
p2 = p;
}
运行结果:
please enter two integer numbers: 5, 9
max=5, min=9
程序分析:从运行结果看,显然与原意不符。程序编写者的意图是:交换指针变量pointer_1和pointer_2的值,使pointer_1指向值大的变量。其设想是:
- 先使pointer_1指向a,pointer_2指向b,见图8.7(a)。
- 调用swap函数,将pointer_1的值传给p1,pointer_2的值传给p2,见图8.7(b)。
- 在swap函数中使p1与p2的值交换,见图8.7(c)。
- 形参p1与p2将它们的值(是地址)传回实参pointer_1和pointer_2,使pointer_1指向b,pointer_2指向a,见图8.7(d)。然后输出pointer_1和pointer_2,想得到输出“max=9,min=5”。
但是,这是办不到的,在输入“5,9”之后程序实际输出为“max=5, min=9”。问题出在第④步。C语言中实参变量和形参变量之间的数据传递是单向的“值传递”方式。用指针变量作函数参数时同样要遵循这一规则。不可能通过执行调用函数来改变实参指针变量的值,但是可以改变实参指针变量所指变量的值。
注意:函数的调用可以(而且只可以)得到一个返回值(即函数值),而使用指针变量作参数,可以得到多个变化了的值。如果不用指针变量是难以做到这一点的。要善于利用指针法。
例8.5 输入三个整数a, b, c,要求按由大到小的顺序将它们输出。用函数实现。
解题思路:采用例8.3的方法在函数中改变这三个变量的值。用swap函数交换两个变量的值,用exchange函数改变这三个变量的值。
编写程序:
#include <stdio.h>
void exchange(int *q1, int *q2, int *q3); // 函数声明
void swap(int *p1, int *p2); // 函数声明
int main()
{
int a, b, c, *p1, *p2, *p3;
printf("please enter three numbers:");
scanf("%d, %d, %d", &a, &b, &c);
p1 = &a;
p2 = &b;
p3 = &c;
exchange(p1, p2, p3);
printf("The order is: %d, %d, %d\n", a, b, c);
return 0;
}
void exchange(int *q1, int *q2, int *q3) // 定义将3个变量的值交换的函数
{
if (*q1 < *q2) swap(q1, q2); // 如果a < b, 交换a和b的值
if (*q1 < *q3) swap(q1, q3); // 如果a < c, 交换a和c的值
if (*q2 < *q3) swap(q2, q3); // 如果b < c, 交换b和c的值
}
void swap(int *p1, int *p2) // 定义交换两个变量的值的函数
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp; // 交换*p1和*p2变量的值
}
运行结果:
please enter three numbers: 20, -54, 67
The order is: 67, 20, -54
程序分析:exchange
函数的作用是对三个数按大小排序,在执行exchange
函数过程中,要嵌套调用swap
函数,swap
函数的作用是对两个数按大小排序,通过调用swap
函数(最多调用三次)实现三个数的排序。
请读者自己画出如图8.6那样的图,仔细分析变量的值变化的过程。请思考:main
函数中的三个指针变量的值(也就是它们的指向)改变了没有。