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 = ;
};

在这段代码中,我们用两个常量分别作为类的两个成员变量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 = ;
rect.m_nW = ; // 直接访问成员函数
cout<<"Rect的面积是:"<<rect.GetArea()<<endl;

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

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

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

  1. 《大象 Thinking in UML》读书笔记:软件开发——从现实世界到对象世界

    参考:Process-oriented vs. Object-oriented 前言 软件行业在采用OO的思想后,从一开始只对编码使用OO,到现在“分析-设计-编码”全部环节使用OO,形成了OOA.O ...

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

    建议112:将现实世界中的对象抽象为类,将可复用对象圈起来就是命名空间 在我们身边的世界中,对象是什么?对象就是事物,俗称“东西”.那么,什么东西算得上是一个对象呢?对象有属性.有行为.以动物为例,比 ...

  3. 学习笔记DL003:神经网络第二、三次浪潮,数据量、模型规模,精度、复杂度,对现实世界冲击

    神经科学,依靠单一深度学习算法解决不同任务.视觉信号传送到听觉区域,大脑听学习处理区域学会“看”(Von Melchner et al., 2000).计算单元互相作用变智能.新认知机(Fukushi ...

  4. UGUI和现实世界的比例关系

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

  5. AR Engine光照估计能力,让虚拟物体在现实世界更具真实感

    AR是一项现实增强技术,即在视觉层面上实现虚拟物体和现实世界的深度融合,打造沉浸式AR交互体验.而想要增强虚拟物体与现实世界的融合效果,光照估计则是关键能力之一. 人们所看到的世界外观,都是由光和物质 ...

  6. 关于C++的变量和类的声明和定义

    什么是变量?变量或者叫对象,是一个有具名的.可以供程序操作的存储空间.这里具名是指变量是有名字的,可供操作是指能进行加减乘除或者输入输出等操作,存储空间则是指有一块属于它的内存空间. 为了便于说明,标 ...

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

    第5章 用函数封装程序功能 在完成功能强大的工资程序V1.0之后,我们信心倍增,开始向C++世界的更深远处探索. 现在,我们可以用各种数据类型定义变量来表达问题中所涉及的各种数据:用操作符连接这些变量 ...

  8. Go/Python/Erlang编程语言对比分析及示例 基于RabbitMQ.Client组件实现RabbitMQ可复用的 ConnectionPool(连接池) 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil 分享基于MemoryCache(内存缓存)的缓存工具类,C# B/S 、C/S项目均可以使用!

    Go/Python/Erlang编程语言对比分析及示例   本文主要是介绍Go,从语言对比分析的角度切入.之所以选择与Python.Erlang对比,是因为做为高级语言,它们语言特性上有较大的相似性, ...

  9. 创建类模式(二):抽象工厂(Abstract Factory)

    定义 为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类. 抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态.抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式. ...

随机推荐

  1. Compare Version Number

    package cn.edu.xidian.sselab.string; /** *  * @author zhiyong wang * title: Compare Version Numbers  ...

  2. 原生JavaScript拖动div兼容多种浏览器

    说句题外话,虽然博客园嵌入式氛围不行,Web前端氛围还是很好的.我又从 chinaunix 回来了. <html> <head> <script type="t ...

  3. 转:VS2010解决方案转换到VS2008

    原文链接地址:http://www.codeproject.com/Tips/80953/Converting-VS2010-Solution-to-VS2008 如果你使用VS2010的任何版本写代 ...

  4. 《University Calculus》-chaper13-向量场中的积分-线积分

    线积分: 基于二重积分和三重积分的引入,我们对于线积分的引入过程将会轻车熟路. 对于一根不均匀密度的铜丝,我们如何求其总质量?如下图. 类似二重积分和三重积分的引入,我们首先基于实际问题给出黎曼和的形 ...

  5. JavaScript 操作 DOM 常用 API 总结

    文本整理了javascript操作DOM的一些常用的api,根据其作用整理成为创建,修改,查询等多种类型的api,主要用于复习基础知识,加深对原生js的认识. 基本概念 在讲解操作DOM的api之前, ...

  6. 纠结的CLI C++与Native C++的交互

    最近在写点东西,涉及到了CLR C++与Native C++的互相调用的问题,结果...........纠结啊. 交互原型 交互原型是这样的: void* avio_alloc_context( un ...

  7. 获取contenteditable的内容 对html进行处理 兼容 chrome、IE、Firefox

    var html = $(this).html();if(html){ var lineSign = html.indexOf('<div>'); if(html.indexOf('< ...

  8. idea unknow facet type web 解决方案

    菜单 -->Preferences-->Plugins 添加tomcat支持 如图: 然后 项目project-setting中 可以添加 web类型的facets了 pasting

  9. Android代码混淆

    混淆器(ProGuard) --- 混淆器通过删除从未用过的代码和使用晦涩名字重命名类.字段和方法,对代码进行压缩,优化和混淆.结果是一个比較小的.apk文件,该文件比較难进行逆向project.因此 ...

  10. Android GridView属性集合

    GridView的一些特殊属性: 1.android:numColumns=”auto_fit”   //GridView的列数设置为自动 2.android:columnWidth=”90dp &q ...