第 14 章 指针

42
联联联联http://www.rzchina.net 联联联联联联联联联联联联 第 14 第 第第 第第第第第第第 第第第第第第第第第第 ,: 第第第第第第第第第第第第第第第第第第第第第第第第第 第第第第第第第 、; 第第第第第第第第第第第第第第第第第第第第第第void 第第第第第第

Upload: laszlo

Post on 15-Mar-2016

129 views

Category:

Documents


3 download

DESCRIPTION

第 14 章 指针. 通过本章的学习,需要掌握以下知识点: 指针的概念以及指针变量的内存访问方式; 指针变量的定义、赋值和初始化; 使用指针作为函数形参和函数值; 函数型指针的使用; void 型指针的使用。. 14.1 什么是指针. 简单地说,指针就是一种数据类型,用来表示内存地址。使用指针数据类型声明的变量就是指针变量,使用指针变量可以灵活地对内存空间进行灵活的操作。本节将讨论内存访问的两种方式、以及指针的概念和指针变量的定义等内容。. 14.1.1 访问内存的两种方式. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

第 14 章 指针通过本章的学习,需要掌握以下知识点:指针的概念以及指针变量的内存访问方式;指针变量的定义、赋值和初始化;使用指针作为函数形参和函数值;函数型指针的使用;void 型指针的使用。

Page 2: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1 什么是指针简单地说,指针就是一种数据类型,用来表示内存地址。使用指针数据类型声明的变量就是指针变量,使用指针变量可以灵活地对内存空间进行灵活的操作。本节将讨论内存访问的两种方式、以及指针的概念和指针变量的定义等内容。

Page 3: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.1 访问内存的两种方式在 C 语言中,对内存空间的访问提供了两种访问方式,第一种是直接访问,第二种是间接访问。1. 直接访问在第 2 章关于变量的讨论中已经知道,如果程序中定义了一个变量,那么系统会为这个变量分配一块内存。每一个变量都有两个属性:变量值和变量地址。变量的地址指示了该变量在内存中存储的位置。而变量值就是该内存上的内容。要访问该空间上的内容可以直接使用变量名,例如:int a = 241;printf(“%d”, a);

Page 4: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.1 访问内存的两种方式在第 1 行语句中,系统会为变量 a 分配一个空间,并将变量名 a 与该地址对应起来;在第 2 行语句中,系统会根据变量名 a 和其地址的对应关系找到变量名 a 对应的内存地址,然后再根据该地址值,找到内存空间取出空间上的内容。变量 a 在内存中的存储形式如下图所示。

这种直接从空间的地址获取该内存内容的访问方式叫做内存的“直接访问”。

Page 5: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.1 访问内存的两种方式2. 间接访问在前面的章节中已经知道,可以使用地址操作符来获得变量的地址,例如对上面的 a 取 &a ,可以得到 a 的地址,即 0016 。再使用指针操作符可以获得该地址的内容,例如:*(&a)这样便可以获得 a 的内容。可以将 &a 赋值给另一个类型的变量,例如:b = &a;变量 b 的内存空间如右图所示。

Page 6: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.1 访问内存的两种方式借助变量 b ,以如下方式也可以获得 a 的内容:*b

在这个取 a 的内容的过程中,系统要访问 b ,先找到与变量名 b 对应的内存地址,再根据该地址找到为 b 分配的空间,获取 b 的值,即 &a ,也就是 0016 ;根据 b 的类型,系统可以知道这个值是一个地址,于是继续查找地址为0016 的内存空间,最后获取这个空间上的内容,即 241。

提示:这种先从其他内存空间获得要访问的内存空间的地址,再根据该地址访问目的空间的方法就是内存的“间接访问”。

Page 7: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.2 指针的概念在 C 语言中,表示内存地址的数据类型就是指针类型。所以,地址就是指针型数据,一个变量的地址就是一个指针型常量,用来保存地址的变量就是一个指针型变量。通过指针访问内存空间的方式为间接访问。在 14.1.1 节讨论的间接访问内存的过程中, a 的地址( 0016 )和 b 的地址( 1010 )都是指针型数据,而用来保存要访问的目的地址(即 a 的地址, 0016 )的变量 b 就是指针变量。

Page 8: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.2 指针的概念普通变量有两个属性,而指针变量则关联到 4 个属性:指针变量的地址,即为指针变量分配的内存空间的地址,在上例中为 1010 。指针变量的值,即该内存空间的内容,在上例中为 0016 。以指针变量的值为地址的空间地址,也称为指针指向的空间地址,上例中为 0016 。指针指向的内存空间的内容,上例中为 241 。指针的所有变化都会在这 4 个属性上反应出来。

Page 9: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.2 指针的概念虽然指针变量的值和其指向空间地址的值是一样的,但实际上还是有本质上的不同的。指针变量的值,为一个内存空间的内容,是可以改动的。指针变量指向的空间的地址是一个内存空间的地址,这是一个常量,是本身是不可改变的。

Page 10: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.3 指针变量的定义C 语言中指针类型由两部分组成:数据类型名和指针操作符。定义指针变量的标准形式如下所示。数据类型名 * 指针变量名 ;

其中,数据类型名可以为任何数据类型,该数据类型名声明了指针变量指向的内存空间存储的数据类型。

Page 11: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.3 指针变量的定义指针操作符的位置可以和数据类型名紧挨在一起,也可以和指针变量名挨在一起,也可以放在中间,而且中间的空格可以为任意多个。例如,下列的指针变量定义方式都是合法的。数据类型名 * 指针变量名 ;数据类型名 * 指针变量名 ;数据类型名 * 指针变量名 ;数据类型名 * 指针变量名 ;数据类型名 * 指针变量名 ; /* 中间任意多个空格 */注意:定义指针变量时,一般将指针操作符放在靠近变量名的位置。

Page 12: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.1.3 指针变量的定义下面是几个定义指针变量的例子。char * cp;

int * ip;

float * fp;

Page 13: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2 指针的使用由于指针的特殊性,对指针变量的赋值和初始化和一般变量有所不同。本节将先介绍指针变量的依次赋值和多次赋值,并且讨论将指针变量赋值为整数的问题,然后讨论指针变量初始化,最后介绍了如何使用 const 修饰符来声明指针变量。

Page 14: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.1 指针变量的一次赋值为指针变量赋值就是为指针设定它指向的内容空间的过程。指针变量只能保存内存地址,因此给指针变量赋值时必须赋给它一个地址。例如:01char c = ‘a’;02char * cp;03cp = &c;第 2 行,字符型指针 cp 只是分配了内存,并没有设定指向的内存空间,指针变量值是不可知的。第 3 行,将指针 cp 的值设置为字符变量 c 的地址,那么 cp指向变量 c 的空间。

Page 15: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.1 指针变量的一次赋值此时,要访问 c 的内容,就可以使用直接访问和间接访问两种方式:01printf(“%c”, c); /* 直接访问 */02printf(“%c”, *cp); /* 间接访问 */第 2 行语句中的 *cp 就是取指针变量 cp 指向的内存空间的内容。若要对变量 c 赋值,也可采用以下直接和间接两种方式:01c = 2; /* 直接访问 */02*cp = 2; /* 间接访问 */

Page 16: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.2 指针变量的多次赋值当然,指针变量的值在一次赋值后,可以再次被赋值为其他地址。这种情况下,指针变量的指向从一个空间转移到另一个空间。下面的范例讨论了在多次赋值过程中指针变量各个属性的变化。

Page 17: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.2 指针变量的多次赋值

Page 18: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.2 指针变量的多次赋值

Page 19: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.3 将指针变量赋值为整数原则上,不能将整数数值赋值给指针变量;否则指针变量会指向以该整数数值为地址值的内存空间,此时对该指针变量的操作会操作内存中不可知的一些内存空间,从而会导致一些不可预测的问题。例如,以下行为是危险的:01int * p1 = 12; /* p1 指向地址为 12 的内存空间 */02*p1 = 23; /* 将地址为 12 的内存空间上的内容改为 23 */但是,如果赋给指针的整数值是一个有效的空间地址,那么程序还是可以正常工作的。不过,这仍是危险的做法,因为很难保证该整数值是有效的。

Page 20: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.4 初始化指针变量使用指针变量时,也应该在其定义语句中为其需要初始化,否则指针将指向一个不可知的空间。

Page 21: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.4 初始化指针变量下面是几个指针变量初始化的例子:/* 1 */int a = 0;int * p1 = &a; /* 正确的初始化 *//* 2 */char c = ‘e’;char *p2 = &c; /* 正确的初始化 *//* 3 */char c = ‘e’;int *p3 = &c; /* 不提倡 *//* 4 */int * p4 = 0xffff22; /* 不提倡,危险的做法 *//* 5 */int * p5 = NULL; /* 正确的初始化 */

Page 22: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.5 使用 const声明指针变量在第 4 章中已经学习了使用限定词 const 声明变量可以带来很多好处。同样地,限定词 const 也可以用来声明指针变量。根据 const 关键字在声明中出现位置的不同,可以得到多种 const 指针类型,下面将依次进行讲解。为方便表述,以 int 型代表数据类型名,以 p代表变量名。

Page 23: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.5 使用 const声明指针变量1.指向 const 的指针变量声明指向 const 的指针变量有以下两种方式:int const *pconst int * p由于变量声明中, const 和数据类型名的出现次序可以随意排列,因此以上两种声明表达式是等效的。以上的两个表达式将变量 p 都声明为指向存储 const int 型数据的内存空间的指针变量,该类指针指向的内存空间的内容是不可变的。例如,以下操作是错误的:01 const int a = 1;02 const int * p1 = &a;0304 *p1 = 2;第 4 行语句通过赋值表达式改变 p1 指向的内存空间的内容,而声明中 p1 指向的内容是不可改变的,因此该语句是错误的。

Page 24: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.5 使用 const声明指针变量2. const 型指针变量int * const p该表达式声明了一个 int 型的 const 指针变量,即该指针变量的值是不可以改变的,也就是说 const 型指针变量指向的内存空间是固定的,初始化后不能将其指向其他空间。例如,以下操作是错误的:01 int a = 1;02 int b = 2;03 int * const p = &a;04 *p = 12;/* 正确 */05 p = &b; /* 错误 */第 4 行,对 int 型 const 指针变量指向的内存空间赋值是允许的;但是第 5 行,试图改变 const 指针变量 p 的值,将 p 指向变量

b 的内存空间,这是错误的,因为 const 指针的值是不可变的。

Page 25: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.2.5 使用 const声明指针变量3.指向 const 的 const 指针变量const int * const p该表达式声明了一个指向存放 const int 型空间的 const 指针变量,该指针变量的值和该指针指向的空间的值都是不可改变的。以下行为是错误的:01 int a = 1;02 int b = 2;03 int const * const p = &a;04 *p = 12; /* 错误 */05 p = &b; /* 错误 */第 4 行,试图改变 p 指向的内存空间的内容,也就是 p 指向的变量 a 的值,这是错误的;第 5 行,试图改变 p 的值,也是错误的。该指针变量的值及其指向空间的值都是不可改变的。

Page 26: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.3 指针与函数在函数一章中,已经知道可以使用整型变量、浮点型变量和字符型变量作为函数参数,指针变量也可以作为函数参数使用。此外,还可以使用指针型变量作为函数的函数值。本章将讨论如何使用指针型变量作为函数的形参和函数值。

Page 27: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.3.1 指针形参函数调用时,会实参的值赋值给形参。使用指针变量作为函数参数,可以将一个内存空间的地址传递到函数中,可以通过该地址来操作该地址上的内存空间。例如,若有以下函数声明:void func(int * pt, int a);

该函数含有两个形参,一个为 int 型指针变量,一个为 int型变量。

Page 28: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.3.1 指针形参若有以下语句:01int *p;02int x, y;03x = 5;04y = 20;05p = &x;06func(p, y);函数调用时,形参 pt 和 a 分别被赋值为实参 p 和 y 的值。该过程可以简单地视为:pt = p;a = y;

Page 29: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.3.1 指针形参此时, pt 的值为 x 的地址,即 pt 也指向 x ; a 的值为 20。赋值后, pt 的值的改变不会影响 p 的值, a 的改变也不会影响 y 的值。但是,此时如果改变 *pt ,例如:*pt = 20;

该语句将 pt 指向的空间赋值为 20 ,即 a 被赋值为 20 。此时,函数内的操作改变了函数外的变量值,指针参数可以通过地址传递来间接改变外部的变量值,这种功能是其他类型的参数不能实现的。这种传递变量地址的方式称为地址传递。

Page 30: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.3.2 指针型函数值函数的函数返回值也可以是指针型的数据,即地址。返回该类型值时,执行机制与返回其他类型完全相同。含有指针型函数值的函数的声明一般为:数据类型 * 函数名 ( 形参列表 );

其中,数据类型和指针操作符组成指针类型。例如:int * max(int a, int b, int c);

此 max 函数中的 return 语句必须返回一个变量的地址或一个指针变量的值。

Page 31: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.3.3 函数型指针C 程序中的函数也都是存放在代码区内的,它们同样也是有地址的。那么如何取得函数的地址呢?在前面也说过函数定义的时候实际上是定义了一个函数变量,那么是否可以将函数变量赋值给其他变量呢?回答这些问题需要涉及到另外一个概念:函数型指针。按照已有的指针的知识,顾名思义,函数型指针就是指向函数的指针。如果有一个函数声明为:int func(const int a, const int b);

Page 32: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.3.3 函数型指针那么此时声明的函数变量 add 的地址即为这个函数的地址,同时 add 的值保存为这个函数的地址,这个特性与数组相似:数组变量与数组变量的地址均为数组的起始地址。而在这个函数声明中,函数类型为 int (const int a, cons

t int b) 。使用该函数类型来定义一个函数型指针,其方式如下:int (* fp)(const int a, const int b); /* 其中,参数列表的参数名 a 和 b 可省 */

Page 33: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.3.3 函数型指针上述语句将变量 func 定义为指向类型为 int (const int a, c

onst int b) 的指针操作符和变量名两侧的小括号不可省,否则其含义大不相同。例如:int * fp(const int a, const int b);此时,指针操作符与数据类型 int结合为 int 型指针类型,该语句只是声明了一个 fp 函数,而非定义一个函数指针。为该函数型指针赋值的方式如下:fp = func;被赋值的函数变量的类型必须与 fp 的类型完全一致,包括其返回类型和每一个形参的类型。否则程序将报错。

Page 34: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.3.3 函数型指针注意:函数型指针变量赋值时,左值与右值的类型必须完全一致。使用函数型指针变量调用函数的方法与使用函数变量类似,得到函数地址后再带上参数列表即可。可以使用下面两种方式来调用函数:fp(5, 6);或(*fp)(5, 6);由于 fp 被赋值为函数变量 func 的地址,而 func 的值又等于其地址,所以 *fp 可以得到 func 函数的地址。因此,在调用方式上,可以粗略地将两者视为一致(实际上其后台的处理略有不同)。

Page 35: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.4 void型指针从前面的学习中知道,可以使用关键字 void 作为函数的形参列表,其表示该函数不需要参数。 void 还可以用作函数的函数值类型,其表示该函数没有返回值。此外, voi

d 还可以用做指针类型,这种指针被成为 void 型指针。本节将讨论 void 型指针的含义和使用。

Page 36: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.4.1 void型指针的含义void 型指针就是无类型指针。一般的数据类型指针只能指向存储该数据类型的空间,而 void 型指针则可以指向存储任意数据类型的空间。其定义形式如下:void * 变量名 ;可以知道,不能在不同的数据类型指针变量之间赋值。例如:

char * p1 = “Goodbye”;int * p2 = p1;上述语句将 char 型指针变量的值赋值给 int 型指针变量,这是不允许的。编译器会产生如下的警告信息:warning C4133: 'initializing' : incompatible types - from

'char *' to 'int *'

Page 37: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.4.1 void型指针的含义要消除警告信息,必须进行强行转换。例如:

int * p2 = (int *)p1;

而 void型指针变量可以赋值为任意类型的地址值,也可以用来作为赋值表达式的右值。例如:

void * p1 = “Goodbye”;

int * p2 = p1; /* 不推荐的做法 */

Page 38: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.4.1 void型指针的含义第 1行中,将 void型指针变量 p1赋值 char型地址值;第

2行中,将 void型指针的值赋值给 int型指针变量 p2。在大部分编译器中,这两个行为都是允许的。但在一些编译器中,不允许直接将 void型指针变量赋值给其他类型指针,同样也需要类型转换。例如:

int * p2 = (int *)p1; /* 推荐的做法 */void型指针还有一个特殊的地方就是不能对 void型指针做加减运算。因为,对指针的加减是基于指针指向的类型空间的字节长度进行的。例如:

int * p1;++p1;

Page 39: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.4.1 void型指针的含义第 2行语句将 p1加 1,其实际地址值增加了 sizeof(p1)。而 void型指针变量指向的地址空间的类型是不可知的,因此无法对其做加减运算。由于同样的原因,也不能对void型指针取内容。以下操作是错误的:

void * p2;

++p2; /* 错误 */

*p2; /* 错误 */

提示:不能对 void 型指针作加减运算和取内容操作。

Page 40: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.4.2 void指针型形参由于 void型指针具有可以赋值为所有其他类型指针的特性,因此

void型指针经常用做函数的形参,以使该形参能接受指向任意类型的指针变量,使该函数能处理更大范围的数据。若有函数声明如下:

void initial(void * p)那么 initial函数可以接受各种类型的实参,例如:int a = 2;char b = ‘c’;double c = 3.2;initial(&a);initial(&b);initial(&c);因此,当一个函数需要处理各种不同类型的形参时,可以考虑使用

void型指针作为形参。

Page 41: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.4.3 void指针型函数值void 型指针变量也可以用作函数的函数值。函数声明如下:void * 函数名 ( 形参类型 );具有 void 指针型函数值的函数经类型转换后可以赋值给任意类型的指针变量。例如,有以下声明的函数:void * func(int a);可以使用该函数作为赋值表达式的右值,例如:int * p1 = NULL;char * p2 = NULL;…p1 = (int *)func(sizeof(int));p2 = (char *)func(sizeof(char));

Page 42: 第 14 章  指针

联系方式: http://www.rzchina.net

北京源智天下科技有限公司

14.5 综合练习1. 使用指针从标准输入获取三个整数,并求其中的最大值。2. 实现一个可以初始化各种类型数据的函数。