您的位置:首页 > 编程语言 > C语言/C++

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

2016-12-29 19:03 756 查看
指针法访问数组

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

#include <stdio.h>
#include <stdlib.h>
/*
这个程序用于测试数组的实质
*/
#define n 5
int main()
{
int a
={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。

数组类型:

作为实参的数组:

声明时分配数组空间

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

作为形参的数组:

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

总结:作为形参的数组实际上就是指针变量,作为实参的数组实际上就是指针常量(数组的首地址的值)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐