第 6 章 指针和引用

118
6 6 第 第第第第第 第 第第第第第 第第第第第第第第第第第第第第1 1 第第第第第第第第第第 、; 第第第第第第第第第第 、; 2 2 第第第第第第第第第第第第第第 、; 第第第第第第第第第第第第第第 、; 3 3 第第第第第第第第第第第第 、; 第第第第第第第第第第第第 、; 4 4 第第第第第第第第第第第第第 、; 第第第第第第第第第第第第第 、; 5 5 第第第第第第第第第第第第 、; 第第第第第第第第第第第第 、; 6 6 第第第第第第第第第第 、; 第第第第第第第第第第 、; 7 7 第第第第第第第第第第 第第第第第第第第第第第 、、; 第第第第第第第第第第 第第第第第第第第第第第 、、; 8 8 第第第第第第第第第第第第 、。 第第第第第第第第第第第第 、。

Upload: liesel

Post on 17-Mar-2016

173 views

Category:

Documents


0 download

DESCRIPTION

第 6 章 指针和引用. 本章学习的目标: 1 、掌握指针的基本概念; 2 、掌握指向数组元素指针的用法; 3 、了解指向数组指针的概念; 4 、掌握指针形参的作用和用法; 5 、了解返回指针函数的用法; 6 、了解函数指针的概念; 7 、掌握引用的基本概念、引用形参的作用和用法; 8 、了解返回引用函数的用法。. 6.1 指针基本概念 6.2 指向数组元素的指针 6.3 指针形参 6.4 数组形参 6.5 指向二维数组一整行的指针 6.6 指针数组 6.7 多级指针 6.8 函数指针 6.9 返回指针值的函数 6.10 引用. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第 6 章 指针和引用

第第 66 章 指针和引用章 指针和引用本章学习的目标:本章学习的目标:11 、掌握指针的基本概念;、掌握指针的基本概念;22 、掌握指向数组元素指针的用法;、掌握指向数组元素指针的用法;33 、了解指向数组指针的概念;、了解指向数组指针的概念;44 、掌握指针形参的作用和用法;、掌握指针形参的作用和用法;55 、了解返回指针函数的用法;、了解返回指针函数的用法;66 、了解函数指针的概念;、了解函数指针的概念;77 、掌握引用的基本概念、引用形参的作用和用法;、掌握引用的基本概念、引用形参的作用和用法;88 、了解返回引用函数的用法。、了解返回引用函数的用法。

Page 2: 第 6 章 指针和引用

6.1 指针基本概念 6.2 指向数组元素的指针6.3 指针形参 6.4 数组形参6.5 指向二维数组一整行的指针6.6 指针数组6.7多级指针6.8 函数指针6.9 返回指针值的函数6.10 引用

Page 3: 第 6 章 指针和引用

6.1 6.1 指针基本概念指针基本概念指针是系统程序设计语言的一个重要概念。指针在指针是系统程序设计语言的一个重要概念。指针在 CC程序中有以下多方面的作用:程序中有以下多方面的作用:(1)(1) 利用指针能间接引用它所指的对象。利用指针能间接引用它所指的对象。(2)(2) 指针能用来描述数据和数据之间的关系,以便构指针能用来描述数据和数据之间的关系,以便构造复杂的数据结构。造复杂的数据结构。(3)(3) 利用各种类型指针形参,能使函数增加活力。利用各种类型指针形参,能使函数增加活力。(4)(4) 指针与数组结合,使引用数组元素的形式更加多指针与数组结合,使引用数组元素的形式更加多样、访问数组元素的手段更加灵活。样、访问数组元素的手段更加灵活。(5)(5) 熟练正确应用指针能写特别紧凑高效的程序熟练正确应用指针能写特别紧凑高效的程序。

Page 4: 第 6 章 指针和引用

变量、变量的地址及变量的值变量、变量的地址及变量的值现实世界中的数据对象在程序中用变量与其对应,现实世界中的数据对象在程序中用变量与其对应,程序用程序用变量定义引入变量、指定变量的类型和名。变量定义引入变量、指定变量的类型和名。程序执行时,程序中的变量在内存中占据一定的存储单元,程序执行时,程序中的变量在内存中占据一定的存储单元,存储单元的开始地址称为变量的地址。存储单元的开始地址称为变量的地址。在存储单元中存储的数据信息称为变量的内容。在存储单元中存储的数据信息称为变量的内容。编译系统根据编译系统根据类型类型确定变量所需的确定变量所需的内存空间的字节数量内存空间的字节数量和和它的它的值的表示形式值的表示形式,检查程序对变量,检查程序对变量操作的合法性操作的合法性,对合,对合法的操作翻译出正确的计算机指令。变量的名供程序引用法的操作翻译出正确的计算机指令。变量的名供程序引用它,它,程序按名引用变量的内容或变量的地址。程序按名引用变量的内容或变量的地址。

Page 5: 第 6 章 指针和引用

如代码: 如代码: int x = 1; x = x + 2;int x = 1; x = x + 2;其中“其中“ x = x+2;”x = x+2;” 中的中的第一个第一个 xx 表示引用变量表示引用变量 xx 的地的地址址,,第二个第二个 xx 表示引用变量表示引用变量 xx 的内容的内容。。该语句的意义是该语句的意义是 ::完成取完成取 xx 的内容,加上的内容,加上 22 的计算,并将计算结果存入变的计算,并将计算结果存入变量量 xx 的地址所对应的单元中。的地址所对应的单元中。在程序执行时,源程序中按名对变量的引用,已被转换在程序执行时,源程序中按名对变量的引用,已被转换成按地址引用,利用变量的地址或取其内容或存储值。成按地址引用,利用变量的地址或取其内容或存储值。

Page 6: 第 6 章 指针和引用

指针变量和它所指向的变量指针变量和它所指向的变量运算符 运算符 & & 取变量的地址 取变量的地址 &x&x 的值就是的值就是变量变量 xx 的地址的地址。。变量的地址也可作为一种值被存储和运算。变量的地址也可作为一种值被存储和运算。源程序也可利用变量的地址引用变量。源程序也可利用变量的地址引用变量。这为引用变量开辟了一条灵活、可变的途径。这为引用变量开辟了一条灵活、可变的途径。按变量名引用变量习惯称为按变量名引用变量习惯称为直接引用直接引用,,而将变量而将变量 AA 的地址存于另一变量的地址存于另一变量 BB 中中 (B(B =&=&AA ),借助于变量),借助于变量 BB 引用变量引用变量 AA 称为对称为对 AA 的的间接间接引用引用。。

Page 7: 第 6 章 指针和引用

变量地址类型是一种指针类型,变量类型不同,变量地址类型是一种指针类型,变量类型不同,变量地址类型变量地址类型 (( 指针类型指针类型 )) 也不同。也不同。存储变量地址的变量称为存储变量地址的变量称为指针变量指针变量,指针变量,指针变量类型由它能存储的程序对象地址类型来区分。类型由它能存储的程序对象地址类型来区分。如果指针变量如果指针变量 BB 存储着程序对象存储着程序对象 AA 的地址,则的地址,则称指针变量称指针变量 BB 指向程序对象指向程序对象 AA 。。定义指针变量的一般形式为:定义指针变量的一般形式为: 类型 类型 * * 标识符标识符 ;;

指针变量名

是一种指针类型是一种指针类型指针变量能取这种类型的程序对象的地址指针变量能取这种类型的程序对象的地址

Page 8: 第 6 章 指针和引用

例如:例如: int *ip;//int *ip;// 指针变量指针变量 ipip 能取能取 intint 类型变量的地址类型变量的地址 float *fp;//float *fp;// 指针变量指针变量 fpfp 能取能取 floatfloat 类型变量的地类型变量的地址址 指针变量指针变量 ipip 的类型是的类型是 int*int* 的的,, 指针变量指针变量 fpfp 的类型是的类型是 float*float* 的的。。

Page 9: 第 6 章 指针和引用

例如,以下代码将整型变量例如,以下代码将整型变量 ii 的地址赋值给指针变量的地址赋值给指针变量 ip:ip: int i = 2;int i = 2; ip = &i;ip = &i;这样的赋值使这样的赋值使 ipip 与与 ii 之间建立如图之间建立如图 6.16.1 所示的关系。当所示的关系。当指针变量指针变量 ipip 的值为变量的值为变量 ii 的地址时,就说指针变量的地址时,就说指针变量 ipip 指指向变量向变量 ii 。。

图图 6.1 6.1 指针变量与它所指变量关系图指针变量与它所指变量关系图

i p 2

i

Page 10: 第 6 章 指针和引用

指针变量定义时也可指定初值。例如指针变量定义时也可指定初值。例如 int j;int j; int *intpt = &jint *intpt = &j ;;在定义在定义 int*int* 类型指针变量类型指针变量 intptintpt 时,给它初始化为时,给它初始化为 inintt 类型变量类型变量 jj 的地址,使它指向的地址,使它指向 jj 。。当定义局部指针变量时,如果未给它指定初值,则它当定义局部指针变量时,如果未给它指定初值,则它的值是不确定的。程序在使用它们时,应首先给它们的值是不确定的。程序在使用它们时,应首先给它们赋值,让指针变量确实指向某个变量。误用值不确定赋值,让指针变量确实指向某个变量。误用值不确定的指针变量会引起意想不到的错误。的指针变量会引起意想不到的错误。

Page 11: 第 6 章 指针和引用

为明确表示指针变量暂不指向任何变量,用为明确表示指针变量暂不指向任何变量,用 0(0( 记为记为 NUNULL)LL) 表示这种情况。一个值为表示这种情况。一个值为 NULLNULL 的指针变量,习惯的指针变量,习惯称它是空指针。称它是空指针。例如,例如, ip = NULL;ip = NULL;让让 ipip 是一个空指针,暂不指向任何变量。是一个空指针,暂不指向任何变量。对于静态的指针变量,如果在定义时对于静态的指针变量,如果在定义时未给它指定初值,未给它指定初值,系统自动给它指定初值为系统自动给它指定初值为 00 ,让它暂时是一个空指针。,让它暂时是一个空指针。

Page 12: 第 6 章 指针和引用

指针变量取程序对象的指针变量取程序对象的 (( 开始开始 )) 地址值,不能地址值,不能将任何其他非地址值赋给指针变量。指针变量将任何其他非地址值赋给指针变量。指针变量对所指对象也有类型限制,不能将一个不能指对所指对象也有类型限制,不能将一个不能指的对象的地址赋给指针变量。例如:的对象的地址赋给指针变量。例如: int i = 100, j, *ip, *intpt; int i = 100, j, *ip, *intpt; float f, *fp;float f, *fp; ip = 100; /* ip = 100; /* 指针变量不能赋整数值 指针变量不能赋整数值 */*/ intpt = j; /* intpt = j; /* 指针变量不能赋整型变量的值 指针变量不能赋整型变量的值 */*/ fp = &ifp = &i;;        /*float*/*float* 类型变量,不能指向类型变量,不能指向 intint 型变量 型变量 */*/ fp = ip; /* fp = ip; /* 不同类型指针变量不能相互赋值 不同类型指针变量不能相互赋值 */*/

Page 13: 第 6 章 指针和引用

而以下都是正确的赋值:而以下都是正确的赋值: ip = &i ; /* ip = &i ; /* 使 使 ip ip 指向 指向 i */i */ intpt = ip ; /* intpt = ip ; /* 使 使 intpt intpt 指向 指向 ip ip 所指变量 所指变量 **// fp = &f ; /* fp = &f ; /* 使 使 fp fp 指向 指向 f */f */ ip = NULL ; /* ip = NULL ; /* 使 使 ip ip 不再指向任何变量 不再指向任何变量 */*/同类型的指针变量可以相互赋值;同类型的指针变量可以相互赋值;可以赋指针变量能指向的程序对象地址给指针变量。可以赋指针变量能指向的程序对象地址给指针变量。

Page 14: 第 6 章 指针和引用

引用指针变量所指的变量引用指针变量所指的变量当指针变量明确指向一个对象当指针变量明确指向一个对象 (( 值不是 值不是 NULL)NULL) 时,可时,可以以用运算符“用运算符“ *”*” , 引用指针变量所指向的对象, 引用指针变量所指向的对象。。如代码: 如代码: ip = &i; j = *ip;ip = &i; j = *ip;实现将指针变量实现将指针变量 ipip 所指变量的内容所指变量的内容 (( 即变量即变量 ii 的内的内容容 )) 赋给变量赋给变量 jj 。其中,赋值号右边的。其中,赋值号右边的 *ip *ip 表示引用表示引用ipip 所指变量的内容。所指变量的内容。上述赋值等价于: 上述赋值等价于: j = i;j = i;代码 代码 *ip = 200;*ip = 200;实现向指针变量实现向指针变量 ipip 所指变量所指变量 (( 即变量即变量 i)i) 赋值赋值 200200 。。其中,赋值号左边的其中,赋值号左边的 *ip*ip 表示引用表示引用 ipip 所指变量。上述所指变量。上述赋值等价于 赋值等价于 i = 200 ;i = 200 ;

Page 15: 第 6 章 指针和引用

记号“记号“ ** 指针变量名”与指针变量所指变量的“变量指针变量名”与指针变量所指变量的“变量名”等价。名”等价。如代码如代码 intpt = &j; *intpt = *ip+5 ;intpt = &j; *intpt = *ip+5 ;与代码 与代码 j = i+5 ; j = i+5 ; 等价。等价。

Page 16: 第 6 章 指针和引用

【例【例 6.16.1 】说明指针变量与它所指变量之间关系的示意程序】说明指针变量与它所指变量之间关系的示意程序#include <stdio.h>#include <stdio.h>int main()int main(){ int k; //{ int k; // 一个整型变量一个整型变量 int *kPtr; //int *kPtr; // 一个整型指针变量 一个整型指针变量 k = 7;k = 7; kPtr = &k;//kPtr = &k;// 使使 kPtrkPtr 指向变量指向变量 kk 。。 printf("kprintf("k 的地址是 的地址是 %x\nkPtr%x\nkPtr 的值是 的值是 %x",&k, kPtr);%x",&k, kPtr); printf("\nkprintf("\nk 的值是 的值是 %d\n*kPtr%d\n*kPtr 的值是 的值是 %d", k ,*kPtr);%d", k ,*kPtr); printf("\nprintf("\n 以下表明运算符 以下表明运算符 * * 和 和 & & 是互逆的:是互逆的: \n&*kP\n&*kPtr = %x\n*&kPtr = %x\n",&*kPtr,*&kPtr);tr = %x\n*&kPtr = %x\n",&*kPtr,*&kPtr); return 0;return 0;}}

Page 17: 第 6 章 指针和引用

【程序说明】【程序说明】kPtrkPtr 指向变量指向变量 kk 。第一个输出代码输出变量。第一个输出代码输出变量 kk 的地址和指针变的地址和指针变量量 kPtrkPtr 的值。第二个输出代码输出变量的值。第二个输出代码输出变量 kk 的值和指针变量的值和指针变量 kPtkPtrr 所指变量的值。由于所指变量的值。由于 kPtrkPtr 所指的正是变量所指的正是变量 kk ,所以,所以 kk 的地址的地址值与值与 kPtrkPtr 的内容相同,的内容相同, kk 的值就是的值就是 kPtrkPtr 所指变量的值。第三所指变量的值。第三个的输出代码表示个的输出代码表示 &*kPtr&*kPtr 与与 *&kPtr*&kPtr 是一样的。运行以上程序,是一样的。运行以上程序,将输出将输出 kk 的地址是 的地址是 12ff7c12ff7c kPtrkPtr 的值是 的值是 12ff7c12ff7c kk 的值是 的值是 77 *kPtr*kPtr 的值是 的值是 77 以下表明运算符 以下表明运算符 * * 和 和 & & 是互逆的:是互逆的: &*kPtr = 12ff7c&*kPtr = 12ff7c *&kPtr = 12ff7c*&kPtr = 12ff7c上述程序的输出发现变量上述程序的输出发现变量 kk 的地址和变量的地址和变量 kPtrkPtr 的值是相同的,的值是相同的,确信变量确信变量 kk 的地址赋值给了指针变量的地址赋值给了指针变量 kPtrkPtr 。运算符。运算符 && 和和 ** 是互是互为相反的。当它们以不同的次序作用于指针变量时,得到相同为相反的。当它们以不同的次序作用于指针变量时,得到相同的结果。不同系统上运行以上程序,输出的地址可能不同。的结果。不同系统上运行以上程序,输出的地址可能不同。

Page 18: 第 6 章 指针和引用

要特别注意指针变量之间的赋值,指针变量所指向的要特别注意指针变量之间的赋值,指针变量所指向的变量之间的赋值,这两种赋值在表示方法上的区别。变量之间的赋值,这两种赋值在表示方法上的区别。如代码 如代码 intpt = ip;intpt = ip;使两个指针变量使两个指针变量 intptintpt 与与 ipip 指向同一个对象,或都不指向同一个对象,或都不指向任何对象指向任何对象 (( 如果如果 ipip 的值为的值为 NULL)NULL) 。。

Page 19: 第 6 章 指针和引用

通过指针变量间接引用,实际引用的是哪一个通过指针变量间接引用,实际引用的是哪一个变量,取决于指针变量的值。改变指针变量的变量,取决于指针变量的值。改变指针变量的值,就是改变指针变量的指向,从而能实现用值,就是改变指针变量的指向,从而能实现用同样的间接引用代码,引用不同的变量。同样的间接引用代码,引用不同的变量。指针变量最主要的应用有两个方面,一是让指指针变量最主要的应用有两个方面,一是让指针变量指向数组的元素,以便逐一改变指针变针变量指向数组的元素,以便逐一改变指针变量的指向,遍历数组的全部元素。二是让函数量的指向,遍历数组的全部元素。二是让函数设置指针形参,让函数体中的代码通过指针形设置指针形参,让函数体中的代码通过指针形参间接引用调用环境中的变量,或引用变量的参间接引用调用环境中的变量,或引用变量的值,或改变变量的值。值,或改变变量的值。

Page 20: 第 6 章 指针和引用

为正确理解和使用指针变量,指出以下三点注意事项:为正确理解和使用指针变量,指出以下三点注意事项:(( 11 ))定义指针变量与间接引用指针变量所指对象采用相似的定义指针变量与间接引用指针变量所指对象采用相似的标记形式标记形式 (* (* 指针变量名指针变量名 )) ,,但它们的但它们的作用与意义是完全不同作用与意义是完全不同的。在指针变量定义中的。在指针变量定义中 (( 例如,例如, int *ip;)int *ip;) ,指针变量名之前,指针变量名之前的符号“的符号“ *”*” 说明其随后的标识符是指针变量名。如果指针变说明其随后的标识符是指针变量名。如果指针变量定义时带有初始化表达式,例如量定义时带有初始化表达式,例如 int iint i ,, *ip = &i;*ip = &i;初始化表达式的地址是赋值给指针变量本身,而不是指针变量初始化表达式的地址是赋值给指针变量本身,而不是指针变量所指对象。所指对象。实际上,在初始化之前,指针变量还未指向一个确实际上,在初始化之前,指针变量还未指向一个确定的对象。定的对象。(2)(2) 通过某个指向变量通过某个指向变量 ii 的指针变量的指针变量 ipip 间接引用变量间接引用变量 ii 与直接与直接按变量名按变量名 ii 引用变量引用变量 ii ,效果是相同的,,效果是相同的,凡能直接按变量名可凡能直接按变量名可引用的地方,也可以用指向它的某个指针变量间接引用它。例引用的地方,也可以用指向它的某个指针变量间接引用它。例如,有如,有 int i, *ip = &i;int i, *ip = &i;则凡变量则凡变量 ii 能使用的地方,能使用的地方, *ip*ip 一样能用。一样能用。

Page 21: 第 6 章 指针和引用

(3)(3) 因单目运算符因单目运算符 ** 、、 && 、、 ++++ 和和 ---- 是从右向左是从右向左结合的。注意分清运算对象是指针变量、还是结合的。注意分清运算对象是指针变量、还是指针变量所指对象。指针变量所指对象。例如,有变量定义例如,有变量定义 int i, j, *ip = &i ;int i, j, *ip = &i ;代码代码 j = ++*ip;j = ++*ip;先是先是 *ip*ip ,间接引用,间接引用 ipip 所指向的变量所指向的变量 (( 变量变量 i)i) ,,然后对变量然后对变量 ii 的自增运算,并将运算结果赋值的自增运算,并将运算结果赋值给变量给变量 jj 。。++*ip++*ip 相当于相当于 ++(*ip)++(*ip) 。。

Page 22: 第 6 章 指针和引用

代码代码 j = *ip++;j = *ip++;是先求子表达式是先求子表达式 ip++ip++ 。。 ip++ip++ 的值为原来的值为原来 ipip 的值,然的值,然后是后是 ipip 自增。自增。表达式表达式 *ip++*ip++ 的值与的值与 *ip*ip 相同,并在求出表达式值的相同,并在求出表达式值的同时,同时, ipip 增加了增加了 11 个单位。相当于代码个单位。相当于代码 j = *ip; ip++;j = *ip; ip++;经上述表达式计算后,经上述表达式计算后, ipip 不再指向变量不再指向变量 ii 。。这种情况常用在指针指向数组元素的情况,在引用数这种情况常用在指针指向数组元素的情况,在引用数组某元素之后,自动指向数组的下一个元素。组某元素之后,自动指向数组的下一个元素。代码代码 j = (*ip)++ ;j = (*ip)++ ;则是先间接引用则是先间接引用 ipip 所指向的变量,取这个变量的值赋所指向的变量,取这个变量的值赋值给值给 jj ,并让该变量自增。,并让该变量自增。

Page 23: 第 6 章 指针和引用

将数组元素的地址赋值给指针变量,就能使指针指向数组元素。 例如:例如: int a[100], *p;   p = &a[0] /* 或写成 p = a; */表示:表示:把 a[0]元素的地址赋给指针变量 p,即 p 指向数组 a的下标为 0的元素。好处:好处:1.为引用数组元素提供了一种新的途径 ;2. 比用下标引用数组元素更灵活和简洁,因为指针有一定的运算能力。

6.26.2 指向数组元素的指针指向数组元素的指针

Page 24: 第 6 章 指针和引用

对指向数组元素的指针允许作有限的运算:对指向数组元素的指针允许作有限的运算:例如例如: : int *p, *q, a[100]; int *p, *q, a[100]; p = &a[10]; p = &a[10]; q = &a[50];q = &a[50];(1)(1) 当两个指针指向同一个数组的元素时,这两个指当两个指针指向同一个数组的元素时,这两个指针可以作关系比较针可以作关系比较 (< (< ,, <=<= , , ==== ,, >> , , >=>= , , !=)!=) 。。若 若 p==qp==q 表示表示 pp ,, qq 指向数组的同一个元素;指向数组的同一个元素; p<qp<q 表示表示 pp 所指向的数组元素的下标小于所指向的数组元素的下标小于 qq 所所指向的数组元素的下标。指向的数组元素的下标。 p>qp>q 表示表示 pp 所指向的数组元素的下标大于所指向的数组元素的下标大于 qq 所所指向的数组元素的下标。指向的数组元素的下标。

Page 25: 第 6 章 指针和引用

(2) (2) 指向数组元素的指针可与整数进行加减运算。指向数组元素的指针可与整数进行加减运算。由于数组元素在内存中顺序连续存放,因此表达式由于数组元素在内存中顺序连续存放,因此表达式 a+a+11 为为 a[1]a[1] 的地址,的地址, a+2a+2 为为 a[2]a[2] 的地址。的地址。若指针 若指针 p = &a[0]; p = &a[0]; 则表达式 则表达式 p+i p+i 的值为 的值为 a[i]a[i] 的的地址。或者说,地址。或者说, p+ip+i 的值为指向的值为指向 a[i]a[i] 的指针值。的指针值。(3) (3) 引用数组元素的方法引用数组元素的方法1.1. 用数组元素的下标法,如用数组元素的下标法,如 a[5]a[5] 、、 a[i]a[i] 。。2.2. 利用数组名,如 利用数组名,如 *(a+i)*(a+i) 表示表示 a[i]a[i] 。。3.3. 利用指针变量,如 利用指针变量,如 p = &a[0]; p = &a[0]; 则 则 *(p+i)*(p+i) 表示表示 a[i]a[i] 。 。 *(p+i)*(p+i) 也可写成也可写成 p[i]p[i] 。。

Page 26: 第 6 章 指针和引用

用数组名与指针的区别:用数组名与指针的区别:指针变量的值可改变,例如指针变量的值可改变,例如 p++p++ ;数组名只代表数组;数组名只代表数组首元素的指针,是不可改变的(首元素的指针,是不可改变的(是常量是常量)。)。

Page 27: 第 6 章 指针和引用

int a[100], b[100], *p, *q;int a[100], b[100], *p, *q;for(p = a; p < a + 100;) /*for(p = a; p < a + 100;) /* 利用指针遍历数组,利用指针遍历数组,输入数组的全部元素输入数组的全部元素 */*/ scanf(“%d”, p++);scanf(“%d”, p++);for(p = a; p <= &a[99]; p++)/*for(p = a; p <= &a[99]; p++)/* 利用指针遍历数组,利用指针遍历数组,输出数组的全部元素输出数组的全部元素 */*/ printf(“*p = %d\t”, *p);printf(“*p = %d\t”, *p);printf(“\n”);printf(“\n”);for(p = a, q = b; p < a + 100;)/* for(p = a, q = b; p < a + 100;)/* 利用指针将已利用指针将已知数组复制到另一个数组知数组复制到另一个数组 */*/ *q++ = *p++;*q++ = *p++;

Page 28: 第 6 章 指针和引用

当指针变量指向字符串某字符时,习惯称这样当指针变量指向字符串某字符时,习惯称这样的指针为字符串的字符指针。的指针为字符串的字符指针。字符串存储于字符数组中,所以指向字符串字字符串存储于字符数组中,所以指向字符串字符的指针也是指向数组元素的指针。符的指针也是指向数组元素的指针。但因字符但因字符串有字符串结束符,具体编写字符串处理程序串有字符串结束符,具体编写字符串处理程序时不再需要指定字符串的字符个数,而处理普时不再需要指定字符串的字符个数,而处理普通数组通常需要指定数组的元素个数。通数组通常需要指定数组的元素个数。首先指出程序存储字符串常量的方法。为字符首先指出程序存储字符串常量的方法。为字符串常量提供存储空间有两种方法串常量提供存储空间有两种方法 ::

Page 29: 第 6 章 指针和引用

(1)(1) 把字符串常量存放在一个字符数组中。把字符串常量存放在一个字符数组中。例如:例如: char s[] = "I am a string.";char s[] = "I am a string.";数组数组 ss 共有共有 1515 个元素,其中个元素,其中 s[14]s[14] 为’为’ \0’\0’ 。对于这。对于这种情况,编译程序根据字符串常量所需的字节数为字符种情况,编译程序根据字符串常量所需的字节数为字符数组分配存储单元,并把字符串常量填写到数组中,即数组分配存储单元,并把字符串常量填写到数组中,即对数组初始化。对数组初始化。(2)(2) 字符串常量放在程序运行环境的常量存储区中。字符串常量放在程序运行环境的常量存储区中。表达式中出现的字符串常量由编译系统将它与程序中出表达式中出现的字符串常量由编译系统将它与程序中出现的其他常量一起存放在常量存储区中。程序要访问存现的其他常量一起存放在常量存储区中。程序要访问存于常量存储区中的字符串常量,需用一个字符串的字符于常量存储区中的字符串常量,需用一个字符串的字符指针指向它的第一个字符。指针指向它的第一个字符。

Page 30: 第 6 章 指针和引用

当字符串常量作为表达式出现时,字符串常量放入常当字符串常量作为表达式出现时,字符串常量放入常量存储区,而把表达式转换成该字符串常量存储区的量存储区,而把表达式转换成该字符串常量存储区的第一个字符的指针。第一个字符的指针。字符串常量赋值给字符指针变量,向字符指针变量赋字符串常量赋值给字符指针变量,向字符指针变量赋值的是字符串常量的第一个字符的指针,而不是向指值的是字符串常量的第一个字符的指针,而不是向指针变量赋字符串常量的全部字符。针变量赋字符串常量的全部字符。

Page 31: 第 6 章 指针和引用

例如:例如: char *cp1, *cp2 = “I am a string”;/*char *cp1, *cp2 = “I am a string”;/* 用用字符串常量初始化字符指针变量字符串常量初始化字符指针变量 */*/ cp1 = ”Another string”; /*cp1 = ”Another string”; /* 用字符串常量用字符串常量赋值给字符指针变量赋值给字符指针变量 */*/使字符指针变量使字符指针变量 cp2cp2 指向字符串常量“指向字符串常量“ I am a strinI am a string”g” 的第一个字符的第一个字符 II ,使,使 cp1cp1 指向字符串常量”指向字符串常量” AnotheAnother string ”r string ” 的第一个字符的第一个字符 AA 。以后就可通过。以后就可通过 cp2cp2 或或 cpcp11 分别访问这两个字符串常量中的其它字符。分别访问这两个字符串常量中的其它字符。例如,例如, *cp2*cp2 或或 cp2[0]cp2[0] 就是’就是’ I’I’ ,, *(cp1+3)*(cp1+3) 或或 cp1cp1[3][3] 就是字符’就是字符’ t’t’ 。。要特别强调指出,企图通过指针要特别强调指出,企图通过指针变量修改存于常量区中的字符串常量是不允许的。变量修改存于常量区中的字符串常量是不允许的。

Page 32: 第 6 章 指针和引用

例如,调用库函数例如,调用库函数 strlen()strlen() 求字符串常量的长度:求字符串常量的长度: strlen("I am a string.")strlen("I am a string.")该函数调用的结果是该函数调用的结果是 1414 ,表示这个字符串常量由,表示这个字符串常量由 1414 个有个有效字符组成。效字符组成。如果如果 ss 是一个字符数组,其中已存有字符串,是一个字符数组,其中已存有字符串, cpcp 是一个字是一个字符指针变量,它指向某个字符串的首字符符指针变量,它指向某个字符串的首字符 ::char s[] = “I am a string.”char s[] = “I am a string.” , , *cp = "Another string.";*cp = "Another string.";则代码: 则代码: printf(“%s\n”,s);printf(“%s\n”,s);输出存于字符数组输出存于字符数组 ss 中的字符串。中的字符串。而代码: 而代码: printf(‘%s\n”,cp);printf(‘%s\n”,cp);输出字符指针变量输出字符指针变量 cpcp 所指向的字符串。所指向的字符串。

Page 33: 第 6 章 指针和引用

【例【例 6.26.2 】将一个已知字符串复制到一个字符数组,设】将一个已知字符串复制到一个字符数组,设 fromfrom为已知字符串的首字符指针,为已知字符串的首字符指针, toto 为存储复制字符串的字符数为存储复制字符串的字符数组首元素的指针。若用下标引用数组元素标记法,完成复制的组首元素的指针。若用下标引用数组元素标记法,完成复制的代码可写成:代码可写成: k = 0;k = 0; while ((to[k] = from[k]) != ‘\0’)while ((to[k] = from[k]) != ‘\0’) k++;k++;如采用字符指针描述有:如采用字符指针描述有: while ((*to++ = *from++) != ‘\0’);while ((*to++ = *from++) != ‘\0’);由于字符串结束符’由于字符串结束符’ \0’\0’ 的值为的值为 00 ,上述测试当前复制字符,上述测试当前复制字符是不是字符串结束符的代码中,“是不是字符串结束符的代码中,“ !=’\0’”!=’\0’” 是多余的,字是多余的,字符串复制更简洁的写法是:符串复制更简洁的写法是: while (*to++ = *from++);while (*to++ = *from++);

Page 34: 第 6 章 指针和引用

【例【例 6.36.3 】将字符串】将字符串 ss 中的某种字符去掉,假设要去掉中的某种字符去掉,假设要去掉的字符与字符变量的字符与字符变量 cc 中的字符相同。中的字符相同。一边考察字符,一边复制不去掉的字符来实现。引入一边考察字符,一边复制不去掉的字符来实现。引入字符指针字符指针 pp 和和 qq ,其中,其中 pp 指向当前正要考察的字符,若指向当前正要考察的字符,若它所指的字符与它所指的字符与 cc 中的字符不相同,则应将它复制到新中的字符不相同,则应将它复制到新字符串中。否则,该字符不被复制,也就从新的字符字符串中。否则,该字符不被复制,也就从新的字符串中去掉了;串中去掉了; qq 指向下一个用于存储复制字符的存储位指向下一个用于存储复制字符的存储位置。每次复制一个字符后,置。每次复制一个字符后, qq 增加增加 11 。每次考察了一个。每次考察了一个字符后,字符后, pp 就增就增 11 。。 for(p = q = s; *p; p++) for(p = q = s; *p; p++) if (*p != c) *q++ = *p; /* if (*p != c) *q++ = *p; /* 复制 复制 */*/ *p = ’\0’; /* *p = ’\0’; /* 重新构成字符串 重新构成字符串 */*/

Page 35: 第 6 章 指针和引用

在函数计算过程中,函数不能修改实参变量。但许多应在函数计算过程中,函数不能修改实参变量。但许多应用要求被调用函数能修改由实参指定的变量。用要求被调用函数能修改由实参指定的变量。 CC 语言的语言的指针形参能实现这种特殊的要求。指针形参能够指向的指针形参能实现这种特殊的要求。指针形参能够指向的对象的类型在形参说明时指明。例如,以下函数说明中对象的类型在形参说明时指明。例如,以下函数说明中void divNum(int *intPt, int d);void divNum(int *intPt, int d);intPtintPt 是一个指针形参,它能指向是一个指针形参,它能指向 intint 类型的变量。类型的变量。调用有指针形参的函数时,对应指针形参的实参必须是调用有指针形参的函数时,对应指针形参的实参必须是某个变量的指针。某个变量的指针。指针形参从实参处得到某变量的指针,指针形参从实参处得到某变量的指针,使指针形参指向一个变量。这样,函数就可用这个指针使指针形参指向一个变量。这样,函数就可用这个指针形参间接访问被调用函数之外的变量,或引用其值,或形参间接访问被调用函数之外的变量,或引用其值,或修改其值。修改其值。因此,指针类型形参为函数改变调用环境中因此,指针类型形参为函数改变调用环境中的变量提供了手段。的变量提供了手段。

6.3 6.3 指针形参指针形参

Page 36: 第 6 章 指针和引用

【例【例 6.46.4 】本例中的两个函数用于说明一般形参与】本例中的两个函数用于说明一般形参与指针形参的区别。函数指针形参的区别。函数 squreByValue()squreByValue() 有一个整型有一个整型形参形参 nn ,求得,求得 nn 的二次方幂返回。函数的二次方幂返回。函数 squreByPoinsqureByPoint()t() 设有一个整型指针形参,该函数根据指针形参所设有一个整型指针形参,该函数根据指针形参所指变量,将变量的值改成是它调用之前的二次幂。指变量,将变量的值改成是它调用之前的二次幂。

Page 37: 第 6 章 指针和引用

#include <stdio.h>#include <stdio.h>int squreByValue(int n)int squreByValue(int n){ return n*n;//{ return n*n;// 以以 nn 的平方为结果返回的平方为结果返回}}void squreByPoint(int *nPtr)void squreByPoint(int *nPtr){ *nPtr = *nPtr * *nPtr;/*{ *nPtr = *nPtr * *nPtr;/* 将形参所指向的变量值平方,将形参所指向的变量值平方,并存于该变量中并存于该变量中 */*/}}

Page 38: 第 6 章 指针和引用

int main()int main(){ int m = 5;{ int m = 5; printf(”printf(”数数 mm原来的值是 原来的值是 %d\n”, m);%d\n”, m); printf(”printf(”函数调用函数调用 squreByValue(m)squreByValue(m)的返回值是 的返回值是 %d\n”,%d\n”, squreByValue(m));squreByValue(m)); printf(”printf(”函数调用后函数调用后mm的值是 的值是 %d\n”, m); %d\n”, m); printf(”*******************************************************\n”);printf(”*******************************************************\n”); printf(”printf(”数数 mm原来的值是 原来的值是 %d\n”, m);%d\n”, m); squreByPoint(&m); //msqureByPoint(&m); //m值将因调用函数而改变值将因调用函数而改变 printf(printf(””函数调用函数调用 squreByPoint (&m)squreByPoint (&m)后后mm的值是 的值是 %d\n”%d\n”, m);, m); return 0;return 0;}}

Page 39: 第 6 章 指针和引用

图 6.2 示意一般形参的函数调用过程 void main() m{ int m = 5; printf(”%d\n”, squreByValue(m));}

5 int squreByValue(int n){ return n * n;} n 无定义

主函数调用函数 squreByValue() 之前 void main() m{ int m = 5; printf(”%d\n”, squreByValue(m));}

5 int squreByValue(int n){ return n * n;} n 5

squreByValue() 接受调用后 void main() m{ int m = 5; printf(”%d\n”, squreByValue(m));}

5 int squreByValue(int n){ return n * n; 结果 25} n 无定义

函数 squreByValue() 计算出形参的二次幂之后 void main() m{ int m = 5; printf(”%d\n”,squreByValue(m));// 输出 25}

5 int squreByValue(int n){ return n * n; } n 无定义

函数调用 squreByValue() 结束,返回主函数之后

不占存储空间

不占存储空间

存储空间被释放

Page 40: 第 6 章 指针和引用

图 6.3 示意指针形参的函数调用过程 void main() m{ int m = 5; squreByPoint(&m);}

5 void squreeByPoint(int *nPtr){ *nPtr = *nPtr * *nPtr;} nPtr 无定义

主函数调用函数 squreByPoint() 之前 void main() m{ int m = 5; squreByPoint(&m);}

5 void squreeByPoint(int *nPtr){ *nPtr = *nPtr * *nPtr;} nPtr

squreByPoint() 接受调用后 void main() m{ int m = 5; squreByPoint(&m);}

25 void squreeByPoint(int *nPtr){ *nPtr = *nPtr * *nPtr;} nPtr

函数 squreByPoint() 执行,返回之前 void main() m{ int m = 5; squreByPoint(&m);}

25 void squreeByPoint(int *nPtr){ *nPtr = *nPtr * *nPtr;} nPtr 无定义

函数 squreByPoint() 返回之后

计算结果赋值给 m

不占存储空间

不占存储空间

获得存储空间,指向 m

Page 41: 第 6 章 指针和引用

【例【例 6.56.5】说明指针形参用法的示意程序。】说明指针形参用法的示意程序。程序中的函数程序中的函数 swap()swap() 的功能是交换两个整型变量的值,函数的功能是交换两个整型变量的值,函数 swap()swap() 设置了设置了两个整型指针形参。两个整型指针形参。在函数在函数 swap()swap() 的体中,用指针形参间接引用它们所指向的变量。的体中,用指针形参间接引用它们所指向的变量。调用函数调用函数 swap()swap() 时,提供的两个实参必须是要交换值的两个变量的指针,时,提供的两个实参必须是要交换值的两个变量的指针,而不是变量的值。而不是变量的值。

Page 42: 第 6 章 指针和引用

#include <stdio.h>#include <stdio.h>int main()int main(){ int a = 1, b = 2;{ int a = 1, b = 2;        void swap(int *, int *);void swap(int *, int *); printf(”printf(”调用调用 swapswap函数之前:函数之前:a = %d\tb = %d\n“, a, b);a = %d\tb = %d\n“, a, b); swap(&a,&b);/*swap(&a,&b);/*以变量的指针为实参,而不是变量的值 以变量的指针为实参,而不是变量的值 */*/ printf(”printf(”调用调用 swapswap函数之后:函数之后:a = %d\tb = %d\n“, a, b);a = %d\tb = %d\n“, a, b); return 0;return 0;}}void swap(int *pu, int *pv)/* void swap(int *pu, int *pv)/* 函数设置两个指针形参 函数设置两个指针形参 */*/{ int t; { int t;    t = *pu; /* t = *pu; /* 函数体通过指针形参,间接引用和改变调用环境中的变量 函数体通过指针形参,间接引用和改变调用环境中的变量 */*/ *pu = *pv; *pv = t;*pu = *pv; *pv = t;}}

调用 swap 函数之前: a = 1 b = 2

调用 swap 函数之后: a = 2 b = 1

&a &b

Page 43: 第 6 章 指针和引用

【例【例 6.66.6 】对于非指针类型形参,实参向形参传值,函数不能改变实参变量值的示意程序。】对于非指针类型形参,实参向形参传值,函数不能改变实参变量值的示意程序。#include <stdio.h>#include <stdio.h>void paws(int u, int v)void paws(int u, int v){ int t = u;{ int t = u; u = v; v = t;u = v; v = t; printf(”printf(” 在函数 在函数 paws paws 中:中: u = %d\tv = %d\n“, u, v);u = %d\tv = %d\n“, u, v);}}int main()int main(){ int x = 1, y = 2;{ int x = 1, y = 2; paws(x, y);paws(x, y); printf(”printf(” 在主函数 在主函数 main main 中:中: x = %d\ty = %d\n“, x, y);x = %d\ty = %d\n“, x, y); return 0;return 0;}}

在函数 paws 中: u = 2 v = 1

在主函数 main 中: x = 1 y = 2

1 2

Page 44: 第 6 章 指针和引用

例子说明,只有实参向形参单向传递值,函数执行时,形参值的例子说明,只有实参向形参单向传递值,函数执行时,形参值的改变不会影响实参变量。改变不会影响实参变量。希望函数能按需要改变由实参指定的变量,需要在三个方面协调希望函数能按需要改变由实参指定的变量,需要在三个方面协调一致。一致。(1)(1) 函数应设置指针形参;函数应设置指针形参;(2)(2) 函数体必须通过指针形参间接引用变量;函数体必须通过指针形参间接引用变量;(3)(3)调用函数时,必须以希望改变值的变量的指针为实参。调用函数时,必须以希望改变值的变量的指针为实参。

Page 45: 第 6 章 指针和引用

读程序,回答程序的输出结果。读程序,回答程序的输出结果。 #include <stdio.h>#include <stdio.h> void f1(int x, int y) { int t = x; x = y; y = t; }void f1(int x, int y) { int t = x; x = y; y = t; } void f2(int *x, int *y) { int t = *x; *x = *y; *y = t;}void f2(int *x, int *y) { int t = *x; *x = *y; *y = t;} void f3(int **x, int **y) { int *t = *x; *x = *y; *y = t;}void f3(int **x, int **y) { int *t = *x; *x = *y; *y = t;} int main()int main() { int x = 1, y = 2; int *xpt = &x, *ypt = &y;{ int x = 1, y = 2; int *xpt = &x, *ypt = &y; printf(”Firstprintf(”First:: x = %d\ty = %d\n“, x, y); f1(x, y);x = %d\ty = %d\n“, x, y); f1(x, y); printf(”After call f1()printf(”After call f1():: x = %d\ty = %d\n“, x, y);x = %d\ty = %d\n“, x, y); x = 1; y = 2; f2(&x, &y);x = 1; y = 2; f2(&x, &y); printf(”After call f2()printf(”After call f2():: x = %d\ty = %d\n“, x, y);x = %d\ty = %d\n“, x, y);

Page 46: 第 6 章 指针和引用

读程序,回答程序的输出结果。读程序,回答程序的输出结果。 x = 1; y = 2;x = 1; y = 2; printf(”Befor call f3()printf(”Befor call f3() :: x = %d\ty = %d\n“, x, y);x = %d\ty = %d\n“, x, y); printf(”Befor call f3()printf(”Befor call f3() :: *xpt = %d\t*ypt = %d\n“, *xpt = %d\t*ypt = %d\n“, *xpt, *ypt);*xpt, *ypt); f3(&xpt, &ypt);f3(&xpt, &ypt); printf(”After call f3()printf(”After call f3() :: x = %d\ty = %d\n“, x, y);x = %d\ty = %d\n“, x, y); printf(”After call f3()printf(”After call f3() :: *xpt = %d\t*ypt = %d\n“, *xpt = %d\t*ypt = %d\n“, *xpt, *ypt);*xpt, *ypt); return 0;return 0; }}

Page 47: 第 6 章 指针和引用

为了能使函数处理不同的数组,函数应设置数组形参。对应数为了能使函数处理不同的数组,函数应设置数组形参。对应数组形参的实参是数组某元素的地址,最通常的情况是数组首元组形参的实参是数组某元素的地址,最通常的情况是数组首元素的地址。由于数组名能代表数组首元素的地址,所以常用数素的地址。由于数组名能代表数组首元素的地址,所以常用数组名实参对应数组形参。例如,下面定义的函数组名实参对应数组形参。例如,下面定义的函数 sum()sum() 用于求用于求nn 个数之和,这个函数正确地设置有两个形参,一个形参是数个数之和,这个函数正确地设置有两个形参,一个形参是数组,用于对应实在数组;另一个形参是整型的,用于指定求和组,用于对应实在数组;另一个形参是整型的,用于指定求和数组的元素个数。数组的元素个数。 int sum(int a[], int n)int sum(int a[], int n) { int i, s;{ int i, s; for(s = i = 0; i < n; i++)for(s = i = 0; i < n; i++) s += a[i];s += a[i]; return s;return s; }}

6.4 数组形参

Page 48: 第 6 章 指针和引用

利用以上定义的函数利用以上定义的函数 sum()sum() ,如果有以下变量定义:,如果有以下变量定义: int x[] = {1, 2, 3, 4, 5};int x[] = {1, 2, 3, 4, 5}; int i, j;int i, j;则语句则语句 i = sum(x, 5);i = sum(x, 5); j = sum(&x[2], 3);j = sum(&x[2], 3); printf("i = %d\nj = %d\n", i, j);printf("i = %d\nj = %d\n", i, j);将输出将输出 i = 15i = 15 j = 12j = 12

将数组 x 的地址 (&x[0]) 传送给数组形参a ,

将数组 x 中的 x[2] 的地址 (&x[2]) 传送给形参a

Page 49: 第 6 章 指针和引用

对于数组类型的形参来说,函数被调用时,与对于数组类型的形参来说,函数被调用时,与它对应的实在数组由多少个元素是不确定的,它对应的实在数组由多少个元素是不确定的,可能会对应一个大数组,也可能会对应一个小可能会对应一个大数组,也可能会对应一个小数组,甚至会对应数组中的某一段。数组,甚至会对应数组中的某一段。在数组形参说明中,形参数组不必指定数组元在数组形参说明中,形参数组不必指定数组元素的个数。素的个数。为了正确指明某次函数调用实际参与计算的元为了正确指明某次函数调用实际参与计算的元素个数,应另引入一个整型形参来指定,就如素个数,应另引入一个整型形参来指定,就如函数函数 sum()sum() 那样。那样。

Page 50: 第 6 章 指针和引用

因传递给数组形参的实参是数组段的开始地址,函数内对数组形因传递给数组形参的实参是数组段的开始地址,函数内对数组形参的访问就是对实参所指数组的访问。函数也可以改变实参所指参的访问就是对实参所指数组的访问。函数也可以改变实参所指数组元素的值。例如,以下数组元素的值。例如,以下 initArry()initArry() 函数的定义:函数的定义: void initArray(int x[], int n, int val)void initArray(int x[], int n, int val) { int i;{ int i; for(i = 0; i < n; i++) x[i] = val;for(i = 0; i < n; i++) x[i] = val; }}函数函数 initArray()initArray() 是给数组元素赋指定值的。如有数组定义是给数组元素赋指定值的。如有数组定义 int a[10], b[100];int a[10], b[100];语句语句 initArray(a, 10, 1); initArray(b, 50, 2);initArray(a, 10, 1); initArray(b, 50, 2); initArray(&b[50], 50, 4);initArray(&b[50], 50, 4);分别给数组分别给数组 aa 的所有元素赋值的所有元素赋值 11 ,为数组,为数组 bb 的前的前 5050 个元素赋值个元素赋值 22 ,,后后 5050 个元素赋值个元素赋值 44 。。

Page 51: 第 6 章 指针和引用

数组形参也可以是多维数组。当数组形参是多维时,除数组形数组形参也可以是多维数组。当数组形参是多维时,除数组形参的第一维大小不必指定外,其他维的大小必须明确指定。例参的第一维大小不必指定外,其他维的大小必须明确指定。例如,下面的函数如,下面的函数 sumAToB()sumAToB() ,用于将一个,用于将一个 n×10n×10 的二维数组各的二维数组各行的行的 1010 个元素之和存于另一个数组中。个元素之和存于另一个数组中。 void sumAToB(int a[][10], int b[], int n)void sumAToB(int a[][10], int b[], int n) { int i, j;{ int i, j; for(i = 0; i < n; i++)for(i = 0; i < n; i++) for(b[i] = 0, j = 0; j < 10; j++)for(b[i] = 0, j = 0; j < 10; j++) b[i] += a[i][j];b[i] += a[i][j]; }}在函数在函数 sumAToB()sumAToB() 的定义中,对形参的定义中,对形参 aa 的说明写成的说明写成 int a[][]int a[][]是错误的。因二维数组的元素只是一行行存放,并不自动说明是错误的。因二维数组的元素只是一行行存放,并不自动说明数组的列数(即每行元素个数)。如果在数组形参中不说明它数组的列数(即每行元素个数)。如果在数组形参中不说明它的列数,就无法确定数组元素的列数,就无法确定数组元素 a[i][j]a[i][j] 的实际地址。的实际地址。

Page 52: 第 6 章 指针和引用

【例【例 6.76.7 】为数组输入值的函数。】为数组输入值的函数。 void readArray(int a[], int n)void readArray(int a[], int n) { int i;{ int i; for(i = 0; i < n; i++) { for(i = 0; i < n; i++) { printf("Enter a[%d] ", i);printf("Enter a[%d] ", i); scanf("%d", &a[i]);scanf("%d", &a[i]); }} }}

Page 53: 第 6 章 指针和引用

【例【例 6.86.8 】求数组中最大元素值的函数。】求数组中最大元素值的函数。 int maxInArray(int a[], int n)int maxInArray(int a[], int n) { int i, m;{ int i, m; for(m = 0, i = 1; i < n ; i++)for(m = 0, i = 1; i < n ; i++) if (a[m] < a[i]) m = i;if (a[m] < a[i]) m = i; return a[m];return a[m]; }}

Page 54: 第 6 章 指针和引用

因函数的数组形参对应的实参可以是数组某元素的地址,即数组某元因函数的数组形参对应的实参可以是数组某元素的地址,即数组某元素的指针,所以数组形参也是一种指针形参,只是它要求对应的实参素的指针,所以数组形参也是一种指针形参,只是它要求对应的实参是数组某元素的指针,而不是一般变量的指针。所以任何数组形参说是数组某元素的指针,而不是一般变量的指针。所以任何数组形参说明:明: 类型 形参名类型 形参名 [][]都可改写成:都可改写成: 类型 类型 ** 形参名形参名例如,前面的函数例如,前面的函数 sum()sum() 的定义可改写成如下形式:的定义可改写成如下形式: int sum(int *a, int n)int sum(int *a, int n) { int i, s;{ int i, s; for(s = i = 0; i < n; i++)for(s = i = 0; i < n; i++) s += a[i];s += a[i]; return s;return s; }}

Page 55: 第 6 章 指针和引用

函数的形参也是函数的一种局部变量,指针形参就是函数的指函数的形参也是函数的一种局部变量,指针形参就是函数的指针变量,函数针变量,函数 sum()sum() 的定义又可改写成如下形式:的定义又可改写成如下形式: int sum(int *a, int n)int sum(int *a, int n) { int s = 0;{ int s = 0; for(; n--; )for(; n--; ) s += *a++;s += *a++; return s;return s; }}

Page 56: 第 6 章 指针和引用

在许多场合,函数是对表进行操作。为使函数的操作在许多场合,函数是对表进行操作。为使函数的操作对象具有一般性,可为这样的函数设置表的首元素指对象具有一般性,可为这样的函数设置表的首元素指针和元素个数等形参,而函数体用指向表元素的指针针和元素个数等形参,而函数体用指向表元素的指针对表作处理。例如,函数对表作处理。例如,函数 sum()sum() 也可改写成以下形式。也可改写成以下形式。 int sum(int *a, int n)int sum(int *a, int n) { int *ap, s;{ int *ap, s; for(s = 0, ap = a; ap < a+n; ap++)for(s = 0, ap = a; ap < a+n; ap++) s += *ap;s += *ap; return s;return s; }}

Page 57: 第 6 章 指针和引用

函数为了处理顺序存储的线性表,通常至少需要两个函数为了处理顺序存储的线性表,通常至少需要两个形参,形参,一个是线性表首元素的指针,另一个是线性表一个是线性表首元素的指针,另一个是线性表的元素个数。的元素个数。如果将以上讨论的内容应用于字符串处如果将以上讨论的内容应用于字符串处理,由于字符串存储于字符数组中,所以有关指向数理,由于字符串存储于字符数组中,所以有关指向数组元素的指针形参的用法都可应用于编写字符串处理组元素的指针形参的用法都可应用于编写字符串处理函数。函数。因字符串包含有字符串结束符的特殊性,表示因字符串包含有字符串结束符的特殊性,表示数组元素个数的形参就可省去数组元素个数的形参就可省去。。处理字符串的函数有一个字符指针形参,对应的实参处理字符串的函数有一个字符指针形参,对应的实参或者是字符数组某个元素的指针,或者是字符串的首或者是字符数组某个元素的指针,或者是字符串的首字符指针。在编写字符串处理函数时还会有许多小技字符指针。在编写字符串处理函数时还会有许多小技巧,下面以常用字符串库函数的实现为例,讨论编写巧,下面以常用字符串库函数的实现为例,讨论编写字符串处理函数的方法。字符串处理函数的方法。

Page 58: 第 6 章 指针和引用

将一个字符串从一个函数传递给另一个函数,可将一个字符串从一个函数传递给另一个函数,可以使用指向字符串的指针变量作为参数。以使用指向字符串的指针变量作为参数。【例【例 6.96.9 】字符串拷贝函数 】字符串拷贝函数 strcpy()strcpy() void strcpy (char *to, char * from)void strcpy (char *to, char * from) { while (*to++ = *from++); }{ while (*to++ = *from++); } void main()void main() { char a[ ]="Fudan University", b[100];{ char a[ ]="Fudan University", b[100]; strcpy (b, a);strcpy (b, a); printf ("string a=%s\nstring b=%s\n", a,b);printf ("string a=%s\nstring b=%s\n", a,b); } }

Page 59: 第 6 章 指针和引用

该函数的功能是比较两字符串的大小:该函数的功能是比较两字符串的大小: strcmp()strcmp() 有两有两个形参个形参 ss 和和 tt ;分别为两个要比较字符串的首字符指针。;分别为两个要比较字符串的首字符指针。如如 ss 所指的字符串小于所指的字符串小于 tt 所指的字符串,函数返回值小所指的字符串,函数返回值小于于 00 ;如;如 ss 所指字符串大于所指字符串大于 tt 所指字符串,函数返回值所指字符串,函数返回值大于大于 00 ;如果两个字符串相同,则函数返回;如果两个字符串相同,则函数返回 00 。。函数以函数以 ss 和和 tt 为两字符串的顺序考察工作指针,用循环为两字符串的顺序考察工作指针,用循环比较比较 ss 和和 tt 所指的两字符,直至两个字符不相等结束比所指的两字符,直至两个字符不相等结束比较循环。较循环。循环过程中要判字符串是否结束,如果字符串循环过程中要判字符串是否结束,如果字符串已结束,则两字符串相同,函数返回已结束,则两字符串相同,函数返回 00 ;否则,两字符;否则,两字符指针分别加指针分别加 11 ,准备比较下一对字符。当两字符不相等,准备比较下一对字符。当两字符不相等结束循环时,函数可直接以两字符的差返回。结束循环时,函数可直接以两字符的差返回。

【例 6.10 】两字符串比较函数 strcmp()

Page 60: 第 6 章 指针和引用

int strcmp(char *sint strcmp(char *s ,, char *t)char *t)/* return <0, if s<t; 0, if s==t; >0,if s>t *//* return <0, if s<t; 0, if s==t; >0,if s>t */ {{ while (*s == *t) {/* while (*s == *t) {/* 对应字符相等循环 对应字符相等循环 */*/ if (*s == '\0') return 0;if (*s == '\0') return 0; s++; t++;s++; t++; }} return *s - *t; /* return *s - *t; /* 返回比较结果 返回比较结果 */*/ }}

Page 61: 第 6 章 指针和引用

程序也可定义指向二维数组一整行的指针变量,这种指针变量程序也可定义指向二维数组一整行的指针变量,这种指针变量增减增减 11 个单位,指针变量就会向前或向后移一整行。要定义指个单位,指针变量就会向前或向后移一整行。要定义指向二维数组一整行的指针变量,用以下形式的代码向二维数组一整行的指针变量,用以下形式的代码 int (*p)[4];int (*p)[4];定义指针变量定义指针变量 pp 能指向一个由四个能指向一个由四个 intint 型元素组成的数组。在型元素组成的数组。在以上定义中,圆括号是必需的。以上定义中,圆括号是必需的。例如,代码,例如,代码, int *q[4];int *q[4];是定义一个指针数组是定义一个指针数组 qq ,数组,数组 qq 有四个元素,每个元素是一个有四个元素,每个元素是一个指向整型变量的指针。指向整型变量的指针。在这里,在这里, pp 是一个指向由四个整型元素组成的数组,对是一个指向由四个整型元素组成的数组,对 pp 作增作增减减 11 运算,就表示向前进或向后退四个整型元素。运算,就表示向前进或向后退四个整型元素。

6.56.5 指向二维数组一整行的指针 指向二维数组一整行的指针

Page 62: 第 6 章 指针和引用

不妨假设有以下变量定义:不妨假设有以下变量定义: int a[3][4] = {{1, 2, 3, 4},int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8},{5, 6, 7, 8}, {9, 10, 11, 12}};{9, 10, 11, 12}}; int (*p)[4];int (*p)[4];则赋值则赋值 : p = a;: p = a;使使 pp 指向二维数组指向二维数组 aa 的第的第 11 行,表达式行,表达式 p+1p+1 的指针值为指向二的指针值为指向二维数组维数组 aa 的第二行,表达式的第二行,表达式 p+ip+i 指向二维数组指向二维数组 aa 的第的第 i+1i+1 行。行。对于二维数组和指向二维数组的一整行的指针,在引用二维数对于二维数组和指向二维数组的一整行的指针,在引用二维数组元素时,另有一些特别的表示形式。继续以上述的二维数组组元素时,另有一些特别的表示形式。继续以上述的二维数组aa 和指向二维数组一整行指针和指向二维数组一整行指针 pp 为例。从行的方向看数组为例。从行的方向看数组 aa ,,数组数组 aa 有三个元素,分别为有三个元素,分别为 a[0]a[0] ,, a[1]a[1] ,, a[2]a[2] 。它们又分别。它们又分别是一个一维数组,各有是一个一维数组,各有 44 个元素。个元素。例如,例如, a[0]a[0] 所代表的一维数组为所代表的一维数组为 a[0][0]a[0][0] 、、 a[0][1]a[0][1] 、、 a[0][2]a[0][2] 、、 a[0][3]a[0][3] 。。

Page 63: 第 6 章 指针和引用

与一维数组名可看作数组的第一个元素与一维数组名可看作数组的第一个元素 (( 下标为下标为 0)0) 的地址约定的地址约定相一致,二维数组名相一致,二维数组名 aa 可以看作可以看作 aa 的首元素一维数组的首元素一维数组 a[0]a[0] 的地的地址,即表示二维数组址,即表示二维数组 00 行的首地址。行的首地址。a+ia+i 可以看作数组可以看作数组 aa 的元素一维数组的元素一维数组 a[i]a[i] 的地址,即二维数组的地址,即二维数组i+1i+1 行的首地址。行的首地址。a[0]a[0] 能表示一维数组能表示一维数组 a[0]a[0] 的首元素的首元素 a[0][0]a[0][0] 的地址;的地址; a[1]a[1] 能能表示一维数组表示一维数组 a[1]a[1] 首元素首元素 a[1][0]a[1][0] 的地址。一般地,的地址。一般地, a[i]a[i] 能能表示一维数组表示一维数组 a[i]a[i] 首元素首元素 a[i][0]a[i][0] 的地址。的地址。a+ia+i 与与 a[i]a[i] 的意义的意义 (( 类型类型 )) 不同,不同, a+ia+i 表示整个一维数组表示整个一维数组 a[i]a[i]的开始地址,的开始地址, a[i]a[i] 表示一维数组表示一维数组 a[i]a[i] 首元素首元素 a[i][0]a[i][0] 的地址。的地址。因因 a[i]a[i] 可写成可写成 *(a+i)*(a+i) ,所以,所以 a+i a+i 与与 *(a+i)*(a+i) 也有不同意义。也有不同意义。a[i]a[i] 或或 *(a+i)*(a+i) 表示二维数组表示二维数组 aa 的元素的元素 a[i][0]a[i][0] 的地址,即的地址,即 &a&a[i][0][i][0] 。。根据地址运算规则,根据地址运算规则, a[i]+ja[i]+j 即代表数组即代表数组 aa 的元素的元素 a[i][j]a[i][j] 的地的地址,即址,即 &a[i][j]&a[i][j] 。。因因 a[i]a[i] 与 与 *(a+i)*(a+i) 等价,所以等价,所以 *(a+i)+j*(a+i)+j 也与也与 &a[i][j]&a[i][j] 等价。等价。

Page 64: 第 6 章 指针和引用

利用二维数组元素利用二维数组元素 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))[j] 。。 *(a[i]+j)*(a[i]+j) 、、 *(*(a+i)+j)*(*(a+i)+j) 、、 (*(a+i))[j](*(a+i))[j]对于对于 a[0][0]a[0][0] ,它的等价表示形式有,它的等价表示形式有 *a[0]*a[0] 和和 **a**a 。。

Page 65: 第 6 章 指针和引用

数组元素数组元素a[i][j]a[i][j]的地址也有三种等价的表示形式:的地址也有三种等价的表示形式:a[i]+ja[i]+j、、*(a+i)+j*(a+i)+j、、&a[i][j]&a[i][j]。。以上关于由二维数组名引用二维数组中的行和二维数组中的元素的约定,也一样可用于指向二维数组一整行的指针。以上关于由二维数组名引用二维数组中的行和二维数组中的元素的约定,也一样可用于指向二维数组一整行的指针。如果有如果有p = a+1p = a+1,则,则a[i+1][j]a[i+1][j]有以下三种等价的表示形式:有以下三种等价的表示形式:*(p[i]+j)*(p[i]+j)、、*(*(p+i)+j)*(*(p+i)+j)、、(*(p+i))[j](*(p+i))[j]。。以下三种形式都表示数组元素以下三种形式都表示数组元素a[i+1][j]a[i+1][j]的地址:的地址:p[i]+jp[i]+j、、*(p+i)+j*(p+i)+j、、&p[i][j]&p[i][j]。。

Page 66: 第 6 章 指针和引用

【例【例6.116.11】说明指向数组元素的指针和指向数组的指针的区别的示意程序。】说明指向数组元素的指针和指向数组的指针的区别的示意程序。#include <stdio.h>#include <stdio.h>int main()int main(){ int a[3][4] = { { 1, 3, 5, 7},{ 9, 11, 13, 15},{ int a[3][4] = { { 1, 3, 5, 7},{ 9, 11, 13, 15}, {17, 19, 21, 23}};{17, 19, 21, 23}}; int i, *ip, (*p)[4];int i, *ip, (*p)[4]; p = a+1; ip = p[0]; p = a+1; ip = p[0]; /* ip = p = &a[1][0] */ for(i = 1; i <= 4; ip += 2, i++)printf("%d\t", *ip);for(i = 1; i <= 4; ip += 2, i++)printf("%d\t", *ip); printf("\n"); p = a; printf("\n"); p = a; /* p = &a[0][0] */ for (i = 0; i < 2; p++, i++)for (i = 0; i < 2; p++, i++) printf("%d\t", *(*(p+i)+1));printf("%d\t", *(*(p+i)+1)); printf("\n");printf("\n"); return 0;return 0;}}

程序运行后,将输出程序运行后,将输出 9 13 17 219 13 17 21 3 193 19

Page 67: 第 6 章 指针和引用

在程序中,开始时在程序中,开始时pp指向二维数组指向二维数组aa的第的第22行,行,p[0]p[0]或者或者*p*p是是 a[1][0]a[1][0]的地址,的地址,ipip指指向 向 a[1][0]a[1][0]。在第一个循环中,每次循环后修改。在第一个循环中,每次循环后修改ipip,使,使ipip增加增加22。在第二个循环中,。在第二个循环中,每次对每次对pp的修改,使的修改,使pp指向二维数组的下一行,而指向二维数组的下一行,而*(*(p+i)+1)*(*(p+i)+1)引用后引用后ii行的第行的第22列元素。列元素。其中其中 *(*(p+i)+1)*(*(p+i)+1)也可写成也可写成 *(p[i]+1)*(p[i]+1)。。

Page 68: 第 6 章 指针和引用

当一个数组的元素均为指针类型时,该数组称为指针当一个数组的元素均为指针类型时,该数组称为指针数组。数组。定义形式:定义形式:类型说明符 类型说明符 * * 数组名数组名 [[ 常量表达式常量表达式 ]] ;;例子:例子: int *p[10];int *p[10];定义:定义:指针数组指针数组 pp 有有 1010 个元素,每个元素都是一个个元素,每个元素都是一个可可 指向整型数据的指针变量。 指向整型数据的指针变量。说明:说明:和一般的数组定义一样,数组名和一般的数组定义一样,数组名 pp 也可作为也可作为 p[0]p[0] 的地址。的地址。注意:注意:在“在“ *”*” 与数组名之外不能加上圆括号,否则 与数组名之外不能加上圆括号,否则 变成指向数组的指针变量。 变成指向数组的指针变量。如:如: int (*q)[10]; int (*q)[10]; 是定义指向由是定义指向由 1010 个整型元个整型元 素组成的数组的指针。 素组成的数组的指针。

6.66.6 指针数组指针数组

Page 69: 第 6 章 指针和引用

引入指针数组的主要目的是便于统一管理同类的指针。引入指针数组的主要目的是便于统一管理同类的指针。例如,利用指针数组能实现对一组独立的变量以数组例如,利用指针数组能实现对一组独立的变量以数组的形式对它们作统一处理。如果有以下定义:的形式对它们作统一处理。如果有以下定义:int a, b, c, d, e, f;int a, b, c, d, e, f;int *ap[] = {&a, &b, &c, &d, &e, &f};int *ap[] = {&a, &b, &c, &d, &e, &f};下面的循环语句能顺序访问独立的变量下面的循环语句能顺序访问独立的变量 aa 、、 bb 、、 cc 、、dd 、、 ee 、、 ff ::for(k = 0; k < 6; k++)for(k = 0; k < 6; k++) printf(“%d\t”, *ap[k]); /* printf(“%d\t”, *ap[k]); /* 其中其中 *apt[k]*apt[k]可写成可写成 **(apt+k) */**(apt+k) */

Page 70: 第 6 章 指针和引用

#include <stdio.h>#define N sizeof ap/sizeof ap[0] /* /* 整个数组占用的字节数整个数组占用的字节数 ÷÷ 一个元素占用的字节数一个元素占用的字节数 得到该数组有多少个元素 得到该数组有多少个元素 */*/int a, b, c, d, e, f;int main(){ int *ap[ ] = { &a, &b, &c, &d, &e, &f }; int k, j, t; printf ( "Enter a, b, c, d, e, f.\n" ); for ( k = 0; k < N; k++ ) scanf( "%d",ap[k] ); /* ap[k] 也可写为 *(ap+k) */

【例【例 6.126.12 】 】 排序时交换变量值排序时交换变量值

Page 71: 第 6 章 指针和引用

for ( k = 1; k < N; k++ ) for ( j = 0; j < N-k; j++ ) if ( *ap[j] > *ap[j+1] ) { t = *ap[j]; /* 交换变量的值 */ *ap[j] = *ap[j+1]; *ap[j+1] = t; } for ( k = 0; k < N; k++ ) printf ( "%d\t", *ap[k] ); printf("\n"); return 0;}

Page 72: 第 6 章 指针和引用

#include <stdio.h> #define N sizeof ap/sizeof ap[0] int a, b, c, d, e, f; int main() { int *ap[ ] = { &a, &b, &c, &d, &e, &f }; int k, j, *t; printf ( "Enter a, b, c, d, e, f.\n"); for ( k = 0; k < N; k++ ) scanf ( "%d", ap[k] );

【例【例 66.. 1313】排序时不交换变量的值,而是交换】排序时不交换变量的值,而是交换它们的指针它们的指针

Page 73: 第 6 章 指针和引用

for ( k = 1; k < N; k++ ) for ( j = 0; j < N-k; j++ ) if ( *ap[j] > *ap[j+1] ) { t = ap[j]; /* 交换变量的指针 */ ap[j] = ap[j+1]; ap[j+1] = t; } for ( k = 0; k < N; k++ ) printf ( "%d\t", *ap[k] ); printf( "\n" ); return 0; }

Page 74: 第 6 章 指针和引用

当指针数组的元素指向不同的一维数组的元素时,也可通过指针数组,把它们当作两维数组那样来引用。如以下代码所示: char w0[ ] = “Sunday", w1[ ] = "Monday", w2[ ] = "Tuesday", w3[ ] = "Wednesday", w4[ ] = "Thursday", w5[ ] = "Friday", w6[ ] = "Saturday";char *wName[] = {w0,w1, w2, w3, w4, w5, w6};则语句 for(i = 0; i <= 6; i++) printf( "%s\n", wName[i] );输出星期英文名称。 wName[2][4]引用字符 w2[4],其值为’d’。

Page 75: 第 6 章 指针和引用

#include <stdio.h> #define N 8 int p[N*(N+1)/2], i, j, *pt[N]; void main() { for ( pt[0] = p, i = 1; i < N; i++ ) pt[i] = pt[i-1] + i; for ( i = 0; i < N; i++ ) { pt[i][0] = pt[i][i] = 1; for(j = 1; j < i; j++) pt[i][j] = pt[i-1][j-1] + pt[i-1][j]; } for(i = 0; i < N; i++) { printf ( "%*c", 40-2*i, ' ' ); for (j = 0; j <= i; j++ ) printf ( "%4d", pt[i][j] ); printf ( "\n" ); } }

【例 6.14】程序把一维数组分割成不等长的段,从指针数组方向来看,把它当作两维数组来处理。

Page 76: 第 6 章 指针和引用

程序产生如下形式的二项式的系数三角形: 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1 1 7 21 35 35 21 7 1

Page 77: 第 6 章 指针和引用

指向指针变量的指针称为多级指针,即指针的指针。指向指针变量的指针称为多级指针,即指针的指针。定义形式:定义形式:类型说明符 类型说明符 **** 变量名;变量名;例如:例如: int **pp, *ip, iint **pp, *ip, i ;; ip = &i;ip = &i; /* /* 指向整型变量指向整型变量 i */i */ pp = &ip;pp = &ip; /* /* 指向指针变量指向指针变量 ip */ip */变量变量 pppp 、、 ipip 和和 ii 关系示意图如下:关系示意图如下:

66..77 多级指针多级指针

pppp ipip ii

Page 78: 第 6 章 指针和引用

多级指针往往与指针数组有密切的关系。多级指针往往与指针数组有密切的关系。例如:例如: char *lines[ ] = { "ADA", "ALGOL", "C", char *lines[ ] = { "ADA", "ALGOL", "C", "C++", "FORTRAN", "PASCAL" };"C++", "FORTRAN", "PASCAL" };则:则: lineslines 指针数组的每个元素分别指向每个字符串指针数组的每个元素分别指向每个字符串常量的首字符地址。数组名常量的首字符地址。数组名 lineslines 是首元素是首元素 lines[0]lines[0]的指针,的指针, lines+klines+k 是元素是元素 lines[k]lines[k] 的指针。的指针。

lines[0]lines[0]lines[1]lines[1]lines[2]lines[2]lines[3]lines[3]lines[4]lines[4]lines[5]lines[5]

lineslines ADAADAALGOLALGOLCCC++C++FORTRANFORTRANPASCALPASCAL

引入指针引入指针变量变量 cpcp

Page 79: 第 6 章 指针和引用

则:则: cp = &lines[k]cp = &lines[k] 。。因此:因此: cp cp 就是指向字符指针的指针变量。就是指向字符指针的指针变量。定义方法定义方法 :: char **cp;char **cp;说明:说明: cpcp 前面有两个前面有两个 ** 号。由于号。由于 ** 自右向左结合,自右向左结合,首先是首先是 *cp *cp 表示 表示 cp cp 是指针变量是指针变量,再有,再有 **c**cp p 表示 表示 cp cp 是指向一个字符指针变量。是指向一个字符指针变量。例如:例如: cp = &lines[1]cp = &lines[1] ;;则:则: *cp *cp 表示引用 表示引用 lines[1] ( lines[1]lines[1] ( lines[1] 也是一个指也是一个指针针 )) ,指向字符串,指向字符串 "ALGOL""ALGOL" 的首字符。的首字符。 **cp**cp 表示引用表示引用 lines[1][0]lines[1][0] ,其值是字符’,其值是字符’ A’A’

Page 80: 第 6 章 指针和引用

顺序输出指针数组顺序输出指针数组 lineslines 各元素所指字符串:各元素所指字符串: for ( cp = lines; cp < lines+6; cp++ )for ( cp = lines; cp < lines+6; cp++ ) printf ( "%s\n", *cp );printf ( "%s\n", *cp );采用”采用” %c”%c” 格式,逐一输出指针数组格式,逐一输出指针数组 lineslines 各元素各元素所指的字符串:所指的字符串: int i, j;int i, j; for ( i = 0; i < 6; i++ ) for ( i = 0; i < 6; i++ ) { for ( j = 0; lines[i][j] != '\0'; j++ ){ for ( j = 0; lines[i][j] != '\0'; j++ ) printf ( "%c", lines[i][j] );printf ( "%c", lines[i][j] ); printf ("\n");printf ("\n"); }}

Page 81: 第 6 章 指针和引用

设有数组设有数组 a[]a[] 和指针数组和指针数组 pt[]pt[] 有以下代码的关系:有以下代码的关系: int a[ ] = { 2, 4, 6, 8, 10};int a[ ] = { 2, 4, 6, 8, 10}; int *pt[ ] = { &a[3], &a[2], &a[4], &a[0], int *pt[ ] = { &a[3], &a[2], &a[4], &a[0], &[1]};&[1]}; int **p;int **p;利用指针数组利用指针数组 pt[]pt[] 和指针的指针和指针的指针 pp ,遍历数组,遍历数组 a[]a[] :: for ( p = pt; p < pt + 5; p++ )for ( p = pt; p < pt + 5; p++ ) printf ( "%d\t", **p );printf ( "%d\t", **p ); 指向指针数组元素的指针即为指针的指针,如以指向指针数组元素的指针即为指针的指针,如以上程序中的指针变量上程序中的指针变量 pp 。。 *p*p 能引用能引用 pp 所指的数组元素,所指的数组元素,**p**p 能引用能引用 pp 所指数组元素所指的变量。程序中用所指数组元素所指的变量。程序中用 **p**p访问数组访问数组 a[]a[] 的元素。的元素。

Page 82: 第 6 章 指针和引用

函数指针:函数指针:一个函数在编译后有一个入口地址,称为函数一个函数在编译后有一个入口地址,称为函数指针。指针。函数的指针用单独使用的函数名标识。函数的指针用单独使用的函数名标识。函数指针类型:函数返回值类型、函数形参的顺序,及形函数指针类型:函数返回值类型、函数形参的顺序,及形参的类型被抽象成函数指针类型。例如,函数指针参的类型被抽象成函数指针类型。例如,函数指针 sumsum 的的类型是:函数返回值类型是类型是:函数返回值类型是 intint ,有两个形参,第一个形,有两个形参,第一个形参的类型是参的类型是 int*int* ,第二个形参的类型是,第二个形参的类型是 intint 。。

6.8 6.8 函数指针函数指针

Page 83: 第 6 章 指针和引用

函数指针类型可以用类型定义命名,定义函数指针类型的一般形式是:函数指针类型可以用类型定义命名,定义函数指针类型的一般形式是:typedef typedef 函数返回值类型函数返回值类型(*(*函数指针类型名函数指针类型名)()(形参类型表形参类型表));;例如,代码:例如,代码:typedef int (*sumPtType)(int *, int);typedef int (*sumPtType)(int *, int);定义的定义的 sumPtTypesumPtType是一种函数指针类型。由是一种函数指针类型。由 sumPtTypesumPtType函数指针类型说明知道,函数指针类型说明知道,它代表的是这样一类函数:函数返回它代表的是这样一类函数:函数返回intint类型值,并有两个形参,第一个形参类型值,并有两个形参,第一个形参类型是类型是int*int*,第二个形参类型是,第二个形参类型是intint。前面提及的函数。前面提及的函数 sum()sum()就是具有这样类就是具有这样类型的函数,所以可以认为型的函数,所以可以认为 sumsum的类型是的类型是 sumPtTypesumPtType函数指针类型。函数指针类型。

Page 84: 第 6 章 指针和引用

函数指针变量函数指针变量函数指针变量是存储函数指针的变量,函数指针变量也能作为复杂数据结构的函数指针变量是存储函数指针的变量,函数指针变量也能作为复杂数据结构的成分,也能作为调用函数的实参。当函数指针变量存储某个函数的指针时,就成分,也能作为调用函数的实参。当函数指针变量存储某个函数的指针时,就称它指向这个函数,程序就可利用这个函数指针变量间接调用它指向的函数。称它指向这个函数,程序就可利用这个函数指针变量间接调用它指向的函数。函数指针变量的类型特性用函数指针类型来刻划,由函数指针类型来判别函数函数指针变量的类型特性用函数指针类型来刻划,由函数指针类型来判别函数指针变量指向函数的合理性。函数指针变量只能指向与它的类型特性要求一致指针变量指向函数的合理性。函数指针变量只能指向与它的类型特性要求一致的函数。定义函数指针变量有两种方法。由早先定义的函数指针类型,定义函的函数。定义函数指针变量有两种方法。由早先定义的函数指针类型,定义函数指针变量。数指针变量。

Page 85: 第 6 章 指针和引用

例如,代码:例如,代码: sumPtType sumPt;sumPtType sumPt;定义函数指针变量定义函数指针变量 sumPtsumPt,它能指向的函数是:返回值类型是,它能指向的函数是:返回值类型是intint的,有两个形参,第一个的,有两个形参,第一个形参类型是形参类型是int*int*的,第二个形参是的,第二个形参是intint的。的。也可直接指定函数指针变量的类型特性:函数返回值类型和形参类型。这种方式定义函数指也可直接指定函数指针变量的类型特性:函数返回值类型和形参类型。这种方式定义函数指针变量的一般形式为:针变量的一般形式为:  函数返回值类型   函数返回值类型 (* (* 指针变量名指针变量名)()(形参类型表形参类型表););例如: 例如: int (*fp)(int, double);int (*fp)(int, double);定义定义fpfp是这样一个函数指针变量,它能指向的函数的返回值是是这样一个函数指针变量,它能指向的函数的返回值是intint类型的,有类型的,有intint和和 doubledouble类型的两个形参。类型的两个形参。

括号使括号使 fpfp 先与先与 ** 结合结合

Page 86: 第 6 章 指针和引用

函数指针变量能存放函数指针,程序能向函数指针变量赋值满足类型要求的函数指针,让它指向某函数。即函数的入口地址。例如,代码 sumPt = sum;使函数指针变量sumPt指向函数sum()。如果有函数说明: int fac(int, double);由于函数fac()的函数指针类型与函数指针变量fp的类型相同,以下代码使函数指针变量fp指向函数fac(). fp = fac;

Page 87: 第 6 章 指针和引用

C语言约定,单独的函数名本身就是函数的入口地址。C语言约定,单独的函数名本身就是函数的入口地址。一般函数调用形式:一般函数调用形式:函数名函数名 (( 实参表实参表 ))用函数指针变量调用形式:用函数指针变量调用形式: (*(* 函数指针变量名函数指针变量名 ) () ( 实参表实参表 ))例如:例如:求两个整数中小的值的函数求两个整数中小的值的函数 minmin ,它返回,它返回 intint值值void main()void main(){ int (*fp) (int,int){ int (*fp) (int,int) ,, x=5, y=8x=5, y=8 ;; int min (int,int);int min (int,int); fp = min;fp = min; printf("%d" ,(*fp)(x, y));printf("%d" ,(*fp)(x, y));}}

利用函数指针调用函数

int min (int x,int y){ return x<y ? x : y;}

Page 88: 第 6 章 指针和引用

例如,直接调用函数例如,直接调用函数 sum()sum() 的代码是的代码是 int x[] = {1, 2, 3, 4, 5}, z;int x[] = {1, 2, 3, 4, 5}, z; z = sum(x, 5);z = sum(x, 5);利用函数指针变量利用函数指针变量 sumPtsumPt 指向函数指向函数 sum()sum() ,间接调用函,间接调用函数数 sum()sum() 的代码是的代码是 z = (*sumPt)(x, 5);z = (*sumPt)(x, 5);上述赋值号右边的代码中,第一对圆括号是必须的,上述赋值号右边的代码中,第一对圆括号是必须的,代码代码 (*sumPt)(*sumPt) 是让是让 sumPtsumPt 先与先与 ** 结合,对结合,对 sumPtsumPt 作间作间接引用,即间接调用接引用,即间接调用 sumPtsumPt 所指向的函数。所指向的函数。如果没有这对圆括号,将使如果没有这对圆括号,将使 sumPtsumPt 先与右边的括号结先与右边的括号结合,变成一般的函数调用。由于合,变成一般的函数调用。由于 sumPtsumPt 不是函数,系不是函数,系统将报告错误。统将报告错误。

Page 89: 第 6 章 指针和引用

【例【例 6.156.15 】使用函数指针变量调用函数的示意程序。】使用函数指针变量调用函数的示意程序。#include <stdio.h>#include <stdio.h>int main()int main(){ int (*fp)(int, int), x, y, z;{ int (*fp)(int, int), x, y, z; int min(int, int), max(int, int);int min(int, int), max(int, int); printf("Enter x, y: ");scanf(“%d%d”,&x,&y);printf("Enter x, y: ");scanf(“%d%d”,&x,&y); fp = min; /* fp = min; /* 让让 fpfp 指向函数指向函数 min() */min() */ z = (*fp)(x, y); /* z = (*fp)(x, y); /* 调用调用 fpfp 所指函数 所指函数 */*/ printf("MIN(%d,%d) = %d\n”, x, y, z);printf("MIN(%d,%d) = %d\n”, x, y, z); fp = max;/*fp = max;/* 现在更改现在更改 fpfp ,使它指向函数,使它指向函数 max() */max() */ z = (*fp)(x, y); /* z = (*fp)(x, y); /* 调用调用 fpfp 所指函数 所指函数 */*/ printf("MAX(“%d,%d) = %d\n”, x, y, z);printf("MAX(“%d,%d) = %d\n”, x, y, z); return 0;return 0;}}

Page 90: 第 6 章 指针和引用

int min(int a, int b)int min(int a, int b) { { return a < b ? a : b;return a < b ? a : b; }} int max(int a, int b)int max(int a, int b) {{ return a > b ? a : b;return a > b ? a : b; }}

Page 91: 第 6 章 指针和引用

将函数名或指针作为实参传递给被调用的函数,要求将函数名或指针作为实参传递给被调用的函数,要求形参是一个函数指针形参。形参是一个函数指针形参。【例【例 6.166.16 】对给定的实数表,求它的最大值、最小值】对给定的实数表,求它的最大值、最小值和平均值。和平均值。 程序有三个函数程序有三个函数 max()max() 、、 min()min() 和和 ave()ave() ,另设一个,另设一个函数函数 afun()afun() 。主函数调用函数。主函数调用函数 afun()afun() ,并提供数组,并提供数组名、数组元素个数和求值函数指针作为实参。名、数组元素个数和求值函数指针作为实参。由函数由函数 afun() afun() 根据主函数提供的函数指针实参来调根据主函数提供的函数指针实参来调用相对应的函数。用相对应的函数。

函数指针作函数的形参

Page 92: 第 6 章 指针和引用

#include <stdio.h>#include <stdio.h>#define N sizeof a / sizeof a[0]#define N sizeof a / sizeof a[0]double max(double a[ ], int n)double max(double a[ ], int n){ int i; double rmax;{ int i; double rmax; for (rmax = a[0], i = 1; i < n; i++)for (rmax = a[0], i = 1; i < n; i++) if (rmax < a[i]) rmax = a[i];if (rmax < a[i]) rmax = a[i]; return rmax;return rmax;}}double min(double a[ ], int n)double min(double a[ ], int n){ int i; double rmin;{ int i; double rmin; for (rmin = a[0], i = 1; i < n; i++)for (rmin = a[0], i = 1; i < n; i++) if (rmin > a[i]) rmin = a[i];if (rmin > a[i]) rmin = a[i]; return rmin;return rmin;}}

Page 93: 第 6 章 指针和引用

double ave (double a[ ], int n){ int i; double rave; for (rave = 0.0, i = 0; i < n; i++) rave += a[i]; return rave/n;}double afun (double a[ ], int n, double (*f)(double *, int)){ return (*f)(a, n);}

Page 94: 第 6 章 指针和引用

int main() { double a[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; printf("\n结果是:\n "); printf("最大值 = %f", afun(a, N, max)); printf(" 最小值 = %f", afun(a, N, min)); printf(" 平均值 = %f\n", afun(a, N, ave)); return 0;}

Page 95: 第 6 章 指针和引用

函数也可以返回指针值,可以是某变量的指针,或函数也可以返回指针值,可以是某变量的指针,或是某函数的指针。是某函数的指针。返回变量指针的函数返回变量指针的函数 定义形式:定义形式: 类型说明符 类型说明符 * * 函数名函数名 (( 形参表形参表 ););例如:例如: int *f(int xint *f(int x ,, int y);int y);说明:说明: ff 是函数名,调用是函数名,调用 ff 函数后能得到一个指向函数后能得到一个指向整型数据的指针(地址),整型数据的指针(地址), xx 和和 yy 是两个整型形参。是两个整型形参。注意:注意:在函数名的两侧分别为在函数名的两侧分别为 ** 运算符和运算符和 ( )( ) 运算运算符。因为符。因为 ( )( ) 的优先级高于的优先级高于 ** ,所以得出,所以得出 ff 是函数是函数的说明形式;在函数名之前有一个的说明形式;在函数名之前有一个 ** ,表示函数返,表示函数返回指针类型的值。回指针类型的值。

6.9 6.9 返回指针值的函数 返回指针值的函数

Page 96: 第 6 章 指针和引用

【例 6.18】编制在给定的字符串中找特定字符的第一次出现。如果找到,返回找到的字符的指针;反之,如果没有找到,则返回 NULL值。设函数为 searchCh() ,函数应设两个形参,指向字符串首字符的指针和待寻找的字符。查找过程是一个循环,函数从首字符开始,在当前字符还不是字符串结束符,并且当前字符不是要查找字符情况,继续考察下一个字符。待查找循环结束,如果当前字符不是字符串结束符,则找到,返回当前字符指针;否则,就是没有找到,函数返回 NULL。

Page 97: 第 6 章 指针和引用

char *searchCh(char *s, char c) { while (*s && *s != c) s++; return *s ? s : NULL; }

Page 98: 第 6 章 指针和引用

返回函数指针的函数返回函数指针的函数返回函数指针的函数的定义或说明的一般形式为:返回函数指针的函数的定义或说明的一般形式为: 类型说明符 类型说明符 (*(* 函数名函数名 (( 形参表形参表 ))())( 形参类型形参类型表表 ););首先,函数名标识符与后面的圆括号结合,说明该标首先,函数名标识符与后面的圆括号结合,说明该标识符是函数名,圆括号中列出的是这个函数的形参说识符是函数名,圆括号中列出的是这个函数的形参说明。接着与前面的星号结合,函数返回指针。再与最明。接着与前面的星号结合,函数返回指针。再与最后的圆括号结合,说明返回的是函数指针,圆括号中后的圆括号结合,说明返回的是函数指针,圆括号中的内容是指针所指函数的形参说明。最后,最前面的的内容是指针所指函数的形参说明。最后,最前面的是指针所指函数的返回值的类型。下面的程序例子说是指针所指函数的返回值的类型。下面的程序例子说明返回函数指针函数的用法。明返回函数指针函数的用法。

Page 99: 第 6 章 指针和引用

其中代码其中代码 double (*menu(char **))(double*, int)double (*menu(char **))(double*, int)首先是首先是 menu(char **)menu(char **) ,标识符,标识符 menumenu 同圆括号结合,同圆括号结合,说明说明 menumenu 是函数名,有一个字符指针数组形参。随是函数名,有一个字符指针数组形参。随后是后是 (*menu(char **))(*menu(char **)) ,函数同,函数同 ** 结合,说明函数结合,说明函数返回指针。接着是返回指针。接着是 (*menu(char **))(double*, int)(*menu(char **))(double*, int) ,,与最后面的圆括号结合,表示返回的是指向函数的指与最后面的圆括号结合,表示返回的是指向函数的指针针 ,, 所指向的函数有所指向的函数有 double*double* 类型和类型和 intint 类型两个形类型两个形参。最后是参。最后是 double (*menu(char **))(double*,int)double (*menu(char **))(double*,int) ,,说明指向的函数的返回值类型是说明指向的函数的返回值类型是 doubledouble 类型。类型。

Page 100: 第 6 章 指针和引用

函数函数 menu()menu() 是一个菜单函数,它接受用户选择,返回相是一个菜单函数,它接受用户选择,返回相应处理函数的指针。主函数调用应处理函数的指针。主函数调用 menu()menu() 函数,并利用函函数,并利用函数数 menu()menu() 返回的函数指针调用相应的处理函数。返回的函数指针调用相应的处理函数。【例【例 6.196.19 】函数返回函数指针的示意程序】函数返回函数指针的示意程序#include <stdio.h>#include <stdio.h>#define N sizeof a / sizeof a[0]#define N sizeof a / sizeof a[0]double max(double *, int), min(double *, int), ave(double *, int);double max(double *, int), min(double *, int), ave(double *, int);//// 其中函数其中函数 max()max() 、、 min()min() 和和 ave()ave() 与前面的例子相同。与前面的例子相同。double (*fpt[])(double *, int) = { max, min , adouble (*fpt[])(double *, int) = { max, min , ave, NULL}; /* ve, NULL}; /* 函数指针数组 函数指针数组 */*/char *title[] = {"char *title[] = {" 最大值最大值 ", "", " 最小值最小值 ", "", " 平均值平均值 "};"};char *menuName[] char *menuName[] ={"={" 求最大值求最大值 ",""," 求最小值求最小值 ",""," 求平均值求平均值 ",""};",""};

Page 101: 第 6 章 指针和引用

double a[] ={1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};double a[] ={1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};double (*menu(char **titptr))( double *, int) /* double (*menu(char **titptr))( double *, int) /* 函数返回函数的指针 函数返回函数的指针 */*/{ int ans, k;{ int ans, k; printf(”printf(”请选择以下菜单命令。请选择以下菜单命令。 \n”);\n”); for(k = 0; menuName[k][0] != ’\0’ ; k++) for(k = 0; menuName[k][0] != ’\0’ ; k++) printf(”\t%d : %s\n”, k+1, menuName[k]);printf(”\t%d : %s\n”, k+1, menuName[k]); printf(“\tprintf(“\t 其它选择结束程序运行。其它选择结束程序运行。 \n”);\n”); scanf(“%d”, &ans);scanf(“%d”, &ans); if (ans < 1 || ans > 3) return NULL;if (ans < 1 || ans > 3) return NULL; *titptr = title[ans-1];*titptr = title[ans-1];//// 函数带回功能注释字符串函数带回功能注释字符串 return fpt[ans-1]; // return fpt[ans-1]; // 返回函数指针 返回函数指针 } }

Page 102: 第 6 章 指针和引用

int main ()int main () { double (*fp)( double *, int); char *titstr;{ double (*fp)( double *, int); char *titstr; while (1) {while (1) { if ((fp = menu(&titstr)) == NULL) break;if ((fp = menu(&titstr)) == NULL) break; printf("\nprintf("\n 结果是:结果是: %s = %f\n”, %s = %f\n”, titstr, (*fp)(a, N));titstr, (*fp)(a, N)); }} return 0;return 0; }}

Page 103: 第 6 章 指针和引用

6.10 6.10 引用引用在在 CC 语言中,函数的形参是函数的一种局部变量。语言中,函数的形参是函数的一种局部变量。对于非指针类型的形参来说,函数调用时,实参的对于非指针类型的形参来说,函数调用时,实参的值单向传递给形参,函数在执行过程中,改变形参值单向传递给形参,函数在执行过程中,改变形参的值,不会影响与它对应的实参。对于指针类型的的值,不会影响与它对应的实参。对于指针类型的形参来说,函数调用时,实参向形参传递变量的指形参来说,函数调用时,实参向形参传递变量的指针,函数能借助形参间接访问和修改形参所指向的针,函数能借助形参间接访问和修改形参所指向的变量。实际编写程序时,还需要一种更简洁的形参变量。实际编写程序时,还需要一种更简洁的形参类别,即引用类别的形参。函数调用时,引用类型类别,即引用类别的形参。函数调用时,引用类型形参成为对应实参的引用,直接标识实参,函数对形参成为对应实参的引用,直接标识实参,函数对形参的访问,实际上是直接对实参变量的访问。 形参的访问,实际上是直接对实参变量的访问。

Page 104: 第 6 章 指针和引用

引用的基本概念引用的基本概念已学习了两种标识对象的方法,用对象的名标识对象,已学习了两种标识对象的方法,用对象的名标识对象,和用指向对象的指针间接标识对象。和用指向对象的指针间接标识对象。用对象的引用标识对象。用对象的引用标识对象。引用虽是一种类型,但不是值,只能用它标识另一个引用虽是一种类型,但不是值,只能用它标识另一个对象。从理论意义上说,引用是一种映射,把一个标对象。从理论意义上说,引用是一种映射,把一个标识符映射到一个对象。从直观意义上说,引用是用一识符映射到一个对象。从直观意义上说,引用是用一个标识符给一个对象起了一个别名,引用标识对象,个标识符给一个对象起了一个别名,引用标识对象,就是用一个别名标识对象。就是用一个别名标识对象。

Page 105: 第 6 章 指针和引用

如果有类型为如果有类型为 TT 的变量的变量 xx ,要用标识符,要用标识符 rr 引用引用 xx ,声,声明明 rr 为为 xx 的引用的代码写成:的引用的代码写成: T &r = x;T &r = x;以后,程序就可用以后,程序就可用 rr 标识变量标识变量 xx 。或者说标识符。或者说标识符 rr映映射到射到 xx ,或者说,或者说 rr 是是 xx 的别名。例如,代码的别名。例如,代码 r = 5r = 5 、、或或 y = ry = r 、或、或 &r&r 等。这里所有的等。这里所有的 rr ,实际都是,实际都是 xx 。。引用不是值,因此不占存储空间,声明引用后,存储引用不是值,因此不占存储空间,声明引用后,存储状态不会改变。状态不会改变。如果有引用如果有引用 rr 标识对象标识对象 xx ,而对象,而对象 xx 的类型为的类型为 TT ,则,则称称 TT 是是 rr 的基类型。的基类型。

Page 106: 第 6 章 指针和引用

说明引用的一般形式为: 类型说明符 & 标识符(左值表达式)或 类型说明符 & 标识符 = 左值表达式上述说明形式在标识符与左值表达式所标识的对象之间建立了一个映射关系。为了确保一个引用总能是某个对象的别名,程序在声明引用时,必须对它初始化。引用一旦初始化,它就与目标之间建立了一种映射关系,再也不分开。任何对引用的赋值,就是对目标的赋值。

Page 107: 第 6 章 指针和引用

【例 6.20 】说明引用是一种映射的例子。 #include <stdio.h> int main() { int a[] = {0, 2, 4, 6, 8}; int j; printf("Enter j "); scanf(“%d”, &j); int & ref = a[j]; ref = 44; printf(“%d\n”, ref); return 0; }

Page 108: 第 6 章 指针和引用

【程序说明】如果程序运行时,输入的是整数 2,则 ref 被映射到对象 a[2]上。 ref 标识对象 a[2] 后,代码 ref = 44 ,就是更新 a[2]。对一个引用的初始化,与对它赋值,是完全不同的。除了外表形式之外,实际上根本就没有能对引用本身进行操作的运算符。例如,下列代码: int x; int & r1 = x; int & r2 = r1; r2 = 1;其中, r1 和 r2都标识变量 x,使 x被设置为 1。代码: int k = 0; int &rk = k; rk++; int *pt = &rk;rk++ 并没有对引用本身做增量操作,运算 ++是应用到变量 k上。同样, &rk也是引用 k的地址。因此,一个引用在初始化之后就不可以改变,它总是引用它在初始化时所束定的那个对象。

Page 109: 第 6 章 指针和引用

引用与指针有很大的差别引用与指针有很大的差别指针是个变量,任何时候可以向它赋值某个符指针是个变量,任何时候可以向它赋值某个符合要求的变量的地址。合要求的变量的地址。而建立引用时必须进行初始化,并且不会再映而建立引用时必须进行初始化,并且不会再映射到其它变量,直至引用不再有效为止。 射到其它变量,直至引用不再有效为止。

Page 110: 第 6 章 指针和引用

什么能被引用什么能被引用如果一个标识符被声明为如果一个标识符被声明为 T&T& 的引用时,它必须用的引用时,它必须用 TT 类型的变类型的变量给这个引用初始化。量给这个引用初始化。可以有指针变量的引用可以有指针变量的引用。例如,以下代码:。例如,以下代码:int *p;int *p;      int *&q = p;//int *&q = p;// 标识符标识符 qq 是对是对 pp 的引用的引用int y = 9;int y = 9;q = &y; q = &y; //// 由于由于 qq 是指针变量是指针变量 pp 的别名,将的别名,将 yy 的地址赋值给指针的地址赋值给指针 pp不能有不能有 voidvoid 类型的引用。类型的引用。例如:例如: void &r = 5; //void &r = 5; // 出错出错voidvoid 只在语法上相当于一个类型,没有类型为只在语法上相当于一个类型,没有类型为 voidvoid 的变量。的变量。不能建立引用的数组。例如,代码不能建立引用的数组。例如,代码int a[5];int a[5];int &ra[5] = a;//int &ra[5] = a;// 出错出错数组是某种数据类型元素的集合,数组名能表示该元素集合数组是某种数据类型元素的集合,数组名能表示该元素集合空间的开始地址,数组名不是一个名副其实的数据类型。空间的开始地址,数组名不是一个名副其实的数据类型。

Page 111: 第 6 章 指针和引用

引用本身不是一种数据类型,所以没有引用的引用,引用本身不是一种数据类型,所以没有引用的引用,也没有引用的指针。也没有引用的指针。例如:例如:int x;int x;int &rx = x;int &rx = x;int & *p = &rx;//int & *p = &rx;// 企图定义一个引用的指针,出错企图定义一个引用的指针,出错int &int & 不是一种有内存空间要求的类型,用它定义引不是一种有内存空间要求的类型,用它定义引用时,不要求存储空间。所以,引用之上的引用不存用时,不要求存储空间。所以,引用之上的引用不存在。引用的指针也就没有可指向的地址。在。引用的指针也就没有可指向的地址。引用不能用类型来初始化。引用不能用类型来初始化。例如,代码例如,代码 int & rt = int; //int & rt = int; // 出错出错引用是变量的引用,而不是类型的引用。引用是变量的引用,而不是类型的引用。有空指针,有空指针,没有空的引用。没有空的引用。例如,以下代码例如,以下代码int &er = NULL;//int &er = NULL;// 没有任何意义没有任何意义

Page 112: 第 6 章 指针和引用

引用形参引用形参语言引入引用的主要目的是让函数能设置引用形参。语言引入引用的主要目的是让函数能设置引用形参。函数调用时,引用形参成为实参的别名,函数体中形函数调用时,引用形参成为实参的别名,函数体中形参的出现就是实参。这样,函数中对以形参标识的对参的出现就是实参。这样,函数中对以形参标识的对象的引用就是对实参变量的引用,或访问实参变量,象的引用就是对实参变量的引用,或访问实参变量,或修改实参变量。或修改实参变量。

Page 113: 第 6 章 指针和引用

【例【例 6.216.21 】函数设置引用形参的例子。】函数设置引用形参的例子。#include <stdio.h>#include <stdio.h>void print(int &p)void print(int &p){{ printf(“%d\n”, p);//printf(“%d\n”, p);// 将实参变量的值输出将实参变量的值输出 p = 0; //p = 0; // 修改实参变量的值,置它的值为修改实参变量的值,置它的值为 00}}int main()int main(){ int i = 10, j = 5;{ int i = 10, j = 5; print(i); printf(“%d\n”, i);print(i); printf(“%d\n”, i); print(j); printf(“%d\n”, j);print(j); printf(“%d\n”, j); return 0;return 0;}}

将输出10050

Page 114: 第 6 章 指针和引用

【程序说明】【程序说明】函数调用函数调用 print(i)print(i) 使形参使形参 pp 成为成为 ii 的别名,函数体的别名,函数体中所有的中所有的 pp 就是就是 ii 。函数输出。函数输出 ii 的值,并置的值,并置 ii 为为 00 ,,函数返回后,函数返回后, pp 与与 ii 的映射关系随之结束。接着函数的映射关系随之结束。接着函数调用调用 print(j)print(j) 使形参使形参 pp 成为成为 jj 的别名,函数体中所的别名,函数体中所有的有的 pp 就是就是 jj 。函数输出。函数输出 jj 的值,并置的值,并置 jj 为为 00 。同样。同样函数返回后,函数返回后, pp 与与 jj 的映射关系也随之结束。以上例的映射关系也随之结束。以上例子说明调用有引用形参的函数要特别当心,函数可能子说明调用有引用形参的函数要特别当心,函数可能会修改实参变量。为了提高程序的可读性,通常应该会修改实参变量。为了提高程序的可读性,通常应该尽可能避免让函数去修改调用它们时的实参。相反,尽可能避免让函数去修改调用它们时的实参。相反,应该让函数返回一个值。应该让函数返回一个值。

Page 115: 第 6 章 指针和引用

参见以下示意函数:参见以下示意函数:void increment(int &x) { ++x;}void increment(int &x) { ++x;}int next(int x){return x+1;}int next(int x){return x+1;}void incr(int *x) { ++*p;}void incr(int *x) { ++*p;}以上三个函数都能实现增以上三个函数都能实现增 11 的功能,但是代码的功能,但是代码 increincrement(y)ment(y) 较难使读程序的人联想到会使较难使读程序的人联想到会使 yy 增增 11 。而写。而写成成 y = next(y)y = next(y) 和和 incr(&y)incr(&y) 可能更有助于读程序的可能更有助于读程序的人理解。人理解。

Page 116: 第 6 章 指针和引用

用常量作为引用形参的实参用常量作为引用形参的实参如果引用要标识的对象类型与它的基类型不相同,但如果引用要标识的对象类型与它的基类型不相同,但编绎程序知道如何将要标识的对象的类型转换为引用编绎程序知道如何将要标识的对象的类型转换为引用的基类型;或引用要标识一个常对象,则程序在运行的基类型;或引用要标识一个常对象,则程序在运行时能建立一个类型为引用的基类型的匿名对象,引用时能建立一个类型为引用的基类型的匿名对象,引用是标识这个匿名对象。是标识这个匿名对象。【例【例 6.226.22 】常量实参对应引用形参的例子。】常量实参对应引用形参的例子。#include <stdio.h>#include <stdio.h>int h(const int& rint h(const int& r)//const)//const 声明函数不会对引用的对象有修改声明函数不会对引用的对象有修改{ return r; }{ return r; }int main()int main(){ printf(“%d\n”, h(4)); return 0; }{ printf(“%d\n”, h(4)); return 0; }函数函数 hh 被调用时,将在被调用时,将在 main()main() 函数的运行环境中建函数的运行环境中建立一个值为立一个值为 44 的匿名对象,使函数的匿名对象,使函数 h()h() 的形参的形参 rr 标识标识这个匿名对象。这个匿名对象。

Page 117: 第 6 章 指针和引用

返回引用的函数返回引用的函数函数返回值时,系统要生成一个值的副本。函数的返函数返回值时,系统要生成一个值的副本。函数的返回值也可以是引用,而函数返回引用时,不生成引用回值也可以是引用,而函数返回引用时,不生成引用的副本。函数返回引用能使函数调用可以写在赋值表的副本。函数返回引用能使函数调用可以写在赋值表达式等号的左边。达式等号的左边。【例【例 6.236.23 】函数返回引用的例子。】函数返回引用的例子。#include <stdio.h>#include <stdio.h>int & f(int a[],int index){return a[index];}int & f(int a[],int index){return a[index];}int main()int main(){ int i, s[] = {0, 2, 4, 6, 8};{ int i, s[] = {0, 2, 4, 6, 8}; i = f(s, 3);i = f(s, 3);//// 函数返回引用,保存引用所标识的变量的值函数返回引用,保存引用所标识的变量的值 f(s, 3)=22;f(s, 3)=22;//// 函数返回引用,对引用所标识的变量赋值函数返回引用,对引用所标识的变量赋值 printf(“%d\n”,f(s,3));printf(“%d\n”,f(s,3)); // // 输出引用所标识的变量的输出引用所标识的变量的值值 return 0;return 0;}}

Page 118: 第 6 章 指针和引用

【程序说明】【程序说明】以上程序在执行函数以上程序在执行函数 f()f() 时,形参数组时,形参数组 aa 是实参数组是实参数组ss ,, a[index]a[index] 就是就是 s[3]s[3] 。当函数返回时,返回。当函数返回时,返回 s[3]s[3]的引用到函数调用处。这样,函数调用表达式的标识的引用到函数调用处。这样,函数调用表达式的标识对象就是对象就是 s[3]s[3] ,程序置,程序置 s[3]s[3] 为为 2222 。。由于函数的自动变量在函数返回时被释放,所以函数由于函数的自动变量在函数返回时被释放,所以函数不能返回对自动变量的引用。不能返回对自动变量的引用。例如:例如: int &g(int a[], int index)int &g(int a[], int index) { int r = a[index];{ int r = a[index]; return r;return r; }}因为当函数因为当函数 gg 返回时,自动变量返回时,自动变量 rr 已不存在,不能返已不存在,不能返回对回对 rr 的引用。的引用。