- 内容提要
- 前言
- 第 1 章 预备知识
- 第 2 章 开始学习 C++
- 第 3 章 处理数据
- 第 4 章 复合类型
- 第 5 章 循环和关系表达式
- 第 6 章 分支语句和逻辑运算符
- 第 7 章 函数——C++的编程模块
- 第 8 章 函数探幽
- 第 9 章 内存模型和名称空间
- 第 10 章 对象和类
- 第 11 章 使用类
- 第 12 章 类和动态内存分配
- 第 13 章 类继承
- 第 14 章 C++中的代码重用
- 第 15 章 友元、异常和其他
- 第 16 章 string 类和标准模板库
- 第 17 章 输入、输出和文件
- 第 18 章 探讨 C++新标准
- 附录 A 计数系统
- 附录 B C++保留字
- 附录 C ASCII 字符集
- 附录 D 运算符优先级
- 附录 E 其他运算符
- 附录 F 模板类 string
- 附录 G 标准模板库方法和函数
- 附录 H 精选读物和网上资源
- 附录 I 转换为 ISO 标准 C++
- 附录 J 复习题答案
7.6 函数和结构
现在将注意力从数组转到结构。为结构编写函数比为数组编写函数要简单得多。虽然结构变量和数组一样,都可以存储多个数据项,但在涉及到函数时,结构变量的行为更接近于基本的单值变量。也就是说,与数组不同,结构将其数据组合成单个实体或数据对象,该实体被视为一个整体。前面讲过,可以将一个结构赋给另外一个结构。同样,也可以按值传递结构,就像普通变量那样。在这种情况下,函数将使用原始结构的副本。另外,函数也可以返回结构。与数组名就是数组第一个元素的地址不同的是,结构名只是结构的名称,要获得结构的地址,必须使用地址运算符&。在 C 语言和 C++中,都使用符号&来表示地址运算符;另外,C++还使用该运算符来表示引用变量,这将在第 8 章讨论。
使用结构编程时,最直接的方式是像处理基本类型那样来处理结构;也就是说,将结构作为参数传递,并在需要时将结构用作返回值使用。然而,按值传递结构有一个缺点。如果结构非常大,则复制结构将增加内存要求,降低系统运行的速度。出于这些原因(同时由于最初 C 语言不允许按值传递结构),许多 C 程序员倾向于传递结构的地址,然后使用指针来访问结构的内容。C++提供了第三种选择——按引用传递(将在第 8 章介绍)。下面介绍其他两种传递方式,首先介绍传递和返回整个结构。
7.6.1 传递和返回结构
当结构比较小时,按值传递结构最合理,下面来看两个使用这种技术的示例。第一个例子处理行程时间。有些地图指出,从 Thunder Falls 到 Bingo 城需要 3 小时 50 分钟,而从 Bingo 城到 Gotesquo 需要 1 小时 25 分钟。对于这种时间,可以使用结构来表示——一个成员表示小时值,另一个成员表示分钟值。将两个时间加起来需要一些技巧,因为可能需要将分钟值转换为小时。例如,前面列出的两个时间的总和为 4 小时 75 分钟,应将它转换为 5 小时 15 分钟。下面开发用于表示时间值的结构,然后再开发一个函数,它接受两个这样的结构为参数,并返回表示参数的和的结构。
定义结构的工作很简单:
接下来,看一下返回两个这种结构的总和的 sum( ) 函数的原型。返回值的类型应为 travel_time,两个参数也应为这种类型。因此,原型应如下所示:
要将两个时间相加,应首先将分钟成员相加。然后通过整数除法(除数为 60)得到小时值,通过求模运算符(%)得到剩余的分钟数。程序清单 7.11 在 sum( ) 函数中使用了这种计算方式,并使用 show_time( ) 函数显示 travel_time 结构的内容。
程序清单 7.11 travel.cpp
其中,travel_time 就像是一个标准的类型名,可被用来声明变量、函数的返回类型和函数的参数类型。由于 total 和 t1 变量是 travel_time 结构,因此可以对它们使用句点成员运算符。由于 sum( ) 函数返回 travel_time 结构,因此可以将其用作 show_time( ) 函数的参数。由于在默认情况下,C++函数按值传递参数,因此函数调用 show_time(sum(trip, day3)) 将执行函数调用 sum(trip, day3),以获得其返回值。然后,show_time( ) 调用将 sum( ) 的返回值(而不是函数自身)传递给 show_time( )。下面是该程序的输出:
7.6.2 另一个处理结构的函数示例
前面介绍的有关函数和 C++结构的大部分知识都可用于 C++类中,因此有必要介绍另一个示例。这次要处理的是空间,而不是时间。具体地说,这个例子将定义两个结构,用于表示两种不同的描述位置的方法,然后开发一个函数,将一种格式转换为另一种格式,并显示结果。这个例子用到的数学知识比前一个要多,但并不需要像学习数学那样学习 C++。
假设要描述屏幕上某点的位置,或地图上某点相对于原点的位置,则一种方法是指出该点相对于原点的水平偏移量和垂直偏移量。传统上,数学家使用 x 表示水平偏移量,使用 y 表示垂直偏移量(参见图 7.6)。x 和 y 一起构成了直角坐标(rectangular coordinates)。可以定义由两个坐标组成的结构来表示位置:
图 7.6 直角坐标
另一种描述点的位置的方法是,指出它偏离原点的距离和方向(例如,东偏北 40 度)。传统上,数学家从正水平轴开始按逆时针方向度量角度(参见图 7.7)。距离和角度一起构成了极坐标(polar coordinates)。可以定义另一个结构来表示这种位置:
图 7.7 极坐标
下面来创建一个显示 polar 结构的内容的函数。C++库(从 C 语言借鉴而来)中的数学函数假设角度的单位为弧度,因此应以弧度为单位来测量角度。但为了便于显示,我们将弧度值转换为角度值。这意味着需要将弧度值乘以 180/——约为 57.29577951。该函数的代码如下:
请注意,形参的类型为 polar。将一个 polar 结构传递给该函数时,该结构的内容将被复制到 dapos 结构中,函数随后将使用该拷贝完成工作。由于 dapos 是一个结构,因此该函数使用成员运算符句点(参见第 4 章)来标识结构成员。
接下来,让我们试着再前进一步,编写一个将直角坐标转换为极坐标的函数。该函数接受一个 rect 参数,并返回一个 polar 结构。这需要使用数学库中的函数,因此程序必须包含头文件 cmath(在较旧的系统中为 math.h)。另外,在有些系统中,还必须命令编译器载入数学库(参见第 1 章)。可以根据毕达哥拉斯定理,使用水平和垂直坐标来计算距离:
数学库中的 atan2( ) 函数可根据 x 和 y 的值计算角度:
还有一个 atan( ) 函数,但它不能区分 180 度之内和之外的角度。在数学函数中,这种不确定性与在生存手册中一样不受人欢迎。
有了这些公式后,便可以这样编写该函数:
编写好函数后,程序的其他部分编写起来就非常简单了。程序清单 7.12 列出了程序的代码。
程序清单 7.12 atrctfun.cpp
注意:
有些编译器仅当被明确指示后,才会搜索数学库。例如,较早的 g++版本使用下面这样的命令行:
下面是该程序的运行情况:
程序说明
程序清单 7.12 中的两个函数已经在前面讨论了,因此下面复习一下该程序如何使用 cin 来控制 while 循环:
前面讲过,cin 是 istream 类的一个对象。抽取运算符(>>)被设计成使得 cin>>rplace.x 也是一个 istream 对象。正如第 11 章将介绍的,类运算符是使用函数实现的。使用 cin>>rplace.x 时,程序将调用一个函数,该函数返回一个 istream 值。将抽取运算符用于 cin>>rplace.x 对象(就像 cin>>rplace.x>>rplace.y 这样),也将获得一个 istream 对象。因此,整个 while 循环的测试表达式的最终结果为 cin,而 cin 被用于测试表达式中时,将根据输入是否成功,被转换为 bool 值 true 或 false。例如,在程序清单 7.12 中的循环中,cin 期望用户输入两个数字,如果用户输入了 q(前面的输出示例就是这样做的),cin>>将知道 q 不是数字,从而将 q 留在输入队列中,并返回一个将被转换为 fasle 的值,导致循环结束。
请将这种读取数字的方法与下面更为简单的方法进行比较:
要提早结束该循环,可以输入一个负值。这将输入限制为非负值。这种限制符合某些程序的需要,但通常需要一种不会将某些数值排除在外的、终止循环的方式。将 cin>>用作测试条件消除了这种限制,因为它接受任何有效的数字输入。在需要使用循环来输入数字时,别忘了考虑使用这种方式。另外请记住,非数字输入将设置一个错误条件,禁止进一步读取输入。如果程序在输入循环后还需要进行输入,则必须使用 cin.clear( ) 重置输入,然后还可能需要通过读取不合法的输入来丢弃它们。程序清单 7.7 演示了这些技术。
7.6.3 传递结构的地址
假设要传递结构的地址而不是整个结构以节省时间和空间,则需要重新编写前面的函数,使用指向结构的指针。首先来看一看如何重新编写 show_polar( ) 函数。需要修改三个地方:
- 调用函数时,将结构的地址(&pplace)而不是结构本身(pplace)传递给它;
- 将形参声明为指向 polar 的指针,即 polar *类型。由于函数不应该修改结构,因此使用了 const 修饰符;
- 由于形参是指针而不是结构,因此应间接成员运算符(->),而不是成员运算符(句点)。
完成上述修改后,该函数如下所示:
接下来对 rect_to_polar 进行修改。由于原来的 rect_to_polar 函数返回一个结构,因此修改工作更复杂些。为了充分利用指针的效率,应使用指针,而不是返回值。为此,需要将两个指针传递给该函数。第一个指针指向要转换的结构,第二个指针指向存储转换结果的结构。函数不返回一个新的结构,而是修改调用函数中已有的结构。因此,虽然第一个参数是 const 指针,但第二个参数却不是。也可以像修改函数 show_polar( ) 修改这个函数。程序清单 7.13 列出了修改后的程序。
程序清单 7.13 strctptr.cpp
注意:
有些编译器需要明确指示,才会搜索数学库。例如,较早的 g++版本使用下面这样的命令行:
从用户的角度来说,程序清单 7.13 的行为与程序清单 7.12 相同。它们之间的差别在于,程序清单 7.12 使用的是结构副本,而程序清单 7.13 使用的是指针,让函数能够对原始结构进行操作。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论