你好,C++(32) 类是对现实世界的抽象和描述 6.2.1 类的声明和定义

6.2  类:当C++爱上面向对象

类这个概念是面向对象思想在C++中的具体体现:它既是封装的结果,同时也是继承和多态的载体。因此,要想学习C++中的面向对象程序设计,也就必须从“类”开始。

6.2.1  类的声明和定义

面向对象思想把现实世界中的所有事物都看成是对象,而类是对所有相同类型对象的抽象,是对它们总体的一个描述。比如,学校有很多老师,张老师、李老师、王老师,虽然每个老师各不相同,是不同的对象个体。但他们都是老师这一类型的对象,有着共同的属性(都有姓名、职务)和相同的行为(都能上课、批改作业)。我们把某一类型对象的共同属性和相同行为抽象出来,分别用变量和函数加以描述,然后把这些变量和函数用类这个概念封装起来,就成了可以用来描述这一类对象的新的数据类型,这种新的数据类型因此也被称为类。在C++中,声明一个类的语法格式如下:

class 类名
{
public :
    // 公有成员,通常用来描述这类对象的相同行为
protected:
    // 保护型成员
private:
    // 私有成员,通常用来描述这类对象的共同属性
};  // 注意这里有个分号表示类的结束

其中,class是C++中用以声明类的关键字,其后跟所要声明的类的名字,通常是某个可以概括这一类对象的名词。其命名规则类似于之前介绍的变量名命名规则。这里,我们要定义一个类来描述“老师”这类对象,所以我们用“Teacher”作为这个类的名字。

在后面的章节中,我们还将学到C++中的类还有基类与派生类之分,它是面向对象思想的继承机制在类当中的体现。如果这个类是从某个基类继承而来的,我们在“class 类名”后面还要加上这个类的继承方式(public、protected或private)以及它所继承的基类的名字。这样,声明一个类的语法格式相应地就变为:

class 类名:继承方式 基类名
{
    // 成员变量和成员函数的声明…
};

如果某个类没有继承关系,则类声明中的继承方式可以省略。这里的Teacher类本身就是基类,并不是由其他类继承而来,所以这里继承方式应当省略。

完成类的名字及继承关系的定义后,就可以开始在类的主体中描述这个类的属性和行为了。对象的属性属于数据,所以我们在类声明中定义一些变量来描述对象的属性。比如,“老师”这类对象拥有姓名这个属性,所以我们就可以定义一个string类型的变量strName来描述。这些变量描述了对象的属性,成为了这个类整体的一部分,所以也被称为成员变量。

最佳实践:在类声明中给成员变量初始值

如果类的某些成员变量具有初始值,我们可以在类中声明这些成员变量的同时给它一个初始值,这样在运行期间,类就可以在进入构造函数之前,直接使用这个初始值完成相应成员变量的初始化。例如:

class Teacher
{
// 具有初始值的成员变量
protected:
    // 用字符串常量“Name”作为成员变量m_strName的初始值
    string m_strName = “Name”;       // 姓名
private:
    // 用常数2000作为成员变量m_unBaseSalary的初始值
    unsigned int m_unBaseSalary = 2000;
};

在这段代码中,我们用两个常量分别作为类的两个成员变量m_strName和m_unSalary的初始值。经过这样的声明之后,在创建这个类的对象时,无需在构造函数中进行额外的初始化,它的这两个成员变量就会拥有相应的初始值。这一特性可以用于那些所有类的对象都拥有相同初始值的情况,比如所有“Teacher”对象的“m_unBaseSalary”(基本工资)都是2000元。

除了声明成员变量来描述对象的属性之外,对象的另外一个重要组成部分就是它的行为。在C++中,我们用函数来描述一个行为动作。同样,我们也将函数引入类中成为它的成员函数,用来描述类对象的行为。比如,一个“老师”对象有备课的行为动作,我们就可以为老师这个类添加一个PrepareLesson()函数,在这个函数中可以对老师备课动作进行具体的定义。类的构成如图6-7所示。

图6-7  类的构成

最佳实践:为类设计对程序员友好的接口

我们所设计的类不仅供我们自己使用,更多时候它还会提供给其他程序员使用,以达到代码复用或者实现团队协作的目的。这时,类的接口设计的好坏,将会影响到他人能否正确并轻松地使用我们所设计的类。因此,它也成为了衡量一个程序员水平高低的重要标准。

类的接口,就像类的使用说明书一样,是向类的使用者说明它所需要的资源及它所能够提供的服务等。只要类的接口对程序员友好,从接口就可以轻松地知道如何正确地使用这个类。要做到这一点,应当遵守下面这些设计原则。

l  遵循变量与函数的命名规则

成员变量也是变量,成员函数也是函数。所以,作为类的接口的它们,在命名的时候也同样应该遵守普遍的命名规则,让它们的名字能够准确而简洁地表达它们的含义。

l  简化类的视图

接口,代表了类所能够向用户提供的服务。所以,在进行类的接口设计时,只需要将必要的成员函数公有(public)就可以了,使用受保护的(protected)或者私有的(private)成员来向用户隐藏不必要的细节。因为隐藏了用户不应该访问的内容,自然也就减少了用户犯错误的机会。

l   使用用户的词汇

类设计出来最终是让用户使用的,所以在设计类的接口时,应该从用户的角度出发,使用用户所熟悉的词汇,让用户在阅读类的接口时,不需要学习新的词汇或概念,这样可以平滑用户的学习曲线,让我们的类使用起来更容易。

除了在类中定义变量和函数来表示类的属性和行为之外,还可以使用public、protected及private这三个关键字来对这些成员进行修饰,指定它们的访问级别。按照访问级别的不同,类的所有成员被分成了三个部分。通常,使用public修饰的成员外界可以访问,我们会在public部分定义类的行为,提供公共的函数接口供外部访问;使用protected修饰的成员只有类自己和它的派生类可以访问,所以在protected部分,我们可以定义遗传给下一代子类的属性和行为;最后private修饰的成员只有类自己可以访问,所以在private部分,我们可以定义这个类所私有的属性和行为。关于类的继承方式和访问控制,稍后将进行详细介绍。这里先来看一个实际的例子。例如,要定义一个类来描述老师这一类对象,通过对这类对象的抽象,我们发现老师这类对象拥有只有自己和子类可以访问的姓名属性和大家都可以访问的上课行为。当然,老师还有很多其他属性和行为,这里根据需要作了简化。最后,我们使用面向对象的封装机制,将这些属性和行为捆绑到一起,就有了老师这个类的声明。

// 老师
class Teacher
{
// 成员函数
// 描述对象的行为
public: // 公有部分,供外界访问
    void GiveLesson();     // 上课
// 成员变量
// 描述对象的属性
protected:// 受保护部分,自己和子类访问
    string m_strName;           // 姓名
private:
};

通过这段代码,我们声明了一个Teacher类,它是所有老师这种对象的一个抽象描述。这个类有一个public关键字修饰的成员函数GiveLesson(),它代表老师这类对象拥有大家都可以访问的行为——上课。它还有一个protected关键字修饰的变量m_strName,表示老师这类对象的只有它自己和子类可以访问的属性——姓名。这样,通过在一个类中声明函数描述对象的行为,声明变量描述对象的属性,就完整地声明了一个可以用于描述某类对象的类。

完成类的声明之后,我们还需要对类的行为进行具体的定义。类成员函数的具体定义可以直接在类中声明成员函数的时候同时完成:

class Teacher
{
// 成员函数
// 描述对象的行为
public:
    // 声明成员函数的同时完成其定义
    void GiveLesson()
    {
        cout<<"老师上课。"<<endl;
    };

//…

};

更多时候,我们只是将类的声明放在头文件(比如,Teacher.h文件)中,而将成员函数的具体实现放在类的外部定义,也就是相应的源文件(比如,Teacher.cpp)中。在类的外部定义类的成员函数时,我们需要在源文件中引入类声明所在的头文件,并且在函数名之前还要用“::”域操作符指出这个函数所属的类。例如:

#ifndef _TEACHER_H  // 定义头文件宏,防止头文件被重复引入
#define _TEACHER_H  // 在稍后的7.3.1小节中会有详细介绍
// Teacher.h  类的声明文件
class Teacher
{
    // …
public:
    void GiveLesson();  // 只声明,不定义
};

#endif

// Teacher.cpp  类的定义文件
// 引入类声明所在的头文件
#include "Teacher.h"
// 在Teacher类外完成成员函数的定义
void Teacher::GiveLesson()
{
    cout<<"老师上课。"<<endl;
}

这里可以看到,成员函数的定义跟普通函数并无二致,同样都是用函数来完成某个动作,只是成员函数所表示的是某类对象的动作。例如,这里只是输出一个字符串表示老师上课的动作。当然,在实际应用中,类成员函数还可以对成员变量进行访问,所完成的动作也要比这复杂得多。

知道更多:C++中用以声明类的另一个关键字——struct

在C++中,要声明一个类,除了使用正牌的“class”关键字之外,之前在3.8节中介绍过的用来定义结构体的“struct”关键字也同样可以用来声明一个类。在语法上,“class”和“struct”非常相似,两者都可以用来声明类,而两者唯一的区别就是,在没有指定访问级别的默认情况下,用“class”声明的类当中的成员是私有的(private),而用“struct”声明的类当中的成员是公有的(public)。例如:

// 使用“struct”定义一个Rect类
struct Rect
{
    // 没有访问权限说明
// 类的成员函数,默认情况下是公有的(public)
    int GetArea()
    {
        return m_nW * m_nH;
    }

    // 类的成员变量,默认情况下也是公有的(public)
    int m_nW;
    int m_nH;
};

这里,我们使用“struct”声明了一个Rect类,因为没有使用public等关键字显式地指明类成员的访问控制,在默认情况下,类成员都是公有的,所以可以直接访问。例如:

Rect rect;
// 直接访问成员变量
rect.m_nH = 3;
rect.m_nW = 4;

// 直接访问成员函数
cout<<"Rect的面积是:"<<rect.GetArea()<<endl;

这两个关键字的默认访问控制要么过于保守,要么过于开放,这种“一刀切”的方式显然无法适应于所有情况。所以无论是使用“class”还是“struct”声明一个类,我们都应该在声明中明确指出各个成员的合适的访问级别,而不应该依赖于关键字的默认行为。

“class”和“struct”除了上面这点在类成员默认访问级别上的差异之外,从“感觉”上讲,大多数程序员都认为它们仍有差异:“struct”仅像一堆缺乏封装的开放的内存位,更多时候它是用以表示比较复杂的数据;而“class”更像活的并且可靠的现实实体,它可以提供服务、有牢固的封装机制和定义良好的接口。既然大家都这么“感觉”,那么仅仅在类只有很少的方法并且有较多公有数据时,才使用“struct”关键字来声明类;否则,使用“class”关键字更合适。

时间: 2024-10-19 14:23:24

你好,C++(32) 类是对现实世界的抽象和描述 6.2.1 类的声明和定义的相关文章

编写高质量代码改善C#程序的157个建议——建议112:将现实世界中的对象抽象为类,将可复用对象圈起来就是命名空间

建议112:将现实世界中的对象抽象为类,将可复用对象圈起来就是命名空间 在我们身边的世界中,对象是什么?对象就是事物,俗称“东西”.那么,什么东西算得上是一个对象呢?对象有属性.有行为.以动物为例,比如猫(Cat).Cat可以有Name,这就是属性:Cat有一个恶习ScratchSofa(挠沙发),这就是行为.我们把这些属性和行为结合起来,就称为一个类型: class Cat { public string Name { get; set; } public void ScratchSofa()

如何让强化学习走进现实世界?

来源商业新知网,原标题:应用|如何让强化学习走进现实世界? 火遍全球的AlphaGo让我们知道了强化学习打游戏究竟有多6,这么强大的算法什么时候才能打破次元壁,走进现实.控制物理世界中的物体呢? DeepMind已经开始往这方面努力.他们昨天发布的控制套件“ DeepMind Control Suite ”,就为设计和比较用来控制物理世界的强化学习算法开了个头. 就像ALE(Arcade Learning Environment)极大推动了用强化学习打电子游戏的研究一样,DeepMind希望他们

区块链世界与现实世界最大的分歧点是什么?

区块链世界与现实世界最大的分歧点是什么? 1.区块链,天使还是恶魔? 目前社会上对区块链的看法有两种极端,第一种是认为区块链是未来,是非常伟大的创新,是继互联网之后最伟大的科技革命:另外一种看法则认为区块链没有实际意义,无法落地,现在的区块链项目都是些骗子项目. 其实人们对于区块链技术本身是没有那么大疑问的,比如目前你能想到的互联网的巨头企业都已经纷纷的投入资金.人才.精力来进行区块链项目的研发,有很多项目方甚至已经拿出了产品的原型. 而且国家层面也多次提到区块链,有地方政府已经将发展区块链写入

你回到现实世界后,下次再进入未来时

对了,现在你所处的地方,叫起点镇,是免费的默认传送点,以后你进入未来时,如果你想传送到其他地方,可以在传送前进行选择,系统将按距离扣除你相应的点数.” 说到这里,七号用力将口里的口香糖吐了出去,又厌恶地吐了口唾沫,继续道:“进入未来后,你只要完成指定的任务,就可以返回现实世界休整十天,然后再重新进入,一直重复下去,直到你完成了未来的所有任务,以后就不用再进来了.不过,任务难度会越来越大,不是打击你,成功混到最后的机率非常小,你中途挂掉的可能性大得多.还有,这里完成任务的奖励点数与华丽人生奖励点数

UGUI和现实世界的比例关系

之前测试过默认大小的 Cube 在现实中的 比例关系,得出基本单位为 m 的结论,至于 UGUI和现实世界的比例关系 看下图就知道了: Cube 的大小: Button 的大小: 其实基本单位都是m....哈哈哈,你们不用动手测试了

现实世界里的 SOA

在现实世界中,SOA 常常是失败的.确保 SOA 成功的最好的实践是熟悉 SOA 的设计模式和成功案例. 一个成功案例 SOA 是解决数据管理问题的一个强大工具.在你的企业里是否存在数据不一致的问题?将存在这种问题的数据实体的 CRUD (Create.Read.Update 和 Delete) 都封装在同一个服务中.这是对你的数据进行集中控制和方便访问的一个简单办法.我们这个成功案例就是这样 - ACME Carrot 公司 (化名) 正面临一些客户数据实体的问题.如上图所示,客户数据会在众多

告别2017虚拟世界,走进2018现实世界

很久很久以前,一直喜欢使用VMware Workstation来模拟企业生产环境! 谈不上喜欢! 更多的时候,是企业,个人没有这么多真实环境! 在2017年,尽量抛弃虚拟环境! 在2017年,尽量采取真实环境! 准备了半年多,计划2017年07月01日开工测试,分享! 结果,各种因素,将时间推迟到2018年! 在今年,我将走向现实世界的一年! 由于时间问题,2017年12月31日,2018年1月1日,一直忙过不停 ! 未能用心来写作此博文! 此博文,只是记录2018年一个新的开始! 好戏还在后面

GAIAWORLD神盾协议:能与现实世界交互的预言机

微信公众号:GAIAWorld 预言机VS图灵机   区块链应用落地最大的障碍在于难以和现实世界交互,链上世界和现实世界之间存在一条难以跨越的鸿沟,智能预言机是目前唯一可行的链接区块链网络和现实世界的方式.智能预言机通过黑盒的方式将现实世界的参数导入区块链网络,并使用链上图灵机回答决定性问题.  简单而言,智能预言机=能映射现实世界的预言者+图灵机. 图灵机的作用在一些区块链项目中被故意扭曲和夸大:"我们是图灵完备的,所以能做任何事情".图灵完备只代表能做任何计算,不代表能做任何事情,

(转载)你好,C++(24)好大一个箱子!5.1.1 函数的声明和定义

你好,C++(24)好大一个箱子!5.1.1 函数的声明和定义 第5章 用函数封装程序功能 在完成功能强大的工资程序V1.0之后,我们信心倍增,开始向C++世界的更深远处探索. 现在,我们可以用各种数据类型定义变量来表达问题中所涉及的各种数据:用操作符连接这些变量对其进行运算:用程序流程控制结构来控制对这些数据的复杂处理过程,最终实现对数据进行处理得到结果,而这就是程序了.但是,随着要处理的问题越来越复杂,程序的代码自然也就会越来越复杂.如果把所有程序代码都放到main()主函数中,主函数也会越