数组与指针

1、数组元素的指针

  个变量有地址,一个数组包含若干元素,每个数组元紫都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素(把某元素的地址放到一个指针变量中)。所谓数组元索的指针就是数组元素的地址。引用数组元素可以用下标法(如a[3]),也可以用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。

/*定义一个指向数组元素的指针变量的方法*/
int a[10];  //定义a为包含10个整型数据的数组
int *p;     //定义p为指向整型变量的指针变量
/*应当注意:如果数组为int型,则指针变量的基类型也为int型,下面对该指针变量赋值*/
p=&a[0];   //把a[0]元素的地址赋给指针变量p
/*C语言规定,数组名代表数组中首元素的地址*/
p=&a[0] <=> p=a   //等价关系
/*注意数组名a不代表整个数组,上述p=a的作用是把a数组元素的首地址赋给指针变量p,而不是将数组a各元素的值赋给p*/

2、通过指针引用数组元素

  假设p已定义为一个指向整型数据的指针变量,并已给它赋了一个整型数组元素的地址,使它指向某一个数组元素。如果有赋值语句:*p=1;  表示将1赋给ρ当前所指向的数组元素。按C语言的规定:如果指针变量p已指向数组中的一个元素,则p+1指向同数组中的下一个元素而不是将P的值(地址)简单地加1。例如,数组元素是float型,每个元素占4个字节,则p+1意味着使p的值(是地址)加4个字节,以使它指向下一元素。p+1所代表的地址实际上是p+1*d,d是一个数组元素所占的字节数(在TurboC++中,对int型,d=2;对float和long型,d=4;对char型,d=1。在VisualC++ 6. 0中,对int.long和float型,d=4;对char型,d=1)。

  如果p的初值为&.a[0],则:

  (1) p+i和a+i就是a[i]的地址或者说,它们指向a数组的第i个元素,这里需要特别注意的是a代表数组首元素的地址,a+i也是地址,它的计算方法同p+i,即它的实际地址为a+iXd。例如,p+9 和a+9的值是&a[9],它指向a[9]。

  (2) *(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。例如*(p+5)或*(a+5)就是a[5]。即*(p+5)、*(a+5)、a[5]三者等价。实际上,在编译时,对数组元素a[i]就是按*(a+i)处理的,即按数组首元素的地址加上相对位移量得到要找的元素的地址,然后找出该单元中的内容。若数组a的首元素的地址为1000,设数组为float 型,则a[3]的地址是这样计算的:1000+3X4= 1012,然后从1012地址所指向的float型单元取出元素的值,即a[3]的值。可以看出,[ ]实际上是变址运算符,即将a[i]按a+i计算地址,然后找出此地址单元中的值。

  (3) 指向数组的指针变量也可以带下标,如:p[i]与*(p+i)等价。引用一个数组元素有两种方法, ①. 下标法,如a[i]形式。②. 指针法,如*(a+i)或*(p+i)。其中a是数组名,p是指向数组元素的指针变量,其初值:p=a。

//输出数组中的全部元素
/*假设有一个a数组,整型,有10个元素。要输出各元素的值有以下三种方法:*/
//1、下标法
#include<stdio.h>
int main(){
    int a[10];
    for(int i=0;i<10;i++)
        scanf("%d",&a[i]);
    printf("
下标法输出:
");
    for(int i=0;i<10;i++)
        printf("%d ",a[i]);
    printf("
");
    return 0;
} 
//通过数组名计算数组元素地址,找出元素的值 
#include<stdio.h>
int main(){
    int a[10];
    for(int i=0;i<10;i++)
        scanf("%d",&a[i]);
    printf("
");
    for(int i=0;i<10;i++)
        printf("%d ",*(a+i));
    return 0; 
} 
//用指针变量指向数组元素
#include<stdio.h>
int main(){
    int a[10];
    int *p;
    for(int i=0;i<10;i++)
        scanf("%d",&a[i]);
    for(p=a;p<a+10;p++)
        printf("%d ",*p);
    return 0;
} 
 对3种方法的比较:
  (1) 对上述的第①和第②种方法执行效率是相同的。C编译系统是将a[i]转换为*(a+i)处理的,即先计算元素地址。因此用第①和第②种方法找数组无素费时较多。
  (2) 第③种方法比第①、第②方法快,用指针变量直接指向元素,不必每次都重新计算地址,像p+ +这样的自加操作是比较快的。这种有规律地改变地址值(p+ +)能大大提高执行效率。
  (3) 用下标法比较直观,能直接知道是第几个元素。例如,[5]是数组中序号为5的元素(注意序号从0算起)。用地址法或指针变量的方法不直观,难以很快地判断出当前处理的是哪一个元素。例如,上述第③种方法所用的程序,要仔细分析指针变量p的当前指向,才能判断当前输出的是第几个元素。
在使用指针变量指向数组元索时,有以下几个问题要注意:
  ① 可以通过改变指针变量的值指向不同的元素。例如,上述第③种方法是用指针变量p来指向元素,用p+ +使p的值不断改变从而指向不同的元素,这是合法的。如果不用p而使数组名a变化(例如,用a++)行不行呢?假如将上述第③种方法中的程序的最后两行改为:
for(p=a;a<(p+ 10);a+ +)
  printf("%d", *a);
是不行的。因为数组名a代表数组首元素的地址,它是一个指针常量,它的值在程序运行期间是固定不变的。既然a是常量,所以a+ +是无法实现的。
  ② 要注意指针变量的当前值,比如下面的例子:
//通过指针变量输出a数组的10个元素
#include<stdio.h>
int main(){
    int *p,a[10];
    p=a;
    for(int i=0;i<10;i++)
        scanf("%d",p++);
    printf("
");
    for(int i=0;i<10;i++,p++)
        printf("%d ",*p);
    printf("
");
    return 0;
} 

运行结果如下:   

 

显然输出的数值并不是a数组中各元素的值。原因是指针变量的初始值为a数组首元素(即a[0])的地址,但经过第一个for循环读人数据后,p已指向a数组的末尾,因此,在执行第二个for循环时,p的起始值不是&a[0]了,而是a+10。由于执行第二个for 循环时,每次要执行p++,因此p指向的是a数组下面的10个元素,而这些存储单元中的值是不可预料的。解决这个问题的办法,只要在第二个for循环前加上一个赋值语句:p=a; 使p的初始值回到&a[0],这样结果就对了,程序如下:

#include<stdio.h>
int main(){
    int *p,a[10];
    p=a;
    for(int i=0;i<10;i++)
        scanf("%d",p++);
    printf("
");
    p=a; //使p回到初始值
    for(int i=0;i<10;i++,p++)
        printf("%d ",*p);
    printf("
");
    return 0;
} 

运行结果如下:

  ③ 从上例可以看到,虽然定义数组时指定它包含10个元素,并用p指向某一数组元素,但是实际上指针变量p可以指向数组以后的内存单元。如果引用数组元素a[10],C编译程序并不认为非法,系统把它按*(a+10)处理,即先找出(a+10)的值(是一个地址),然后找出它指向的单元的内容。这样做虽然是合法的(在编泽时不出错),但应避免出现这样的情况,这会使程序得不到预期的结果。这种错误比较隐蔽,初学者往往难以发现。在使用指针变量指向数组元索时,应切实保证指向数组中有效的元素。注意:*(p++)与*(+ +p)作用不同。前者是先取*p值,然后使p加1。后者是先使p加1,再取*p。若p初值为a(即&a[0]),则*(p++)为a[0],而*(++p)为a[1]。++(* p)表示P所指向的元素值加1,如果p=a,则++(*p)相当于++a[0]。若a[0]=3,则在执行++(*p)(即++a[0])后,a[0]的值为4。注意:是元素值a[0]加1.而不是指针p的值加1。

3、用数组名作函数参数

void main(){
    void f(int arr[],int n);
    int array[10];
        ....
    f(array,10);
        ....
}
void f(int arr[],int n){
    ....
}

  array为实参数组名,arr为形参数组名。当用数组名作参数时,如果形参数组中各元素的值发生变化,实参数组中的值随之变化。先看数组元素作实参时的情况,如果已定义个函数,其原型为:

void swap(int x,int y);

假设函数的作用是 将两个形参(x.y)的值交换,今有以下的函数调用:

swap(a[1].,a[2]);

用数组元素a[1]、a[2]作实参的情况与用变最作实参时一样,是“值传递”方式,将a[1]和a[2]的值单向传递给x和y。当x和y的值改变时a[1]和a[2]的值并不改变。再看用数组名作函数参数的情况。我们知道,实参数组名代表该数组首元素的地址,而形参是用来接收从实参传递过来的数组首元素地址的。因此,形参应该是一个指针变量(只有指针变量才能存放地址)。实际上,C编译都是将形参数组名作为指针变量来处理的。例如,上面给出的函数f的形参是写成数组形式的:

f(int arr[ ],int n);

但在编译时是将arr按指针变量处理的,相当于将函数f的首部写成:

f(int *arr, int n)

以上两种写法是等价的。在该函数被调用时,系统会建立一个指针变量arr,用来存放从主调函数传递过来的实参数组首元素的地址。当arr接收了实参数组的首元素地址后,arr就指向实参数组首元素,也就是指向array[0]。因此,* arr就是array[0]。arr+1指向array[1],arr+2指向array[2],arr+3指向array[3]。也就是说,* (arr+1)、* (arr+2)、* (arr+3)分别是array[1]、array[2]、array[3]。我们知道,* (arr+i)和arr[i]是无条件等价的。因此,在调用两数期间,arr[0]和* arr以及array[0]都代表数组array序号为0的元素,依此类推,arr[3]、* (arr+ 3)、array[3]都代表array数组序号为3的元素。

常用这种方法通过调用一个函数来改变实参数组的值。

  需要说明的是C语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为的数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。在用数组名作为函数实参时,既然实际上相应的形参是指针变量,为什么还允许使用形参数组的形式呢?这是因为在C语言中用下标法和指针法都可以访问一个数组(如果有一个数组a,则a[i]和* (a+i)无条件等价),用下标法表示比较直观,便于理解。因此许多人愿意用数组名作形參,以便与实参数组对应。从应用的角度看,用户可以认为有一个形参数组它从实参数组那里得到起始地址,因此形参数组与实参数组共占同一段内存单元,在调用函数期间,如果改变了形参数组的值,也就是改变了实参数组的值。当然在主调函数中可以利用这些已改变的值。应该注意:实参数组名代表一个固定的地址或者说是指针常量,但形参数组并不是一个固定的地址值,而是作为指针变量,在函数调用开始时,它的值等于实参数组首元素的地址,在函数执行期间,它可以再被赋值。例如:

void f(int arr[],int n){
    printf("%d
",*arr);//输出array[0]的值 
    arr=arr+3;
    printf("%d
",*arr);//输出array[3]的值 
} 
//实现将数组a中n个整数按相反的顺序存放
#include<stdio.h>
void stran(int x[],int n){
    int temp,i,j;
    int m=(n-1)/2;
    for(i=0;i<=m;i++){
        j=n-1-i;
        temp=x[i];
        x[i]=x[j];
        x[j]=temp;
    }
}
int main(){
    int a[9]={1,2,3,4,5,6,7,8,9};
    printf("初始数组: ");
    for(int i=0;i<9;i++)
        printf("%d ",a[i]);
    stran(a,9);
    printf("
对换后的数组: "); 
    for(int i=0;i<9;i++)
        printf("%d ",a[i]);
    return 0; 
}

运行结果:

  对这个程序可以作一些改动。将函数stran中的形参x改成指针变量。实参为数组名a,即数组a首元素的地址,将它传给形参指针变量x,这时x就指向a[0]。x+m是a[m]元素的地址。设i和j以及p都是指针变量,用它们指向有关元素。i的初值为x,j的初值为x+n-1,使*i与*j交换就是使a[i]与a[j]交换。

#include<stdio.h>
void stran(int *x,int n){
    int m,*i,*j,temp;
    m=(n-1)/2;
    i=x;
    j=x+n-1;
    for(;i<=x+m;i++,j--){
        temp=*i;
        *i=*j;
        *j=temp;
    }
}
int main(){
    int a[9]={1,2,3,4,5,6,7,8,9};
    printf("初始数组: ");
    for(int i=0;i<9;i++)
        printf("%d ",a[i]);
    stran(a,9);
    printf("
对换后的数组: "); 
    for(int i=0;i<9;i++)
        printf("%d ",a[i]);
    return 0; 
}

运行结果和上一程序一样。

用实参指针变量改写上述程序,代码如下:

#include<stdio.h>
void inv(int *x,int n){
    int temp,*i,*j;
    int m=(n-1)/2;
    i=x;
    j=x+n-1;
    for(;i<=x+m;i++,j--){
        temp=*i;
        *i=*j;
        *j=temp;
    }
    return ; 
}
int main(){
    int i,arr[10],*p=arr;
    printf("初始数组:
");
    for(i=0;i<10;i++,p++)
        scanf("%d",p);
    printf("
");
    p=arr;
    inv(p,10);
    printf("交换后数组:
");
    for(p=arr;p<arr+10;p++){
        printf("%d ",*p);
    }
    printf("
");
    return 0;
}

运行结果:

注意:上面的main函数中的指针变量p是有确定值的。如果在main函数中不设数组,只有指针变量,就会出错,假如主函数做如下修改,编译时会出错,原因是指针变量arr没有确定值,谈不上指向哪个变量。

int main(){
    void f(int *x,int n);
    int *arr;
    for(int i=0;i<10;i++)
        scanf("%d",arr+i);
    f(arr,10);
    for(i=0;i<10;i++)
        printf("%d ",*(arr+i);
    return 0;
}
//用选择法对10个整数按由大到小的顺序排序
#include<stdio.h>
void sort(int x[],int n){
    int i,j,k,t;
    for(i=0;i<n-1;i++){
        k=i;
        for(j=i+1;j<n;j++)
            if(x[j]>x[k])
                k=j;
        if(k!=i){
            t=x[i];
            x[i]=x[k];
            x[k]=t;
        }
    }
}
int main(){
    int *p,i,a[10];
    p=a;
    for(i=0;i<10;i++,p++)
        scanf("%d",p);
    printf("
");
    p=a;
    sort(p,10);
    for(p=a;p<a+10;p++)
        printf("%d ",*p);
    return 0;
} 

为了便于理解,函数sort中用数组名作为形参,用下标法引用形参数组元素。这样的程序很容易看懂。当然也可以改用指针交量这时sort 函数的首部可以改为:

sort(int *x,int n)

其他不改,程序运行结果不变。可以看到,即使在函数sort中将x定义为指针变量,在函数中仍可用x[i]、x[k]这样的形式表示数组元素,它就是x+i和x+k所指的数组元素。上面的sort函数等价于:

void sort(int *x,int n){
    int i,j,k,t;
    for(i=0;i<n-1;i++){
        k=i;
        for(j=i+1;j<n;j++){
            if(*(x+i)>*(x+j))
                k=j;
        }
        if(k!=j){
            t=*(x+i);
            *(x+i)=*(x+j);
            *(x+j)=t; 
        }
    }
}
原文地址:https://www.cnblogs.com/geziyu/p/9670237.html