C语言程序设计(第二版)学习笔记——第九章

第九章 指针

9.1指针与指针变量

指针是 C 语言中的一个重要概念,也是 C 语言的重要特色。

指针类型是 C 语言的一种特殊的数据类型。

正确而灵活地应用指针,可以有效地表示复杂的数据结构、动态地分配内存、方便地使用字符串、有效而方便地使用数组及直接处理内存地址等。

使用指针编写的程序在代码质量比用其他方法效率更高,因此指针在 C 程序中用得非常多。

9.1.1 指针的概念

本质上指针就是地址.指针是一种数据类型,是一个变量在内存中所对应单元的地址。

指针是用来存放地址的量。这个地址可以是变量的地址,也可以是数组、函数的起始地址,还可以是一个指针变量的地址。

某个指针变量存放了一个变量的地址,就可以说该指针指向了这个变量。

高级语言中的变量具有3个属性:变量的名、变量的值和变量的地址。

在程序中,需要定义一个变量时,首先要定义变量的数据类型,数据类型决定了一个变量在内存中所占用的存储空间的大小及允许执行的运算;其次要定义变量名。

C 语言的编译系统会根据变量的类型在适当的时候为指定的变量分配内存单元,在确定了变量的地址之后,就可以通过变量名对内存中变量对应的地址进行操作。

在计算机的内部,所有的内存单元都要统一进行"编号",即所有的内存单元都要有地址,每一个内存单元都具有唯一的内存地址。

系统为每一个已定义的变量按照变量的类型分配一定存储空间,使变量名与内存的一个地址相对应。

为一个变量进行赋值操作,实质就是要将变量的值存入系统为该变量名分配的内存单元中,即变量的值要存入变量名对应的内存地址中。

程序进行运算时,要根据地址取出变量所对应的内存单元中存放的值,计算结果最后要存入变量名对应的内存单元中。

下面来说明几个术语:

直接访问:是一种通过变量名 i 直接访问变量 i 的数据的方式。

间接访问:如果将变量 i 的地址存放在另一个变量 p 中,通过访问变量 p ,间接达到访问变量 i 的目的。

所谓指针变量就是保存其他变量地址的变量。因此,可以认为:指针是用于指向其他变量的变量。

当指针指向变量时,利用指针可以引用变量;当指针指向数组时,利用指针可以访问数组中的所有元素;

指针还可以指向函数,存放函数的入口地址,利用指针调用该函数;当指针指向结构体时,利用指针引用结构体变量的成员。

9.1.2 指针变量的定义

指针是一种存放地址的变量,像其他的变量一样,必须在使用前定义。

指针变量定义的形式为:

类型*指针变量名;
//类型为 C 语言所允许的类型;变量名前面要加上*,说明该变量为指针类型的变量,称为指针变量。
//指针变量的类型是指针变量所指对象的类型,并非指针变量自身的类型。

C 语言中允许指针指向任何类型的对象,包括指向另外的指针。

9.1.3 指针变量的两种运算符

C 语言提供了两种指针运算符。

(1)&:取地址运算符。

(2)*:指针内容运算符(也称为间接访问运算符)。

在 C 语言中,如果 int x =10, y ,* px ;,且有 px =& x ;,则变量 x 与指向变量 x 的指针 px 之间有以下等价关系。

y = x ;   等价于 y =* px ;         /* y =指针变量 px 的内容*/
x ++;    等价于(* px )++;        /*对指针变量 px 的内容加1*/
y = x +5  等价于 y =(* px )+5;     /*这里的括号是不能省略的*/
y = x ;   等价于 y =*(& x );       /* y =变量 x 地址的内容*/   

C 语言中用 NULL 表示空指针。若有语句 p = NULL ;,表示指针 p 为空,没有指向任何对象。

注意如下几点:

(1)&运算符只能作用于变量,包括基本类型变量和数组的元素,结构体类型变量或结构体的成员,不能作用于数组名、常量或寄存器变量。

(2)单目运算符*是&的逆运算,它的操作数是对象的地址,*运算的结果是对象本身。单目*称为间接访问运算符,是通过变量的地址存取(或引用)变量。

(3)->也是指针运算符,是利用结构体类型指针变量指向其结构体成员时使用的,

9.1.4 指针变量的初始化

在使用指针变量前,要先对指针变量进行初始化,让指针变量指向一个具体的变量。进行指针变量初始化的方式有如下两种。

方法一:使用赋值语句进行指针初始化。如:
 

int a ,* pa ;

pa =& a ;

赋值后将变量 a 的地址赋给指针 pa 。

方法二:在定义指针变量的同时进行初始化。如:

int a ,* pa =& a ;

将变量 a 的地址赋给指针。

注意,不能将其与一般的赋值语句混淆,也不能表示成 int a ,* pa ;* pa =& a ;,这是错误的。

9.1.5 引用指针变量

引用变量的方式有两种:用变量名直接引用,通过指向变量的指针间接引用。

【例】分析程序的执行过程,比较变量引用方式。

//(1)直接引用方式。
//程序代码如下:

/*c09_02-1.c*/
# include < stdio . h > 
main ()  /*通过变量名直接引用方式*/
{int a , b ;   /*在 scanf ()函数中直接使用变量 a 和 b 的地址*/ 
scanf ("% d % d ",& a ,& b ); 
printf (" a =8d, b =% d \ n ", a , b );/*直接输出变量 a 和 b 的值*/
}

//(2)间接引用方式。
//程序代码如下:

/*c09_02-2.c*/
# include < stdio . h >
 main ()/*通过变量的地址指针输出变量的值*/
{ int a , b ,* pa ,* pb ;
 pa =& a , pb =& b ;/*指针 pa 和 pb 分别指向变量 a 和 b */ 
scanf ("% dtd ", pa , pb );/*将键盘输入的数分别送到变量 a 和 b 的地址中*/ 
printf (" a =8d, b =8d\ n ",* pa ,* pb );/*通过*运算符实现间接访问*/
}

//运行结果如下:
//21  7 
//a =21, b =7

9.2 指针与函数

函数利用指针传递参数有如下3种方式。

(1)指针作为函数的参数。

(2)指针作为函数的返回值。

(3)利用指向函数的指针传递。

9.2.1 指针作函数的参数

利用指针传递函数的参数是一种间接传址方式。

知道变量的地址就可以通过地址间接访问变量的数值,通过地址间接访问变量的数值就是通过指针间接访问指针所指的内容。

指针作函数的参数就是在函数间传递变量的地址。

在函数间传递变量地址时,函数间传递的不再是变量中的数据,而是变量的地址。

此时,变量的地址在调用函数时作为实参,被调用函数使用指针变量作为形参接收传递的地址。这里实参的数据类型要与作为形参的指针所指的对象的数据类型一致。

在学习函数时知道,函数调用结束时返回一个结果,是函数的返回值,这个返回值只有一个。

如果试图用一个函数交换主函数 main ()中两个变量值,并将交换的结果返回主函数,使用普通变量作为函数参数是无法实现的。可以采用两个指针变量传递地址的方式完成,即设置两个指针变量作为函数的参数。

* px =* py ;
//含义是"取指针变量 py 的内容赋给指针变量 px 所指的变量中",
//即该语句实现对指针变量所指内容之间的相互赋值。

px = py ;
//含义是"将指针变量 py 的值赋给指针变量 px ",
//即实现的是指针变量之间的相互赋值。

指针变量所指单元的内容(简称指针的内容)是通过指针取指针所指向单元的变量的值。

指针变量的值(简称指针的值)是指针变量本身的值(即指针变量中存的某存储单元的地址)。

9.2.2 函数返回指针

指针能作为函数的返回值。之前将整型等简单类型作为返回值,现在将函数的返同值设置为指针,一般定义形式:

数据类型*函数名(参数列表)
{...
}
//"数据类型"后面的*表示函数的返回值是一个指向该数据类型的指针。
//注意,此时定义的是函数,而不是指针。

9.2.3 指向函数的指针

在 C 语言中,在定义一个函数之后,编译系统为每个函数确定一个入口地址,当调用该函数的时候,系统会从这个"入口地址"开始执行该函数。存放函数的入口地址的指针就是一个指向函数的指针,简称函数的指针。

函数指针的定义方式是:
 

类型标识符(*指针变量名)()

//类型标识符为函数返回值的类型。

//由于 C 语言中,()的优先级比*高,因此,"*指针变量名"外部必须用括号,
//否则指针变量名首先与后面的()结合,就是前面介绍的"返回指针的函数"。

试比较下面两个定义语句:

int (* pf )();  /*定义一个指向函数的指针,该函数的返回值为整型数据*/

int * f ();     /*定义一个返回值为指针的函数,该指针指向一个整型数据*/

和变量的指针一样,函数的指针也必须赋初值,才能指向具体的函数。

由于函数名代表了该函数的入口地址,因此,一个简单的方法是直接用函数名为函数指针赋值,即:

函数指针名=函数名函数型

指针经定义和初始化之后,在程序中可以引用该指针,目的是调用被指针所指的函数。由此可见,使用函数型指针,增加了函数调用的方式。

注意的是,一个函数指针可以先后指向不同的函数,将哪个函数的地址赋给它,它就指向哪个函数,使用该指针,就可以调用函数。但是,必须用函数的地址为函数指针赋值。

另外,如果有函数指针(* pf )(),则 pf + n 、 pf ++和 pf --等运算是无意义的。

9.3 指针与数组

指针使用很灵活,可以定义指针来访问数组元素。

一个变量在内存中有相应的地址,而数组包含多个元素,每个数组元素在内存中都占有一个内存地址,因此也可以定义一个指针指向这个地址。

9.3.1 通过指针引用一维数组元素

在数组中已经知道,可以通过数组的下标唯一确定某个数组元素在数组中的顺序和存储地址。

由于每个数组元素相当于一个变量,因此指针变量可以指向数组中的元素,也就是可以用"指针方式"访问数组中的元素。

当定义一个数组时,编译系统会按照其类型和长度在内存中分配一块连续的存储单元。

数组名的值为数组在内存中所占用单元的起始地址。也就是说,数组名代表了数组的首地址。

指针是用来存放内存地址的变量,用赋值语句 p = a ;, p 指针就指向数组的首地址了。这样就可以通过 p 指针来访问数组 a 中的元素,

如果数组为 int 类型,那么指针变量也应该是指向 int 类型的,也就是说,指针变量的类型必须和它要指向的数组的类型相一致。

当指针指向数组时,可通过数组名和指针两种方式来访问数组元素。

因为指针是变量,而数组名是常量,所以指针的值可以改变,这就特别需要注意指针当前的指向。

如果已经指向了数组所占内存之外的地方,则会出现问题,这是指针使用中容易出错的地方,也是指针使用最危险之处。

下标法、通过数组名计算数组元素地址和通过指针变量指向数组元素3种方法实现的效果是一样的,都输出了数组元素的值,但是它们的效率不一样。

用下标法比较直观,能直接知道是第几个元素。用地址法和指针变量的方法不是太直观,很难判断出当前处理的是哪个元素,必须仔细分析变量 p 的指向,才能判断出当前输出的是第几个元素。在

使用指针时,一定要注意不要使指针为"空",即不指向任何地方,也不要使指针的指向超越了数组的边界。

用指针方式实现对数组的访问是很方便的,可以使源程序更紧凑、更清晰。

9.3.2 指针基本运算

对于指针的运算有指针的加减运算和两个指针的关系运算。

1、指针的加、减运算

当指针 p 指向数组中的元素时, n 为一个正整数,表达式 p ± n 表示指针 p 所指向当前元素之后或之前的第 n 个元素。

指针加运算 p ++的含义是指针加1,指向数组中的后一个元素;

指针减运算 p --的含义是指针减1,指向数组中的前一个元素。

由于指针 p 所指的具体对象不同,所以对指针与整数进行加减运算时, C 语言会根据所指的不同数据类型计算出不同的存储单元的大小,以保证正确操作实际的运算对象。

数据类型的存储单元的大小等于一个该数据类型的变量所占用的内存单元数。

对于字符型,存储单元的大小为1;

对于整型,存储单元的大小为2;

对于长整型,存储单元的大小为4;

对于双精度浮点型,存储单元的大小为8。

2.两个指针的关系运算

只有当两个指针指向同类型的数据元素时,才能进行关系运算。

当指针 p 和指针 q 指向同类型的数据元素时则:

p < q :当 p 的地址小于 q 的地址时,表达式的值为1;反之为0。

p > q :当 p 的地址大于 q 的地址时,表达式的值为1;反之为0。

p == q :当 p 和 q 指向同一元素时,表达式的值为1;反之为0。

p != q :当 p 和 q 不指向同一元素时,表达式的值为1;反之为0。

任何指针 p 与 NULL 进行 p == NULL 或 p != NULL 运算均有意义, p == NULL 的含义是当指针 p 为空时成立, p != NULL 的含义是当 p 不为空时成立。

指向两个不同数组的指针进行比较,没有任何实际的意义。

9.3.3 通过指针引用二维数组元素

用指针变量可以指向一维数组,也可以指向多维数组,但是指向多维数组的指针要复杂得多。

现在说明一下二维数组指针,设有一个二维数组定义为: int a [4][2]=({1,2),(3,4),(5,6),(7,8));可以把数组 a 看作是只有4个元素的一维数组,即 a [0]、 a [1]、 a [2]和 a [3],而每一个元素又是一个包含两个元素的一维数组。数组在内存中是连续存储的,数组元素的内存分配顺序为 a [0][ o ]、 a [ o ][1]、 a [1][0]、 a [1][1]、 a [2][0]、 a [2][1]、 a [3][0]、 a [3][1]。

假设数组的开始地址为2000,它应代表整个二维数组的首地址,也就是二维数组第0行的首地址。数组存储及数组元素和指针的对应关系如图9.8所示。

数组中一维下标的对应关系数组元素存放通过指针访问数组元素 a [0]=( a +0)2000 a [0][0]1* a a [0][1]2*(( a +1) a / lj -"( a + l )-2004 a [1][0]"( a + l )3 a [1][1]4*(( a +1)+1) a [2}=*( a +2)--2008 a [2][0]5*( a +2) a [2][1]6*(*( a +2)+1) a [3]=*( a +3)→2012 a [3][0]*( a +3) a [3][1]8*(*( a +3)+1)图9.8二维数组元素内存中存放顺序及对应关系说明如下。(1) a [0], a [1] a [2], a [3]是一维数组名,在 C 语言中数组名代表数组的首地址,因此 a [0]代表第0行一维数组中第0列元素的地址,即& a [0][0]; a [1]的值就是& a [1][0].....…(2)既然 a [0]是第0行的首地址,那么 a [0]+1就是第0行第1列元素的地址了,即2002。其实这很好理解,因为可以把 a [0]看作是一个一维数组的首地址,那么 a [0]+1自然就是这个一维数组的第2个元素的地址了。(3)我们已经知道, a [0]和*( a +0)等价, a [1]和*( a +1)等价, a [ i ]和*( a + i )等价。因此, a [0]+1和*( a +0)+1的值都是& a [0][1],即2002。既然如此,那么*( a [0]+1)和*(*( a +0)+1)就是 a [0][1]中的值了,也就是内存单元2002中的内容。请记住 a [ i ]和*( a + i )是等价的。(4)从形式上看,可以认为 a [ i ]是第 i 个元素。但是,如果 a 是一个一维的数组名,那么 a [ i ]实际上就是数组 a 第 i 个元素中的内容,在这里 a [ i ]是有物理地址的,是占内存单元的。如果 a 是一个二维数组,则 a [ i ]是一个一维数组名,它本身并不占用内存单元,它只是一个地址,也就是这个二维数组第+1行的首地址。同样的道理, a +1是地址,也就是 a [1]。而*( a +1)的含义也是 a [1]。我们知道, a [1]其实是一个一维数组名,数组名代表的是地址,这样 a +1和*( a +1)都是地址,与 a [1]等价,地址值为2004。元素 a [ i ][ j ]存储的地址是数组的首地址+ iXN + j 。、

9.4 字符串与指针

9.4.1 字符数组与字符指针

字符指针也可以指向一个字符串,可以用字符串常量对字符指针进行初始化。

对字符指针进行初始化。此时,字符指针存放的是一个字符串常量的首地址,即指向字符串的首地址。

9.4.2 常见的字符串操作

由于使用指针编写的字符串处理程序比使用数组方式处理字符串的程序更简洁、更方便,所以在 C 语言中,大量使用指针对字符串进行各种处理。在处理字符串的函数中,一般都使用字符指针作为形参。由于数组名代表数组的首地址,因此在函数之间可以采用指针传递整个数组,这样在被调用函数的内部,就可以用指针方式访问数组中的元素。

若定义了一个指针变量,并使它指向一个字符串,就可以用下标形式引用指针变量所指向的字符串中的字符。

使用数组名作为实际参数也就是将数组的首地址传递给被调用函数。程序中用指针比用数组有更多的优点,它使程序更紧凑、简练。

9.5指针数组、数组指针及应用

9.5.1指针数组与数组指针

数组中每个元素都具有相同的数据类型,数组元素的类型就是数组的基类型。

如果一个数组中的每个元素均为指针类型,即由指针变量构成的数组,这种数组称为指针数组,它是指针的集合。

指针数组定义的形式为:类型+数组名[常量表达式];

数组指针定义的形式为:类型(*数组名)[常量表达式];

指针数组常适用于指向若干字符串,这样使字符串处理更加灵活方便。

9.5.2 main 函数的参数

 main ()函数的第一行全部写成 main (),括号中为空,表示没有参数。

实际上 main ()函数是可以带参数的,其一般形式为:
 

main ( argc , argv )  
int argc ;         /* argc 表示命令行参数个数*/ 
char * argv [];    /* argv 指向命令行参数的指针数组*/ 

//argc 和 argv 是 main ()函数的形参。

在操作系统下运行 C 程序时,可以以命令行参数形式,向 main ()函数传递参数。命令行参数的一般形式是:

运行文件名参数1参数2.参数 n 

//运行文件名和参数之间,各个参数之间要用一个空格分隔。 
//argc 表示命令行参数个数(包括运行文件名在内),
// argv 是指向命令行参数的数组指针。
//指针 argv [0]指向的字符串是运行文件名, 
//argv [1]指向的字符串是命令行参数1, 
//argv [2]指向的字符串是命令行参数2等。

9.6指向指针的指针

一个指针可以指向任何一种数据类型,包括指向一个指针。当指针变量 p 中存放另一个指针 q 的地址时,则称 p 为指针型指针,也称为级指针。

若有定义: char * pointer ;

pointer 是指向字符串的指针,用它可以存放字符型变量的地址,并且可以用它对所指向的变量进行间接访问。

进一步定义:

char ** p ;

从运算符*的结合性可以知道,上述定义相当于 char *(* p );。

这是指向字符串的指针,即指向指针的指针。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值