2.指针与数组
数组名表示数组首地址,不代表整个数组;
数组元素的指针就是数组元素的地址;
当一个指针变量指向数组时,对数组元素的访问有下标法(如a[3]),也可以用指针法。
例如:
int a[10]={1,2,3,4,5,6,7,8}; //定义a为包含10个整型数据的数组
int *p; //定义p为指向整型变量的指针变量
p=&a[0]; //把a[0]元素的地址赋给指针变量p
// int *p;p=&a[0];等价于 int *p=&a[0];
2-1指针与一维数组
1)在C语言中,当定义一个一维数组a后,数组名a本身就代表了该数组的首地址,并且它是一个地址常量。
一维数组元素的地址可以通过数组名a加下标值来取得。a即a+0代表数组元素a[0]的地址,a+1代表数组元素a[1]的地址,a+2代表数组元素a[2]的地址,......。
如果定义一个指针变量,使其指向一个一维数组,则该指针变量称为指向一维数组的指针变量。指向数组元素的指针变量的定义与指向普通变量的指针变量的定义方法一样。
如:
int a[10], *p1.*p2;
p1=a; //等价于p1=&a[0];
//表示数组a的首地址(不是数组各元素)赋给指针变量p(而不是赋给*p)。
//或者说指针变量p指向数组的首地址a[0]。
p2=&a[4]; //指针不指向数组首地址时只有一种写法
2)数组定义和初始化之后,可以通过数组的下标、数组名或指向数组的指针变量来引用数组元素。
例如:
int a[10]={1,2,3,4,5,6, 7,8, 9,10};
可以通过数组的下标来访问数组元素,如:printf("%d",a[5]);
也可以通过数组名访问数组元素:
如:*(a+0)(即*(a))与a[0]相等,*(a+1)与a[1]相等,*(a+2)与a[2]相等,......,*(a+i)与a[i]相等。
使用数组名时,需要注意不能用a++的方式,因为a是地址常量,常量是不能被重新赋值的。
3)定义指针变量p(int *p;),并使p指向数组a(即p=a),则可以用指针变量来访问数组元素。
p指向数组的第一个元素a[0],
则p+1指向数组的下一个元素a[1],p+2指向数组元素a[2],...,p+i指向数组元素a[i]。
*(p+i)与a[i]等价,可以使用*(p+i)来访问元素a[i]。
p+i等价于a+i,都表示元素a[i]的地址。
指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价,表示元素a[i]。
表示数组元素a[i]值的4种方式:a[i]、*(a+i)、*(p+i)、p[i]。
表示数组元素a[i]地址的3种方式:&a[i]、a+i、p+i。
4)指针变量储存数组首地址(p=a) 的理解【 不同编译器不一样,以纯C编译器为例】
对于数组元素是int型,p+1表示地址p的地址值加2个字节;
对于数组元素是char型,p+1表示地址p的地址值加1个字节;
对于数组元素是float型,p+1表示地址p的地址值加4个字节;
对于数组元素是double型,p+1表示地址p的地址值加8个字节。
2-2指针与二维数组
二维数组是由类型相同的变量构成的集合,它的元素在内存中按行的顺序存放。
1)二维数组和一维数组的关系
可以看成二维数组是由类型相同的一维数组作为元素构成的一维数组。组成二维数组的每一行用一个一维数组表示,这个一维数组的地址就是二维数组的某一行的首地址。
二维数组的地址或指针可以分为两种:①指向二维数组元素的地址,称为元素地址/列地址,又叫元素指针/列指针;②指向一维数组的指针,称为行地址/行指针。
二维数组中的元素a[i][j]的地址或指针可以用&a[i][j]来表示。可将二维数组的每一行看成一个一维数组,a[i]是一维数组名,由一维数组与指针的关系可知:元素a[i][j]的地址或指针可以用a[i]+j来表示。
例如:
int a[3][2]={{12,3},{5,6},{52,9}};
C语言规定,数组名代表数组的首地址。二维数组名a是一个行地址,a+0,a+1,a+2是下标0、1、2行的首地址,就是以a[0]、a[1]、a[2]为一维数组名的行地址。即a+0是一维数组a[0]的首地址,a+1是一维数组a[1]的首地址,a+2是一维数组a[2]的首地址。
2)二维数组的地址或值的表示
二维数组中的元素a[i][j]的行地址:&a[i],a
二维数组中的元素a[i][j]的列地址:&a[i][j], a[i]+j, *(a+i)+j, a[[i]
数组元素a[i][j]的值: a[i][j], *(a[i]+j), *(*(a+i)+j), *a[i]
3)行指针变量
指存放二维数组某一行的首地址。
类型说明符 (*行指针变量名)[数组长度];
注意:“*行指针变量名”外的括号不能缺,否则成了指针数组(数组的每个元素都是一个指针)
例如:
int (*p)[4];// 定义一个指向一维数组的行指针变量p,指向含有4个整型元素的一维数组
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};//二维数组名是一个地址常量
//a为3行4列的二维数组,即每一行是含有4个元素的一维数组。
p=a;
//将数组a的首地址赋给p,使行指针变量p指向该二维数组的首行,即指向一维数组a[0]。
4) 行指针变量的引用
通过行指针变量可以表示二维数组的首地址、行地址、元素地址、元素等。
假设行指针变量p指向二维数组a的第i行即p=a[i](或p=a+i),则:
① p:等价于a+i,指向第i行的首地址&a[i];
② p+1:等价于a+i+1,指向第i+1行的首地址&a[i+1];
③ p++:p向后移动一行,等价于a+i+1;
④*p:第i行的第0个元素的地址,等价于*(a+i)或a[i];
⑤*p+j:第i行第j列元素的地址,等价于*(a+i)+j、a[i]+j或&a[i][j];
⑥*(*p+j):第i行第j列元素的值,等价于*(*(a+i)+j)、*(a[i]+j)或a[i][j]。
2-3指针与字符数组
1)字符串
在C语言中,既可以用字符数组表示字符串,也可以用字符指针变量表示字符串。
既可以逐个引用字符串,也可以整体引用字符串。
字符串在内存中的起始地址称为字符串的指针,可以定义一个指针变量来指向一个字符串。
2)字符数组和字符指针变量
操作字符串的两种方式:
①使用字符数组
#include <stdio.h>
int main()
{
char ch[ ]="she is our teacher ";//ch是数组名,代表字符数组的首地址。
printf("%s\n",ch); //可以用下标方式访问数组,也可以用指针方式访问数组
return 0;
}
②使用字符指针变量
#include <stdio.h>
int main()
{
char *ch ="she is our teacher"; //ch是一个指针变量
//
printf("%s\n",ch);
return 0;
}
char *ch ="she is our teacher";
等价于
char *ch;
ch= "she is our teacher";
相当于是将字符串常量的首地址赋给指针变量ch,不能理解为把字符串常量赋值给指针变量ch。若写成下面的形式则是错误的。
*ch ="she is our teacher";
注意:不能采用赋值的方式将字符串常量直接赋值给字符数组,只能在初始化是时候赋值,但是可以将字符串常量直接赋值给字符指针
从上面两个例子中,可以得出的结论:
(1)字符数组和字符指针变量的概念不同。
(2)字符指针变量指向字符串的首地址。在C语言中,字符串按数组方式处理。因此,字符数组和字符指针变量的访问方式相同。如,都可以使用%s格式控制符进行整体输入输出。
注意:若不是字符数组,而是整型、实型等数组,则只能逐个元素处理。
将一个字符串从一个函数传递到另一个函数,可以用字符数组名或字符型指针变量作为参数。有如下4种情况:
- 实参和形参都为字符数组名。
- 实参为字符型指针变量,形参为数组名。
- 实参和形参都为字符型指针变量。
- 实参为数组名,形参为字符型指针变量。
3)字符数组和字符指针变量的区别
①存储内容不同:字符型指针变量中存储的是字符串的首地址,而字符型数组中存储的是字符串本身(数组的每个元素存放一个字符)。
②赋值方式不同:对于字符型指针变量,可以采用下面的赋值语句赋值;
char *p;
p="I love apples.";
而字符型数组,虽然可以在定义时初始化,但不能用赋值语句进行整体赋值。
char ch[20];ch ="I love apples.";/*错误用法*/
③指针变量的值是可以改变的,字符型指针变量也不例外;
char *a ="abcdefg ";
a=a+3;
puts (a);
执行后输出“defg”
而数组名代表数组的起始地址,是一个常量,常量是不能被改变的。
char a[6]="china";
a=a+3; /*是错误的,因为a代表数组的起始地址,a是一个常量*/
puts(a);
④存储单元的内容:编译时为字符数组分配若干存储单元,以存放各元素的值,而对字符指针变量,只分配一个存储单元(Visual C++为指针变量分配4个字节)。
如果定义了字符数组,但未对它赋值,这时数组中的元素的值是不可预料的。可以引用(如输出)这些值,结果显然是无意义的,但不会造成严重的后果,容易发现和改正。
如果定义了字符指针变量,应当及时把一个字符变量(或字符数组元素)的地址赋给它,使它指向一个字符型数据,如果未对它赋予一个地址值,它并未具体指向一个确定的对象。此时如果向该指针变量所指向的对象输入数据,可能会出现严重的后果。
char *a;//定义字符指针变量a
scanf(" %s" ,a);//企图从键盘输入一个字符串,使a指向该字符
(5)字符数组中各元素的值是可以改变的(可以对它们再赋值),但字符指针变量指向的字符串常量中的内容是不可以被取代的(不能对它们再赋值)。
char a[]="House"; //字符数组a初始化
char *b="House"; //字符指针变量b指向字符串常量的第一个字符
a[2]='r'; //合法,r取代a数组元素a[2]的原值u
b[2]='r'; //非法,字符串常量不能改变
2-4指针数组
指针数组的每一个元素都是一个指针变量。指针数组的定义格式如下:
类型标识符 *数组名[数组长度]
例如:
int *p[4];//定义的指针数组名为p,数组p有4个元素,这4个元素都是指向整型的指针变量
int (*p)[4];//定义的是行指针变量p,p指向具有4个元素的一维数组
2-5 指向指针的指针【二级指针】
定义一个指针,当他所指向的变量又是一个指针时,称为指向指针的指针。
二级指针变量的定义格式如下:
类型标识符 **指针变量名;//指针变量名前面有几个*就是几级指针
其中,类型说明符是“指针名”所指向的指针所指向的变量的数据类型,指针名前面有两个“*”。
例如:
int a=10,*p1,**p2; p1=&a; p2=&p1; | 定义了一个指向指针p1的指针p2,将int型变量a的地址&a赋值给指针p1,则 p1是指针变量,指向了变量a;然后,将p1的地址&p1赋值给指针p2,则p2指向p1,从而间接地指向了变量a。这里的p2就是指向指针的指针。 |
指向关系如图所示。
注意:指向指针的指针是间接地指向目标变量,因此,通常将直接指向目标变量的指针称为一级指针,指向指针的指针称为二级指针。
2-6多级指针
直接指向数据对象的指针称为一级指针,一级指针变量中存放的是数据的地址;二级指针变量不直接指向数据对象,而是指向一级指针变量,即二级指针变量存放的是一个一级指针变量的地址;三级指针变量所存放的是一个二级指针变量的地址,三级指针变量指向二级指针变量。多级指针可依此类推。
2-7.指针使用的细节
以下变量均已定义
①设指针p指向数组a (p=a),则有以下几点需要注意。
执行p++(或p+=1)后,p指向数组的下一个元素。
*p++相当于*(p++)。因为,*和++同优先级,++是右结合运算符。
*(p++)与*(++p)的作用不同。*(p++)为先取*p,再使p加1。
*(++p)为先使p加1,再取*p。
(*p)++表示p指向的数组元素值加1。
②如果p当前指向数组a的元素(即p=&a[i]),则有以下几点需要注意。
*(p--)相当于a[i--],先取*p,再使p减1。
*(++p)相当于a[++i],先使p加1,再取*p。
*(--p)相当于a[--i],先使p减1,再取*p。
③如果两个指针变量p、q指向同一个数组,那么还可以对两个指针变量进行比较:
q>p表示q所指数组元素位于p所指数组元素之后,
q<p表示q所指数组元素位于p所指数组元素之前。
如果两个指针不指向同一个数组,对它们的比较也没有意义。
④当两个指针变量指向同一数组时,可以求它们的差,得到的结果是对应的两个数组元素的下标之差(可能是负整数)。
例如,“n=p-q;”表示整型变量n中存储的是一个带符号整数。
当两个指针不指向同一数组时,求它们的差就没有意义了。