第 8章 指针第 8章 指针
8.1 指针的概念
8.2 指针变量的定义与引用
8.3 指针运算
8.4 指针和数组
8.5 指针与字符串
8.6 指针数组和指向指针的指针
8.7 指针与内存的动态分配
8.8 指针与数组作为函数的参数
8.9 带参数的 main 函数
8.10 返回指针值的函数
8.11 函数指针的定义与引用
8.12 本章小结第 8章 指针
8.1 指针的概念
8.1.1 变量的地址与变量的内容计算机为了方便管理内存,为每一个内存单元都编了号,这个编号称为内存单元的地址 。 一般把存储器中的一个字节称为一个内存单元 (亦称存储单元 ),变量的地址与变量的内容是两个不同的概念 。 变量在内存中所占存储空间的首地址就称为变量的地址 。 而变量在内存所占的内存单元中存放的数据就称为变量的内容 。
第 8章 指针
8.1.2 直接访问与间接访问变量值的存取都是通过地址进行的,例如:
priintf(“%d”,&i)的执行是这样的 。 先找到变量 i的地址
200,然后从 200开始的两个字节中取出数据 ( 假若变量 i的值为 5) 把它输出 。 这种按变量地址存取变量的方式称为直接访问方式 。 还可以采用另一种称为间接访问方式,将变量 i的地址存放在另一个变量中 。 假设一个变量 p,用来存放变量 i的地址,它被分配为 300,
301两个单元 。 将 i的地址存放到 p中,要存取变量 i的值,先找到存放 i地址的变量 p,从 p中取出 i的地址
( 200),然后到 200,201中取出 i的值 5。
第 8章 指针
8.1.3 指针与指针变量对于一个内存单元来说,单元的地址即为指针,
其中存放的数据是该单元的内容 。 在 C语言中,允许用一个变量来存放指针,这种变量称为指针变量 。 因此,
一个指针变量的值就是某个内存单元的地址,或称为某内存单元的指针 。
第 8章 指针图 8.1 指向变量 C的指针变量 P
0110 H,K,
P C
0110 H ( 地址 )
第 8章 指针如图 8-1 所示,设有字符变量 C,其内容为 K
(ASCII码为十进制数 75),C占用了 0110H号单元 (地址用十六进制表示 )。 当有指针变量 P,内容为 0110H时,
我们称为,P指向变量 C”或者,P是指向变量 C的指针,。
严格地说,一个指针是一个地址,是一个常量,
而一个指针变量却可以被赋予不同的指针值,是变量 。
但通常把指针变量简称为,指针,。 为了避免混淆,
我们约定:,指针,是指地址,是常量,,指针变量,
是指取值为地址的变量 。 定义指针的目的是为了通过指针去访问内存单元 。
第 8章 指针既然指针变量的值是一个地址,那么这个地址不仅可以是变量的地址,而且也可以是其他数据结构的地址 。 在一个指针变量中存放一个数组或一个函数的首地址有何意义呢? 因为数组或函数都是连续存放的,
所以通过访问指针变量取得了数组或函数的首地址,
也就找到了该数组或函数 。 这样一来,凡是出现数组,
函数的地方都可以用一个指针变量来表示,只要该指针变量中赋予数组或函数的首地址即可 。 这样做,将会使程序的概念十分清楚,程序本身也精练,高效 。
第 8章 指针在 C语言中,一种数据类型或数据结构往往都占有一组连续的内存单元。用“地址”这个概念并不能很好地描述一种数据类型或数据结构,而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址,它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。这也是引入“指针”概念的一个重要原因。
第 8章 指针
8.2 指针变量的定义与引用
8.2.1 指针变量的定义指针变量定义的一般形式为类型说明符 *指针变量名;
其中,*为说明符,表示这是一个指针变量;指针变量名为用户自定义标识符;类型说明符表示该指针变量所指向的变量的数据类型。
第 8章 指针例如:
int *p1;
该定义表示 p1是一个指针变量,它的值是某个整型变量的地址,或者说 p1指向一个整型变量 。 至于 p1
究竟指向哪一个整型变量,应由向 p1赋予的地址来决定 。 对于指针变量的类型说明应包括以下三个方面的内容:
(1) 指针类型说明,即定义变量为一个指针变量 。
(2) 指针变量名 。
(3) 变量值,即指针变量所指向变量的地址 。
第 8章 指针例如:
staic int *p2;
/*p2是指向静态整型变量的指针变量 */
float *p3; /*p3是指向浮点型变量的指针变量 */
char *p4; /*p4是指向字符型变量的指针变量 */
应该注意的是,一个指针变量只能指向同类型的变量,如 P3只能指向浮点型变量,不能时而指向一个浮点型变量,时而又指向一个字符型变量 。
第 8章 指针
8.2.2 指针变量的引用指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值 。 未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机 。 同时,
指针变量的赋值只能赋予地址,决不能赋予任何其他数据,否则将引起错误 。
在 C语言中,初始变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址 。
第 8章 指针
C语言中提供了地址运算符 &来表示变量的地址 。
其一般形式为
&变量名如 &a表示变量 a的地址,&b表示变量 b的地址 。 变量本身必须预先说明或定义 。
设有指向整型变量的指针变量 p,如要把变量 a 的地址赋予 p可以有以下两种方式:
(1) 指针变量初始化的方法:
int a;
int *p=&a;
第 8章 指针
(2) 赋值语句的方法:
int a;
int *p;
p=&a;
不允许把一个数赋予指针变量,如下面的赋值是错误的:
int *p;
p=1000;
被赋值的指针变量前不能再加,*” 说明符,如写为 *p=&a 也是错误的 。
第 8章 指针
8.3,1 指针变量的算术运算指针变量可以进行某些运算,但其运算的种类是有限的:如部分算术及关系运算 。
设 px与 py是指向具有相同类型且连续存储的一组数据 。 如指向同一个数组 。 若 n代表整数,则指针可进行下列加减运算 。
px± n,px++,px--,++px,--px,px-py; py-px;
(1) px± n,就是,px的地址 ± n*px的类型占用的单元数,所对应的地址 。
8.3 指针运算第 8章 指针
(2) px++,++px,px--,--px,显然是 px± n的特
( n=1)。即指针加 1或减 1。
(3) px-py,py-px 两个指针相减运算结果是整数,即它们所指向数组元素下标相差的整数。
设 px指向数组元素 a[2],py指向数组元素 a[6],则下列表达式及其运算结果:
px-py 结果为整数 –4。
py-px 结果为整数 4 。
第 8章 指针
【 例 8.1】 指针变量的定义,赋值及简单应用
main( )
{ int a=5,*p=&a;
printf ("%d",*p);
}
程序运行情况:
5
程序说明:在定义指针变量 p后,利用指针变量 p
取得整型变量 a的地址,并在最后的输出中,利用指针变量获取变量 a的值进行输出 。
第 8章 指针
8.3.2 指针的关系运算它的运算规则是:当两个指针变量的值 ( 地址值 )
潢足关系运算时,结果为 1( 真 ) ;否则结果为 0
( 假 ),所以两个指针变量进行关系运算的结果也是逻辑值 。
定义了数组 a和同类型的指针变量 px,py;使 px指向数组元素 a[1]; py指向数组元素 a[4]。 请看下列的关系表达式及其运算结果:
px<py 结果为 1( 真 ) 。
px++==py 结果为 0( 假 ),注意 ++是后缀 。
第 8章 指针
--py==px +2 结果为 1( 真 ),注意 --是前缀 。
py<&a[5] 结果为 1( 真 ),&a[5]地址是地址常量 。
py>=px+2 结果为 1( 假 ),px+2是地址型表达式,
代表 a[3]的地址 。
指什的运算应 注意 以下几点:
l 指什变量只能和整数或整型变量相加减,而不能和实型数或实型变量相加减 。 如 px+3.5是错误的,。
l 指什变量不能进行乘法和除法运算 。 如 py*4或 py/2
都是错误的 。
两个指什变量相减,必须指向同一个数组,否则不能进行减法运算 。
第 8章 指针
8.4.1 指针与一维数组定义一个整型数组和一个指向整型的指针变量 如下
int a[10],*p;
假定给出赋值运算
p=&a[0];
此时,p指向数组中的第 0号元素,即 a[0],指针变量 p中包含了数组元素 a[0] 的地址,由于数组元素在内存中是连续存放的,因此,我们就可以通过指针变量 p及其有关运算间接访问数组中的任何一个元素。
8.4 指针和数组第 8章 指针则 表示元素地址 &a[i],还可以用 a+i,p+i,&p[i],
表示数组中元素 a[i],还可以用 p[i],*(a+i),*(p+i )。
下面我们用指针给出数组元素的地址和内容的几种表示形式 。
(1) p+i和 a+i均表示 a[i]的地址,它们均指向数组第 i号元素,即指向 a[i]。
(2) *(p+i)和 *(a+i)都表示 p+i和 a+i所指对象的内容,
即为 a[i]。
(3)指向数组元素的指针,也可以表示成数组的形式,也就是说,它允许指针变量带下标,如 p[i]与 *(p+i)等价 。
第 8章 指针
【 例 8,2】 数组与指针变量的综合应用举例 。
方法 1:用下标法引用数组元素 。
main()
{ int a[10],i;
for (i=0; i<10; i++)
scanf("%d",&a[i]);
for(i=0; i<10; i++)
printf("%d",a[i]);
}
第 8章 指针方法 2:用指针变量法引用数组元素 。
main()
{ int a[10],*p,i;
p=a;
for(i=0; i<10; i++)
scanf("%d",p+i);
for(i=0; i<10; i++)
printf("%d",*(p+i));
}
第 8章 指针方法 3:通过指针变量自增运算引用数组元素 。
main()
{ int a[10],*p;
for(p=a; p<a+10; p++)
scanf("%d",p);
for(p=a; p<a+10; p++)
printf("%d",*p);
}
第 8章 指针方法 4:用指针的下标表示法引用数组元素 。
main()
{ int a[10],*p,i;
p=a;
for(i=0; i<10; i++)
scanf("%d",&p[i]);
for(i=0; i<10; i++)
printf("%d",p[i]);
}
程序运行结果为:
1 2 3 4 5 6 7 8 9 0↙
1 2 3 4 5 6 7 8 9 0
第 8章 指针
8.4.2 指针与二维数组用指针可以指向一维数组,也可以指向二维数组 。
1.二维数组的地址为了说明问题,我们定义以下二维数组:
int a[2][4]={{0,1,2,3},{4,5,6,7}};
a为二维数组名,此数组有 3行 4列,共 12个元素 。 但也可这样来理解,数组 a由二个元素组成,a[0],a[1]。 而它们中每个元素又是一个一维数组,且都含有 4个元素,例如,
a[0]所代表的一维数组所包含的 4 个元素为 a[0][0],
a[0][1],a[0][2],a[0][3]。
第 8章 指针如图 8.2所示:
但从二维数组的角度来看,a代表二维数组的首地址,当然也可看成是二维数组第 0
行的首地址。 a+1就代表第 1行的首地址。
如果此二维数组的首地址为 1000,由于第 0
行有 4个整型元素,所以 a+1为 1008。如图
8.3所示
a[0] 0,1 2 3
a[1] 4 5 6 7
图 8.2 a[0],a[1]代表二维数组中的行第 8章 指针
a[0] a[0]+1 a[0]+2
a[0]+3
a 1000
0
1002
1
1004
2
1006
3
a+1 1008
4
1010
5
1012
6
1014
7
图 8.3 二维数组中的行地址和列地址示意图二维数组元素 a[i][j]可表示成 *(a[i]+j)或
*(*(a+i)+j),它们都与 a[i][j]等价,或者还可写成
(*(a+i))[j]。见表 8.1
第 8章 指针表 8.1 指针变量的含义及对应地址表示方式 含义 地址
a 二维数组首地,0行首地址
1000
a[0]+0,*(a+0),*a 表第 0行第 0列元素地址 1000
a+1,a[1] 第 1行首地址 1008
a[1]+2,*(a+1)+2,&a[1][2] 第 1行第 2列元素地址 1012
*(a[1]+2),*(*(a+1)+2),
a[1][2]
第 1行第 2列元素的值 6
*(a[i]+j),*(*(a+i)+j),a[i][j] 第 i行第 j列元素的值第 8章 指针另外,要补充说明一下,如果你编写一个程序输出打印 a和 *a,你可发现它们的值是相同的,这是为什么呢?
我们可这样来理解,首先,为了说明问题,我们把二维数组人为地看成由两个数组元素 a[0],a[1]组成,将 a[0],
a[1]]看成是数组名它们又分别是由 4个元素组成的一维数组 。 因此,a表示数组第 0行的地址,而 *a即为 a[0],它是数组名,当然还是地址,它就是数组第 0 行第 0列元素的地址 。
第 8章 指针
2 指向二维数组的指针变量在了解上面的概念后,可以用指针变量指向二维数组及其元素 。
( 1) 指向数组元素的指针变量
【 例 8.3】 用指针变量输出数组元素的值 。
main()
{static int a [2][4]={1,2,3,4,5,6,7,8}; int *p;
for(p=a[0]; p<a[0]+8; p++)
{if((p-a[0])%4==0) printf("\n"); printf("%4d",*p); }
}
程序运行结果为:
1 2 3 4
5 6 7 8
第 8章 指针如果读者对 p的值还缺乏具体概念的话,可以把 p的值 ( 即数组元素的地址 ) 输出 。 若将程序最后两行改为:
printf("元素地址 =%o,元素值 =%4d\n",p,*p);
这时输出如下:
元素地址 =236,元素值 =1
元素地址 =240,元素值 =2
元素地址 =242,元素值 =3
元素地址 =244,元素值 =4
元素地址 =246,元素值 =5
元素地址 =250,元素值 =6
元素地址 =252,元素值 =7
元素地址 =254,元素值 =8
第 8章 指针计算 a[i][j]在数组中的相对位置的计算公式为:
i*m+j
其中 m为二维数组的列数 ( 二维数组大小为 n*m),例如,
对 3*4( 3行 4列 ) 的二维数组,它的第 2行第 3列元素
(a[2][3])对 a[0][0]的相对位置为 2*4+3=11。
可以看到,C语言规定数组下标从 0开始,对计算上述相对位置比较方便,只要知道 i和 j 的值,就可以直接用
i*m+j公式计算出 a[i][j]相对于数组开头的相对位置 。 如果规定下标从 1开始,则计算 a[i][j]的相对位置所用的公式就要改为,(i-1)*m+(j-1),这就增加了计算的工作量 。
第 8章 指针
( 2) 指向由,m个整数组成的一维数组的指针变量这里的指针变量 p不是指向整型变量,而是指向一个包含 m个元素的一维数组 。 如果 p先指向 a[0],则 p+1
不是指向 a[0][1],而是指向 a[1],p的增值以一维数组的长度为单位,见图 8.4
8.4
第 8章 指针
【 例 8.4】 输出二维数组任一行任一列元素的值 。
main()
{int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
int (*p)[4],i,j;
p=a;
scanf("i=%d,j=%d",&i,&j);
printf("a[%d][%d]=%d\n",i,j,p[i][j]);
}
第 8章 指针程序运行结果为:
i=1,j=2 ↙ ( 本行为键盘输入 )
a[1][2]=13
程序第三行,int(*p)[4]”表示 P是一个指针变量,它指向包含 4个元素的一维数组 。 注意 *P两侧的括号不可缺少,如果写成 *P[4],由于方括号 []运算级别高,因此 P先与 [4]结合,是数组,然后再与前面的 *结合,*p[4]是指针数组
( 参看本章 8.6),有的读者感到,(*p)[4]”这种形式不好理解 。
程序的 p+i是二维数组 a的第 i行的地址,*( p+2) +3是 a
数组第 2行第 3列元素地址,这是指向列的指针,
*((*p+2)+3)是 a[2][3]的值 。
第 8章 指针
8.5.1 字符串的表示与引用在 C语言中,既可以用字符数组表示字符串,也可用字符指针变量来表示;引用时,既可以逐个字符引用,
也可以整体引用 。
前面章节中介绍了用字符数组来表示字符串,以及对字符的引用 。 本节重点介绍利用字符指针变量对字符串的表示和引用 。
8.5 指针与字符串第 8章 指针
1 逐个引用
【 例 8,5】 使用字符指针变量表示和引用字符串 。
main()
{char *string="Beijing 2008.";
while(*string!='\0')
{printf("%c",*string); string++; }
printf("\n\n");
}
程序运行结果为:
Beijing 2008.
第 8章 指针注意,
( 1) 字符指针变量 string中,仅存储串常量的首地址,而串常量的内容 ( 即字符串本身 ) 存储由系统自动开辟的内存中,并在串尾添加一个结束标志 '\0'。
( 2) 若定义了一个指针变量,使它指向一个字符串后,可以用下标形式引用指针变量所指的字符串中的字符 。 如:
第 8章 指针
【 例 8,6】
# include "stdio.h"
main()
{char *a=" Beijing 2008.";
int i=0;
printf("The length of the string is %d\n",strlen(a) );
while(a[i]!='\0')
{putchar(a[i]); i++; }
putchar('\n');
}
第 8章 指针程序运行结果为:
The length of the string is 13
Beijing 2008.
2整体引用
【 例 8,7】 对 【 例 8.6】 采取整体引用的办法 。
main()
{char *string=" Beijing 2008.";
printf("%s\n",string);
}
第 8章 指针程序运行结果为:
Beijing 2008.
说明,printf(“%s\n”,string);语句通过指向字符串的指针变量 string,整体引用它所指向的字符串 。 其原理是:系统首先输出 string指向的第一个字符,然后使 string自动加 1,使之指向下一个字符;重复上述过程,直至遇到字符串结束标志 。
显然,对于字符串输出而言,整体引用比逐个字符引用更简洁。
第 8章 指针
3 字符指针变量与字符数组的比较虽然用字符指针变量和字符数组都能实现字符串的存储和处理,但二者是有区别的,不能混为一淡 。
(1) 存储内容不同字符指针变量中存储的是字符串的首地址,而字符数组中存储的是字符串本身 ( 数组的每个元素存放一个字符 ) 。
(2) 赋值方式不同对于字符指针变量,可采用下面的赋值语句赋值:
char *p;
p =” How are you!”;
第 8章 指针
(3) 指针变量的值是可以改变的,字符指针变量也不例外,而数组名代表数组的起始地址是一个常量,而常量是不能被改变的 。
例如,char *p =" How are you!";
p =p +3;
printf("%s",a );
针变量 a的值可以变化,输出字符串从当前所指向的单元开始输出各个字符,直到 ‘ \0’为止,而数组名代表地址,但它的值是不能改变的 。 以下为错误赋值 。
char str[ ]="How are you!";
str =str +3; /*不能给常量赋值 */
第 8章 指针
8.6.1 指针数组其元素均为指针类型数据的数组,称为指针数组 。 指针数组中的每一个元素都相当于一个指针变量,且这些指针变量指向相同数据类型的变量 。
指针数组定义的一般形式为:
类型名 *数组名 [整型常量 ];
其中,,整型常量,规定了指针数组的长度,类型名则代表指针数组元素可以指向的数据类型 。
8.6 指针数组和指向指针的指针第 8章 指针例如,
int *p[6];
由于 [ ]比 *优先级高,因此 p先与 [6]结合,形成 p[6]形式,这显然是数组形式,它有 6个元素,然后再与前面的
,*” 结合,*” 表示此数组是指针类型的,每个数组元素 ( 相当于一个指针变量 ) 都可以指向一个整型变量 。
即 p有 6个元素的数组,每个元素是一个指向 int 类型数据的指针 。
注意,int *p[6]与 int (*p)[6],请读者自行分析 。
第 8章 指针
【 例 8,8】 编程将如下表示地址名的字符串按字母顺序由小到大排序,然后输出这些字符串,Beijing,Shanghai,
Nanchang,Guangzhou。
# include "stdio.h"
main()
{ int i,j; char temp[10];
char str[4][10]=
{"Beijing","Shanghai","Nanchang","Guangzhou"};
printf("Before sorted:\n");
for (i=0; i<4; i++) printf(“%s,”,str[i]); /*输出排序前的 5个字符串 */
第 8章 指针
/*交换法排序 */
for (i=0; i<3; i++)
for (j=i+1; j<4; j++)
if (strcmp(str[j],str[i])<0)
{ /*则交换 str[j]所指字符串和 str[i]所字符串 */
strcpy (temp,str [i]);
strcpy(str[i],str[j]);
strcpy(str[j],temp);
}
第 8章 指针
printf("\nAfter sorted:\n");
for (i=0; i<4; i++) printf ("%s,",str [i]); /*输出排序后的 4个字符串 */
}
程序运行结果为:
Before sorted:
Beijing,Shanghai,Nanchang,Guangzhou,
After sorted:
Beijing,Guangzhou,Nanchang,Shanghai,
第 8章 指针对于二维数组与指针数组之间的区别初学都有常常会感到困惑,例如:
char str[4][10]= {"Beijing","Shanghai",
"Nanchang","Guangzhou"};
char *ptr[4]= {"Beijing","Shanghai",
"Nanchang","Guangzhou"};
虽然 str[i]和 ptr[i]都代表第 i+1个字符串的首地址,都是对第
i+1字符串的合法引用,但 str与 ptr的含义却又完全不同,
第 8章 指针
str代表一个真正的二维数组,str是它的名字,编译时需分配 4*10个字节的存储空间用于存放 40个字符型数据,同时将初绍化列表中提供的字符串赋值给字符数组的元素,
而对于指针数组 ptr只需分配 4个用于存放指针型数据的存储单元即可 。
第 8章 指针
8.6.2 指向指针的指针指向指针数据的指针变量,简称为指向指针的指针 。
定义一个指向指针数据的指针变量,格式如下:
类型符 * *指针变量 ;
如,char * * p;
p的前面有两个 *号 。 *运算符的结合性是从右到左,因此
**p相当于 *( *p),显然 *p是指针变量的定义形式 。
第 8章 指针
8.6.1节中介绍了指针数组,该类数组中的元素是指针型数据,故指针数组中的元素也可看作是指向指针的指针,即所谓的二级指针变量 。
下面举例说明指针数组和指向指针的指针的使用方法 。
第 8章 指针
【 例 8.9】 指针数组与指向指针的指针使用举例 。
main()
{char *name[ ]={"Follow me","BASIC","Great Wall",
"FORTRAN","computer design"};
char **p;
int i;
for (i=0; i<5; i++)
{p=name+i; printf("%s\n",*p); }
}
第 8章 指针运行结果如下:
Follow me
BASIC
FORTRAN
Great Wall
Computer design
程序说明,p是指向指针的指针变量,在第一次执行循环体时,赋值语句,p=name+i;,使 p指向 name数组的 0号元素 name[0],*p是 name[0]的值,即第一个字符串的起始地址,
用 printf函数输出第一个字符串 ( 格式符为 %s),依次输出
5个字符串 。
第 8章 指针指针数组的元素也可以不指向字符串,而指向整型数据或实型数据等,例如:
int a[5]={1,3,5,7,9};
int *num[5]
int * *p;
for (i=0; i<5; i++)
num[i]=&a[i ];
例 8.10是一个指针数组的元素指向整型的简单例子。
第 8章 指针
【 例 8.10】
main()
{static int a[5]={1,3,5,7,9};
static int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
int **p,i; p=num;
for (i=0; i<5; i++)
{printf ("%d\t",**p); p++; }
}
运行结果为:
1 3 5 7 9
第 8章 指针
C语言有两种分配内存的方式:
静态内存分配方式和动态内存分配方式 。
静态内存分配方式:
是指所占用的数据存储位置和字节数在程序运行过程中是固定不变的 。
缺点是:对于事先无法准确估计数据量的情况下,无法做到既满足处理需要,又不浪费内存空间 。
8.7 指针与内存的动态分配第 8章 指针动态内存分配方式:
是指在程序运行过程中,根据程序的实际需要分配一块大小合适的内存 。 程序可以动态地分配给一个数组,也可以动态地分配给其它类型的数据单元 。 动态分配的内存只需要一个指针变量,记录内存的地址 。
优点是:动态内存区能用于不同的任务 。 对于一个任务分配占用了的内存,当该任务完成以后就可释放并收回,以后又可再把它们重新分配给其它的任务使用 。
第 8章 指针下面就介绍 C语言编译系统库函数提供的有关动态内存分配的几个函数 。
1 malloc( )函数调用方式:
void *malloc(unsigened size)
功能:在内存的动态存储区中分配一个长度为 size个字节的连续空间 。 此函数的返回值是一个指向分配域起始地址的指针 ( 基类型为 void),若此函数未能调用成功 ( 例如,内存空间不足 ),则返回空指针 ( NULL) 。
第 8章 指针如果要将函数调用的返回值赋给某个指针,则应根据该指针的类型将返回值进行强制类型转换 。
例如:
int *p1;
p1=(int*)malloc( 10) ;
其中,malloc(10)表示申请了一个长度为 10个字节的存储空间。 malloc( 10)的返回值经强制类型转换后赋给指针变量 p1,即执行语句 p1=(int*)malloc(10)后,表示用一个指向 int数据的指针变量 p1指向了这段存储空间的首地址。
第 8章 指针
2 colloc( )函数调用方式:
void *colloc(unsigened n,unsigened size)
功能:在内存的动态存储区中分配 n个长度为 size个字节的连续空间 。 此函数的返回值是一个指向分配域起始地址的指针 ( 基类型为 void),如果分配不成功,则返回空指针 ( NULL),若要将返回值赋给某个指针,要进行强制类型转换 。 例如:
float *p2;
p2=(float *) calloc(10,sizeof(float));
第 8章 指针表示系统申请了 10个连续的 float 类型的存储单元,并用指针 p2指向连续存储单元的首地址 。
3 free()函数调用方式:
void free(void*p)
功能:释放由 p指向的内存区,使这部分内存区能被其它变量使 用,该函数无返回值 。
例如,为将前面已申请的指向整型数据的指针 p1,所指向的确 10个字节的存储空间释放掉,可使用下列语句:
free(p1);
第 8章 指针
【 例 8.12】 编程输入一个班的某课程的学生成绩,计算其平均分,然后输出 。 班组人数由用户用键盘输入 。
程序如下:
#include <stdio.h>
#include<stdlib.h>
main( )
{ int *p,n,i,sum;
printf("Please enter array size:");
scanf("%d",&n); /*输入学生人数 */
第 8章 指针
p=(int *)malloc(n*sizeof(int)); /*向系统申请 n个 sizeof(int)字节的连续存储空间 */
printf("Please enter the score:");
for(i=0; i<n; i++) /*输入每个学生的分数 */
scanf("%d",p+i); /*累加变量 sum赋初值 */
sum=0;
for(i=0; i<n; i++)
sum=sum+*(p+i); /*计算总分数 */
printf(“aver=%d\n:”,sum/n); /*打印平均分 */
free(p); /*释放向系统申请的存储空间 */
}
第 8章 指针程序运行结果如下:
Please enter array size,5↙
Please enter the score,90 85 70 95 80
aver=84
说明:
⑴ malloc( )和 calloc()函数是动态内存分配函数,
如果分配失败则返回空指针,所以可以检查是否分配成功。若分配失败可用 exit(1)函数终止程序运行。
第 8章 指针
⑵ free()函数只能用于已由 malloc( )和 colloc( )
分配地址的指针来调用 。 如用其它指针来调用则可能破坏内存管理机制,造成对系统的破坏 。
⑶ malloc( ),caolloc( ),free( )函数原形对于 ANSI标准在,stdlib.h”头文件中,而其它 C编译则在,alloc.h”头文件中 。
第 8章 指针
8.8.1 指针变量作为函数的参数指针变量,既可以作为函数的形参,也可以作为函数的实参,指针变量作实参时,与普通变量一样,也是 "值传递 "。 即将指针变量的值传递给调函数的形参 。 但由于指针变量的值是一个地址,实际是实现
,地址的传递,。
8.8 指针与数组作为函数的参数第 8章 指针
【 例 8.13】
main()
{void sub (int *px,int *py);
int x=8,y=11;
printf("%d,%d\n",x,y);
sub (&x,&y);
printf("%d,%d\n",x,y);
}
第 8章 指针
void sub (int *px,int *py)
{*px=10;
*py=20;
}
运行结果:
8,11
10,20
第 8章 指针说明:
对 sub()函数调用时,实参是 x变量的地址和 y变量的地址,它们分别传递给指针变量 px和 py,即参数传递后 px指向 x变量,py指向 y变量,执行 *px=10;
*py=20;语句后把 px地址的内容赋值为 10,py地址内容赋值为 20。 函数调用结束后,这两个地址的值已经改变,即 x=10; y=20;此例采用传址方式,通过被调函数参数的改变,影响主调参数 。
第 8章 指针
【 例 8.14】 求一维数组中全部元素之和
int add (int *pt,int n)
{int i,sum=0;
for (i=0; i<n; i++)
sum=sum+pt[i];
return (sum);
}
第 8章 指针
main( )
{int a[10]={1,2,3,4,5,6,7,8,9,10};
int *p,total;
p=a;
total=add(p,10);
printf("total=%d\n",total);
}
运行结果:
total=55
第 8章 指针
8.8.2数组名作为函数的参数指针的本质是地址,而数组名代表数组的首地址 。 指针可以作为参数,数组名也就可以做参数,
一维数组名作为函数形参时,数组名后面要跟着
[ ],[ ] 中间可以有整数常量,也可以没有,其实一维数组参数的函数名后面的 [ ] 只是一个标记,
中间的整数将被忽略 。
第 8章 指针
【 例 8.15】 求一维数组各元素的平均值
float average(float p[],int n)
{int i;
float sum=0.0,ave;
for (i=0; i<n; i++)
sum=sum+p[i];
ave=sum/n;
return(ave);
}
第 8章 指针
#define N 10
main()
{ float num[N],aver;
int i;
for (i=0; i<N; i++)
scanf("%f",&num[i]);
aver=average(num,N) ;
printf("average=%8.2f\n",aver);
}
第 8章 指针运行情况如下:
10 20 30 40 50 60 70 80 90 100↙
average=55.00
程序说明:
本例中,首先定义了一个实型函数 average( ),有一个形参为实型数组名 p。 在函数体中,把各元素值相加求出平均值,返回给主函数 。
第 8章 指针实参与形参的对应关系可以有以下几种:
实参 形参数组名 数组名数组名 指针变量指针变量 数组名指针变量 指针变量第 8章 指针一维数组的地址可以作为函数参数传递,二维数组的地址也可作函数参数传递,也可以用指针娈量或数组名作形参以接受实参数组名传递来的地址 。 在二维数组名作为函数的形参时,数组名右边第一个 [ ]可以为空 ( 即使不空也会被忽略 ),第二个 [ ]中间不能为空,它是二维数组的列数 。
第 8章 指针二维数组指针作函数参数举例 。
【 例 8.16】 有一个班,3个学生,各学习 4门课,计算总平均分数,以及第 n个学生的成绩 。
main()
{void average(float *p,int n );
void search(float (*p)[4],int n);
float score[3][4]={{65,67,70,60},{80,87,90,81},
{90,99,100,98}};
average(*score,12); search(score,2);
}
第 8章 指针
void average(float *p,int n) /*定义求总平均分的函数,
形参是指针变量 */
{float *p_end;
float sum=0,aver;
p_end=p+n-1;
for(; p<=p_end; p++)
sum=sum+(*p);
aver=sum/n;
printf("average=%5.2f\n",aver);
}
第 8章 指针
/*定义求第 n个学生成绩的函数,形参是二维数组名 */
void search (float p[ ][4],int n)
{int i;
printf("the scores of No,%d are,\n",n);
for (i=0; i<4; i++)
printf("%5.2f",p[n][i]);
}
第 8章 指针程序运行结果如下:
average =82.25
the scores of NO,2 are,
90.00 99.00 100.00 98.00
第 8章 指针带参数 main函数一般形式如下:
main(int argc,char *avgv[ ])
说明,1,main的形参名并不一定非用 argc和 argv 不可,只是习惯上用这两个名字 。
2,argc用于统计命令行中参数的个数 ( 包含 C文件名 ),
argv数组各元素依次存放命令行各参数所形成字符串的首地址 。
8.9 带参数的 main函数第 8章 指针
main函数是由系统调用的:当系统处于操作命令状态时,以键盘输入文件名,计算机就运行这个文件
( 可执行的二进制目标文件 ) 。 例如有一源文件名为
cfile.c,经过编译连接后得到的目标文件名为
cfile.exe(在 Turbo C情况下,执行文件的后缀为,exe)。
命令行格式:
C文件名 [参数 1 [参数 2 …… 参数 n ]]
说明:,C文件名,为所要运行的可执行文件名,[ ]表示这项可以省略,各参数间用空格隔开 。
第 8章 指针
【 例 8.17】 带参数 main函数应用举例 。
main(int argc,char *argv[])
{while(argc>1)
{++argv;
printf("%s\n",*argv);
--argc;
}
}
第 8章 指针如果从键盘输入的命令行为:
Cfile Computer C_Language↙
则输出为:
Computer
C_Language
第 8章 指针一个函数可以带回一个整型值,字符值,实型值等,
也可以带回指针型的数据,即地址 。
返回指针值的函数一般定义形式为:
类型名 *函数名 ( 参数表 )
例如:
int *a(int x,int y)
8.10 返回指针值的函数第 8章 指针
【 例 8.18】 编写一个 strchr函数,它的作用是在一个字符串中找一个指定的字符,返回该字符的地址 ( 库函数中有此函数,今要求自己编写 ) 。
char *strchr (char *str,char ch)
{while (*str ++!= '\0')if(*str= =ch) return (str);
return (0);
}
第 8章 指针
main()
{ char *pt,ch,line[]="I love China";
ch='c';
pt=strchr (line,ch);
printf ("\nstring starts at address %o,\n",line);
printf ("First occurrence of char %c is address %o,\n".ch,pt);
printf ("This is position %d (staring from 0) \n",pt-line);
}
第 8章 指针运行结果如下:
string starts as address 177720
First occurrence of char C is address 177727
This is position 7(starting from 0) 。
返回指针的函数是很有用的,许多库函数都是返回指针值的 。 读者应很好地掌握它 。
第 8章 指针
8.11.1函数指针的定义可以用指针变量指向整型变量,字符串,数组,也可以指向一个函数 。 一个函数在编译时被分配给一个入口地址,这个入口地址就称为函数的指针,即函数指针 。
函数指针变量定义的一般形式:
数据类型标识符 ( *指针变量名 ) ( )
8.11 函数指针的定义与引用第 8章 指针例如:
int (*p)( )
指向整型函数的指针变量 。 注意 *p两侧的括号不能省略,否则成了,返回指针值的函数,了 。
第 8章 指针
8.11.2 函数指针变量的赋值与数组名代表数组的起始地址一样,函数名代表函数的入口地址 。
其格式为,函数指针变量 =函数名;
例如,p=max;
假设已定义了一个函数 max,则上述赋值语句的作用是将 max函数的入口地址赋给指针变量 p。即使 p指向函数 max。注意函数名后不能带括号和参数。
第 8章 指针
8.11.3函数指针变量的引用函数指针变量的引用格式为:
( *函数指针变量 ) ( 实参表列 ) ;
例如:若 p指向了函数 max,则 (*p)(a,b)就相当于 max(a,b)。
即用实参 a,b调用函数 max 。
注意,对指向函数的指针变量 p,象 p+n,p++,p-- 等运算是无意义的。
第 8章 指针
8.11.4 函数指针变量作为函数的参数函数指针变量常用的用途之一是把指针作为参数传递到其它函数。这个问题是 C语言应用的一个比较深入的部分,在本书中只作简单的介绍,
以便在今后用到时不致感到困惑。进一步的理解和掌握有待于读者今后深入的学习和提高。
第 8章 指针指向函数的指针变量作为参数,实现函数地址的传递,也就是将函数名传给形参,其原理可以简述如下:
有一个函数 ( 假设函数名为 sub),它有两个形参
( x1和 x2),定义 x1和 x2为指向函数的指针变量 。 在调用函数 sub时,实参用两个函数名 f1和 f2给形参传递函数地址 。 这样在函数 sub中就可以调用 f1和 f2函数了 。
第 8章 指针例如:实参函数名 f1 f2
↓ ↓
sub ( x1,x2)
int(*x1) ( ),(*x2) ( ); /*定义 x1,x2为函数指针变量 */
{int a,b,i,j;
a=(*x1)(i); /*调用 f1函数 */
b=(*x2)(i,j); /*调用 f2函数 */
……
}
第 8章 指针其中 i和 j是函数 f1和 f2所要求的参数 。 函数 sub
中的指针变量 x1,x2在函数 sub未被调用时并不占内存单元,也不指向任何函数 。 在 sub被调用时,把实参数 f1和 f2的函数入口地址传给形参指针变量 x1、
x2,使 x1和 x2指向函数 f1和 f2,见图 8.25。 这时,
在函数 sub中,用 *x1和 *x2就可以调用函数 f1和 f2。
(*x1) ( i )就相当于 f1(i),(*x2) (i,j)就相当于 f2(i,j)。
第 8章 指针第 8章 指针说明:
如果在每次调用 sub函数时,要调用的函数不是固定的,这次调用 f1和 f2,而下次要调用的 f3和 f4,第三次要调用的是 f5和 f6。 这时,用指针变量就比较方便了 。
只要在每次调用 sub函数时给出不同的函数名作为实参即可,sub函数不必作任何修改 。 这种方法是符合结构化程序设计方法原则的,是程序设计中常使用的 。
第 8章 指针
1,指针即地址,对象 ( 变量,数组,字符串,函数等 ) 在内存中的起始地址,称为对象的指针 。 指针变量是指专门用于存储其他对象指针的变量,它只能用来存放指针 ( 即地址 ) 。
2,针变量定义的一般格式为:
数据类型 *指针变量 1[,*指针变量 2…… ];
8.12 本章小结第 8章 指针与指针变量相关的运算符有:
l ( 1) 地址运算符 &:通过,&变量名,,可以给指针变量赋值 。
( 2) 指针运算符 *:通过,*指针变量名,,可以间接访问指针变量所指向的变量 。
3,当 px和 py是指向数组的指针变量时,可以进行的算术运算有:
( 1) px± n 将指针从当前位置向前 ( +n) 或回退 ( -n) n
个数据单位,而不是 n个字节 。
( 2) px++,++px和 px--,--px 其中 px++使 px指向下一个数组元素,px--使 px指向上一个数组元素 。
第 8章 指针
( 3) px-py,两指针之间的数据个数,而不是指针的地址之差 。
4,指向数组的指针变量 ( px和 py) 间的关系运算:
表示两个指针所的地址之间,位 置的前后关系:前者为小,后者为大 。
第 8章 指针
5,假设有 int a[10],*p=a;语句,则,
(1) a,p,&a[0]表示 a[0]的地址 。
(2) a+i,p+i,&a[i]表示 a[i]的地址 。
(3) *(a+i),*(p+i),p[i],表示数组元素 a[i]的值 。
6,假设有 int a[3][4],*p=a[0];语句:则:
( 1) p,a,a[0],表示 a[0][0]元素的地址 。
( 2) (p+i*4+j),*(a+i)+j,*(a[0]+i)+j表示 a[i][j]元素的地址 。
( 3) *( p+i*4+j),*(*a+i)+j),*(*(a[0]+i)+j)表示 a[i][j]
元素的值 。
第 8章 指针
7,假设有 int a[3][4],(*p)[4]=a[0];语句,则:
( 1) p[0],p,a,a[0],表示 a[0][0]元素的地址,也可以说是第 0行的首地址 。
( 2) p[i]+j,*(p+i)+j,*(a+i)+j,a[i]+j表示 a[i][j]
元素的地址 。
( 3) *(p[i]+j),*(a[i]+j),*(*(p+i)+j),*(*(a+i)+j)表示 a[i][j]
元素的值第 8章 指针
8,通过字符数组名或指向字符串的指针变量,可以将字符数组中各元素的值 ( 一个字符 ) 作为一个整体输出 ( 即输出一个字符串 ) 。 而其他类型的数组,是不能用数组名来一次性输出它的全部元素的,只能逐个元素输出 。
9,指针数组:是指数组的每个元素都是一个指针数据。 指针数组的定义格式:
数据类型 *数组名 [元素个数 ]
第 8章 指针
10,指向指针的指针变量是一个两级的指针变量,其定义格式为:
数据类型 **指针变量名
11,指针变量,数组名都可作为函数的形参和实参 。
实参 形参数组名 数组名数组名 指针变量指针变量 数组名指针变量 指针变量第 8章 指针
12,带参数的主函数 main( )的形式:
main(int argc,char *argv[])
{……}
( 1) 命令行:可执行文件名 实参 1 [ 实参 2 ……]
( 2) 形参 argc是命令行中参数的个数 ( 可执行文件名本身也算一个 ) 。
( 3) 形参 argv是一个指向实参字符串的字符型指针数组,元素 avgv[0]指向第 1个实参字符串,元素 argv[1]指向第 2个实参字符串,以此类推 。
第 8章 指针
13,返回指针值的函数称为指针函数,其定义格式:
函数类型 *函数名 ( 参数表 )
当函数类型为,void”时 ( 称为无类型指针 ),
返回值指针可以指向任何类型的数据 。 在使用返回值为无类型指针的函数时,必须将其返回值的类型强制转换为被赋值指针变量的数据类型,以免出错 。
第 8章 指针
14,指向函数的指针变量的常用用途之一,就是将函数指针作参数,传递到其他函数 。
( 1) 定义格式:函数类型 ( *指针变量 ) ( ) ;
( 2) 赋值:指向函数的指针变量 =函数名;
( 3) 调用格式,( *函数指针变量 ) ( [实参表 ])