C编译程序用数组名存放数组在内存中的首地址。
指针访问内存比变量名访问速度更快,数组采用指针加减1的方式访问数组,增加了访问内存的灵活性。
指针与一维数组
- 指针移动方式访问数组元素:
int a[5], *p; // a存放了数组的首地址 p=a; // 数组首地址赋给p p++; // 向下移动4个字节
指针与数组名都是地址,因此可以混合使用访问数组元素。
int *pa=a; 和 int *pa=&a[0];(// a的首地址)两者完全等价!
pa和a可以混合使用的图片说明,前面一列是地址(a和pa都表示地址),加*表示里面的元素。指针还可以移动,如图2。
- 数组元素的等价引用形式:
a[i]
pa[i] // pa和a可以混合使用,pa和a都表示首地址
*(a+i) // 表示地址的前面加一个*,就代表该地址里面的元素
*(pa+i) // pa和a混合使用
- 对应的数组元素的地址表现形式:
&a[i] // 加取地址符号就表示地址
&pa[i]
(a+i) // *和&互为逆运算,因此前面没有了符号
(pa+i)
注意(区别):
数组名是指针常量,不是指针变量,因此不能给数组名赋值!但指针可以。例如:pa=pa+1这是可以的,但是不能有a=a+1这种操作,任何修改数组名的操作都是错误的!
程序1:用指针访问数组,计算数组的元素之和。
/* 用指针访问数组,计算数组的元素之和 */ #include<stdio.h> #include<stdlib.h> int main() { int iarray[10]={0,2,4,6,8,10,12,14,16,18},*p=iarray; int i,sum=0; for(i=0;i<10;i++) { sum+=*p; p++; // sum+=*iarray; iarray++是错误的!数组名不能进行自增操作! } printf("sum is %d\n",sum); system("pause"); return 0; }
注意:p++并不是简单的加1,而是加了它的基类型所占的字节数,即移动了sizeof(int)=4个字节。sizeof()宏定义可获取所占字节数。
指针快速访问数组,但也只能逐个处理,不能一次性处理所有元素。
- 用指针进行数组元素的输入输出(同样只能一次处理一个元素,用for循环):
for(i=0;i<4;i++)
{
scanf_s(“%d”,&a[i]); // 输入
printf(“%d”,a[i]); // 输出
}
for(p=a;p<(a+4);p++)
{
scanf_s(“%d”,p); // 用指针输入,p本身是个地址,不用再加取地址符号
printf(“%d”,*p); // 用指针输出
}
- 访问数组元素的方法(五种,可混合使用):
void main() // 定义和初始化
{
int sum1,sum2,sum3,sum4,sum5;
int iarray[]={1,3,2,5,2,56,34,2,7,60};
int *iptr;
int size,n;
sum1=sum2=sum3=sum4=sum5=0;
size=sizeof(iarray)/sizeof(*iarray);
}
第一种:数组名和下标的方式
for(n=0;n<size;n++)
{
sum1+=iarray[n];
}
第二种:通过移动指针,逐个元素的访问
iptr=iarray;
for(n=0;n<size;n++)
{
sum2+=*iptr;
}
第三种:通过指针和间接地址的方式,访问数组的所有元素
iptr=iarray;
for(n=0;n<size;n++)
{
sum3+=*(iptr+n);
}
第四种:通过指针,引用下标的方式,按照数组的方式访问数组所有元素
iptr=iarray;
for(n=0;n<size;n++)
{
sum4+=iptr[n];
}
第五种:通过数组名加偏移量的方式
for(n=0;n<size;n++)
{
sum5+=*(iarray+n);
}
- 求数组的首地址(两种):
第一种:通过数组名获得首地址(数组名)
第二种:通过数组的第一个元素来获得首地址(首个元素地址),这里指针变量p是通过数组名获得指向数组a的第一个元素。
- 通过数组首地址访问数组元素的方式:
第一种:直接访问(数组名+下标变量,如a[1])
第二种:指针加偏移量的间接地址访问,如*(p+i)
第三种:用数组名做地址值(指针值)的间接地址访问,如与a[i]等价的语句为*(a+i)
第四种:将指针变量看做数组名,再加下标变量,如p[i]。
总结:
数组名是表示数组元素的连续空间的首地址,可以看做一个“常量”指针,它的值不能被修改,即不能修改其指向。数组元素可通过 下标法来引用,也可通过指针来引用,还可以混合使用。同样地,指针也可以当做一个数组名来使用。(方便、灵活、易混淆、易出错)
指针与二维数组
C语言是按照行优先的原则存储数组的。
int a[2][3];
这样理解:我们把每一行看成一个数据元素,那么就有a[0]和a[1]两个元素(两个一维数组),地址分别为a+0和a+1。
注意:这里a[i]不表示元素,还是表示地址,是这一行一维数组的首地址,那么a[i]+j就表示第i行第j个元素的地址。即a[i]+j和&a[i][j]完全等价(都表示第i行第j列元素的地址),a[i][j]和*a[i]+j完全等价(都表示第i行第j列的元素)。
- 二维数组与行:
(1) a代表二维数组的首地址,也是第0行的首地址;
(2) a+i代表第i行元素的首地址,即&a[i];
(3) a+i或&a[i]表示行地址,每次加一表示会移动到下一行;
(4) *(a+i)即a[i],不代表具体元素,依然是个地址(第i行的首地址)。
- 二维数组与列:
(1) 对于二维数组,第i个元素依然是一个一维数组,即*(a+i)即a[i]依然是一个地址;
(2) 对于每一个一维数组a[i]或*(a+i),a[i]+j或*(a+i)+j表示该一维数组a[i]中第j个元素的地址,即&a[i][j];
(3) *(a[i]+j)或*(*(a+i)+j)代表一维数组a[i]中第j个元素的值,即a[i][j];
(4) a[i]或*(a+i)表示列地址,每次加一会移动一个元素。
- 二维数组的理解(行地址、列地址):
例:int a[2][4];
(1) 有两行,a[0]和a[1]分别为两行的首地址;
(2) 每一行有四个元素;
(3) 对于每一行的地址是a+0,a+1,或&a[0],&a[1];
(4) a+0或&a[0]对这一行是首地址,因为我们叫它“一维数组名a[0]”,后面的中括号就是该一维数组名的下标。如下图:
问题:指针增一怎么判断是行增一还是列增一?
答:由指针类型决定!
- 行指针、列指针
1. 行指针:
int a[3][4];
int (*p)[4]; // 定义一个行指针,指向一个数组,有四个元素。
p=a; // 用行地址初始化
理解:p—>*—>[4]—>int。先表示它是一个指针,然后让它指向一个长度为4的数组,最后说明是整型指针。因为后面括号是4,因此每次移动p++就移动4个整数,就是一行。即指针移动多少,与我们定义时指向的数组长度有关系。
区分:int *p[4]; // 长度为4的指针数组!与行指针不同!
规律:定义行指针一定要先把(*p)用括号括起来!后面跟的中括号[4]表示每移动一次,移动多少个元素,即二维数组的列数是多少!(指针数组定义见后面)
问题:怎么通过行指针访问数组的每个元素?
int (*p)[4];
p=a;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
printf(“%d”,p[i][]j); // 下标的索引方式
理解:p指向数组的首地址,因此它和数组名a完全一样,因此我们可以用指针p或数组名a访问元素。也可以用其他方式访问。但要注意的是数组名不能做自增自减操作!除此之外,指针和数组名是完全一样的!
2. 列指针:
int *p=*a; // 用列地址初始化。
列指针就是对一个一维数组的操作,因此它和普通指针是一样的!注意:不能让p指向a,因为a是二维数组的首地址,是行地址!*a是第0行的首地址。
问题:怎么用列指针访问数组元素?
int *p=*a; // 即p=*a; *a是第0行的首地址
for(i=0;i<m;i++) // 行
for(j=0;j<n;j++) // 列
printf(“%d”,*(p+i*n+j)); // 或下标索引:p[i*n+j]; 它是一个元素一个元素处理的。
总结:
(1) 二维数组首地址是行地址;
(2) 行指针指向二维数组名,行指针加减一,就移动一行;
(3) 行指针+i表示第i行的行地址,取*就转换为列地址,转为对一维数组的处理;
指针数组
int a[3][4];
int *p[4];
p=a; // 错误!指针数组中没有这种初始化形式!只有行指针才能指向行地址!这里的p不是行指针!
int (*p)[4];
p=a; // 行指针指向二维数组行地址
int *p[4]; // 指针数组
指针数组:一个数组中若每个元素都是一个指针,那么称它为指针数组。
(1) 由基类型相同的指针构成;
(2) 每个元素都是一个指针;
(3) 使用前必须初始化;
(4) 常用于对多个字符串进行处理。(一维字符数组可以处理一个字符串,那么二维字符数组可以存储和处理多个字符串)
例如:
char *proname[]={“FORTRAN”,”C”,”C++”}; // 定义一个指针数组并初始化
表示:有三个字符串,因此指针数组的长度为3,即这个数组有3个元素,每个元素都是一个指针。字符串常量是存放在数据区的const存储区中,可能连续也可能不连续。proname存储了字符串常量的首地址。
在内存中的表示:
可以看出,指针数组可以处理多个字符串。指针数组的长度是多少,就可以最多处理多少个字符串。
问题:指针数组和二维字符数组有什么区别?
#define N 100 // 最多处理100个字符串
char *str[N]; // 指针数组
#define N 100 // 最多处理100个字符串
#define M 50 // 每个字符串长度不超过50
char str[N][M]; // 二维字符数组
例如:
假设有100个字符串,90个字符串的长度为2,10个字符串的长度为50。
解释:
(1) 一个是字符指针数组,每个元素都是用来存储字符串的地址。
(2) 字符串的长度可能不同,并且字符串之间可能没有关系,存储空间可能连续也可能不连续。
(3) 字符指针数组所需空间大小:100*4+90*2+10*50=1080(100个字符串的地址,每个地址占4个字节+字符串本身占据的空间)。二维字符数组所需空间:要求字符串长度最大为M,那么如果字符串长度超过M会发生越界,如果小于M,不会出错,但所需空间(1080)远小于我们产生的空间大小(5000),造成浪费。
(4) 字符指针数组元素指向的字符串可以是不规则的长度。字符二维数组的每个元素的长度必须相同,在定义时已确定。
(5) 因此可以看出,字符指针数组对空间的需求更少,更适合处理多个字符串。