学习笔记---指针法访问数组、数组的实质、数组/指针作为函数参数

来源:互联网 发布:网络红猫包子脸猫 编辑:程序博客网 时间:2024/06/02 04:24

指针法访问数组


首先,通过一个小程序来初步窥探数组的实质:

#include <stdio.h>#include <stdlib.h>/*这个程序用于测试数组的实质*/#define n 5int main(){    int a[n]={123,5,9,11,33};    printf("%d\n",a);//如果直接输出数组名字所代表的值    printf("%x\n",a);//如果用16进制输出数组名字所代表的值    printf("%x\n",&a[0]);//如果用16进制输出数组第一个元素的地址    printf("%d\n",a[0]);    printf("%d\n",*a);//如果对数组的名字使用取指针运算。。    return 0;}
结果:


解析:

1.当输出数组名所代表的值时,发现输出的不是数组内所有的元素,甚至不是数组内任何一个元素。

2.用十六进制输出数组名所代表的值和数组第一个元素的地址值,发现他们是相同的。

3.对数组名使用取指针运算,得到的结果就是数组第一个元素的值。

4.综上所述,可以得出结论:数组名代表的是数组第一个元素的地址值。


数组和指针


已知:数组名代表数组的起始地址(数组首元素的地址)

因此:

当  int a[10],*p;

p=&a[0];等价于p=a;

由此,进行以下测试:

代码示例:

#include <stdio.h>#include <stdlib.h>/*这个程序用来测试数组的本质核心:数组的本质是在内存空间中定义一段连续的存储单元,数组名等同于指向数组首元素的指针*/int main(){    int a[10]={1,3,5,7,9,11,13,15,17,19},*p,i;//定义一个整型的数组和指针    p=a;//使得指针指向数组中第一个元素的地址    /*用传统的方式输出数组中的元素*/    printf("output1:\t");    for(i=0;i<10;i++)        printf("%d ",a[i]);    /*因为p是数组中第一个元素的地址,所以。。*/    printf("\noutput2:\t");    for(i=0;i<10;i++)        printf("%d ",*(p+i));    /*因为a也是数组中第一个元素的地址,所以。。*/    printf("\noutput3:\t");    for(i=0;i<10;i++)        printf("%d ",*(a+i));    /*更激进的尝试。。。*/    printf("\noutput:4\t");    for(i=0;i<10;i++)        printf("%d ",p[i]);    return 0;}
结果:


解析:

1.p+1代表的是p加一个内存单元的值,如果p代表数组第一个元素的地址,那么p+1代表的就是数组第二个元素的地址。

2.验证得出,以上4中方法都能输出数组中的值。

3.  [  ] 这种形式的常用于数组之后的符号,其实是一种运算符。


注1:

运算符优先级运算符功能结合方式    1   [   ]   数组    由左向右

该运算符的运算实质:

1.按(a+i*d)计算数组元素的地址(d为数据类型占用的字节数,如int a[] 则d的值为int型代表的4字节)

2.取出地址所指向的单元的值


注2:

对以上内容的图解:



结论:

引用数组元素有两种方法

1.下标法:

示例:a[i]  p[i]

特点:简洁明了


2.指针法:

示例:*(a+i)   *(p+i)

特点:效率高


指针法作为C语言中特色的数组使用方法,虽然比起下标法更难驾驭,但为了写出效率更高的代码。这是必须要学会的东西——指针法标准写法:

p=a;while(p<a+10)    printf("%d",*p++);

指针的运算


以上指针法调用数组的代码中,大量运用了指针的加法(p+1、p++)

这里,将指针的加减等运算法则做一个深入的解析


指针变量加减一个整数:

例如:

p++,p--,p+i,p-i,p+=i,p-=i;

结果为p的值加减一个p所代表的数据类型的存储单元

如int *p;则p-1为p的值减去int所代表的4字节存储单元,而double *p;则p-1为p的值减去double 所代表的8字节存储单元。


两个指针变量相减:

例如:

int a[10];

int *p2=&a[4],*p1=a;

此时:

p2-p1=(a+4)-(a+1)=4-1=3

所以:

两个指针变量相减,结果为两指针之间的元素个数。


两个指针相加:

例如:

int a[10];

int *p2=&a[4],*p1=a;

此时:

p2+p1=(a+4)+(a+1)=2*a+5

注意:

因为2*a代表的地址值可能是没有被注册使用的,或者已经被其他程序使用的内存空间!所以贸然调用可能会出现不可预知的后果!

所以:

2*a+5这个地址值没有使用价值(这也可以看成一种野指针),我们规定两个指针变量不能相加


指针变量的比较


有意义的比较——指向同一个数组的两个指针进行比较:

例如:

int a[10];

int *p2=&a[4],*p1=a;

此时,p2>p1为真(实为比较它们的地址值)


无意义的比较——指向不同数组的两个指针进行比较:

例如:

int a[10],b[10];

int *p2=a,*p1=b;

此时,p2>p1可能为真,也可能为假(因为a和b的数组代表的内存空间每次运行都是不同的)


不同数据类型的指针的比较和运算:


当希望比较两个不同数据类型的指针的大小时,需要进行强制类型转换

例如:

/*以下代码只是为了示范,无实际使用价值*/int a=2,*p1;double b=3.0,*p2;p1=&a,p2=&b;if(p1>(int)p2){.....}

指针和NULL:

指针可以被赋值为NULL(所谓的空指针),也可以和NULL进行等值判断

例如:

char *p5=NULL;if(p5==NULL)    ....


指向函数的指针


格式:函数类型 (*指针变量名)(函数形参表);

例如:

int (*p)(int,int);

此时,p就是一个指向函数的指针。

注意:

因为函数是保存在内存的程序区(而非静态/动态数据区)中的,所以当p指向函数时,p指向的就是程序区中内存空间!这意味着通过指针,我们甚至可以操控程序区中的内容!


使用方法:

int (*p)(int,int);int max(int,int);p=max;

如上,可以看到p=max这一句代表的意义:正如数组名代表着数组的起始地址,函数名也代表这函数的起始地址!


指针、数组和函数


数组名作为函数实参


示例:

int f(int array[],int n);int main(){    int a[10],b;    ....    b=f(a,5)    ....}

数组作为函数参数和变量作为函数参数有很大的不同,利用以下代码说明


代码示例:

#include <stdio.h>#include <stdlib.h>/*这个程序用来测试数组名作为函数参数的机制*/void fun(int a[],int b);int main(){    int c[]={2,4},d=10;    printf("%d\t%d\t%d\n",c[0],c[1],d);    fun(c,d);    printf("%d\t%d\t%d\n",c[0],c[1],d);    return 0;}void fun(int a[],int b){    int i;    for(i=0;i<2;i++)    {        a[i]*=10;    }    b*=10;}
结果:


解析:

如上,在自定义函数中分别对形式参数b和数组a[]的值进行修改,结果对a的修改影响到了主函数中的c,而对b的修改却没有影响到d。


参数的传递

地址传递:

实质:

将地址作为实际参数传递至自定义函数中

解析:

如上,用fun(c,d)调用函数fun时,实际参数列表中的c是c数组的名字,而c数组的名字代表的是c数组所占内存空间的起始地址!

所以fun中的a数组被赋值时实际上是:a=c;参照上方指针法调用数组的内容,很容易能理解:a作为一个指向c数组的指针被使用了!

当fun中对a数组进行操作时,实际上是通过c数组的指针直接操作c数组。这就是为什么fun中对c的操作能被保留




值传递:

实质:

将值作为实际参数传递至自定义函数中

解析:

在函数初步篇已解析,不做赘述。


代码示例:

#include <stdio.h>#include <stdlib.h>/*这个程序用来演绎冒泡排序算法原理:1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。3.针对所有的元素重复以上的步骤,除了最后一个。4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。冒泡排序法的精巧之处在于:一轮比对完之后,就能获得一组数据中最大的数,然后下一轮获得次大的数,如此重复。同时,相比于选择排序法的一轮比对完只交换一对数字。冒泡排序几乎每次比对都交换一对数字。这使得效率大大提高。如不理解可以通过这里的几段舞蹈直观的认识:http://dapenti.com/blog/more.asp?name=xilei&id=65524*/void bubblesort(int [],int n);//核心算法int main(){    int i;    int d[10]={51,29,34,11l,547,2,43,66,20,5};    bubblesort(d,10);    for(i=0;i<10;i++)        printf("%d ",d[i]);    return 0;}void bubblesort(int a[],int n){    int i,j,t;    for(j=0;j<n-1;j++)    {        for(i=0;i<n-j-1;i++)        {            if(a[i]>a[i+1])            {                t=a[i];                a[i]=a[i+1];                a[i+1]=t;            }        }    }    return;}
结果:


解析:

这里再次放出冒泡排序法的代码,可以看出:bubblesort()函数就是利用了数组作为函数参数时的特性,使得在自定义函数中对数组做的排序到主函数中依然生效。


指针作为函数参数


如果将上述冒泡排序代码进行修改:

#include <stdio.h>#include <stdlib.h>/*这个程序用来演绎冒泡排序算法原理:1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。3.针对所有的元素重复以上的步骤,除了最后一个。4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。冒泡排序法的精巧之处在于:一轮比对完之后,就能获得一组数据中最大的数,然后下一轮获得次大的数,如此重复。同时,相比于选择排序法的一轮比对完只交换一对数字。冒泡排序几乎每次比对都交换一对数字。这使得效率大大提高。如不理解可以通过这里的几段舞蹈直观的认识:http://dapenti.com/blog/more.asp?name=xilei&id=65524*/void bubblesort(int *p,int n);//核心算法int main(){    int i;    int d[10]={51,29,34,11l,547,2,43,66,20,5};    bubblesort(d,10);    for(i=0;i<10;i++)        printf("%d ",d[i]);    return 0;}void bubblesort(int *p,int n)//将形参中的数组换成了指针{    int i,j,t;    for(j=0;j<n-1;j++)    {        for(i=0;i<n-j-1;i++)        {            if(*(p+i)>*(p+i+1))            {                t=*(p+i);                *(p+i)=*(p+i+1);                *(p+i+1)=t;            }        }    }    return;}
结果:


解析:

1.函数依然正常运行,这原因应当很容易理解。只是把实参中的数组地址赋值给形参中的指针而已。

2.由上可以推断,形参和实参的定义可以是数组、数组,数组、指针,指针、数组,指针、指针等组合


核心思想:

数组即指针


指针分类:

指针常量

例如:

int main(){    int a[5]={0,1,2,3,4};}
这里的a看似是数组,其实就是指针,是指针常量

注1:因为a代表的是数组的起始地址,所以a的值必须是确定且固定的的(为了防止内存的调度出错),即:a虽然也是指针,但a是指针常量

注2:这里如果使用a++或a+=1;等代码将出错,因为a代表的虽然是指针。但是一种常量指针,而非变量。类比数据类型的变量和常量之别,我们无法给常量赋值


指针变量

例如:

void f(int arr[],int n);int main(){    int a[5]={0,1,2,3,4}    f(a,n);    return 0;}void f(int arr[],int n){    arr+=3;    printf("%d\n",arr[1]);}
这样的函数并不会出错,因为在f中,arr看似是一个数组名(指针常量)实际上却是一个指向a函数的指针变量

注1:当执行f函数时,先给arr创建内存空间,然后将传入的a的地址赋给arr。

注2:作为变量,arr的值是可以被改变的。所以arr+=3是合法的。最后这段程序的输出将是4。


数组类型:

作为实参的数组:

声明时分配数组空间

其名字代表一个不可改变的固定的地址


作为形参的数组:

调用时分配指针空间接受传递的地址


总结:作为形参的数组实际上就是指针变量作为实参的数组实际上就是指针常量(数组的首地址的值)





0 0
原创粉丝点击