| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> Essential C++ Lippman 第4章 基于对象的编程风格 摘录 -> 正文阅读 |
|
[C++知识库]Essential C++ Lippman 第4章 基于对象的编程风格 摘录 |
一般而言,class由两部分组成:一组公开的(public)操作函数和运算符,以及一组私有的(private)实现细节。这些操作函数和运算符称为class的member function(成员函数),并代表这个class的公开接口。 身为class的用户,只能访问其公开接口。 Class的private实现细节可由member function的定义以及与此class相关的任何数据组成。 Class用户通常不会关心此等实现细节。身为一个用户,我们只利用其公开接口来进行编程。这种情形下,只要接口没有更改,即使实现细节重新打造,所有的应用程序代码也不需要变动。 4.1 如何实现一个Class一般来说,我们会从所谓的抽象(abstraction)开始。比如Stack后进先出,push叠放数据,pop取出最后一个数据。查询是否为空,已满,或者查询元素个数,还需提供查看最后一个元素数值。 我们应该让Stack存放哪一类型的元素呢? 通用型的Stack应该可以存放各种类型元素。如果将其定义为class template,便可以达到这个目的。 class的声明以关键字class开始,其后接一个class名称。
此句只是作为Stack class的前置声明(forward declaration),将class名称告诉编译器,并未提供此class的任何信息(像是class支持的操作行为及所包含的data member等)。前置声明使得我们得以进行类指针(class pointer)的定义,或以此class作为数据类型。 接下来,在定义实际的Stack class object或访问Stack的任何一个member之前,必须先定义class本身。class定义的骨干看起来是这样:
Class定义由两部分组成:class的声明,以及紧接在声明之后的主体。主体部分由一对大括号括住,并以分号结尾。主体内的两个关键字public和private,用来标示每个块的“member访问权限”。public?member可以在程序的任何地方被访问,private member只能在member function或是class friend内被访问。以下是Stack class的起始定义:
我的编码习惯是在data member之前加上下划线。 所有的member function都必须在class主体内声明。至于是否要同时进行定义,可自由决定。如果要在class主体内定义,这个member function会自动地被视为inline函数。若在class主体外定义member function,必须使用特殊语法。目的是为了分辨该函数是属于哪一个class。如果希望该函数为inline,应该在最前面指定关键字inline。
上述代码中的Stack::empty()告诉编译器说,empty()是Stack class的一个member。class名称之后的两个冒号(Stack::)即所谓的class scope resolution(类作用域解析)运算符。 然而,带有inline说明符的函数通常放在包含函数声明的文件中。 non-inline member function应该在程序代码文件中定义,该文件通常与class同名,其后接着扩展名。 4.2 什么是构造函数和析构函数这些data member如何被初始化呢? 编译器不会自动为我们处理。如果我们提供一个或多个特别的初始化函数,编译器就会在每次class object被定义出来时,调用适当的函数加以处理。这些特别的初始化函数称为constructor(构造函数)。 constructor的函数名称必须与class名称相同。语法规定,constructor不应指定返回类型,亦不用返回任何值。它可以被重载(overload)。 class object定义出来后,编译器便自动根据获得的参数,挑选出应被调用的constructor。 最简单的constructor是所谓的default constructor。它不需要任何参数(argument)。这意味着两种情况,第一种,它不接受任何参数。第二种,它为每个参数提供了默认值。 Member initialization List(成员初始化列表)
Member initialization list紧接着参数列表最后的冒号后面,是以逗号分隔的列表。其中,欲赋值给member的数值被放在member名称后面的小括号中;这使得他们看起来像是在调用constructor。 Member initialization list主要是将参数传递给member class object的constructor。 和constructor对立的是destructor。所谓destructor乃是用户自定义的一个class member。Destructor主要用来释放在constructor中或对象生命周期中分配的资源。
Destructor的名称也有严格规定:class名称再加上~前缀。它绝对不会有返回值,也没有任何参数。由于其参数列表是空的,所以也绝不可能被重载(overload)。 编译器将会在Matrix对象被定义出来的那一刻,暗暗应用Matrix类的constructor。而在mat对象被清除的那一刻,编译器又会暗暗调用Matrix类的destructor,于是释放_pmat所指的内存。 事实上,C++编程的最难一部分之一,便是了解何时需要定义destructor而何时不需要。 Memberwise initialization(成员逐一初始化)
其中,default memberwise initialization会将mat2的_pmat设为mat的_pmat的值。这会使得两个对象的_pmat都知道heap内的同一数组。当Matrix destructor应用在mat2上,该数组空间被释放。不幸的是,此时mat的_pmat仍指向那个数组,而你知道,对空间已被释放的数组进行操作,是非常严重的错误行为。 这个问题可以通过提供另一个copy constructor达到目的。这个copy constructor,其唯一参数就是一个const reference,指向一个Matrix object。
当我们设计class时,必须先问问自己,在此class进行成员逐一初始化的行为模式是否适当?如果不适当,那么需要另行定义copy constructor和copy assignment operator。 4.3 何为mutable和const
trian只是个const reference参数,因此编译器必须保证trian在sum()之中不会被修改。但是,sum()所调用的任何member function都有可能改变trian的值。因此class的设计者必须在member function身上标注const,以此告诉编译器:这个member function不会更改class object的内容。
const修饰符紧接于函数参数列表之后。凡是在class主体以外定义者,如果它是一个const member function,那么就必须同时在声明和定义中指定const。例如:
但是如果const member function返回一个non-const reference指向member data,实际上是将member data开放出去,允许程序在其他地方加以修改。由于member function可以根据const与否而重载,因此有个方法可以解决这个问题:提供两份定义,一为const版本,以为non-const版本。例如:
non-const class object会调用non-const版,const class object调用const版。 设计class时,鉴定其const member function是一件很重要的事情。如果忘了做,要知道,没有一个const reference class参数可以调用公开接口中的non-const成分(但目前许多编译器对此情况都只给警告)。 mutable data member(可变的数据成员) 以下是sum()的一种形式:
在sum函数中,trian是个const object,而next_reset()和next()都会改变_next值,它们都不是const member function。但是他们却被trian调用,于是造成错误。 如果希望采用sum()的这份实现,则next()和next_reset()势必得改为const。但它们真的改变了_next的值呀! _length和_beg_pos提供了数列的抽象属性。如果我们改变trian的长度或起始位置,形同改变其性质。然而,_next只是让我们得以实现iterator机制,它本身不属于数列抽象概念上的一环。改变_next的值,从意义上说,不能视为改变class object的状态,或者说不算破坏了对象的常量性(constness)。关键字mutable可以让我们做出这样的声明。只要将_next标示为mutable,我们就可以宣称:对_next做出的该改变并不会破坏class object的常量性。
现在,next()和next_reset()即可以修改_next的值,又可以被声明为const member function。这样一来,上述的sum()实现就没得问题了。 4.4 什么是this指针我们设计一个copy函数时,一个可能的实现是:
上述的赋值操作_length指向tr1内相应的成员。不过我们还需要一个可以指向tr1整个对象的方法。所谓的this的指针扮演了这样角色。 this指针在member function内用来指向其调用者(一个对象)。 在编译器工作过程内,this指针被自动加到每一个member function的参数列表内;
在member function内,this指针可以让我们访问其调用者的一切。 4.5 静态类成员static data member用来表示唯一的,可共享的member。它在同一类的所有对象中被访问。
对class而言,static data member 只有唯一的一份实体,因此我们必须在程序代码中提供其清楚的定义。这样看起来很像全局对象(global object)的定义,其名称必须附上class scope运算符。
如果要在class member function内访问static data member,其方式有如访问一般(non-static)数据成员。 像const static int data member,可以在声明时为其明确指定初值。 static member function(静态成员函数) 若member function并未访问任何non-static data member。它的工作与任何对象都没有任何关联,那么可以将其声明为static member function。声明方式实在声明之前加上关键字static。
当我们在class外部进行member function的定义时,无需重复加上关键字static。(这个规则也适用于static data member)。 4.6 打造一个Iterator Class
为上述代码得以工作,我们必须为iterator class定义!=、*、++等运算符。我们可以像定义member function那样来定义运算符。运算符函数看起来很像普通函数,唯一差别是它不用指定名称,只需在运算符前加上关键字operator即可。例如:
Triangular_iterator维护一个用来索引Triangular中存储数列元素的那个static data member,也就是_elems。所以必须赋予Triangular_iterator的member function的特殊访问权限。 任何运算符如果与另一个运算符性质相反,我们通常会以后者实现出前者,取反即可。 运算符重载的规则: ????????不可以引入新的运算符。出来./.*/::/?:四个运算符,其余运算符皆可重载。 ? ? ? ? 运算符的操作数不可改变。 ? ? ? ? 运算符的优先级不可改变; ? ? ? ? 运算符函数的参数列表内,必须至少有一个参数为class类型。 运算符的定义方式,可以像member function一样,也可以像non-member function一样。 non-member运算符的参数列表中,一定会比相应的member运算符多出一个参数,也就是this指针。 接下来我们需要做的,便是为Triangular提供一组begin()/end() member function,并支持前述iterator定义。这需要用到nested type。
嵌套类型(nested type) typedef可以为某个类型设定另一个不同的名称。 其通用形式;
4.7 合作关系必须建立在友谊的基础上任何class都可以将其他function或class指定为friend。而所谓的friend,具备了与class member function相同的访问权限,可以访问class中的private member。 只需要在某个函数原型(prototype)前加上关键字friend,就可以将它声明为某个class的friend。
为了让定义成功通过编译,我们必须在上述两行前,先提供Triangular_iterator的定义让Triangular知道。否则编译器就没有足够的消息可以确定上述两个函数原型是否正确,也无法确定它们是否的确是Triangular_iterator的member function。 也可以令class A与class B建立友元关系,借此让class A的所有member function都成为class B的friend。例如:
如果是class之间建立友元,就不需要再友谊声明之前先显现class的定义了。 友谊的考虑,通常是为了效率。如果只是为了进行某个data member的读取和写入,那么,为它提供具有public范文权限的inline函数,就是建立友谊之外的一个替代权限。 4.8 实现一个copy assignment operator对于default memberwise copy不适用的class,我们需要显示定义一个copy constructor和一个copy assignment operator。 4.9 实现一个function object所谓的function object乃是一个“提供有function call运算符”的class。 例如,当编译器遇到函数调用时:
lt可能是函数名称,可能是函数指针,也可能是提供了function call运算符的function object。如果lt是个class object,则编译器会在内部将此语句转换为lt.operator(ival)。 function call运算符可接受任意个数的参数:零个,一个或更多。举个例子,它可以被用来支持Matrix下多维度下标(subscripting index)的操作,因为语言所提供的subscript运算符仅能接受一个参数。 下面是一个例子:
通常我们会把function object当作参数传递给泛型算法。 4.10 重载iostream运算符
其中ostream对象并未声明为const,因为每个output操作都会更改ostream对象的内部状态。为什么不把output运算符设计成member function?因为member function的左操作数必须是隶属于同一个class的对象。如果设计成member function,那么class object就必须放在output运算符的左侧了。这种形式对于class用户必定感到十分疑惑。 4.11 指针,指向Class Member Functionpointer to member function在声明时,必须指明其所属类。
上式便是将pm声明为一个指针,指向num_sequence的member function,后者的返回类型必须是void,且只能接受单一参数。等于0,只是说明该指针目前不指向任何函数。 给该指针赋值;
由函数指针引出的.*运算符:pointer to member selection运算符,只针对class object工作。我们必须为它加上小括号才能正确工作。
|
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 9:04:50- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |