今天萌新博主来给大家介绍C语言指针中,指针与数组的部分,在这一块也是非常难以理解的,闲言少叙,咱们开始吧!
大家都知道数组与指针有着紧密的联系,数组是由一组若干个元素构成,我们在访问数组的时候,采用的是循环体的方式,就是把数组的下标逐步的循环一次,这样我们就可以读出所有数组元素的值。那么重点来了,数组下标与地址的之间的关系,如果我们掌握好,大家学起来指针与数组就会非常的吃力,萌新博主与大家也是感同身受啊!
【数组的存储方式】
数组的三个特点:
1.数组有若干个元素
2.元素的数据类型必须相同
3.数组中的数据元素是有序排列的
arr1[4]={1,2,3,4}
arr2[4]={4,3,2,1}
//这两个数组不一样
数组元素占用的字节长度都是相等的;
指向数组的指针的初始值就是数组的首地址;
int arr[4]={1,2,3,4}
每个元素占用的字节长度为:sizeof(int);
若sizeof(int)==2,假设数组的第一个元素a[0]的地址为0x00000001,则数组的第二个元素a[1]的地址为0x00000003,则数组的第三个元素a[2]的地址为0x00000005
所以说数组中的数据元素是有序排列的,那我们在查找数组元素的时候,只需要查找到第一个元素的地址,就能按照顺序找到相应的元素了。
#include <stdio.h>
int main(void)
{
int arr[4]={1,2,3,4};
printf("arr[0]=%p\n",&arr[0]);
printf("arr[1]=%p\n",&arr[1]);
printf("arr[2]=%p\n",&arr[2]);
printf("arr[3]=%p\n",&arr[3]);
return 0;
}
运行结果:
那我们再来看指针的性质:
【指针与整数的运算】
指针加n=指针原来的值+nsizeof(指针所指变量类型)
指针减n=指针原来的值-nsizeof(指针所指变量类型)
int *p=NULL
p=&arr[0]
p0x000001
p+10x000001+1×sizeof(int)
p+20x000001+2×sizeof(int)
p+30x000001+3×sizeof(int)
#include <stdio.h>
int main(void)
{
int arr[4]={1,2,3,4};
int *p=NULL;//声明一个指针
int i=0;
p=&arr[0];//等价于p=arr;
printf("sizeof(int)=%d\n",sizeof(int));
printf("arr[0]=%p\n",&arr[0]);
printf("arr[1]=%p\n",&arr[1]);
printf("arr[2]=%p\n",&arr[2]);
printf("arr[3]=%p\n",&arr[3]);
for(i=0;i<=3;i++)
{
printf("p+%d=%p\n",i,p+i);//循环输出指针的值
}
}
结果:
sizeof(int)=4
arr[0]=0019FF20
arr[1]=0019FF24
arr[2]=0019FF28
arr[3]=0019FF2C
p+0=0019FF20
p+1=0019FF24
p+2=0019FF28
p+3=0019FF2C
我们不难发现,我们可以通过指针来访问一维数组
注意:以上对应关系的成立,指针p必须指向数组元素的首地址。
【那指针是如何访问数组的呢?】
#include <stdio.h>
int main(void)
{
int arr[8]={1,2,3,4,5,6,7,8};
int i=0;
int *p=NULL;
p=arr;//指针指向首地址
printf("arr[i]\n");
for(i=0;i<8;i++)
{
printf("arr[%d]=%d\n",i,arr[i]);
}
printf("p+i\n");
for(i=0;i<8;i++)
{
printf("p+%d=%d\n",i,*(p+i));
}
return 0;
}
【访问指针的四种方法】
1.直接下标法
有一个数组a[i]
int *p=a
等价于int *p=&a[0]
2.首地址自加法*(a+i)
数组名就是首地址:a等价于p
3.指针自加法*(p+i)
4.指针下标法p[i]
通过指针可以修改数组元素
int arr1[8]={省略};
int arr2[8]={省略};
int i=1;
int *p1=arr1;//使指针p指向arr的首地址
int *p2=arr2;
p1=p2;//改变指针指向
p++;//让指针指向下一个元素
注意:指针可以自加,但是数组名不可以自加,如arr1++
,同时数组名也不能相互赋值,如arr1=arr2
;
【用数组访问与用指针访问的区别】
1.访问方式不同
使用数组名访问时直接访问,使用指针是间接访问;
下标从0开始的原因:因为下标的实质是地址偏移量
2.占用空间不同
系统为arr分配了“sizeof(类型)*元素个数”个字节,给指针分配了“sizeof(类型)”个字节
换句话说使用指针可以降低空间复杂度,我们来看个程序:
#include <stdio.h>
int main(void)
{
int arr[8]={1,2,3,4,5,6,7,8};
int *p=NULL;
p=arr;//指针指向首地址
printf("sizeof(arr)=%d\n",sizeof(arr));
printf("sizeof(*p)=%d\n",sizeof(*p));
return 0;
}
很明显,系统为arr分配了32个字节,给指针分配了4个字节。
【让我们来回顾二维数组】
二维数组a[i][j],具有两个下标:行下标与列下标。
存储方式:先存储行下标,再存储列下标
即先在一个行下标内把a[0]列存满,再存a[1]列的元素。
a[0][0],a[0][1],a[0][2],a[0][3]
a[1][0],a[1][1],a[1][2],a[1][3]
【二维数组arr[][]的首地址有多种表达形式】
1.将二维数组看成元素是一维数组的元素
2.数组中第0行第0列的地址,&arr[0][0]
3.由于二维数组先存储行,所以第一行的第一个元素的地址,即&a[0]也是首地址
二维数组的元素个数:i×j
arr[2][3]={(1,2,3),(4,5,6)}
arr[2][3]={arr1[3],arr2[3]}
二维数组的首地址:&arr[0][0],&arr[0]
二维数组的数组名,不是首地址
int arr[i][j];
int *p=&arr[0][0];
此时p指向第一行第一个元素,即arr[0][0],而列指针是arr[i],指向第(i+1)列的元素。
不允许操作:p=arr或声明的时候使用int *p=arr(arr会指向一整行的地址)
那我们从a[0][0]跳到a[2][2],如何操作?
#include <stdio.h>
int main(void)
{
int a[3][3]={1,2,3,4,5,6,7,8,9};
int i=0;
int j=0;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
printf("a[%d][%d]=%d\t",i,j,a[i][j]);
}
printf("\n");
}
}
a[0][0]是第一行第一列的元素 ,p=&[0][0]
a[2][2]是第三行第三行的元素
有一个指针p指向二维数组的首地址:&a[0][0]
指针的偏移量为:p+行数×行下标+列下标;
我们来看个例子:
#include <stdio.h>
int main(void)
{
int a[3][3]={1,2,3,4,5,6,7,8,9};
int i=0;
int j=0;
int *p=NULL;
p=&a[0][0];
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
printf("a[%d][%d]=%d\t",i,j,a[i][j]);
}
printf("\n");
}
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
printf("p+N*%d+%d\t",i,j,*(p+3*i+j));
}
printf("\n");
}
printf("p+N*i+j\n");
}
于是我们得到二维数组的四种表现形式,如下图
【二维数组的矩阵转置】
#include <stdio.h>
int main(void)
{
int a[3][3]={1,2,3,4,5,6,7,8,9};
int i=0;
int j=0;
int *p=NULL;
p=&a[0][0];//p指向首地址
printf("转置前:\n");
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
printf("a[%d][%d]==%d\t",i,j,*(p+3*i+j));
}
printf("\n");
}
printf("转置后:\n");
for(j=0;j<3;j++)
{
for(i=0;i<3;i++)
{
printf("a[%d][%d]==%d\t",i,j,*(p+3*i+j));
}
printf("\n");
}
}
在for循环中,我们只需要把i换成j,j换成i就可以实现矩阵的转置,而不需要中间变量。
【二维数组形参的形式】
另外当我们用数组作为函数的形式参数,我们需要给函数传递一个数组首地址和一个数组长度
【将数组和数组长度分别作为形参】
void function(int array[],int size)
void function(int *arry,int size)
我们再来一个例子
【在一个升序数组中查找大于3的元素】
#include <stdio.h>
void printarray(int *arr,int n)
{
int i=0;
for(i=0;i<n;i++)
{
printf("%4d",arr[i]);
}
printf("\n");
}
int main(void)
{
int arr[9]={1,2,3,4,5,6,7,8,9};
int *p=NULL;
p=arr;
while(*p<3)//while循环对指针p进行取值
{
p++;//如果p的值<3,则将地址下移,再继续与3比较
}
printarray(p,9-(p-arr));//经过3次自加后,指针由指向元素1到指向元素4
//那么指针p将以arr[3]为首地址传到函数中,(p-arr)为arr[3]与首地址arr[0]的差值,也就之间的个数
return 0;
}
【用二维数组形参函数来完成矩阵倒置】
#include <stdio.h>
void function1(int *p,int M,int N)
{
int i,j;
for(i=0;i<M;i++)
{
for(j=0;j<M;j++)
{
printf("%d",*(p+i*N+j));//也可以用*p++:输出当前值,地址下移
}
printf("\n");
}
}
void function2(int *p,int M,int N)
{
int i,j;
for(j=0;j<M;j++)
{
for(i=0;i<M;i++)
{
printf("%d",*(p+i*N+j));//*(p+i*N+j)是指针偏移量法
}
printf("\n");
}
}
//注意形参M,N是行列长度,不是下标
int main(void)
{
int arr[3][3]={1,2,3,4,5,6,7,8,9};
int *p=NULL;
p=&arr[0][0];
function1(p,3,3);
printf("\n");
function2(p,3,3);
return 0;
}
二维数组形参的其他形式
数组首地址表示法