那个网站可以找人做兼职,长春火车站什么时候通车,制作ppt的软件叫什么,中山网站制作公司面向对象程序设计基于三个基本概念#xff1a;数据抽象、继承和动态绑定。
继承和动态绑定对编写程序有两方面的影响#xff1a;一是我们可以更容易地定义与其他类相似但不完全相同的新类#xff1b;二是在使用这些彼此相似的类编写程序时#xff0c;我们可以在一定程度上…面向对象程序设计基于三个基本概念数据抽象、继承和动态绑定。
继承和动态绑定对编写程序有两方面的影响一是我们可以更容易地定义与其他类相似但不完全相同的新类二是在使用这些彼此相似的类编写程序时我们可以在一定程度上忽略掉它们的区别。
一、OOP概述
面向对象程序设计object-oriented programming的核心思想是数据抽象、继承和动态绑定。通过使用数据抽象我们可以将类的接口与实现分离使用继承可以定义相似的类型并对其相似关系建模使用动态绑定可以在一定程度上忽略相似类型的区别而以统一的方式使用它们的对象。
继承
基类负责定义在层次关系中所有类共同拥有的成员而每个派生类定义各自特有的成员。
在C语言中基类将类型相关的函数与派生类不做改变直接继承的函数区分对待。对于某些函数基类希望它的派生类各自定义适合自身的版本此时基类就将这些函数声明成虚函数virtual function。
举例定义一个名为Quote的类并将它作为层次关系的基类。Quote的对象表示按原价销售的书籍。Quote派生出另一个名为bulk_quote的类它表示可以打折销售的书籍。 派生类必须通过使用类派生列表class derivation list明确指出它是从哪个哪些基类继承而来的。类派生列表的形式是首先是一个冒号后面紧跟以逗号分隔的基类列表其中每个基类前面可以有访问说明符 在派生类内部成员函数或友元函数使用基类成员时不受继承方式的影响只看该成员在基类中的访问属性。
在派生类外部派生类用户使用基类成员时不同的继承方式决定了基类成员在派生类中的访问属性从而对派生类用户的访问权限产生影响。
public继承所有基类成员在派生类中保持原有的访问级别。
之后我们将继续学习protected继承和private继承。
派生类必须在其内部对所有重新定义的虚函数进行声明。派生类可以在这样的函数之前加上virtual关键字但也并不是非得这么做。C11允许派生类显式地注明它将使用哪个成员函数改写基类的虚函数具体措施是在该函数的形参列表之后增加一个override关键字。
动态绑定 因为在上述过程中函数的运行版本由实参决定即在运行时选择函数的版本所以动态绑定有时又被称为运行时绑定。 二、定义基类和派生类
1. 定义基类 成员函数与继承 任何构造函数之外的非静态函数都可以是虚函数。关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。如果基类把一个函数声明成虚函数则该函数在派生类中隐式地也是虚函数。
成员函数如果没被声明为虚函数则其解析过程发生在编译时而非运行时。
访问控制与继承
派生类可以继承定义在基类中的成员但是派生类的成员函数不一定有权访问从基类继承而来的成员。和其他使用基类的代码一样派生类能访问公有成员而不能访问私有成员。不过有些时候基类中还有这样一种成员基类希望它的派生类有权访问该成员同时禁止其他用户访问。我们用受保护的protected访问运算符说明这样的成员。 2. 定义派生类
派生类必须通过类派生列表明确指出它是从哪个哪些基类继承而来的。
类派生列表的形式是首先是一个冒号后面紧跟以逗号分隔的基类列表其中每个基类前面可以有以下三个访问说明符中的一个public、protected或者private。
派生类必须将其继承而来的成员函数中需要覆盖的那些重新声明。 访问说明符的作用是控制派生类从基类继承而来的成员是否对派生类的用户可见。
派生类中的虚函数
如果派生类没有覆盖其基类中的某个虚函数则该虚函数的行为类似于其他的普通成员派生类回直接继承其在基类中的版本。
派生类对象及派生类向基类的类型转换
C标准并没有明确规定派生类的对象在内存中如何分布。 因为在派生类对象中含有与其基类对应的组成部分所以我们能把派生类的对象当成基类对象来使用而且我们也能将基类的指针或引用绑定到派生类对象中的基类部分上。 这种转换通常称为派生类到基类的类型转换。和其他类型转换一样编译器会隐式地执行派生类到基类的转换。这种隐式特性意味着我们可以把派生类对象或者派生类对象的引用用在需要基类引用的地方同样的我们也可以把派生类对象的指针用在需要基类指针的地方。
派生类构造函数
尽管在派生类对象中含有从基类继承而来的成员但是派生类并不能直接初始化这些成员。和其他创建了基类对象的代码一样派生类也必须使用基类的构造函数来初始化它的基类部分。
派生类对象通过构造函数初始化列表来将实参传递给基类构造函数。 除非我们特别指出否则派生类对象的基类部分会像数据成员一样执行默认初始化。
派生类使用基类的成员
派生类可以访问基类的公有成员和受保护成员。 继承与静态成员
如果基类定义了一个静态成员则在整个继承体系只存在该成员的唯一定义。 派生类的声明
派生类的声明中包含类名但是不包含它的派生列表 一条声明语句的目的是令程序知晓某个名字的存在以及改名字表示一个什么样的实体如一个类、一个函数或一个变量等。
被用作基类的类
如果我们想将某个类用作基类则该类必须已经定义而非仅仅声明 一个类是基类同时它也可以是一个派生类· 在这个继承关系中Base是D1的直接基类direct base同时是D2的间接基类indirect base。
每个类都会继承直接基类的所有成员。
防止继承的发生
C提供了一种防止继承发生的方法即在类名后跟一个关键字final。 3. 类型转换与继承
通常情况下如果我们想把引用或指针绑定到一个对象上则引用或指针的类型应与对象的类型一致或者对象的类型含有一个可接受的const类型转换规则。存在继承关系的类是一个重要的例外我们可以将基类的指针或引用绑定到派生类对象上。例如我们可以用Quote指向一个Bulk_quote对象也可以把一个Bulk_quote对象的地址赋给一个Quote*。
可以将派生类当作基类来使用
可以将基类的指针或引用绑定到派生类对象上有一层极为重要的含义当使用基类的引用或指针时实际上我们并不清楚该引用或指针所绑定对象的真实类型。该对象可能是基类的对象也可能是派生类的对象。 静态类型与动态类型
表达式的静态类型static type在编译时总是已知的它是变量声明时的类型或表达式生成的类型动态类型dynamic type则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知。 基类的指针或引用的静态类型可能与其动态类型不一致。
不存在从基类到派生类的隐式类型转换
在对象之间不存在类型转换
派生类向基类的自动类型转换只对指针或引用类型有效在派生类类型和基类类型之间不存在这样的转换。
当我们初始化或赋值一个类类型的对象时实际上是在调用某个函数。当执行初始化时我们调用构造函数而当执行赋值操作时我们调用赋值运算符。这些成员都包含一个参数该参数的类型是类类型的const版本的引用。 三、虚函数
当我们使用基类的引用或指针调用一个虚成员函数时会执行动态绑定。因为我们直到运行时才能知道到底调用了哪个版本的虚函数所以所有虚函数都必须有定义。通常情况下如果我们不使用某个函数则无须为该函数提供定义。但是我们必须为每一个虚函数都提供定义而不管它是否被用到了这是因为连编译器也无法确定到底会使用哪个虚函数。
对虚函数的调用可能在运行时才被解析
当某个虚函数通过指针或引用调用时编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那一个。 派生类中的虚函数 派生类中虚函数的返回类型也必须与基类函数匹配。该规则有一个例外当类的虚函数返回类型是类本身的指针或引用时上述规则无效。也就是说如果D由B派生得到则基类的虚函数可以返回B*而派生类的对应函数可以返回D*只不过这样的返回类型要求从D到B的类型转换是可访问的。
final和override说明符 虚函数与默认实参 回避虚函数的机制
在某些情况下我们希望对虚函数的调用不要进行动态绑定而是强迫其执行虚函数的某个特定版本。通过作用域运算符可以实现这一目的。 四、抽象基类
纯虚函数
纯虚函数无须定义。我们通过在函数体的位置即在声明语句的分号之前书写 0 就可以将一个虚函数说明为纯虚函数。其中0只能出现在类内部的虚函数声明语句处。
我们也可以为纯虚函数提供定义不过函数体必须定义在类的外部。我们不能在类的内部为一个0的函数提供函数体。
含有纯虚函数的类是抽象基类
含有或者未经覆盖直接继承纯虚函数的类是抽象基类。我们不能直接创建一个抽象基类的对象。
派生类构造函数只初始化它的直接基类 五、访问控制与继承
每个类分别控制自己的成员初始化过程。与之类似每个类还分别控制着其成员对于派生类来说是否可访问accessible。
受保护的成员
一个类使用protected关键字来声明那些它希望与派生类分享但不想被其他公共访问使用的成员。
· 和私有成员类似受保护的成员对于类的用户来说是不可访问的。
· 和公有成员相似受保护的成员对于派生类的成员和友元来说是可访问的。
· 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。 公有、私有和受保护继承
某个类对其继承而来的成员的访问权限受到两个因素影响一是在基类中该成员的访问说明符二是在派生类的派生列表中的访问说明符。
对基类成员的访问权限只与基类中的访问说明符有关。 派生访问说明符的目的是控制派生类用户包括派生类的派生类在内对于基类成员的访问权限 派生访问说明符还可以控制继承自派生类的新类的访问权限 派生类向基类转换的可访问性 友元与继承
就像友元关系不能传递一样友元关系同样也不能继承。 改变个别成员的可访问性
有时我们需要改变派生类继承的某个名字的访问级别通过使用using声明可以达到这一目的。 默认的继承保护级别
默认情况下使用class关键字定义的派生类是私有继承的而使用struct关键字定义的派生类是公有继承的 在使用struct关键字和class关键字定义的类之间唯一的差别就是默认成员访问说明符及默认派生访问说明符除此之外再无其他不同之处。 六、继承中的类作用域
当存在继承关系时派生类的作用域嵌套在其基类的作用域之内。如果一个名字在派生类的作用域内无法正确解析则编译器将继续在外层的基类作用域中寻找该名字的定义。
在编译时进行名字查找
一个对象、引用或指针的静态类型决定了该对象的哪些成员是可见的。即使静态类型与动态类型可能不一致当使用基类的引用或指针时会发生这种情况但是我们能使用哪些成员仍然是由静态类型决定的。
举个例子我们给Disc_quote添加一个新成员该成员返回一个存有最小或最大数量及折扣价格的pair 名字冲突与继承
和其他作用域一样派生类也能重用定义在其直接基类或间接基类中的名字此时定义在内层作用域即派生类的名字将隐藏定义在外层作用域即基类的名字。 通过作用域运算符来使用隐藏的成员 名字查找先于类型查找
如果派生类即内层作用域的成员与基类即外层作用域的某个成员同名则派生类将在其作用域内隐藏该基类成员。即使派生类成员和基类成员的形参列表不一致基类成员也仍然会被隐藏掉 虚函数与作用域 覆盖重载的函数 七、构造函数与拷贝控制
如果一个类基类或派生类没有定义拷贝控制操作则编译器将为它合成一个版本。这个合成的版本可以定义成删除的函数。
1. 虚析构函数
继承关系对基类拷贝控制最直接的影响是基类通常应该定义一个虚析构函数这样我们就嫩滑动态分配继承体系中的对象了。 虚析构函数将阻止合成移动操作
如果一个类定义了析构函数即使它通过default的形式使用了合成的版本编译器也不会为这个类合成移动操作。
2. 合成拷贝控制与继承
派生类中删除的拷贝控制与基类的关系 移动操作与继承
大多数基类都会定义一个虚析构函数。因此在默认情况下基类通常不含有合成的移动操作而且在它的派生类中也没有合成的移动操作。
因为基类缺少移动操作会阻止派生类拥有自己的合成移动操作所以当我们确实需要执行移动操作时应该首先在基类中进行定义。 3. 派生类的拷贝控制成员 定义派生类的拷贝或移动构造函数
当为派生类定义拷贝或移动构造函数时我们通常使用对应的基类构造函数初始化对象的基类部分 派生类赋值运算符
与拷贝和移动构造函数一样派生类的赋值运算符也必须显式地为其基类部分赋值。 无论基类的构造函数或赋值运算符是自定义的版本还是合成的版本派生类的对应操作都能使用它们。
派生类析构函数
在析构函数体执行完成后对象的成员会被隐式销毁。类似的对象的基类部分也是隐式销毁的。和构造函数及赋值运算符不同的是派生类析构函数只负责销毁由派生类自己分配的资源 在构造函数和析构函数中调用虚函数 4. 继承的构造函数
类不能继承默认、拷贝和移动构造函数。如果派生类没有直接定义这些构造函数则编译器将为派生类合成它们。
派生类继承基类构造函数的方式是提供一条注明了直接基类名的using声明语句。 继承的构造函数的特点
和普通成员的using声明不一样一个构造函数的using声明不会改变该构造函数的访问级别。例如不管using声明出现在哪儿基类的私有构造函数在派生类中还是一个私有构造函数受保护的构造函数和公有构造函数也是同样的规则。
而且一个using声明不能指定explicit或constexpr。如果基类的构造函数是explicit或者constexpr则继承的构造函数也拥有相同的属性。 八、容器与继承
当我们使用容器存放继承体系中的对象时通常必须采取间接存储的方式。 在容器中放置智能指针而非对象
当我们希望在容器中存放具有继承关系的对象时我们实际上存放的通常是基类的指针更好的选择是智能指针。这些指针所指的动态类型可能是基类类型也可能是派生类类型。 模拟虚拷贝