C++抽象编程——面向对象(2)——结构体与类

点的表示(Representing points)

类的有用属性之一(但并不意味着唯一的一个),就是它们可以将几个相关的信息合并成一个可以作为一个单元进行操作的复合值。作为一个简单的例子,假设我们正在使用坐标始终为整数的x-y网格中的坐标。尽管可以独立地使用x和y值,但更为方便的是定义将x和ay值组合在一起的抽象数据类型(ADT)。在几何体中,这个统一的坐标值对称为一个点,因此使用名称Point对应的类型。 C++提供了几种定义Point类型的策略,从C语言中一直可用的简单结构类型到使用更现代化的面向对象样式的定义的复杂性。随后的各个部分探讨了这些策略,从基于结构的模型开始,然后转向基于类的形式。

将点定义为结构体类型(Defining Point as a structure type)

在过去的编程经验中,肯定会遇到通过组合已经存在的较简单类型的值定义的类型。这些类型称为记录(records)或结构(structure),其中第一个术语在计算机科学界被广泛使用,第二个术语在C ++程序员中更常见。 鉴于C ++支持C中可用的工具,可以使用以下C风格结构定义将Point类型定义为结构类型:

struct Point {
    int x;
    int y;
};

该代码将Point类型定义为具有两个组件的结构。在结构体中,组件称为字段(fields)或成员(members)。 在此示例中,Point结构包含一个名为x的成员和一个名为y的成员,均为int类型。
当您使用C ++中的结构或类时,请务必记住,定义引入了一种新类型,本身并不声明任何变量。 一旦你有了定义,你可以使用类型名来声明变量,就像使用任何其他类型一样。 例如,如果你包含局部变量的声明:

Point p;

在一个函数中,编译器将为堆栈框架中的空间保留一个名为p的类型的变量,就像声明int类型一样;

int n;

为int类型的变量预留空间。 唯一的区别是,点变量p包括保存其x和y分量的值的内部成员。如果您要绘制一个变量p的空间,它将看起来像这样:

给定一个结构,您可以使用以点形式输入的点运算符(dot operator)来选择各个成员。
var.name

其中var是包含结构化值的变量,name指定所需的成员。例如,您可以使用表达式p.xp.y来选择存储在变量p中的Point结构的单独坐标值。选择表达式是可分配的,因此您可以使用以下代码初始化p的组件来表示点(2,3):

p.x = 2;
p.y = 3;

这个时候的空间状态如下:

结构的基本特征是可以将其视为单个字段的集合和单个值。在较低级别的实现中,存储在各个成员中的值可能很重要。在更高层次的细节上,把值作为一个整体单位来说是通常是很有意义的。
通过定义与整个结构一起使用的几个重要操作,C++可以使得更容易地维护高级视角。给定一个Point值,例如,您可以将该值分配给变量,将其作为参数传递给函数,或者将其作为结果返回。如果您需要单独查看组件,任何客户端都可以选择x和y成员,但通常足以使用该值作为一个整体。这些设计决策意味着你可以通过应用程序的各个层次上下传递结构。

将点定义为类(Defining Point as a class)

虽然结构类型是C++的历史的一部分,但是它们已经在很大程度上被类(class)所取代,因为类提供了更强大的功能和灵活性。前面部分的Point结构与下面的类定义相同:

class Point {
public:
    int x;
    int y;
};

从这个例子可以看出,一个类的成员(也称为实例变量(instance variables)),使用与结构中的字段相同的语法来声明。 唯一的句法差异在于,一个类中的字段被分成公共(public)和私有(private)部分,以控制程序的哪些部分可以访问这些成员。关键字public引入包含可用于使用定义类的任何人可用的成员的公共部分。然而,类定义也可以包括由关键字private引入的私有部分。 在私有部分中声明的字段仅对定义类可见,而不是其任何客户端。在C++中,结构使用类实现,并以完全相同的方式运行,除了结构体默认为public。
根据我们上面的代码,x和y成员被包含在公共部分的一部分中,这使得它们对客户端可见。我们可以使用点运算符选择对象中的公共成员。 例如,如果有一个包含Point类的实例的对象的变量pt,则可以通过运算符来选择其x的成员:

pt.x

就像先前的Point结构体那样。
但是声明public变量,在现代面向对象编程是不提倡的。今天,我们常见的做法是使所有的实例变量都是私有的,这意味着客户端不能直接访问内部变量。客户端使用类导出的方法来访问该类包含的任何信息。将实施细节远离客户端可能会促进简单性,灵活性和安全性。就像我们之前谈过的接口。
使实例变量为私有是很容易的容易。把上面的代码从public更改为private,如下所示:

class Point {
private:
    int x;
    int y;
};

该定义的问题是客户端不再有任何方式来获取存储在Point对象中的信息,这使得它以当前形式无法使用。 至少,客户端需要一些方法来创建Point对象,并从现有Point中寻找单个x和y坐标。
从介绍STL的文章我们知道,创建对象是构造函数的责任,它始终与该类名称相同。类通常定义多个版本的构造函数来考虑不同的初始化模式。特别地,大多数类定义了一个不包含参数的构造函数,这被称为默认构造函数(default constructor)。默认构造函数用于初始化声明的对象,而不指定参数列表。在Point类的情况下,定义一个采用一对坐标值的构造函数。
在计算机科学中,检索实例变量值的方法被正式称为访问器(accessors),通常被称为getter。 按照惯例,getter方法的名称从首字母get后面跟着名字后面的第一个字母大写后开头。所以Point类的getter是getX和getY。
下图给出了类的一般结构:

在大多数情况下,图6-1中的代码应该很容易理解。 可能引起混淆的唯一微妙之处就是构造函数的第二种形式的参数名称。 在逻辑上,构造函数采用x和y坐标,因此期望参数名称将是x和y而不是xc和yc(其中我已经包括c来建议单词坐标)是合理的。使用名称x和y作为构造函数的参数将导致对变量x的引用是要引用参数还是使用该名称的实例变量的混淆。
一个变量在一个较大的范围内隐藏相同命名的变量称为阴影(shadowing)。 以后将学习一种简单的技巧来解决这个歧义。 不幸的是,这种技术取决于那些超出你的知识的概念。因此,此刻,我们通过为参数和实例变量选择不同的名称来避免完全阴影的问题。

在阅读上图中的代码之后,可能会遇到的其他问题是为什么某些方法不是类的一部分。虽然getter方法可以从Point对象获取信息,但是当前定义不能改变这些成员的值。在某些情况下有用的一种方法是导出设置特定实例变量值的方法。这种方法被称为设定者(setter)。* 如果Point类导出一个setX和一个setY方法,允许客户端更改这些成员的值,那么我们可以很轻易地将以前使用旧结构类型的任何应用程序替换为新的Point类的版本。而你需要做的只是利用这种形式来改变成员的赋值。*

pt.x = value;

我们可以这样,用一个方法来替换上面的代码:

pt.setX(value);

然而,在考虑将其实例变量设置为私有的重要性之后,考虑在一个类中添加setter方法是有点不满意的。毕竟,使实例变量为私有的一部分原因是确保客户端没有对它们的无限制访问。让一个类中的每个实例变量都带有一个公共的setter方法来打破这些限制,并消除了首先将这些变量变为私有的优点。通常,允许客户端读取实例变量的值比使客户端更改这些值更为安全。 因此,setter方法远远少于面向对象设计中的getter。
事实上,许多程序员通过在创建对象之后不能更改任何实例变量的值,从而不允许更改更高级别的数据。 以这种方式设计的类是不可改变的。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值