一、地址和指针变量
(一)地址
1、每一个实体都有一个内存位置,每一个内存位置都定义了可以使用连字号(&)运算符访问的地址,它表示在内存中的一个地址。
例如:
内存单元地址从1000开始,若有定义:char a; int b; float c; int d[2]; int max();
则占用内存情况为:
&a=1000 (char型占一个字节)
&b=1001 1002 1003 1004 (int型占4个字节)
&c=1005 1006 1007 1008 (float型占4个字节)
d=1009 1010 1011 1012 1013 1014 1015 1016,其中d[0]=1009 1010 1011 1012,d[1]=1013 1014 1015 1016,(int型数组d占8字节)
max=1017 (函数max()入口地址)
注意:
- 通常关心的不是具体地址值,而是每个实体的起始地址,比如上述变量 b 的起始地址表示为&b=1001;
2、实体地址的2种表示方法:
(1)&取地址运算符:
变量名前加一个连字号(&),表示该变量的地址;
适用于普通变量或数组元素:
- 普通变量,如&a,&b;
- 若有数组int d[3],则数组首地址表示为 d 或 &d[0];
- 若有二维数组int d[3][4],则数组首地址表示为 d 或 &d[0][0] 或 &d[0] 或 d[0],对二维数组,可以用单下标法表示每行首地址,即 d[0],d[1],d[2]分布表示第1,2,3行的首地址;
- 若定义函数max(),则函数入口地址表示为 max;
(2)指针(pointer):
指针是一种特殊的数据类型,用来存放实体的地址值;
指针适合于地址运算;
(二)指针变量
1、定义:用来存放指针(地址值)的特殊变量;
2、格式:
类型标识符 *变量名;
比如:int *a; char *b; float *c;
指针变量a、b、c分别指向某个未确定的整型变量、字符变量、实型变量,但指针变量a、b、c本身是整数(地址)
3、赋值:
例如:int a=5,*p; p=&a;
则p是指向a的地址,*p表示地址中的值,即a的值。
注意:
- 数组名是常量,不能自加、自减或重新赋值,指针变量可以自加、自减或重新赋值;
- 例如:char a[10],*b,x=5; a++(错)a=100(错)a=x(错),b++(可)b=100(可)b=x(编译不会出错,但实际上不行)
- 指针变量可以初始化为 0、NULL或某个地址,具有NULL或0值的指针不指向任何变量,也就是空值指针,值0是唯一能够直接赋给指针变量的整数值;
- 在指针 p 指向某个实体的地址之前,不可对 *p 进行赋值;
(三)指针变量的运算
1、运算符
(1)取地址运算符&:
功能:取出操作对象在内存中的地址;
不能用于表达式、地址、常量、寄存器变量;
(2)指针运算符*:
功能:访问操作对象所指向的变量;
注意:
- 一般,以&开头的是地址,以*开头的是变量值,在&和*组合使用时,可以看作互相抵消;
int a,*p;
p=&a;
那么:
&*p=&a=p;
*&a=*p=a;
2、运算:
(1)算术运算:
只有加、减;
运算单位是实体,而不是字节;
注意:
- 如果两个指针变量指向同一数组,两个指针变量值之差就是两个指针之间的元素个数,但相加无意义;
(2)关系运算:
指针变量指向同一个对象(如数组)的不同单元地址时,才可以进行比较运算,地址在前者为小。
任何指针变量或地址都可以与NULL作相等或不等比较,比如 if(p==NULL)。
(四)指针变量作为函数参数
当形参是指针变量时,实参一定是地址或指针。
当实参是指针时,形参一定是指针,其类型与实参相同。
实参和形参之间是“地址传递”。
二、指针与数组
(一)指针与一维数组
int a[10],*p;
p=a;
p=&a[0];
数组名a代表数组首地址,即a=&a[0],a+i 等于 &a[i],*a相当于 *(a+0)且等于a[0],*(a+i)等于a[i]。
指针变量p等于数组a的首地址,p+i 表示数组元素 a[i]的地址,可以用 p[i],*(p+i)或*(a+i)表示数组元素a[i]。
其实,C语言对数组的处理,实际上是转换成指针地址的运算。
(二)行指针与列指针的关系
在二维数组中,数组名 a 是第 0 行的行指针(行地址),a+1 是下一行的首地址,a+i 是第 i 行的首地址。这里的+1,1代表的是一行的字节长度;
数组名 a[i] 是列指针(列地址),a[i]+1指向a[i]的下一个数组元素的地址,这里的1是一个数组元素的字节长度;
关系:
行指针前加*,就是列指针,列指针前加*,就是值;
值前加&,就是列指针,列指针前加&,就是行指针;
表示形式 | 说明 |
&a[0][0] | 第0行第0列元素的地址,指向列 |
a | 第0行的首地址,指向行 |
a+i | 第 i 行的首地址,指向行 |
&a[i] | 第 i 行的首地址,指向行 |
*a | 第0行第0列元素地址,指向列 |
*(a+i) | 第 i 行第0列元素地址,指向列 |
*(a+i)+j | 第 i 行第 j 列元素地址,指向列 |
a[i] | 第 i 行第0列元素地址,指向列 |
a[i]+j | 第 i 行第 j 列元素地址,指向列 |
&a[i][j] | 第 i 行第 j 列元素地址,指向列 |
**a | a[0][0]的值 |
*(*(a+i)+j) | a[i][j]的值 |
*(a[i]+j) | a[i][j]的值 |
a[i][j] | a[i][j]的值 |
(三)遍历二维数组
1、单下标遍历和双下标遍历
(1)单下标
指的是把二维数组元素按行顺序排列成一个队列,然后用一个下标(单循环)即可遍历整个二维数组各元素;
#include<stdio.h>
int main()
{
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
int *p,i;
p=a[0];
for(i=0;i<12;i++,p++)
{
printf("%d ",*p);
}
return 0;
}
(2)双下标
指的是按行列二维排列,然后用两个下标(二重循环)遍历整个二维数组各元素;
#include<stdio.h>
int main()
{
int i,j,a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
int *p;
for(i=0;i<3;i++)
{
for(j=0;j<4;j++)
{
printf("%d",*(*(a+i)+j));
}
}
p=a[0]+2; //或者 p=&a[0][0]+2;
printf("%d\n",*p); //输出3
return 0;
}
若是将 p=a[0]+2改成p=a+2,会警告指针类型不兼容,但仍可运行,a+2代表第2行的首地址,所以结果为 9;
指针赋值时要注意类型一致或兼容,p是列性质的指针。
(四)指向行数组的指针变量
1、指向行数组的指针变量在对应二维数组时也称为指向一维数组的指针变量,二维数组可以看作由多个一维数组组成,每一行可以看作一个一维数组,行指针即指向某一行并按行移动。
2、定义格式:
int (*p)[n];
含义:p为指向含有n个元素的一维数组的指针变量;
解释:p指向的不是整型变量,而是一个包含n个元素的一维数组;
p的增值以一维数组的长度为单位,比如p=a[0],则p++不是指向a[0][1],而是指向a[1]。
int a[4][5];
int (*p)[5];
p=a; //或 p=&a[0];
那么:
(*p)[0]=a[0][0];
(*p)[1]=a[0][1];
(*p)[2]=a[0][2];
...
(*(p+1))[0]=a[1][0];
(*(p+1))[1]=a[1][1];
即 (*(p+i))[j]=p[i][j]=*(*(p+i)+j)=a[i][j]
三、指针与字符串
(一)指向字符串的指针
1、使用示例:
#include<stdio.h>
int main()
{
char *pc="#Fujian##Province#";
while(*pc)
{
while(*pc=='#') pc++;
if(*pc=='\0') break;
printf("%c",*pc);
pc++;
}
return 0;
}
输出是:FujianProvince
2、字符数组与字符指针使用上的区别:
(1)字符数组:
#include<stdio.h>
int main()
{
char *p,a[12]="abcde";
p=a;
for(;*p;p++)
{
printf("%c",*p);
}
return 0;
}
输出是:abcde
(2)字符指针
#include<stdio.h>
int main()
{
char *p;
p="abcde";
for(;*p;p++)
{
printf("%c",*p);
}
return 0;
}
输出是:abcde
(二)字符数组和字符指针变量的区别
1、存储格式不同
字符数组:存放的是整个字符串;
字符指针变量:存放的是字符串首地址(第一个字符的地址),而不是整个字符串
2、性质不同
字符数组名是地址常量,不能改变,只能指向字符串首地址;
字符指针是地址变量,可以改变,指向不同的字符;
3、赋值方式不同:
字符数组初始化:
char str[14]={"I love you!"};
//下面的是错误的
char str[14];
str="I love you!";
字符指针变量:
char *a="I love you!";
//等价于
char *a;
a="I love you!"; //赋值给 a 的是字符串第一个元素的地址
四、指针作为函数参数
(一)值传递与地址传递
1、值传递:
将变量名作为实参和形参,这时传给形参的是变量的值,传递是单向的。
在函数执行期间,形参的值发生改变后,不会传回给实参。
形参和实参不是同一个存储单元。
数组元素作为函数的实参时也是如此。
2、地址传递:
指针变量作为函数参数时,形参是指针变量,实参是一个变量的地址。
调用函数时,形参(指针变量)指向实参变量单元。
通过形参指针可以改变实参的值,是双向的地址传递。
数组名也是如此。
(二)地址传递方式
1、四种方式:
传递方式 | 主调函数中实参 | 被调函数中形参 | 说明 |
1 | 数组名 a | 数组 b | 本质都是将数 组名 a 或指针 变量p所代表 的数组首地址 ,传给形参首 地址b或x |
2 | 指针变量 p(p=a) | 指针变量 x | |
3 | 数组名 a | 指针变量 x | |
4 | 指针变量 p(p=a) | 数组名 b |
五、指针与函数
(一)指向函数的指针变量
1、在编译时,一个函数被分配一个入口地址;
指向函数的指针变量的值就是函数的入口地址;
2、格式:
类型标识符 (*指针变量名)();
例如:int (*P)(); 指向一个返回整型值的函数。
3、用法:
设有函数 fun(a,b)
令 p=fun
则有 (*p)(a,b),相当于 fun(a,b)
此时:c=(*p)(a,b)与c=fun(a,b)等效
4、2个用途:
(1)调用函数
(2)作为函数的参数
(二)返回指针值的函数
1、指针函数:
返回指针值的函数,也称为指针函数。
返回的是一个地址值。
在主调函数中,函数返回值必须有同类型的指针变量来接收。
2、格式:
类型名 *函数名(函数参数列表);
解释:
()表示这是一个函数,不能省略;
*表示这是一个指针函数,其函数值为指针,返回值是指针(地址);
例如:int *a(int,int); 这个函数的返回值是整型指针。
#include<stdio.h>
float *find(float(*p)[4],int n); //函数声明
int main()
{
static float score[][4]={{83,72,70,68},{86,89,75,76},{94,63,76,85}};
float *p1;
int i,m;
printf("Enter NP.1-3:");
scanf("%d",&m);
printf("\nthe score of NO.%d are:\n",m);
p1=find(score,m-1);
for(i=0;i<4;i++)
{
printf("%5.2f\t",*(p1+i));
}
}
float *find(float(*p)[4],int n) //定义指针函数
{
float *pt;
pt=*(p+n);
return (pt);
}
说明:
函数find()被定义为指针函数,其形参p是指向包含4个元素的一维数组的指针变量;
p+n指向score的第n行(从第0行开始),*(p)[0]指向第0行的第0个元素;
pt是一个指针变量,指向浮点型变量;
六、指针数组与多级指针
(一)指针数组
1、定义:
数组元素全是指针的数组称为指针数组;
指针数组中的元素都具有相同的存储类型,指向相同数据类型的指针变量,存放于一个地址;
2、格式:
类型名 *数组名[数组长度];
例如: char *p[5]; [ ]比*优先级高,所以这是一个指针数组;
3、功能:
对上述p[5],其每个元素p[0]、p[1]、p[2]、p[3]、p[4]都是字符指针变量;
一维数组指针通常用于指向一组字符串,此时,p[i],其下标表示第i个字符串,p[i]本身是第i个字符串的首地址。
4、例1:
#include<stdio.h>
int main()
{
char *str[]={"AA","BB","CC"};
str[1]=str[2];
printf("%s,%s,%s\n",*str,str[1],*(str+2));
return 0;
}
输出是:AA,CC,CC
说明:str[i]是指向第i行的指针变量,等效于*(str+i);
5、带参数的主函数
int main(int argc,char* argv[])
argc:表示字符串的数量,即argc=1+用户字符串数目;
argv:操作系统存储的字符串数组,即多个字符串,argv[0]=可执行文件名称,argv[1]=用户字符串1,argv[2]=用户字符串2;
例如:
#include<stdio.h>
int main(int argc,char* argv[])
{
while(argc>1) printf("%s ",argv[--argc]);
printf("\n");
}
将该文件编译后,在DOS命令提示符下输入命令:test abc 123
运行结果是:123 abc
(二)多级指针
1、可以将一个指针变量的内存地址再赋值给另一个指针变量,即指向指针的指针,称为多级指针。
2、以二级指针为例:
二级指针是指向一级指针的指针,用来存储某个指针变量的内存地址;
3、格式:
类型标识符 **指针变量名
例如:int **p; 等效于 int *(*p);
* 的结合性是从右到左的;
*p是p间接指向的对象的地址;
**p 是p间接指向的对象的值;
解释:p指向一个int型指针变量(这个int型指针变量指向一个int型数据);如果引用*p就得到p所指向的int型指针变量的值,即int型数据的地址;**p就得到int型数据的值;
#include<stdio.h>
int main()
{
char aa[][3]={'a','b','c','d','e','f'};
char (*p)[3]=aa;
int i;
for(i=0;i<2;i++)
{
if(i==0) aa[i][i+1]=**(p++);
}
printf("%c\n",**p);
return 0;
}
输出是:d
分析:
行指针p实际上是一个二级指针,*p相当于取行首地址,**p相当于取行首元素的值;
**(p++) 先取**p的值,再p++(转到下一行)
数组aa变成{'a','a','c','d','e','f'}。