《C++学习教程及习题第五章.docx》由会员分享,可在线阅读,更多相关《C++学习教程及习题第五章.docx(101页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、教学目标 能够使用指针 能用指针按引用调用向函数传递参数 了解指针、数组与字符串之间的密切关系 了解指针在函数中的使用 能够声明和使用字符串数组5. 1 简介本章介绍“C+编程语言个最强大的特性一指针。指针是C+中最难 掌握的问题之一。第3章介绍了引用可以用于实现按引用调用。指针使程序可模 拟按引用调用,生成与操作动态数据结构,即能够伸缩的数据结构,如链表、队 列、堆栈和树。本章介绍基本的指针概念,而且强调了数组、指针与字符串之间 的密切关系.并包括组很好的字符串操作练习。第6章介绍结构中的指针使用。第9章和第10章介绍如何用指针和引用 进行面向对象编程。第15章介绍动态内存管理技术以及生成和
2、使用动态数据结 构的例子。把数组和字符串看成指针是从C语言演变而来的。本书后面会介绍把数 组和字符串当作成熟的对象。6. 2 指针变量的声明与初始化指针变量的值为内存地址。通常变量直接包含特定值,面指针则包含特 定值变量的地址。因此可以说,变量名直接(directly)引用数值,面指针间接 (indirectly)引用数值(如图5. 1)。通过指针引用数值称为间接引用。指针和任何其他变量一样,应先声明后使用。下列声明:int *countPtr, count;声明变量countPtr的类型为int*(即指向整型值的指针),或者说成 countPtr是int的指针”或countPtr指向整数类型
3、的对象。变量count声明 为整数,面不是整型值的指针。声明中的只适用于countPtr。声明为指针的每个变量前面都要加上星号(*)。例如,下列声明:float *xPtr, *yPtr;表示xPtr和yPtr都是指向float值的指针。声明中以这种方式使用时,它 表示变量声明为指针。指针可以声明为指向任何数据类型的对象。常见编程错误5. 1假设对指针的声明会分配到声明中逗号分隔的指针变量名列表中的所有 指针变量名,从而将指针声明为非指针。声明为指针的每个变量前面都要加上星 号(*)。编程技巧5. 1尽管不是必需的,但在指针变量名中加上Ptr字样能更清楚地表示这些 变量是指针,需要相应的处理。
4、图5. 1直接和间接引用变量指针应在声明时或在赋值语句中初始化。指针可以初始化为、NULL或 个地址。数值为或NULL的指针不指任何内容。NULL是头文件iostream. h (和另外几个标准库头文件)中定义的符号化常量。将指针初始化为 NULL等于将指针初始化为0,但C+中优先选择0。指定时,它变为指针的相 应类型。数值是惟一可以不将整数转换为指针类型而直接赋给指针变量的整数 值。5. 3节将介绍将变量地址赋给指针。测试与调试提示5. 1初始化指针以防止其指向未知的或未初始化的内存区。5. 3指针运算符&(地址)运算符是个一元运算符,返回操作数的地址。例如,假设声明:int y = 5;i
5、nt *yPtr;则下列语句:yPtr = &y;将变量y的地址赋给指针变量yPtr。变量yPtr指向y。图5. 2显示了执行 上述语句之后的内存示意图。图中从指针向所指对象画一个箭头.表示“指向关系”。图5.3显示了指针在内存中的表示,假设整型变量y存放在地址600000, 指针变量yPtr存放在地址50000。地址运算符的操作数应为左值,(即要赋值的项目,如变量名).地 址运算符不能用于常量、不产生引用的表达式和用存储类regtster声明的变量。 *”运算符通常称为间接运算符(indirection operator)或复引用运 算符(dereferencing operator),返回
6、操作数(即指针)所指对象的同义词、别名或浑名。例如(图5. 2再次引用), 下列语句:cout * yPtr endl;指向变量y的值(5),如同下列语句:cout y endl;图5. 2指针指向内存中整数变量的示意图这里使用的方法称为复引用指针(dereferencing a pointer) 注意复 引用指针也可以用于赋值语句左边,例如下列语句:*yPtr = 9;将数值9赋给图5. 3中的y。复引用指针也可用于接收输入值,例如:cin *yPtr;复引用的指针是个左值。yptry5000006000006000005图5.3 指针在内存 中的表示常见编程错误5. 2如果指针没有正确地初
7、始化或没有指定指向内存中的特定地址,则复引 用指针可能造成致命的运行时错误,或者意外修改重要数据。虽然运行完程序, 但得到的是错误结果。常见编程错误5. 3复引用非指针是个语法错误。常见编程错误5. 4复引用0指针通常是个致命的运行时错误。图5. 4的程序演示了指针运算符。本例中通过用十六进制整数输出内 存地址(十六进制整数见附录“数值系统”)。可移植性提示5. 1输出指针的格式与机器有关,有些系统用十六进制整数,而有些系统用 十进制整数。注意a的地址和aPtr的值在输出中是一致的,说明a的地址实际赋给了 指针变量aptr。&和运算符是互逆的,如果两者同时作用于aPtr,则打印相同 的结果。图
8、5. 5显示了前面所介绍的运算符的优先级和结合律。1 / Fig. 5.4: fig05_04. cpp2 / Using the & and * operators3 Sinclude 45 int main()6(7 int a;8 iht *aPtr;910 a = 7;11 aPtr = &a;/ a is an integer/ aPtr is a pointer to an integer/ aPtr set to address of a1213 cout The address of a is &a14 nThe value of aPtr is aPtr;1516 cout
9、nnThe value of a is ”a17 nThe value of *aPtr is *aPtr;1819 cout nnShowing that * and & are inverses of20 each other. n&*aPtr = &*aPtr21 n*&aPtr = *&aPtr endl;22 return 0;23 )输出结果:The address of a is 0x0064FDF4The value of aPtr is 0x0064FDF4The value of a is 7The value of *aPtr is 7Showing that * and
10、 & are inverses of each other.&*aPtr = 0x0064FDF4*&aPtr = 0x0064FDF4图5. 4 &与指针运算符运算符结合律类型0 从左向右括号+ 一 +- static_cast()从右向左一元& * / %从左向右乘+ -从左向右加 从左向右插入/读取=从左向右关系= !=从左向右相等&从左向右逻辑ANDII从左向右逻辑或?:从右向左条件= += = 二/= %=从右向左赋值从左向右逗号图5. 5运算符的优先级和结合律5. 4 按引用调用函数C+用三种方式向函数传递数值:按值调用(call-by-value)、用引用参数 按引用调用(cal
11、 1-by-reference reference argument)和用指针参数按引用调用 (call-by-reference pointer argument)。第3章比较了按引用调用与按值调用, 本章主要介绍用指针参数按引用调用。第3章曾介绍过,return可以从被调用函数向调用者返回一个值(或不 返回值而从被调用函数返回控制)。我们还介绍了用引用参数将参数传递给函数, 使函数可以修改参数的原有值(这样可以从函数“返回”多个值),或将大的数据 对象传递给函数而避免按值调用传递对象的开销(即复制对象所需的开销)。指针 和引用一样,也可以修改调用者的一个或几个变量,或将大的数据对象指针传递
12、 给函数而避免按值调用传递对象的开销。在C+中,程序员可以用指针和间接运算符模拟按引用调用(就像C语言 程序中的按引用调用样)。调用函数并要修改参数时,传递该参数地址,通常 在要修改数值的变量名前面加上地址运算符(&)。第4章曾介绍过,数组不能用 地址运算符(&)传递,因为数组名是内存中数组的开始位置(数组名等同于&arrayName0),即数组名已经是个指针。向函数传递参数地址时,可以在函数 中使用间接运算符形成变量名的同义词、别名或浑名,并可用其修改调用者内存 中该地址的值(如果变量不用const声明)。图5. 6和5. 7的程序是计算整数立方函数的两个版本cubeByValue和 cub
13、eByReference。图5. 6按值调用将变量number传递给函数cubeByValue。 函数cubeByValue求出参数的立方,并将新值用return语句返回main,井在main 中将新值赋给number。可以先检查函数调用的结果再修改变量值。例如,在这 个程序中,可以将cubeByValue的结果存放在另变量中,检查其数值,然后再 将新值赋给number 1 / Fig. 5, 6: f ig0506, cpp2 / Cube a variable using call-by-value3 Sinclude 45 int cubeByValue( int );/ prototy
14、pe67 int main()8 9 int number = 5;1011 cout ”The original value of number is number;12 number = cubeByValue( number );13 cout nThe new value of number is” number endl;14 return 0;15 )16 17 int cubeByValue( int n )1819 return n * n * n; / cube local variable n20 )输出结果:The original value of number is
15、5The new value of number is 125图5. 6按值调用求出参数的立方图5. ?的程序按引用调用传递变量mmber(传递number的地址)到函数 cubeByReferenceo 出数cubeByReference取nPtr(int的指针)作为参数。函数复引用指针并求出nPtr 所指值的立方,从而改变main中的number值。图5. 8和5.9分别分析了图5.6和1. 7所示程 序。1 / Fig. 5.7: fig05_07. cpp2 / Cube a variable using cal1-by-reference3 / with a pointer argu
16、ment4 Sinclude 56 void cubeByReference( int* );/ prototype78 int main()9 (10 int number = 5;1112cout The original value of number is number;13 cubeByReference ( &number );14 cout ”nThe new value of number is ” number endl;15 return 0;16 )1718 void cubeByReference( int *nPtr )19 20 *nPtr = *nPtr = *n
17、ptr * *nptr;/ cube number in main21 )输出结果:The original value of number is 5The new value of number is 125图5.7用指针参数按引用调用求出参数的立方常见编程错误5. 5要复引用指针以取得指针所指的值时不复引用指针是个错误。接收地址参数的函数要定义接收地址的指针参数。例如, cubeByReference的函数首部如下所示:void cubeByReference(int *nPtr)这个函数首部指定函数cubeByReference接收整型变量的地址(即整型指针)作 为参数,在nPtr中局部
18、存放地址,不返回值。cubeByReference的函数原型包含括号中的int。和其他变量类型样, 不需要在函数原型中包括指针名。参数名仅用于程序中的说明,编译器将其忽略。在需要单下标数组参数的函数首部和函数原型中,可以用 cubeByReference参数表中的指针符号。编译器并不区分接收指针的函数和接收 单下标数组的函数。当然,函数必须“知道”何时接收数组或要进行按引用调用 的单个变量。编译器遇到形如intb的单下标数组函数参数时,编译器将参数 变为指针符号int* const b(b是指向整数的常量指针),const见第51节介绍。 声明函数参数为单下标数组的两种形式可以互换。编程技巧5
19、. 2除非调用者显式要求被调用函数修改调用者环境中参数变量的值,否则 按值调用将参数传递给函数。这是最低权限原则的另个例子。5. 8典型的按值调用分析图5. 9典型的用指针参数按引用调用分析5. 5 指针与常量限定符const限定符可以使程序员告诉编译器特定变量的值不能修改。软件工程视点5. 1const限定符可以执行最低权限原则。利用最低权限原则正确设计软件 可以大大减少调试时间和不正确的副作用,使程序更容易修改与维护。可移植性提示5. 2尽管ANSI C和C+中定义了 const定符,但有些编译器无法正确实现。几年来,大量C语言遗留代码都是在没有const限定符的情况下编写的。 因此,使用
20、旧版C语言代码的软件工程有很大的改进空间。许多目前使用ANSIC 和C+的程序员也没有在程序中使用const限定符,因为他们是从C语言的早期 版本开始编程的,这些程序员错过了许多改进软件工程的好机会函数参数使用或不用const限定符的可能性有六种,两种用按值调用传 递参数,四种按引用调用传递参数,根据最低权限原则来进行选择。在参数中向 函数提供完成指定任务所需的数据访问,但不要提供更多权限。第3章曾经介绍,按值调用传递参数时,函数调用中要生成参数副本并 将其传递给函数。如果函数中修改副本,则调用者的原值保持不变。许多情况下, 需要修改传入函数的值以使函数能够完成任务。但有时即使被调用函数只是操
21、作 原值的副本,也不能在被调用函数中修改这个值。假设函数取个单下标数组及其长度为参数,并打印数值。这种函数应对数组 进行循环并分别输出每个数组元素。函数体中用数组长度确定数组的最高下标, 以便在打印完成后结束循环。在函数体中不能改变这个数组长度。软件工程视点5. 2如果函数体中不能修改传递的值,则这个参数应声明为const以避免被 意外修改。如果试图修改const类型的值,则编译器会捕获这个错误并发出一个警 告或错误消息(取决于特定的编译器)。轶件工程视点5. 3按值调用时,只舱在调用函数中改变个值。这个值通过函数返回值进 行赋值。要在调用函数中改变多个值,就要按引用传递多个参数。编程技巧5.
22、 3使用函数之前,检查函数原型以确定可以修改的参数。将指针传递给函数有四种方法:非常量数据的非常量指针、常量数据的 非常量指针、非常量数据的常量指针和常量数据的常量指针。每种组合提供不同 的访问权限。最高访问权限是非常量数据的非常量指针,可以通过复引用指针而修改, 指针可以修改成指向其他数据。声明非常量数据的非常量指针时不用consto这 种指针可以接收函数中的字符串,用指针算法处理或修改字符串中的每个字符。 图5. 10中的函数convertToUppercase声明参数sPtr (char*sPtr)为非常量数 据的非常量指针。函数用指针算法一次一个字符地处理字符串strings字符串 中
23、,st至,X,的字符用函数toupper变为相应的大写字母,其余字符不变。函 数toupper取个字符作为参数。如果是小写字母,则返回相应的大写字母,否则返回原字符。函数toupper是字 符处理库ctype. h中(见第16章)的一部分。常量数据的非常量指针,指针可以修改成指向其他数据,但数据不能通 过指针修改。这种指针可以接收函数的数组参数,函数处理数组每个元素而不修 改数据。例如,图5. 11的函数printcharacters将参数sPtr声明为const char* 类型.表示“ sPtr是字符常量的指针”。函数体用for循环输出字符串中的每 个字符,直到遇到null终止符。打印每个
24、字符之后,指针sPtr递增,指向字符 串中下一个宇符。1 / Fig. 5. 10: fig0510. cpp2 / Converting lowercase letters to uppercase letters3 / using a non-constant pointer to non-constant data 4 #include 5 #include 67 void convertToUppercase( char * );89 int main()10 11 char string = characters and $32.98;1213 cout The stringbefo
25、reconversion is: string;14 convertToUppercase(string);15 cout nThe string after conversion is:16 cout string = a & *sPtr z)25*sPtr = toupper( *sPtr );/ convert to uppercase2627 +sPtr;/ move sPtr to the next character28 )29 )输出结果:The string before conversion is: characters and $32.98The string after
26、conversion is: CHARACTERS AND $32. 98图5.10将字符串变成大写1 / Fig. 5. 11 :fig05 11. cpp2 / Printing a string one character at a time using3 / a non-constant pointer to constant data4 ttinclude 56 void printcharacters( const char * );78 int main()9 (10char string = print characters of a string;1112 cout The
27、string is:n;13 printcharacters( string );14 cout endl;15 return 0;16 )1718 / In printcharacters, sPtr is a pointer to a character19 / constant. Characters cannot be modified through sPtr20 / (i. e., sPtr is a read-only pointer).21 void printcharacters( const char *sPtr )22 (23 for ( ; *sPtr != 、0; s
28、Ptr+ )/ no initialization24 cout *sPtr;25 )输出结果:The string is:print characters of a string图5. 11用常量数据的非常量指针打印字符串(一次打印一个字符)图5.12演示了函数接收常量数据的非常量指针,并试图通过指针修改数据在 编译时产生的语法错误消息。1 / Fig. 5. 12: fig05_12. cpp2 / Attempting to modify data through a3 / non-constant pointer to constant data.4 #include 56 void
29、f( const int* );78 int main ()9 10 int y;1112 f( &y );/ f attempts illegak modification1314 return 0;15 1617 / In f, xPtr is a poin er to an integer constant18 void f( const int *xPtr )19 (20 *xPtr = 100;/ cannot modify a const object21 )输出结果:Compiling FIG05 12.CPP:Error FIG05 12. CPP 20: Cannot mod
30、ify a const objectWarning FIG0S12. CPP 21: Parameter xPtr is never used图5. 12试图通过常量数据的非常量指针修改数据众所周知,数组是累计数据类型,用同一名称存放相同类型的相关数据项。第6 章将介绍另种形式的累计数据类型结构(structure),也称为记录(record) 结构可以用同一名称存放不同类型的相关数据项(例如,存放公司每 个员的信息)。调用带数组参数的函数时,数组模拟按引用调用自动传递给函 数。但结构则总是按值调用,传递整个结构的副本。这就需要复制结构中每个数 据项目并将其存放在计算机函数调用堆栈中的执行时开
31、销(函数执行时用函数调 用堆栈存放函数调用中使用的局部变量)。结构数据要传递绐函数时,可以用常 量数据的指针(或常量数据的引用)得到按引用调用的性能和按值调用对数据的 保护。传递结构的指针时,只要复制存放结构的地址。在4字节地址的机器上, 只要复制4字节内存而不是复制结构的几百或几千字节。性能提示5. 7要传递结构之类的大对象时,可以用常量数据的指什(或常量数据的引用) 得到按引用调用的性能和按值调用对数据的保护。非常量数据的常量指针总是指向相同的内存地址,该地址中的数据可以 通过指针修改。这里的数组名是默认的。数组名是数组开头的常量指针,数组中 的所有数据可以用数组名和数组下标访问和修改。非
32、常量数据的常量指针可以接 收数组为函数参数,该函数只用数组下标符号访问数组元素。声明为const的指 针应在声明时初始化(如果是函数参数,则用传入函数的指针初始化)。图5. 13 的程序想修改常量指针,指针ptr的类型声明为int *const,图中的声明表示 “ptr是整数的常量指针”,指针用整型变量x的地址初始化。程序要将y的地 址赋给ptr,但产生一个错误消息。注意数值7赋 给*ptr时不产生错误,说明ptr所指的值是可修改的。常见编程错误5. 6声明为const的指针不在声明时初始化是个语法错误。常量数据的常量指针的访问权限最低。这种指针总是指向相同的内存地 址,该内存地址的数据不能修
33、改。数组传递到函数中,该函数只用数组下标符号 读取,而不能修改数组。图5. 14的程序演示声明指针变量ptr为const int* const,表示“ ptr是常量整数的常量指针”。图中显示了修改ptr所指 数据和修改存放指针变量的地址时产生的错误消息。注意输出ptr所指的值时不 产生错误,因为输出语句中没有进行修改。1 / Fig. 5. 13:fig05 13. cpp2 / Attempting to modify a constant3 / non-constant data4 #include 56 int main()7 (8 int x, y;91011be modified12
34、always points13location.14 *ptr = 7;15 ptr = &y;1617 return 0;18 )输出结果:Compiling FIG05 13.CPP:Error FIG05 13. CPP 15: Cannot modifyint * const ptrpointer to&x; / ptr is a constant pointer to an/ integer. An integer can/ through ptr, but ptr/ to the same memorya const objectWarning FIG0S_13. CPP 18:
35、y is declared but never used修改非常量数据的常量指针1 / Fig. 5. 141 fig05 14. cpp2 / Attempting to modify a constant pointer to3 / constant data.4 #include 56 int main()7 (8 int x = 5, y;910 const iht *const ptr = &x; / ptr is a constant pointer to a11 / constant integer,ptr always12 / points to thesame locatio
36、n13 / and the integerat that14 / location cannotbe modified.15 eout *ptr endl;16 *ptr = 7;17 ptr = &y;1819 return 0;20 )输出结果:Compiling FIG05 14.CPP:Error FIG05_14. CPP 16: Cannot modify a const objectError FIG05_14. CPP 17: Cannot modify a const objectWarning FIG05_14. CPP 20: y is declared but neve
37、r used图5. 14修改常量数据的常量指针5. 6按引用调用的冒泡排序下面将图4. 16的冒泡排序程序修改成用两个函数bubbleSort和swap(如图5.15) 。函数bubbleSort进行数组排序,它调用函数swap,变换数组元素a门1ay j) 和arrayj+l记住,C+强制函数之间的信息隐藏,因此swap并不能访问 bubbleSort中的各个元素。由于bubbleSort要求swap访问交换的数组元素, 因此bubbleSort要将这些元素按引用调用传递给swap,每个数组元素的地址显 式传递。尽管整个数组自动按引用调用传递,但各个数组元素是标量,通常按值调用传递。 因此,
38、bubbleSort对swap调用中的每个数组元素使用地址运算符(&),如下所 示的语句:swap( &array j , arrayj+ 1);实现按引用调用。函数swap用指针变量elementlPtr接收&arrayj。由于信息 隐藏,swap并不知道名称&arrayj,但swap可以用elementlPtr作为&arrayj 的同义词。这样,swap引用elementlPtr时,实际上是引用bubbleSort中的 &arrayj! 同样,swap引用element2Ptr时,实际上是引用bubbleSort中的 arrayj+1 虽然 swap 不能用:hold = array j
39、;array j = array j + 1 ;array j + 1 = hold;但图5. 15中的swaP函数用hold = * elementlPtr;*elementlPtr = *element2Ptr;*element2Ptr = hold;达到相同的效果。1 / Fig. 5. 15: fig05_15. cpp2 / This program puts values into an array, sorts the values into3 / ascending order, and prints the resulting array.4 ttinclude 5 ttin
40、clude 67 void bubbleSort int *, const int );8D int main(10 11 const int arraySize = 10;12 int a arraySize ) = 2, 6, 4, 8, 10, 12, 89, 68, 45, 37 ;13 int i;1415 cout Data items in original ordern;1617 for ( i = 0; t arraySize; i+ )18 cout setw( 4 ) a i ;1920 bubbleSort( a, arraySize );/ sort the arra
41、y21 cout nData items inascending ordern;2223 for ( i = 0; i arraySize; i+ )24cout setw( 4 ) a i ;26 cout endl;27 return 0;28 )2930 void bubbleSort( int *array, const int size )31 32 void swap( int *, iht * );3334 for (int pass = 0; pass size - 1; pass+ )3536for (int j = 0; j array j + 1 )39swap( &ar
42、ray j , &arra j + 1 );40 )4142 void swap( int *elementlPtr, int *element2Ptr )43 (44 int hold = *elementlPtr;45 *elementlPtr = *element2Ptr;46 *element2Ptr = hold;47 )输出结果:Dataitem:inOriginalOrder2648101289684537DataitemsinascendinqOrder2468101237458968图5. 15 按 引用调用的冒泡排序注意函数bubbleSort中的几个特性。函数首部中将ar
43、ray声明为int* array 而不是int array 表示bubbleSort接收单下标数组作为参数(这些符号是可 以互换的)。参数size声明为const以保证最低权限原则。尽管参数size接收 main中数值的副本,且修改该副本并不改变main中的值,但是bubbleSort不 必改变size即可完成任务。bubbleSort执行期间数组的长度保持不变,因此, size声明为const以保证不被修改。如果排序过程中修改数组长度,则排序算 法无法正确执行。bubbleSort函数体中包括了函数swap的原型,因为它是调用swap的惟 函数。将原型放在bubbleSort中,使得只能从bubbleSort正确地调用swap 其他函数要调用swap时无法访问正确的函数原型,这通常会造成语法错误,因 为C+需要函数原型。软件工程视点5. 4将函数原型放在其他函数中能保证最低权限原则,只能从该原型所在函 数中正确地调用。注意函数bubbleSort接收数组长度参数。函数必须知道数组长度才能排 序数组。数组传递到函数时,函数接收数组第一个元素的内存地址。数组长度要 单独传递给函数。通过将函数bubbleSort定义成接收数组长度作为参数,可以让函数在排 序任何长度单下标整型数组的程序中使用。软件工程视点5. 5向函数传递数组时,同时传
限制150内