C语言程序设计教程课件:指针_第1页
C语言程序设计教程课件:指针_第2页
C语言程序设计教程课件:指针_第3页
C语言程序设计教程课件:指针_第4页
C语言程序设计教程课件:指针_第5页
已阅读5页,还剩85页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

指针6.1指针与指针变量6.2指针与数组6.3指针与函数本章小结

本章要点

指针是C语言的重要数据类型,在C语言中占有重要的地位,是C语言的精髓,也是比较难掌握的内容。C语言的高度灵活性及其超强的表达能力,在很大程度上源于巧妙而恰当地使用指针。灵活、正确地使用指针,能够有效、方便地处理数组和字符串,在函数之间传送数据,有效地表示和处理复杂的数据类型,特别有利于动态数据的管理。指针可以直接处理内存地址,因此可以使编写的程序简洁、紧凑和高效。

6.1指针与指针变量

6.1.1指针的概念1.内存及其地址我们知道,尽管计算机技术发展日新月异,但现代计算机仍然采用“基于存储程序和程序控制”的冯·诺依曼原理。“存储程序”就是在程序运行之前将程序和数据存入计算机内存中,而内存是以字节为单位的连续存储空间。为了方便地寻找内存中存放的程序实体(变量、数组、函数等),每个内存单元都有一个编号,称为内存地址(简称地址)。

C语言规定,编程时必须首先说明变量名、数组名。编译时,系统会根据程序中定义的数据类型给变量或数组分配相应大小的内存空间(即多少个内存单元)。例如:

charc;

nti;

floatf;

c是字符型变量,在内存中占1个字节(即占1个存储单元),地址为2010;i是整型变量,在内存中占4个字节,分别是2011~2014,地址为2011;f是单精度变量,在内存中占4个字节,分别是2015~2018,地址是2015,如图6-1所示。图6-1变量c、i和f的地址

2.指针与指针变量

(1)指针:某个程序实体(如变量、数组或函数等)所占用的存储单元的首地址。例如在图6-2中,地址2011是变量i的指针。由于地址唯一确定程序实体的存储位置,就像路标一样,故形象地称为指针。图6-2指针和指针变量

(2)指针变量:专门存放变量(或其他程序实体)地址的变量。

例如,可以通过语句iPointer=&i;将变量i的地址(2011)存放到iPointer中,iPointer就是指针变量,如图6-2所示。这样,由变量iPointer的值(地址,图6-2中为2011)就可以找到变量i,称变量iPointer指向变量i,它存放的地址就称为“指针”。因此,指针就是地址。

指针变量也需要存储单元(存放地址值的整数),它本身也有地址。

3.变量地址的获取

由于变量的存储单元是在编译时(对静态存储变量)或程序运行时(对动态存储变量)分配的,因此变量的地址不能人为确定,而要通过取地址运算符&获取。例如,在如下的程序段中:

charc;

inti;

floatf;

scanf("%d%f%c",&i,&f,&c);

由&i、&f和&c分别得到变量i、f和c的内存地址。值得注意的是,由于常量和表达式没有用户可操作的内存地址,因此&不能作用在常量或表达式上。

4.直接访问与间接访问

(1)直接访问:按变量名或其地址存取变量值。例如:

inti;

scanf("%d",&i);

printf("%d\n",i);

(2)间接访问:通过指针变量(如iPointer)访问它指向的变量(如i)的方式叫间接访问方式。即先找到存放“i的地址”的变量iPointer,从中取出i的地址(2011),然后到2011、2012、2013、2014四个存储单元取出i的值。

6.1.2指针变量的定义与初始化

1.指针的类型

指针也是有类型的,指针的类型又称指针的基类型。指针类型就是它所指向的程序实体(如变量、数组)的类型,由此可确定程序实体所占内存的字节数。当指针变量移动(存放的地址值变化)时,以这个字节数为单位,因此就用这个类型定义指针变量,且往往与它指向的程序实体一道定义,称为“指针变量的类型”或简称“类型”。

2.指针变量的定义

对指针变量的定义包括三个内容:

(1)指针类型说明,即定义变量为一个指针变量;

(2)指针变量名;

(3)变量值(指针)所指向变量的数据类型。

指针变量定义的形式为:

类型*指针变量名1,*指针变量名2,…;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向变量的数据类型。定义了指针变量以后,就为指针变量分配存储单元,准备存储地址值。例如:

int*p1;

表示p1是一个指针变量,它的值是某个整型变量的地址。或者说p1指向一个整型变量,至于p1究竟指向哪一个整型变量,应由向p1赋予的地址来决定。

说明:

(1)“*”表示其后的变量是指针变量,而不是指针变量名的一部分,即指针变量名是p1、p2、p3,不是*p1、*p2、*p3。

(2)一个指针变量只能指向同类型的变量,如p2只能指向浮点变量,不能时而指向一个浮点变量,时而又指向一个整型或字符型变量。

(3)指针变量中只能存放地址,不能将一个非地址类型的数据(如常数)赋给一个指针变量,但0(NULL)除外。例如:

p1=100; /*错误*/

p1=NULL; /*正确,表示它不指向任何对象,即空指针*/

(4)指针变量定义后,如果没有初始化,则变量值是不确定的,使用前必须先赋值。

3.指针变量的初始化

上面定义的指针变量都没有存储地址值,也就没有指向,其值是随机的。指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。只有被赋值以后,指针变量才有确定的指向。

给指针变量赋地址值的方法有:

(1)在程序执行部分赋值。

(2)定义指针变量时初始化赋值。定义指针变量时初始化赋值的形式为:

类型*指针变量名=&变量名,…;

例如:inta;

int*p=&a;

或者:inta,*p=&a;

6.1.3指针变量的使用

C语言中提供了两个与指针有关的运算符:

(1) &:取地址运算符,作用是获得变量所占用的存储单元的首地址(即该变量的地址)。例如:

inta=10,*p;

p=&a; /*&a表示变量a的地址*/

(2)  *:指针运算符(或称“间接访问运算符”),作用是通过指针变量来访问它所指向变量的值(取数据或存数据)。例如:

printf("%d\n",*p); /*输出指针变量p所指向变量a的值,*p与a等价,即10*/

说明:

★在定义指针变量时,“*”表示其后的变量是指针变量;在执行部分的表达式中,“*”是指针运算符。

★&和*都为单目运算符,优先级高于所有双目运算符,与其他的单目运算符具有相同的优先级和结合性(右结合性)。

★&a不能出现在赋值号的左边。

★运算符&只能作用于变量和数组元素,不能作用于表达式或常量。

★根据*运算符的作用,*运算符和取地址运算符&互逆,即:

*(&a)==a&(*p)==p

★ p、&a与&*p完全等价,即下面3个语句等价:

scanf("%d",p);

scanf("%d",&a);

scanf("%d",&*p);

 ★a、*p与*&a完全等价,即下面3个语句等价:

printf("%d",a)

;

printf("%d",*p)

;

printf("%d",*&a)

;

★C语言中,*运算符表示两种运算,编译系统能够自动根据上下文环境来区别*的作用。

inta=2,b,c;

int*p=&b

; /*指针变量p指向b*/

*p=3; /*表示指针运算符*/

c=a**p /*第1个*表示乘法运算符,第2个*表示指针运算符*/

★指针变量可出现在表达式中,例如:

inta,b,*p=&a; /*指针变量p指向a,则*p可出现在a能出现的任何地方*/

b=*p+1; /*把a的内容与1相加后赋给b*/

b=++*p; /*相当于++(*p),即把p

指向的存储单元内容加1后再赋给b*/

b=*p++; /*相当于b=*p;p++;*/

6.1.4指针的基本运算

指针是一个地址的概念,指针的值是某种类型变量的地址,可以用指针间接访问某些变量,参与数值运算。然而指针本身也可以参与运算,它可以参与赋值运算、部分算术运算和关系运算。

1.指针变量的赋值运算

如果指针变量没有一个确定的指向而进行*运算,在编译时会出现警告性错误。为指针变量赋值,使其有明确的指向,是使用指针变量的前提。而C语言中,变量的地址是编译系统分配的,对用户完全透明,用户不知道变量的具体地址。

因此,对指针赋值的常见方式有:

(1)把符号常量NULL赋给指针变量。

例如:int*p=NULL;

说明:NULL由stdio.h定义为0,它也等同于'\0',意为“空指针”。如果一个指针的值为NULL,表示它不指向任何对象。

(2)把一个变量的地址赋给指针。

例如:语句inta,*p=&a;

说明:该语句执行后,p指向a。不要把一个变量的地址赋给与该变量类型不同的指针变量,否则会出现警告性错误。

(3)把一个指针的值赋给同类型的另一个指针。

例如:inta,*p=&a,*q;q=p;

说明:可以在相同类型的指针之间进行赋值,结果是两个指针指向同一个对象。

【例6-1】编一程序,交换两个变量的值。

执行过程如图6-3、图6-4、图6-5、图6-6所示。

2.指针的算术运算

指针的值是某种类型变量地址所允许的整数,但指针不是整数,不能将任意整数赋给指针变量。因为指针的值是某特定类型变量的地址,而变量地址是由编译系统分配的,所以指针运算不能像整型变量那样,对指针进行所有的算术运算。

除了赋值运算外,有意义的指针运算包括下面要介绍的算术运算和关系运算。但前提是:参加运算的指针代表了一些连续的存储单元。前面章节介绍了,只有数组元素才占据内存一些连续的存储单元,而每个数组元素相当于一个普通变量。所以,下面介绍的指针运算,原则上只适用于数组和字符串两种情况。

指针可进行的算术运算有:

(1)指针加、减整数运算。

假定有定义:

intn;float*p;

表达式p±n(其中n≥0)指向的是p所指的数据存储单元之后(或之前)的第n个数据存储单元。注意,这里的数据存储单元不是内存单元,内存单元的大小是固定的(一般按1字节计算),而数据存储单元的大小与数据类型有关。

在VC中,整型数据占4个字节,p是指向整型数组元素的指针,假设p的值是2010,则p+1=2014,p+2=2018,…,p+n=2010+4*n,如图6-7所示。图6-7float数据存储单元

(2)指针变量的++和--运算。

注意:对指针进行++或--运算,指针的值变成后一个或前一个变量(或元素)的地址,并不表示指针的值加1或减1。

例如,若有语句:

inta[10],*p=&a[0];

并假设数组元素a[0]的地址为2010,当执行p++后,p指向数组元素a[1]。因为a数组的每个元素在内存中占4个字节,故a[1]的地址为:

2010+sizeof(int)=2014

所以执行p++后,p的值为2014。

(3)指向同一数组不同元素的指针减法运算。

两个类型相同的指针可以相减,其结果是这两个地址差之间能够存放的数据个数(数据类型为指针所指向变量的类型)。

例如,若有定义:

int*p1,*p2;

假设p1指向2010,p2指向2018,则p2-p1的值为:(2018-2010)/sizeof(int)=2,如图6-8所示。图6-8指针减法运算

3.指针的关系运算

使用关系运算符<、<=、>、>=、==、和!=可以比较指针值的大小。

指针的关系运算即比较指针(地址)大小的运算。如果p和q是指向相同类型的指针变量,并且p和q指向同一段连续的存储空间,p的地址值小于q,则表达式p<q的值为1,否则该表达式的值为0。

6.2指 针 与 数 组

6.2.1指针与一维数组一个数组是由连续的一块内存单元组成的。数组的地址是指数组的首地址,即编译时给数组分配的一段存储空间的起始地址。一个数组也是由各个数组元素(下标变量)组成的,每个数组元素按其类型不同占有几个连续的内存单元。数组名代表数组起始地址,是一个常量。数组中每个数组元素也是占有一定存储空间的,即每一个数组元素也有地址。

定义一个指向数组元素的指针变量的方法,与前面定义指针变量的方法相同,格式仍然为:

类型*指针变量名,……;

例如:

inta[10]; /*定义a为包含10个整型数据的数组*/

int*p; /*定义p为指向整型变量的指针*/

应当注意:因为数组为int型,所以指针变量也应为指向int型的指针变量。下面是对指针变量赋值:

p=&a[0];

把a[0]元素的地址赋给指针变量p。也就是说,p指向a数组中下标为0的元素。

获得地址的方法为:

指针变量名=&数组元素;

或:

指针变量名 = 一维数组名;

当一个指针指向数组后,对数组元素的引用,既可以使用下标法,也可以使用指针法。并且,用指针访问数组元素,程序的效率更高;相对来说,使用下标访问数组元素程序更清晰。

说明:

(1)数组名就是指针,是数组的首地址,它是地址常量,不能像指针变量那样前后移动。下面的赋值是错误的:

a=a+1;

(2)指针变量可以存放数组的起始地址,也可以存放数组元素的地址。

(3)指向数组元素的指针变量,与指向普通变量的指针变量用法相同。

例如:

inta[10],*p;p=a; /*p存放数组a的首地址,等价于:p=&a[0];*/

根据前面介绍的指针算术运算规则,p+1指向数组元素a[1],p+2指向数组元素a[2],p+i指向数组元素a[i]。对于inta[10],*p=a; ,则:

★p+i和a+i都是数组元素a[i]的地址,即&a[i]。

★*(p+i)和*(a+i)就是数组元素a[i]。

★指向数组的指针变量,也可将其看做是数组名,因而可按下标法来使用。

例如,p[i]等价于a[i],也等价于*(p+i)。

★*p表示取得当前所指元素之值。

★p++(或p+=1)表示p指向下一个元素,而不是简单地使指针变量p的值+1。其地址值为:

★p(地址)+1*size(size为一个元素占用字节数)

例如,设指针变量p的当前值为2010,则p+1的值为2010+1*4=2014,而不是2011。

★*p++等价于*(p++),相当于a[i++]。先按p的原值进行*运算,即*p,得到a[0]的值,然后使p的值改变p=p+1,指向下一个数组元素a[1]。

★*p--等价于*(p--),先*p,再p=p–1。

★*(++p)相当于a[++i],表示p=p+1,再*p。

★*(--p)相当于a[--i],表示p=p-1,再*p。

★(*p)++表示p所指向元素的值加1,即a[i]+1。

【例6-2】编写程序,输入5个整数存储在数组中并输出。

算法一:指针法。

算法二:指针法,通过数组名计算元素的地址,找出元素的值。

算法三:下标法。

算法四:下标法。

算法五:指针移动法,使指针变量p每次进行自加运算,指向下一个元素。

从上述5种算法可以看出:

(1)指针p指向a数组的首地址时,p+1、a+i都表示数组元素a[i]的地址,所以*(p+i)、*(a+i)都表示a[i]的内容。

(2)可以将指向数组首地址的指针变量用数组方式操作,即a[i]和p[i]等价,此时下面4项是等价的:

a[i]p[i]*(p+i)*(a+i)

(3)上面前4个算法中,指针变量p保持不变;而算法5中p发生了变化,但不允许进行a++操作,因为a作为数组名是地址常数,其值是不能修改的。所以,下面算法是错误的:

6.2.2指针与二维数组

用指针变量可以指向一维数组,也可以指向二维数组。二维数组的首地址称为二维数组的指针,存放这个指针的变量称为指向二维数组的指针变量。二维数组的指针并不是一维数组指针的简单拓展,它具有自己的独特性质,在概念和使用上,指向二维数组的指针比指向一维数组的指针更复杂。

1.二维数组的地址

设有整型二维数组定义如下:

inta[2][3]={{1,2,3},{4,5,6}};

设数组a的首地址为2010,则各元素的地址及其值如图6-9所示。

根据数组的定义,一个二维数组由若干个一维数组所组成,其中每一个一维数组包含若干个元素。因此,数组a可分解为2个一维数组,即a[0]、a[1]。每一个一维数组又含有3个元素,如图6-10所示。a[0]数组含有a[0][0]、a[0][1]、a[0][1]共3个元素,a[1]数组含有a[1][0]、a[1][1]、a[1][2]共3个元素。图6-9各元素的地址及其值图6-10数组a分解为2个一维数组

从二维数组的角度看,数组a代表二维数组的首地址,也是第0行的首地址,等于2010。a+1代表第1行的首地址,从a[0]到a[1]要跨越一个一维数组的空间(包含3个整型元素,共12个字节),则a+1为2022。这种地址称为行地址。

a[0]、a[1]既然是一维数组名,C语言规定:数组名代表数组首元素的地址,因此a[0]表示第0行中第0列元素的地址,即&a[0][0],值为2010;a[1]表示第1行第0列元素的地址,即&a[1][0],值为2022。a[0]+1表示第0行中第1列元素的地址&a[0][1];a[1]+2=&a[1][2]……这种地址称为列地址。

虽然a和a[0]的值都是2010,但含义不一样,如图6-11所示。图6-11a[i][j]的地址

说明:

(1)行地址+1的作用通俗地讲是跳过一行,a+1将在a(值为2010)的基础上跳过一行,即3个整型数据,a+1的值为2010+3*4=2022。

(2)列地址+1的作用通俗地讲是跳过一列,a[0]+1将在a[0](值为2010)的基础上跳过一列,即1个整型数据,a[0]+1的值为2010+1*4=2014。所以,a[0]+1与&a[0][1]等价。以此类推,a[i]+j与&a[i][j]等价。

前面介绍了*(a+i)与a[i]等价,所以a[0]+1和*(a+0)+1等价,都是&a[0][1],以此类推,a[i]+j和*(a+i)+j等价,都是&a[i][j]。

因此,如果要访问二维数组元素a[i][j],可以表示为:

a[i][j]≡*(a[i]+j)≡*(*(a+i)+j)≡(*(a+i))[j]

其中a[i][j]为下标表示法,其余均为地址表示法,其中考虑到a[i]≡*(a+i)。

【例6-3】用指针法实现输入一个二维数组并输出。

2.用于二维数组的指针变量

把二维数组a分解为一维数组a[0]和a[1]之后,可定义指向一维数组的指针变量(行指针变量),其定义的格式为

数据类型(*指针变量名)[一维数组元素个数];

其中:“数据类型”为所指数组的数据类型;“*”表示其后的变量是指针变量;“一维数组元素个数”表示二维数组分解为多个一维数组时,一维数组的长度,即二维数组的列数。【例6-4】用指向二维数组的指针变量实现输入一个二维数组并输出。

6.2.3指针与字符串

在上一章中介绍了用字符数组处理字符串的方法,本节将介绍用指针处理字符串的方法。使用指针处理字符串会更加灵活、方便。引用字符串时,既可以逐个字符引用,也可以整体引用。

假设有如下定义和初始化程序:

chars[6]="Hello",*p="Hello";

其中:s是字符数组,最多存放6个字符;p是指向字符类型的指针变量,存放的是字符串常量的首地址。初始化后,效果如果6-12所示。图6-12字符数组和字符指针

字符数组和字符指针都可以处理字符串,那么它们有什么区别呢?它们的区别见

表6-1。

【例6-5】用字符指针变量指向一个字符串,然后输出该字符串。

6.2.4指针数组

如果数组的每个元素都是指针类型的数据,则称这种数组为指针数组。

指针数组的定义形式为:

数据类型*数组名[常量表达式]

例如:

int*p[4];

说明:由于[]比*优先级高,因此p先与[]结合,表明p为数组名,数组p中包含4个元素。然后再与*结合,*表示此数组元素是指针类型,每个元素都指向一个整型变量,即每个元素相当于一个指针变量。

6.2.5指针与动态内存分配

通常情况下,在程序运行之前程序中变量的个数和类型是确定的。但有时在程序运行前无法预见需要占用多大的内存空间,需要在运行程序时根据具体情况向系统申请分配内存空间,这种分配机制称为动态内存分配。C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态分配内存空间,还可以把不再使用的内存空间收回待用,提高了内存资源的使用效率。

1.申请内存空间函数malloc

调用形式:

(指针所指对象的数据类型*)malloc(sizeof(指针所指对象的数据类型)*个数)

功能:在内存的动态存储区中申请一块指定字节大小的连续空间。若申请成功,则返回指向所分配内存空间的起始地址;若申请内存空间不成功,则返回NULL(值为0)。该函数的返回值为(void*)类型,因此在具体使用时,要将该函数返回值转换到特定指针类型,赋给一个指针变量。

2.计数动态内容分配函数calloc()

调用形式:

(指针所指对象的数据类型*)calloc(个数,sizeof(指针所指对象的数据类型))

功能:在内存的动态存储区中分配指定个数的连续空间,每一存储空间的长度为sizeof求出的长度,并且分配后把存储块全部初始化为0值。如申请成功,则返回指向所分配内存空间的起始地址;若申请内存空间不成功,则返回NULL(值为0)。

3.释放内存空间函数free

调用形式:

free(指针变量名)

功能:释放由动态内存分配函数申请到的整块内存空间,指针变量名为指向要释放空间的首地址。如果该指针变量是空指针,则free函数什么都不做。该函数无返回值。

例如:

free(p); /*释放p指针所指向的存储空间*/

为了有效利用动态存储区,在知道某个动态分配的存储块不再使用时,应及时将它释放。

6.3指 针 与 函 数

6.3.1指针及数组名作为函数参数指针及数组名作为函数的参数时,是以数据的地址作为实参调用该函数,即作为参数传递的不是数据本身,而是数据对应的地址,使实参和形参指向同一存储单元。所以,调用函数与被调函数存取的将是相同的一组空间,即双向的“地址”传递,也就是说函数调用后,实参指向的对象的值可能会发生变化。

如果实参是指针或数组名,则与之对应的被调函数中的形参应为指针变量或数据名,并且其数据类型必须与被传递参数的数据类型保持一致。实参与形参对应关系有4种组合,见表6-2所示。

(1)实参和形参均为数组名。

(2)实参是数组名,形参是指针变量。

(3)实参是指针变量,形参是数组名。

(4)实参和形参均为指针变量。

【例6-7】利用指针做参数实现两个数据的交换。

说明:调用swap函数时,实参是&a和&b,即把实参变量a和b的地址传递给形参指针变量x和y,即x和y分别指向了a和b。在函数里对指针所指对象的内容进行了交换,调用结束返回main函数后,a和b的内容也交换了。

【例6-8】编写一个字符串的复制函数。

说明:数组名传给指针变量,即指针指向这片存储单元的首地址,对指针所指内容的操作就是对实参数组的操作。因此,函数调用后数组b的内容改变了。

6.3.2指针作为函数的返回值

函数的返回值可以是各种基本数据类型,如整型、字符型和实型等,也可以是指针类型,这样的函数称为返回指针型的函数。

定义返回指针型的函数形式为:

数据类型*函数名(形参表)

例如:

int*fun(intx)

其中fun是函数名,x是形式参数,形参类型为整型,函数名fun前面的数据类型int表示返回的指针指向整型变量,调用该函数后可以返回一个指向整型数据的指针(即地址)。

说明:函数fun两边的运算符分别是*和(),其中()的优先级高于*,因此fun先和()结合,表明fun是函数名;函数名前有一个*,表示此函数返回值类型是指针。

6.3.3指向函数的指针

程序中的每个函数经过编译后,其目标代码在内存中是连续存放的,并被分配一个入口地址(即首地址)。C语言中的指针,不仅可以指向各种数据类型的变量和数组,而且还可以指向函数。C语言规定,函数名代表该函数的入口地址。如果把这个地址送给某个特定的指针变量,这个指针变量就指向了函数,通过这个指针变量可以实现函数的调用。把这种指向函数的指针变量称为“

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论