C语言基础(二):指针

指针

在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等,在前面已有详细的介绍。为了正确地访问这些内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。

内存单元的编号也叫做地址。既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容。

在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。严格地说,一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。定义指针的目的是为了通过指针去访问内存单元。

指针变量

变量的指针就是变量的地址。存放变量地址的变量是指针变量。因此,一个指针变量的值就是某个变量的地址或称为某变量的指针。

为了表示指针变量和它所指向的变量之间的关系,在程序中用“”符号表示“指向”,例如,i_pointer代表指针变量,而i_pointer是i_pointer所指向的变量。因此,下面两个语句作用相同:

i=3;
*i_pointer=3;

第2个语句的含义是将3赋给指针变量i_pointer所指向的变量。

对指针变量的定义包括三个内容:

  1. 指针类型说明,即定义变量为一个指针变量;
  2. 指针变量名;
  3. 变量值(指针)所指向的变量的数据类型。   其一般形式为: 类型说明符 *变量名;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。例如:

int *p1;

表示p1是一个指针变量,它的值是某个整型变量的地址。或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量,应由向p1赋予的地址来决定。

指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。

两个有关的运算符:

  1. &:取地址运算符;
  2. *:指针运算符(或称“间接访问” 运算符)。

C语言中提供了地址运算符&来表示变量的地址。其一般形式为: &变量名;

如&a表示变量a的地址,&b表示变量b的地址。变量本身必须预先说明。

设有指向整型变量的指针变量p,如要把整型变量a 的地址赋予p可以有以下两种方式:

指针变量初始化的方法:

  int a;
  int *p=&a;

赋值语句的方法:

  int a;
  int *p;
  p=&a;

不允许把一个数赋予指针变量。

int i=200, x;
int *ip;

我们定义了两个整型变量i、x,还定义了一个指向整型数的指针变量ip。i、x中可存放整数,而ip中只能存放整型变量的地址。我们可以把i的地址赋给ip:

ip=&i;

此时指针变量ip指向整型变量i。

【例】输入a和b两个整数,按先大后小的顺序输出a和b。

main(){
    int *p1,*p2,*p,a,b;
    scanf("%d,%d",&a,&b);
    p1=&a;
    p2=&b;
    if(a&ltb){
        p=p1;
        p1=p2;
        p2=p;
    }
    printf("\na=%d,b=%d\n",a,b);
    printf("max=%d,min=%d\n",*p1, *p2);
}

指针变量作为参数

函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。

【例】输入的两个整数按大小顺序输出。今用函数处理,而且用指针类型的数据作函数参数。

swap(int *p1,int *p2){
    int temp;
    temp=*p1;
    *p1=*p2;
    *p2=temp;
}
main(){
    int a,b;
    int *pointer_1,*pointer_2;
    scanf("%d,%d",&a,&b);
    pointer_1=&a;pointer_2=&b;
    if(a&ltb) swap(pointer_1,pointer_2);
    printf("\n%d,%d\n",a,b);
}

对程序的说明:

1) swap是用户定义的函数,它的作用是交换两个变量(a和b)的值。swap函数的形参p1、p2是指针变量。程序运行时,先执行main函数,输入a和b的值。然后将a和b的地址分别赋给指针变量pointer_1和pointer_2,使pointer_1指向a,pointer_2指向b。

2) 接着执行if语句,由于a&ltb,因此执行swap函数。注意实参pointer_1和pointer_2是指针变量,在函数调用时,将实参变量的值传递给形参变量。采取的依然是“值传递”方式。因此虚实结合后形参p1的值为&a,p2的值为&b。这时p1和pointer_1指向变量a,p2和pointer_2指向变量b。

3) 接着执行执行swap函数的函数体使p1和p2的值互换,也就是使a和b的值互换。函数调用结束后,p1和p2不复存在(已释放)。

4) 最后在main函数中输出的a和b的值是已经过交换的值。请注意交换p1和p2的值是如何实现的。

指针变量的运算

指针变量可以进行某些运算,但其运算的种类是有限的。它只能进行赋值运算和部分算术运算及关系运算。指针运算符有两种:取地址运算符(&),取内容运算符(*)。

main(){
    int a=5,*p=&a;
    printf ("%d",*p);
}

表示指针变量p取得了整型变量a的地址。printf("%d",*p)语句表示输出变量a的值。

指针变量的赋值运算有以下几种形式。

1) 指针变量初始化赋值,前面已作介绍。

2) 把一个变量的地址赋予指向相同数据类型的指针变量。例如:

int a,*pa;
pa=&a;    /*把整型变量a的地址赋予整型指针变量pa*/

3) 把一个指针变量的值赋予指向相同类型变量的另一个指针变量。如:

int a,*pa=&a,*pb;
pb=pa;    /*把a的地址赋予指针变量pb*/

由于pa,pb均为指向整型变量的指针变量,因此可以相互赋值。

4) 把数组的首地址赋予指向数组的指针变量。例如:

int a[5],*pa;
pa=a;  /* 数组名表示数组的首地址,故可赋予指向数组的指针变量pa */

5) 把字符串的首地址赋予指向字符类型的指针变量。例如:

char *pc;
pc="C Language";

6) 把函数的入口地址赋予指向函数的指针变量。例如:

int (*pf)();
pf=f;     /*f为函数名*/

对于指向数组的指针变量,可以加上或减去一个整数n。

int a[5],*pa;
pa=a;  /*pa指向数组a,也是指向a[0]*/
pa=pa+2;  /*pa指向a[2],即pa的值为&pa[2]*/

指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。【例】

main(){
    int a=10,b=20,s,t,*pa,*pb;  /*说明pa,pb为整型指针变量*/
    pa=&a;  /*给指针变量pa赋值,pa指向变量a*/
    pb=&b;  /*给指针变量pb赋值,pb指向变量b*/
    s=*pa+*pb;  /*求a+b之和,(*pa就是a,*pb就是b)*/
    t=*pa**pb;  /*本行是求a*b之积*/
    printf("a=%d\nb=%d\na+b=%d\na*b=%d\n",a,b,a+b,a*b);
    printf("s=%d\nt=%d\n",s,t);
}

数组指针

所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。

一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的。每个数组元素按其类型不同占有几个连续的内存单元。一个数组元素的首地址也是指它所占有的几个内存单元的首地址。

定义一个指向数组元素的指针变量的方法,与以前介绍的指针变量相同。例如:

int a[10];  /*定义a为包含10个整型数据的数组*/
int *p;  /*定义p为指向整型变量的指针*/

应当注意,因为数组为int型,所以指针变量也应为指向int型的指针变量。下面是对指针变量赋值:

p=&a[0];

把a[0]元素的地址赋给指针变量p。也就是说,p指向a数组的第0号元素。C语言规定,数组名代表数组的首地址,也就是第0号元素的地址。

数组指针变量说明的一般形式为: 类型说明符 *指针变量名;

其中类型说明符表示所指数组的类型。从一般形式可以看出指向数组的指针变量和指向普通变量的指针变量的说明是相同的。

指针引用数组

C语言规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。

引入指针变量后,就可以用两种方法来访问数组元素了。如果p的初值为&a[0],则:

p+i和a+i就是a[i]的地址,或者说它们指向a数组的第i个元素。

(p+i)或(a+i)就是p+i或a+i所指向的数组元素,即a[i]。例如,(p+5)或(a+5)就是a[5]。

指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。

根据以上叙述,引用一个数组元素可以用:

下标法:即用a[i]形式访问数组元素。在前面介绍数组时都是采用这种方法。

指针法:即采用(a+i)或(p+i)形式,用间接访问的方法来访问数组元素,其中a是数组名,p是指向数组的指针变量,其处值p=a。

【例】输出数组中的全部元素(下标法)。

main(){
    int a[10],i;
    for(i=0;i<10;i++)
        a[i]=i;
    for(i=0;i<5;i++)
        printf("a[%d]=%d\n",i,a[i]);
}

【例】输出数组中的全部元素(用指针变量指向元素)。

main(){
    int a[10],i,*p;
    p=a;
    for(i=0;i<10;i++)
        *(p+i)=i;
    for(i=0;i<10;i++)
        printf("a[%d]=%d\n",i,*(p+i));
}

数组名作为函数参数

数组名可以作函数的实参和形参。如:

main(){
    int array[10];
    /* &hellip;&hellip; */
    /* &hellip;&hellip; */
    f(array,10);
    /* &hellip;&hellip; */
    /* &hellip;&hellip; */
}

f(int arr[],int n);
{
    /* &hellip;&hellip; */
    /* &hellip;&hellip; */
}

array为实参数组名,arr为形参数组名。在学习指针变量之后就更容易理解这个问题了。数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址,形参得到该地址后也指向同一数组。这就好象同一件物品有两个彼此不同的名称一样。

同样,指针变量的值也是地址,数组指针变量的值即为数组的首地址,当然也可作为函数的参数使用。

字符串指针

在C语言中,可以用两种方法访问一个字符串。

用字符数组存放一个字符串,然后输出该字符串。

main(){
    char string[]=&rdquo;I love China!&rdquo;;
    printf("%s\n",string);
}

说明:和前面介绍的数组属性一样,string是数组名,它代表字符数组的首地址。

用字符串指针指向一个字符串。

main(){
    char *string=&rdquo;I love China!&rdquo;;
    printf("%s\n",string);
}

字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的。只能按对指针变量的赋值不同来区别。对指向字符变量的指针变量应赋予该字符变量的地址。

字符串指针与字符串数组的区别

用字符数组和字符指针变量都可实现字符串的存储和运算。但是两者是有区别的。在使用时应注意以下几个问题:

1) 字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以‘\0’作为串的结束。字符数组是由于若干个数组元素组成的,它可用来存放整个字符串。

2) 对字符串指针方式

char *ps="C Language";

可以写为:

char *ps;
ps="C Language";

而对数组方式:

static char st[]={"C Language"};

不能写为:

char st[20];
st={"C Language"};

而只能对字符数组的各元素逐个赋值。

函数指针变量运算

在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。

函数指针变量定义的一般形式为: 类型说明符 (*指针变量名)();

其中“类型说明符”表示被指函数的返回值的类型。“( 指针变量名)”表示“”后面的变量是定义的指针变量。最后的空括号表示指针变量所指的是一个函数。例如:

int (*pf)();表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。

【例】本例用来说明用指针形式实现对函数调用的方法。

int max(int a,int b)
{
    if(a>b)return a;
    else return b;
}
main()
{
    int max(int a,int b);
    int(*pmax)();
    int x,y,z;
    pmax=max;
    printf("input two numbers:\n");
    scanf("%d%d",&x,&y);
    z=(*pmax)(x,y);
    printf("maxmum=%d",z);
}

从上述程序可以看出用,函数指针变量形式调用函数的步骤如下:

  1. 先定义函数指针变量,如后一程序中第9行 int (*pmax)(); 定义 pmax为函数指针变量;
  2. 把被调函数的入口地址(函数名)赋予该函数指针变量,如程序中第11行 pmax=max;
  3. 用函数指针变量形式调用函数,如程序第14行 z=(*pmax)(x,y);
  4. 调用函数的一般形式为:(*指针变量名) (实参表)

指针型函数

在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。定义指针型函数的一般形式为:

类型说明符 函数名(形参表){ /函数体*/ }

其中函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。如:

int *ap(int x,int y){
    /*函数体*/
}

表示ap是一个返回指针值的指针型函数,它返回的指针指向一个整型变量。

【例】本程序是通过指针函数,输入一个1~7之间的整数,输出对应的星期名。

main(){
    int i;
    char *day_name(int n); 
    printf("input Day No:\n");
    scanf("%d",&i);
    if(i<0) exit(1);
    printf("Day No:%2d-->%s\n",i,day_name(i));
}
char *day_name(int n){
    static char *name[]={ "Illegal day",
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday",
        "Sunday"
    };
    return((n<1||n>7) ? name[0] : name[n]);
}

指针数组

一个数组的元素值为指针则是指针数组。指针数组是一组有序的指针的集合。 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。指针数组说明的一般形式为: 类型说明符 *数组名[数组长度]

其中类型说明符为指针值所指向的变量的类型。例如:

&nbsp; &nbsp; int *pa[3];

表示pa是一个指针数组,它有三个数组元素,每个元素值都是一个指针,指向整型变量。

通常可用一个指针数组来指向一个二维数组。指针数组中的每个元素被赋予二维数组每一行的首地址,因此也可理解为指向一个一维数组。

main(){
    int a[3][3]={1,2,3,4,5,6,7,8,9};
    int *pa[3]={a[0],a[1],a[2]};
    int *p=a[0];
    int i;
    for(i=0;i&lt3;i++)
        printf("%d,%d,%d\n",a[i][2-i],*a[i],*(*(a+i)+i));
    for(i=0;i&lt3;i++)
        printf("%d,%d,%d\n",*pa[i],p[i],*(p+i));
}

本例程序中,pa是一个指针数组,三个元素分别指向二维数组a的各行。然后用循环语句输出指定的数组元素。其中a[i]表示i行0列元素值;((a+i)+i)表示i行i列的元素值;pa[i]表示i行0列元素值;由于p与a[0]相同,故p[i]表示0行i列的值;*(p+i)表示0行i列的值。读者可仔细领会元素值的各种不同的表示方法。

应该注意指针数组和二维数组指针变量的区别。这两者虽然都可用来表示二维数组,但是其表示方法和意义是不同的。

指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。在前面已经介绍过,通过指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为“单级间址”。而如果通过指向指针的指针变量来访问变量则构成“二级间址”。

从下图可以看到,name是一个指针数组,它的每一个元素是一个指针型数据,其值为地址。Name是一个数据,它的每一个元素都有相应的地址。数组名name代表该指针数组的首地址。 name+1是mane[i]的地址。name+1就是指向指针型数据的指针(地址)。还可以设置一个指针变量p,使它指向指针数组元素。P就是指向指针型数据的指针变量。

怎样定义一个指向指针型数据的指针变量呢?如下:

```java char **p;

  p前面有两个*号,相当于*(*p)。显然*p是指针变量的定义形式,如果没有最前面的*,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个*号,表示指针变量p是指向一个字符指针型变量的。*p就是p所指向的另一个指针变量。

  从下图可以看到,name是一个指针数组,它的每一个元素是一个指针型数据,其值为地址。name是一个数组,它的每一个元素都有相应的地址。数组名name代表该指针数组的首地址。name+1是mane[i]的地址。name+1就是指向指针型数据的指针(地址)。还可以设置一个指针变量p,使它指向指针数组元素。P就是指向指针型数据的指针变量。

![](/media/editor/3_20220623200939574674.gif)

如果有:

```java
p=name+2;
printf(&ldquo;%o\n&rdquo;,*p);
printf(&ldquo;%s\n&rdquo;,*p);

则,第一个printf函数语句输出name[2]的值(它是一个地址),第二个printf函数语句以字符串形式(%s)输出字符串“Great Wall”。

【例】使用指向指针的指针。

main(){
    char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer desighn"};
    char **p;
    int i;
    for(i=0;i<5;i++){
        p=name+i;
        printf("%s\n",*p);
    }
}

说明:p是指向指针的指针变量。

main函数的参数

前面介绍的main函数都是不带参数的。因此main 后的括号都是空括号。实际上,main函数可以带参数,这个参数可以认为是main函数的形式参数。C语言规定main函数的参数只能有两个,习惯上这两个参数写为argc和argv。因此,main函数的函数头可写为:main (argc,argv)

C语言还规定argc(第一个形参)必须是整型变量,argv(第二个形参)必须是指向字符串的指针数组。加上形参说明后,main函数的函数头应写为:

   main (int argc,char *argv[])

由于main函数不能被其它函数调用,因此不可能在程序内部取得实际值。那么,在何处把实参值赋予main函数的形参呢?实际上,main函数的参数值是从操作系统命令行上获得的。

全部评论(0)