8.2 指针变量

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

程序分析

  1. 定义指针变量

    • 在开头处定义了两个指针变量pointer_1pointer_2。此时它们并未指向任何一个变量,只是提供两个指针变量,规定它们可以指向整型变量,至于指向哪一个整型变量要在程序语句中指定。
    • 程序第5、6行的作用就是使pointer_1指向apointer_2指向b。此时pointer_1的值为&a(即a的地址),pointer_2的值为&b,见图8.3。
  2. 输出变量的值

    • 第7行输出变量ab的值:100和10。
    • 第8行输出*pointer_1*pointer_2的值。其中的“*”表示“指向”。*pointer_1表示“指针变量pointer_1所指向的变量”,也就是变量a*pointer_2表示“指针变量pointer_2所指向的变量”,也就是变量b。从运行结果看到,它们的值也是100和10。
  3. 区分定义与使用

    • 程序中有两处出现*pointer_1*pointer_2,二者的含义不同。程序第4行的*pointer_1*pointer_2表示定义两个指针变量pointer_1pointer_2。它们前面的“*”只是表示该变量是指针变量。
    • 程序最后一行printf函数中的*pointer_1*pointer_2则代表指针变量pointer_1pointer_2所指向的变量。

注意:定义指针变量时,左侧应有类型名,否则就不是定义指针变量。例如:

  • *pointer_1; // 企图定义pointer_1为指针变量。出错
  • int *pointer_1; // 正确,必须指定指针变量的基类型

图示

通过这个例子,可以清晰地看到指针变量的定义和使用过程。指针变量是C语言中强大而灵活的工具,掌握它能使程序更加高效和灵活。

 

 

8.2.2 怎样定义指针变量

在例8.1中,我们已经看到怎样定义指针变量。定义指针变量的一般形式为:

类型名 *指针变量名;

例如:

int *pointer_1, *pointer_2;

左端的int是在定义指针变量时必须指定的“基类型”。指针变量的基类型用来指定此指针变量可以指向的变量的类型。例如,上面定义的基类型为int的指针变量pointer_1pointer_2,可以用来指向整型的变量ij,但不能指向浮点型变量ab

基本数据类型和指针

前面介绍过基本的数据类型(如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
定义指针变量的注意事项
  1. 指针变量前的“*”表示该变量为指针型变量

    • 指针变量名是pointer_1pointer_2,而不是*pointer_1*pointer_2。这是与定义整型或实型变量的形式不同的。
    • 程序中的语句不应写成*pointer_1 = &a;*pointer_2 = &b;,因为a的地址是赋给指针变量pointer_1,而不是赋给*pointer_1(即变量a)。
  2. 必须指定基类型

    • 有的读者认为既然指针变量是存放地址的,那么只须指定其为“指针型变量”即可,为什么还要指定基类型呢?要知道不同类型的数据在内存中所占的字节数和存放方式是不同的。
    • 指向一个整型变量和指向一个实型变量,其物理上的含义是不同的。指针变量是用来存放地址的,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不能用这个地址指向整型变量。
    • 从以上可以知道指针或地址是包含有类型信息的。应该使赋值号两侧的类型一致,以避免出现意外结果。
  3. 指针类型的表示

    • 指向整型数据的指针类型表示为int*,读作“指向int的指针”或简称“int指针”。可以有int*, char*, float*等指针类型,如上面定义的指针变量pointer_3的类型是float*pointer_4的类型是char*int*, float*, char*是三种不同的类型,不能混淆。
  4. 指针变量中只能存放地址

    • 不要将一个整数赋给一个指针变量。例如:
    *pointer_1 = 100; // 错误,pointer_1是指针变量,100是整数,不合法
    
    • 原意是想将地址100赋给指针变量pointer_1,但是系统无法辨别它是地址。从形式上看,100是整常数,而整常数只能赋给整型变量,而不能赋给指针变量,判为非法。在程序中不能用一个数值代表地址,地址只能用地址符&得到并赋给一个指针变量,如:
     
    p = &a;
    

通过以上内容,可以清晰地理解如何定义和使用指针变量。掌握指针变量的定义和使用,是深入理解C语言及其高效编程的基础。

 

 

8.2.3 怎样引用指针变量

在引用指针变量时,可能有以下三种情况:

  1. 给指针变量赋值

    • 例如:
    p = &a; // 把a的地址赋给指针变量p
    
    • 指针变量p的值是变量a的地址,p指向a。
  2. 引用指针变量指向的变量

    • 如果已经执行 p = &a;,即指针变量p指向了整型变量a,则:
    printf("%d", *p); // 以整数形式输出指针变量p所指向的变量的值,即变量a的值
    
    • 如果有以下赋值语句:
    *p = 1;
    
    • 表示将整数1赋给p当前所指向的变量,如果p指向变量a,则相当于把1赋给a,即 a = 1;
  3. 引用指针变量的值

    • 例如:
    printf("%o", p); // 以八进制数形式输出指针变量p的值
    
    • 如果p指向了a,就是输出了a的地址,即 &a

注意:要熟练掌握两个有关的运算符:

  1. & 取地址运算符:&a 是变量a的地址。
  2. * 指针运算符(或称“间接访问”运算符):*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

程序分析

  1. 输入a=5, b=9。由于a < b,将p1和p2交换。交换前的情况见图8.4(a),交换后的情况见图8.4(b)。
  2. 注意,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函数的两个形参p1p2是指针变量。程序运行时,先执行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个要改变的值,可以这样做:

  1. 在主调函数中设n个变量,用n个指针变量指向它们;
  2. 设计一个函数,有n个指针形参。在这个函数中改变这n个形参的值;
  3. 在主调函数中调用这个函数,在调用时将这n个指针变量作实参,将它们的值,也就是相关变量的地址传给该函数的形参;
  4. 在执行该函数的过程中,通过形参指针变量,改变它们所指向的n个变量的值;
  5. 主调函数中就可以使用这些改变了值的变量。

请读者按此思路仔细理解例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指向值大的变量。其设想是:

  1. 先使pointer_1指向a,pointer_2指向b,见图8.7(a)。
  2. 调用swap函数,将pointer_1的值传给p1,pointer_2的值传给p2,见图8.7(b)。
  3. 在swap函数中使p1与p2的值交换,见图8.7(c)。
  4. 形参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函数中的三个指针变量的值(也就是它们的指向)改变了没有。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏驰和徐策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值