1 / 42

第 14 章 指针

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

laszlo
Télécharger la présentation

第 14 章 指针

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

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

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

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

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

  6. 14.1.1 访问内存的两种方式 • 借助变量b,以如下方式也可以获得a的内容: • *b • 在这个取a的内容的过程中,系统要访问b,先找到与变量名b对应的内存地址,再根据该地址找到为b分配的空间,获取b的值,即&a,也就是0016;根据b的类型,系统可以知道这个值是一个地址,于是继续查找地址为0016的内存空间,最后获取这个空间上的内容,即241。 • 提示:这种先从其他内存空间获得要访问的内存空间的地址,再根据该地址访问目的空间的方法就是内存的“间接访问”。

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

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

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

  10. 14.1.3 指针变量的定义 • C语言中指针类型由两部分组成:数据类型名和指针操作符。定义指针变量的标准形式如下所示。 • 数据类型名 * 指针变量名; • 其中,数据类型名可以为任何数据类型,该数据类型名声明了指针变量指向的内存空间存储的数据类型。

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

  12. 14.1.3 指针变量的定义 • 下面是几个定义指针变量的例子。 • char * cp; • int * ip; • float * fp;

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

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

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

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

  17. 14.2.2 指针变量的多次赋值

  18. 14.2.2 指针变量的多次赋值

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

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

  21. 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; /* 正确的初始化 */

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

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

  24. 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指针的值是不可变的。

  25. 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的值,也是错误的。该指针变量的值及其指向空间的值都是不可改变的。

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

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

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

  29. 14.3.1 指针形参 • 此时,pt的值为x的地址,即pt也指向x;a的值为20。赋值后,pt的值的改变不会影响p的值,a的改变也不会影响y的值。但是,此时如果改变*pt,例如: • *pt = 20; • 该语句将pt指向的空间赋值为20,即a被赋值为20。此时,函数内的操作改变了函数外的变量值,指针参数可以通过地址传递来间接改变外部的变量值,这种功能是其他类型的参数不能实现的。这种传递变量地址的方式称为地址传递。

  30. 14.3.2 指针型函数值 • 函数的函数返回值也可以是指针型的数据,即地址。返回该类型值时,执行机制与返回其他类型完全相同。含有指针型函数值的函数的声明一般为: • 数据类型 * 函数名(形参列表); • 其中,数据类型和指针操作符组成指针类型。例如: • int * max(int a, int b, int c); • 此max函数中的return语句必须返回一个变量的地址或一个指针变量的值。

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

  32. 14.3.3 函数型指针 • 那么此时声明的函数变量add的地址即为这个函数的地址,同时add的值保存为这个函数的地址,这个特性与数组相似:数组变量与数组变量的地址均为数组的起始地址。而在这个函数声明中,函数类型为int (const int a, const int b)。使用该函数类型来定义一个函数型指针,其方式如下: • int (* fp)(const int a, const int b); /* 其中,参数列表的参数名a和b可省 */

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

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

  35. 14.4 void型指针 • 从前面的学习中知道,可以使用关键字void作为函数的形参列表,其表示该函数不需要参数。void还可以用作函数的函数值类型,其表示该函数没有返回值。此外,void还可以用做指针类型,这种指针被成为void型指针。本节将讨论void型指针的含义和使用。

  36. 14.4.1 void型指针的含义 • void型指针就是无类型指针。一般的数据类型指针只能指向存储该数据类型的空间,而void型指针则可以指向存储任意数据类型的空间。其定义形式如下: • void * 变量名; • 可以知道,不能在不同的数据类型指针变量之间赋值。例如: • char * p1 = “Goodbye”; • int * p2 = p1; • 上述语句将char型指针变量的值赋值给int型指针变量,这是不允许的。编译器会产生如下的警告信息: • warning C4133: 'initializing' : incompatible types - from 'char *' to 'int *'

  37. 14.4.1 void型指针的含义 • 要消除警告信息,必须进行强行转换。例如: • int * p2 = (int *)p1; • 而void型指针变量可以赋值为任意类型的地址值,也可以用来作为赋值表达式的右值。例如: • void * p1 = “Goodbye”; • int * p2 = p1; /* 不推荐的做法 */

  38. 14.4.1 void型指针的含义 • 第1行中,将void型指针变量p1赋值char型地址值;第2行中,将void型指针的值赋值给int型指针变量p2。在大部分编译器中,这两个行为都是允许的。但在一些编译器中,不允许直接将void型指针变量赋值给其他类型指针,同样也需要类型转换。例如: • int * p2 = (int *)p1; /* 推荐的做法 */ • void型指针还有一个特殊的地方就是不能对void型指针做加减运算。因为,对指针的加减是基于指针指向的类型空间的字节长度进行的。例如: • int * p1; • ++p1;

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

  40. 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型指针作为形参。

  41. 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));

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

More Related