c语言进阶6-指针 指针是c语言的一个重要组成部分

是c语言的核心、精髓所在,用好指针可以在c语言编程中起到事半功倍的效果。一方面,可以提高程序的编译效率和执行速度以及实现动态的存储分配;另一方面,使用指针可使程序更灵活,全球表示各种数据结构,编写高质量的程序。

指针是c语言显著的优点之一,其使用起来十分灵活而且能提高某些程序的效率,但是如果使用不当则很容易造成系统错误。许多程序“挂死“往往都是由于错误地使用指针造成的

一、       地址与指针

系统的内存就好比是带有编号的小房间,如果想使用内存就需要得到房间编号。图1定义了一个整型变量i,整型变量需要4个字节,所以编译器为变量i分配的编号为1000~1003.

什么是地址?地址就是内存区中对每个字节的编号,如图1所示的1000/1001/1002和1003就是地址,为了进一步说明来看图2.

内存地址

内容

 

1000

0

变量i

1004

1

变量j

1008

2

 

1012

3

 

1016

4

 

1020

5

 

图2所示的1000、1004等就是内存单元的地址,而0、1就是内存单元的内容,换种说法就是基本整型变量i在内存中的地址从1000开始。因为基本整型占4个字节,所以变量j在内存中的起始地址为1004,变量i的内容是0.

那么指针又是什么呢?这里仅将指针看作是内存中的一个地址,多数情况下,这个地址就是内存中另一个变量的位置,如图3所示。

在程序中定义了一个变量,在进行编译时就会给该变量在内存中分配一个地址,通过访问这个地址可以找到所需的变量,这个变量的地址称为该变量的“指针”。图3所示的地址1000是变量i的指针。

 

二、       变量与指针


变量的地址是变量和指针二者之间连接的纽带,如果一个变量包含了另一个变量的地址,则可以理解成第一个变量指向第二个变量。
所谓“指向”就是通过地址来体现的,因为指针变量是指向一个变量的地址,所以讲一个变量的地址赋给这个指针变量后,这个指针变量就“指向”了该变量。例如,将变量i的地址存放到指针变量p中,p就指向i

c语言进阶6-指针
指针是c语言的一个重要组成部分

在程序代码中是通过变量名对内存单元进行存取操作的,但是代码经过编译后已经将变量名转换为该变量在内存中的存放地址,对变量值的存取都是通过地址进行的。如对图2所示的变量i和变量j进行如下操作;

其含义时:根据变量名与地址的对应关系,找到变量i的地址1000,然后从1000开始读取4个字节数据放到cpu寄存器中,再找到变量j的地址1004,从1004开始读取4个字节的数据放到cpu的另一个寄存器中,通过cpu的加法中断计算出结果。

在低级语言的汇编语言中都是直接通过地址来方位内存单元的,在高级语言中一般使用变量名访问内存单元,但c语言作为高级语言提供了通过地址来访问内存单元的方式。

一、       指针变量

由于通过地址能访问指定的内存存储单元,可以说地址“指向”该内存单元。地址可以形象地称为指针,意思是通过指针能找到内存单元。一个变量的地址称为该变量的指针。如果一个变量专门用来存放另一个变量的地址,它就是指针变量。在c语言中有专门用来存放内存单元地址的变量类型,即指针类型。下面将针对如何定义一个指针变量,如何为一个指针变量赋值及如何引用指针变量这3方面内容加以介绍。

  1. 1.   指针变量的一般形式

如果一个变量专门用来存放另一变量的地址,则它成为指针变量。图4所示的p就是一个指针变量。如果一个变量包含指针(指针等同于一个变量的地址),则必须对它进行说明。定义指针变量的一般形式如下;

类型说明  *变量名

其中“*”表示该变量是一个指针变量,变量名为定义的指针变量名,类型说明表示本指针变量所指向的变量的数据类型。

  1. 2.   指针变量的赋值

指针变量同普通变量一样,使用之前不仅需要定义,而且必须赋予具体的值,未经赋值的指针变量不能使用。给指针变量所赋的值与给其他变量所赋的值不同,给指针变量的赋值只能赋予地址,而不能赋予任何其他数据,否则将引起错误。c语言中提供了地址运算符&来表示变量的地址。其一般形式为:

&变量名;

如&a表示变量a的地址,&b表示变量b的地址。给一个指针变量赋值可以有以下两种方法。

(1)      定义指针变量的同时就进行赋值,例如:

int  a;

int *p=&a;

(2)      先定义指针变量之后再赋值,例如:

int  a;

int *p;

p=&a;

从键盘中输入两个数,利用指针的方法将这两个数输出

#include "stdio.h"

void main()

{

    int a,b;

    int *ipointer1,*ipointer2;  /*声明两个指针变量*/

    scanf("%d,%d",&a,&b);   /*输入两个数*/

    ipointer1=&a;

    ipointer2=&b;   /*将地址赋给指针变量*/

    printf("the number is :%d,%d
",*ipointer1,*ipointer2);

}

通过实例1可以发现程序中采用的赋值方式是上述第二种方法,即先定义再赋值。

这里强调一点,即不允许把一个数赋予指针变量,例如:

int *p;

p=1002;

这样写是错误的。

  1. 3.   指针变量的引用

 引用指针变量是对变量进行间接访问的一种形式。对指针变量的引用形式如下;

*指针变量

其含义时引用指针变量所指向的值

利用指针变量实现数据的输入和输出。

#include "stdio.h"

void main()

{

    int *p,q;

    printf("please input:
");

    scanf("%d",&q);   /*输入一个整型数据*/

    p=&q;

    printf("the number is:
");

    printf("%d
",*p);  /*输出变量的值*/

}

可将上述程序修改成如下形式;

#include "stdio.h"

void main()

{

    int *p,q;

    p=&q;

    printf("please input:
");

    scanf("%d",p);   /*输入一个整型数据*/   

    printf("the number is:
");

    printf("%d
",*p);  /*输出变量的值*/

}
  1. 4.   “&”和“*”运算符

 在前面介绍指针变量的过程中用到了“&”和“*”两个运算符,运算符&是一个返回操作数地址的单目运算符,叫做取地址运算符,例如:

p=&i;

就是将变量i的内存地址赋给p,这个地址是该变量在计算机内部的存储位置。

运算符“*”是单目运算符,叫做指针运算符,作用是返回指定的地址内的变量的值。如前面提到过p中装有变量i的内存地址,则

q=*p;

就是将变量i的值赋给q,假如变量i的值是5,则q的值也是5.

  1. 5.   “&” 和“&”的区别

如果有如下语句:

int a;

p=&a;

 下面通过以上两条语句来分析“&”和“&”的区别,&”和“*”的运算符优先级别相同,按自右向左的方向结合。因此“&*p先进行“*”运算,“*p”相当于变量a;再进行“&”运算,“&*p就相当于取变量a的地址。“*&a”先进行“&”运算,“&a”就是取变量a的地址,然后执行“*”运算,“*&a”就相当于取变量a所在地址的值,实际就是变量a

二、       指针自加自减运算

指针自加自减运算内不同于普通变量的自加自减运算,也就是说并非简单地加1减1,这里面通过下面的实例进行具体分析。

ACM【例5】整型变量地址输出

#include "stdio.h"

void main()

{

    int i;

    int *p;

    printf("please input the number:
");

    scanf("%d",&i);  

    p=&i;      /*将变量i的地址赋给指针变量*/   

    printf("the result1 is:%d
",p);

    p++;      /* 地址加1,这里的1并不代表一个字节*/

    printf("the result2 is:%d
",p);

}

若将实例5改成:

#include "stdio.h"

void main()

{

    short i;

    short *p;

    printf("please input the number:
");

    scanf("%d",&i);  

    p=&i;      /*将变量i的地址赋给指针变量*/   

    printf("the result1 is:%d
",p);

    p++;      /* 地址加1,这里的1并不代表一个字节*/

    printf("the result2 is:%d
",p);

}

基本整型变量i在内存中占4个字节,指针p是指向变量i的地址的,这里的p++不是简单地在地址上加1,而是指向下一个存放基本整型数的地址。图9所示的结果是因为变量i是基本整型,所以执行p++后,p的值增加4(4个字节);图10所示的结果是因为i被定义成了短整型,所以执行p++后,p的值增加了2(两个字节)。

指针都按照它所指向的数据包类型的直接长度进行增或减。

三、       数组与指针

系统须要提供一定量连续的内存来存储数组中的各元素,内存都有地址,指针变量就是存放地址的变量,如果数组的地址赋给指针变量,就可以通过指针变量引用数组。下面就介绍如何用指针来引用一对数组元素。

一维数组与指针

当定义一个一对数组时,系统会在内存中为该数组分配一个存储空间,其数组的名称就是数组在内存中的首地址。若再定义一个指针变量,并将数组的首地址传给指针变量,则该指针就指向了这个一维数组。

例如:

int *p,a[10];

p=a;

这里a是数组名,也就是数组的首地址,将它赋给指针变量p,也就是将数组a的首地址赋给p,也可以写成如下形式;

int *p,a[10];

p=&a[0];

上面的语句是讲数组a中的首个元素的地址赋给指针变量p。由于a[0]的地址就是数组的首地址,因此两条赋值操作效果完全相同,如实例6所示。

ACM【例 6】 输入数组中的元素。

#include "stdio.h"

void main()
{
    int *p,*q,a[5],b[5];
    int i;
    p=&a[0];
    q=b;
    printf("please input array a:
");
    for(i=0;i<5;i++)
    scanf("%d",&a[i]);
    printf("please input array b:
");
    for(i=0;i<5;i++)
    scanf("%d",&b[i]);
    printf("array a is :
");
    for(i=0;i<5;i++)
    printf("%5d",*(p+i));
    printf("
");
    printf("array b is :
");
    for(i=0;i<5;i++)
    printf("%5d",*(q+i));
    printf("
");
}

实例6中有如下两条语句:

p=&a[0];

q=b;

分别表示输出数组a和数组b中对应的元素。

这两种表示方法都是讲数组首地址赋给指针变量。

那么如何通过指针的方式来引用一对数组中的元素呢?有以下语句:

int *p,a[5];

p=&a;

针对上面的语句将通过以下几方面进行介绍

p+n与a+n表示数组元素a[n]的地址,即&a[n]。对整个a数组来说,共有5个元素,n的取值为0~4,则数组元素的地址就可以表示为p+0~p+4或a+0~a+4。

表示数组中的元素用到了前面介绍的数组元素的地址,用*(p+n)和*(a+n)来表示数组中的个元素。

实例6中的语句:

printf("%5d",*(p+i));

和语句:

printf("%5d",*(q+i));

分别表示输出数组a和数组b中对应的元素。

实例6中使用指针指向一对数组及通过指针引用数组元素的过程可以通过图13和图14来表示。

前面提到可以用a+n表示数组元素的地址,*(a+n)表示数组元素,那么就可以将实例6的程序代码改成如下形式:

#include "stdio.h"

void main()

{

    int *p,*q,a[5],b[5];

    int i;

    p=&a[0];

    q=b;

    printf("please input array a:
");

    for(i=0;i<5;i++)

    scanf("%d",&a[i]);

    printf("please input array b:
");

    for(i=0;i<5;i++)

    scanf("%d",&b[i]);

    printf("array a is :
");

    for(i=0;i<5;i++)

    printf("%5d",*(a+i));

    printf("
");

    printf("array b is :
");

    for(i=0;i<5;i++)

    printf("%5d",*(b+i));

    printf("
");

}

程序运行的结果与实例6的运行结果一样。

表示指针的移动可以使用“++”和“——”这两个运算符

利用“++”运算符可将程序改写成如下形式。

#include "stdio.h"

void main()

{

    int *p,*q,a[5],b[5];

    int i;

    p=&a[0];

    q=b;

    printf("please input array a:
");

    for(i=0;i<5;i++)

    scanf("%d",&a[i]);

    printf("please input array b:
");

    for(i=0;i<5;i++)

    scanf("%d",&b[i]);

    printf("array a is :
");

    for(i=0;i<5;i++)

    printf("%5d",*p++);
    printf("
");
    printf("array b is :
");
    for(i=0;i<5;i++)
    printf("%5d",*q++);
    printf("
");
}

 

还可将上面程序再进一步改写,运行结果仍与实例6的运行结果相同,改写后的程序代码如下:

#include "stdio.h"

void main()
{
    int *p,*q,a[5],b[5];
    int i;
    p=&a[0];
    q=b;
    printf("please input array a:
");
    for(i=0;i<5;i++)
    scanf("%d",p++);
    printf("please input array b:
");
    for(i=0;i<5;i++)
    scanf("%d",q++);
    printf("array a is :
");
    for(i=0;i<5;i++)
    printf("%5d",*p++);
    printf("
");
    printf("array b is :
");
    for(i=0;i<5;i++)
    printf("%5d",*q++);
    printf("
");
}

比较上面两个程序会发现,如果在给数组元素赋值时使用了如下语句:

  printf("please input array a: ");

    for(i=0;i<5;i++)

    scanf("%d",p++);

    printf("please input array b: ");

    for(i=0;i<5;i++)

    scanf("%d",q++);

而且在输出数组元素时需要使用指针变量,则需加上如下语句:

p=a;

q=b;

这两个语句的作用是讲指针变量p和q重新指向数组a和数组b在内存中的起始位置,若没有该语句,而直接使用*p++的方法进行输出,则此时将会产生错误。

四、       字符串与指针

访问一个字符中可以通过两种方式,第一种方式就是前面讲过的使用字符数组来存放一个字符串,从而实现对字符串的操作;另一种方式就是下面将要介绍的使用字符指针指向一个字符串,此时可不定义数组。

ACM【例 10】 字符型指针引用。

#include "stdio.h"

void main()

{

    char *string="hello word";

    printf("%s",string);

}

程序运行结果如图19所示。

实例10中定义了字符型指针变量string,用字符串敞亮“hello mingri”为其赋初值,注意这里并不是把“hello mingri”中的所有字符存放到string中,只是把该字符串中的第一个字符的地址赋给指针变量string,如图20所示。

char *string="hello word";

等价于下面两条语句:

char *string;

string="hello word";

ACM【例 11】  输入两个字符串a和b,将字符串a和b连接起来。

#include "stdio.h"

#include "string.h"

void main()

{

    char str1[]="you are beautiful",str2[30],*p1,*p2;

    p1=str1;

    p2=str2;

    while(*p1!='