C++中的面向对象编程设计 · C++PrimerPlus第六版学习笔记(一)

本文详细介绍了C++中的面向对象编程概念,包括抽象、数据隐藏与封装、多态和可重用性。讲解了类的定义、类型、开发过程、数据隐藏、友元、继承、派生类、多态的实现以及类模板等关键知识点,强调了封装和多态在设计中的重要性,并探讨了如何通过继承和多态实现代码的可重用性。
摘要由CSDN通过智能技术生成

说明:本篇笔记是对 C++PrimerPlus 第六版一书中第10-14章有关面向对象编程部分设计的摘录与总结,可供查阅和复习,程序清单及部分细节并未完全记录,若有详细了解的需求请回顾原书。

面向对象编程(OOP)

OOP的特性包括且不限于:

  • 抽象
  • 封装与数据隐藏
  • 多态
  • 继承
  • 代码的可重用性

抽象

简化和抽象是降低复杂性的必要的和有力的手段。通过抽象出问题的本质特征,并通过特征来描述解决方案,可以有效地降低思考的复杂度。抽象是通往用户定义类型的捷径。抽象数据类型(ADT)是这一思想的有力成果。

数据隐藏与封装

数据隐藏指防止程序直接访问数据,而封装指将实现细节聚合并与抽象设计(公共接口)相分离。

多态

方法的行为应取决于调用该方法的对象,这种行为特性称为多态,即同一个方法的行为随上下文而异。

可重用性

OOP的主要目的之一在于提供可重用复用的代码,从而节省时间并减少错误,专注于整体的开发策略。

C++的OOP实现概述

  • 用户定义类型指的是实现抽象接口的类设计
  • 使用访问控制以实现数据隐藏
  • 通过数据隐藏以及分离公共接口与实现细节(隐藏实现细节)实现封装
  • 派生类通过继承对基类进行扩展和修改
  • 通过在派生类中定义基类的方法和使用虚方法实现多态公有继承
  • 通过继承、包含、类模板等多种机制增强代码的可复用性

类型

对于指定的基本类型,需要提前完成以下三项工作:

  1. 决定数据对象的内存数量
  2. 决定如何解释内存中的位
  3. 决定操作数据对象的方法

内置类型关于操作的类型被内置于编译器中。而C++的用户定义类型需自定义以上信息,这一特征换来可以按需定制新数据类型的强大功能和灵活性。

类(Class)是一种将抽象转换为用户定义类型的C++工具,将数据表示和操纵数据的方法组合成简洁的包。接口(Interface)是供两个系统交互时使用的共享框架。对于类,我们说公共接口,这里公众(public)是使用类的程序。交互系统由类对象组成,而接口由编写类的人提供的方法组成。接口让程序员能够编写与类对象交互的代码,从而让程序能够使用类对象。

开发类

开发类的过程有多个阶段,分为多个步骤。通常,C++程序员将接口(类定义)放在头文件中,并将实现(类方法的代码)放在源文件中。

类定义

一般来说,定义类的类规范由两个部分组成:

  • 类声明:以数据成员的方式描述数据部分,以成员函数(方法)的方式描述公共接口
  • 类方法定义:描述如何实现类成员函数

简单地说,类声明提供了类的蓝图,而方法定义提供了细节。关键字class是类定义的标志。

class classname
{
   
private:
    typename datamember; // data member declarations
    void functypeA() {
    datamember = 0; }; // function definition
public:
    typename functypeB(typename num); // member function prototypes
};

类成员函数的实现

成员函数定义与常规函数定义十分类似,既可有函数头与函数体,也可有返回类型与参数。同时,成员函数定义还有两个不同的特性:

  • 定义成员函数时,使用作用域解析运算符::来标识函数所属的类
  • 类方法可以访问类的private组件

对于第一点,类方法的完整名称中包括类名。我们说Stock::update()是函数的限定名(qualified name)而update()是全名的缩写即非限定名(unqualified name),仅能在作用域解析运算符::所限定的类作用域中使用。

同时,类方法也可以无需指定类作用域即访问类中的私有数据成员及私有成员函数。

void Stock::update(double price)
{
   
    share_val = price;
    set_tot();
}

定义位于类声明中的函数都自动成为内联函数,因此在类声明的private部分实现的Stock::set_tot()即为内联函数。类声明通常将短小的成员函数实现为内联函数。

在类声明之外定义的函数通过inline限定符亦可成为内联函数。实际上,根据改写规则(rewrite rule),在类声明中定义方法等同于用原型替换方法定义后在类声明后面将定义改写为内联函数。也就是说前述两种方法是等价的。

class Stock
{
   
    // data member declarations
    void set_tot() {
    total_val = share_val * shares; }
public:
    // member function prototypes
};

inline void Stock::set_tot()
{
   
    total_val = share_val * shares;
}

类与结构

类描述看似很像是包含成员函数以及访问控制可见性标签的结构声明。实际上,C++对结构进行了扩展,使之具有与类相同的特性。

类与结构的唯一区别在于结构的默认访问类型是public,而类为private。C++程序员通常使用类实现类描述,而把结构限制为只表示纯粹数据的对象,又称普通老式数据(POD,Plain Old Data)结构。

类与对象

如何将类方法应用于对象?
首先需要创建对象。最简单的方式是声明类变量。

    Stock Kate, Joe;

之后需要使用对象的成员函数,类似于使用结构成员,需要成员运算符.

    Kate.show();
    Joe.show();

所创建的每个新对象都有自己的储存空间,用于存储其内部变量和类成员;但同一个类的所有对象共享同一组类方法,即每种方法只有一个副本。调用成员函数时,函数会将实现代码应用于调用对象的数据成员。在OOP中,调用成员函数被称为发送消息,因此将同样的消息发送给两个不同的对象将调用同一个方法,但该方法被用于两个不同的对象。

使用类

C++的目标是使得使用类与使用基本的内置类型(int等)尽可能相同。

  • 创建类对象:声明类变量或使用new与构造函数
  • 调用公共接口
  • 使用类对象作为函数的参数与返回值
  • 对象彼此赋值
  • 初始化对象
  • 让 cin 与 cout 识别对象
  • 在相似的类对象间进行自动类型转换等

封装

C++的类设计通过两种机制实现了封装:

  • 数据隐藏
  • 隔离实现

数据隐藏(访问控制)

关键字privatepublic描述了对类成员的访问控制。使用类对象的程序都可以直接访问公有部分(由public所描述),但只能通过公有成员函数(或友元函数)来访问对象的私有成员(由private所描述)。因此,公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。

无论类成员是数据成员还是成员函数,都可以放在类的公有或私有部分声明。但按照OOP的设计目的之一在于隐藏数据,因而一般将数据项置于私有部分而组成类接口的成员函数放在公有部分。当然,也可以将成员函数放在私有部分,此时不能直接从程序中调用这种函数,但公有方法可以使用他们。通常,程序员使用私有成员函数处理不属于公有接口的实现细节。

关键字private是类对象的默认访问控制,因而不必在类声明中使用。

关键字protected用于基类与派生类之间的访问控制。在类外只能用公有类成员来访问protected部分中的类成员,而派生类的成员可以直接访问基类的保护(protected)成员而不能直接访问基类的私有(private)成员。可以说,对于外部世界而言,保护成员的行为与私有成员类似;但对于派生类来讲,保护成员的行为与公有成员相似。

派生类可以直接访问基类的保护数据成员,简化代码的编写工作,但存在设计缺陷。派生类若能直接访问基类的保护数据成员,那么类隐藏数据的措施实际上形同虚设,保护数据成员成为某种意义上的公有变量。因此最好对数据成员采用私有访问控制,不要使用保护访问控制;同时通过基类方法能使派生类能够访问基类数据。然而,保护访问控制能让派生类访问公众不能使用的内部函数。

数据隐藏不仅可以防止直接访问数据,还让开发者和用户无需了解数据如何表示。解耦公共接口与实现细节,有助于独立地改善实现细节,从而有利于程序的维护。

友元

C++控制对类对象私有部分的访问。通常,共有类方法提供唯一的访问途径。在特定的编程情景下,需要缓解这种严格的限制,C++提供了另一种形式的访问权限:友元。通过让函数成为友元函数,可以赋予该函数与类的成员函数相同的访问权限。友元有三种:

  • 友元函数
  • 友元类
  • 友元成员函数
需要实现友元的场景

为类重载二元运算符时(带两个参数的运算符)常常需要友元。考虑Time对象乘以实数。重载的乘法运算符不同于两个参数均为Time类型的加法与减法运算符,其两个参数分别为Time值和double值,这限制了该运算符的使用方式。关键在于,左侧的操作数必须为Time对象,必须为调用对象。

从概念上讲,乘法运算符的两个参数是平等的,运算结果符合交换律,即B * 2.75与2.75 * B是完全相同的。但是从重载运算符的角度,2.75 * B无法调用重载后的乘法运算符,这种写法是不合法的。因此只能限制程序编写顺序为B * 2.75,这是服务器友好-客户警惕的不合适的解决方法。

另一种解决方案是将重载运算符实现为非成员函数,非成员函数并非由对象调用,其所有参数均为显式参数。问题在于常规的非成员函数无法访问对象的私有数据。这时,友元函数便有出现的必要。

创建友元

创建友元函数的第一步是将其原型放在类声明中,并在原型声明前加上关键字friend

friend Time operator*(double m, const Time & t);
// goes in class Time declaration

该原型意味着:

  • 虽然operator*()函数声明于类声明中,但并非成员函数,故不能通过成员运算符调用
  • 虽然operator*()函数不是成员函数,但它于成员函数访问权限相同

第二步是编写函数定义。因为其不是成员函数,所以不要使用Time::限定符,且不应在定义中使用关键字friend

隐藏实现细节

对外部调用者隐藏实现细节有以下两种基本方式:

  • 分离公共接口与实现细节
  • 私有成员函数

继承

C++通过类继承的方法达到比修改代码更好的扩展与修改类的方式。类继承能从已有的类派生出新的类,而派生类继承了基类的特征(包括方法),从而可以更轻松地实现如下功能:

  • 在已有类的基础上添加功能
  • 为已有类添加数据
  • 修改类方法的行为

实际上,C++有3种继承方式:

  • 公有继承
  • 保护继承
  • 私有继承

继承与派生

我们称继承行为中的原始类为基类,而继承类为派生类。

// RatedPlayer derives from the TableTennisPlayer base class
class RatedPlayer : publi
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值