CppPrimer笔记 Chapter15 面对对象程序设计
来源:互联网 发布:淘宝新店的能买吗 编辑:程序博客网 时间:2024/05/04 10:35
CppPrimer笔记 Chapter15 面对对象程序设计
标签: Cpp
- CppPrimer笔记 Chapter15 面对对象程序设计
- OOP概述151
- 派生类与基类152
- 类型转换与继承
- 虚函数152153
- 抽象基类154
- 访问控制符与继承155
- 继承中的类作用域156
- 构造函数与拷贝控制157
OOP概述(15.1)
- OOP的核心在于(实现的核心)
- 数据抽象:将类的接口与实现分离
- 继承:定义相似的类型并对相似关系建模
- 动态绑定:一定程度上忽略相似类型的区别,而以同一方式使用他们的对象(其解析过程发生在运行时,而不是像一般函数一样发生在编译时)
- OOP核心思想:多态性 (产生隔离)
派生类与基类(15.2)
- 派生类对象中含有与其基类对应的组成部分(基类的成员对象)
- 初始化时,首先初始化基类,然后按声明顺序依次初始化派生类的成员
- 静态成员整个继承体系都只有一个,若为可访问,则既能通过基类,也能通过派生类使用
- 派生类声明不应包含派生列表
- 若想利用某个类作为基类,则其必须定义了(那么,一个类不能派生它本身)
- 继承具有传递性,而不像
friend
- 在类名后跟关键字
final
,使其不可作为基类被继承
类型转换与继承
- 基类的指针或引用的静态类型可能与动态类型不一致,这也是实现动态绑定的手段
- 由于基类对象可能是派生类对象的一部分,也可能不是,因而不存在基类向派生类的自动类型转换(即使一个基类指针绑定或引用绑定在一个派生类对象上)
- 派生类向基类的自动类型转换只针对指针或引用类型,在派生类与基类之间不存在这样的转换.
**由此,请注意,在例子的double print_total(ostream &os,
中, 由于是
const Quote &item, size_t n);Quote&
,发生了自动转换,才可以访问到派生类的函数.否则,若定义为Quote
此时会调用拷贝构造函数传值,转化为Quote的临时对象再进入函数,那么此时派生类非基类部分被切掉,不会调用派生类的函数** - 利用派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中的基类部分会被拷贝,移动或赋值,其它部分会被忽略掉
虚函数(15.2,15.3)
- 当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定.也只有使用指针或引用调用虚函数才发生
- 基类通常都应该定义一个虚析构函数
- 基类必须将它的两类成员函数区分开:基类希望派生类直接继承而不要改变的函数;基类希望派生类进行覆盖的函数(通常定义为虚函数)
利用指针或者引用调用虚函数时,该调用将被动态绑定,根据引用或指针所绑定的对象类型不同,该调用可能执行基类的版本,也可能执行某个派生类的版本. - 任何除构造函数以外的非静态函数都可以是虚函数.
- 关键字
virtual
只能出现在类内部声明语句之前,而不能用于类外部的函数定义. - 派生类可以不覆盖基类中的某个虚函数,则该虚函数行为雷诗雨其他的普通成员(即直接继承在基类中的版本)
- 必须为所有虚函数提供定义:由于知道运行时才能知道调用了哪个版本
- 一旦某个函数被声明为虚函数,则所在的所有派生类中它都是虚函数.(派生类也可以再次声明以强调)
- 派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它副高的基类函数完全一致.
存在一个例外:当类的虚函数返回类型是类本身的指针或者引用时,上述规则无效 - 派生类定义一个与基类虚函数名字相同但形参列表不同的函数,认为是一个独立的函数.
- 利用放于形参列表后的
override
显示地注明它覆盖了某个虚函数.此时若没有真正覆盖,会报错.顺序如下void f(int) const & override
- 在函数后跟关键字
final
,则函数为不可覆盖.’fianl’位置与override
相同 - 虚函数可定义默认实参:
若派生类实现了,则调用使用自己默认实参,没有定义自己默认实参,则不能使用(不会去找基类的)
若基类有,无论通过怎样方式得到的基类(即使是从派生类得到的引用)调用时,都会使用原基类的默认参数,因而有以下有趣的程序.
struct B{ virtual void f1(int x = 5)const{ cout << "use B::f1() "; cout << x << endl; } };struct D:B{ //从B继承f2()与f3(),覆盖f1(int) void f1(int x=10)const { cout << "use D::f1() "; cout << x*x << endl; }};int main(){ B b; D d; B& b_r = d; b.f1();//输出 use B::f1() 5 d.f1();//输出 use D::f1() 100 b_r.f1();//输出 use D::f1() 25 //这里就很有趣了...调用的是派生类函数,反而用了基类的默认值}
- 利用作用域运算符来强制执行某个虚函数的特定版本
shared_ptr<Bulk_quote> baseP = make_shared<Bulk_quote>("book1",1.2,10,0.1); cout << baseP->Quote::net_price(10) << endl; cout << baseP->net_price(10);
- 如果派生类虚函数要调用它的基类版本,但是没有使用作用域运算符,则在运行时调用将被解析为对自身的调用,从而无限递归
抽象基类(15.4)
- 在函数体的位置书写
=0
将一个虚函数说明为纯虚函数.=0
只能出现在类内部的虚函数声明语句处 - 但我们可以为纯虚函数提供定义,不过函数体必须定义在类外部.(目前不知道有什么用…?)
- 含有纯虚函数的类是抽象基类,不能直接创建一个抽象基类对象(抽象基类有析构函数,是虚的)
- 派生类构造函数只能初始化它的直接基类
访问控制符与继承(15.5)
访问控制
protected
- 对类的用户来说不可访问
- 对派生类成员和友元来说可访问.
- 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员.派生类对一个基类对象中的受保 护成员没有任何访问特权.(在派生类函数中的基类对象仍然不可以访问protected成员)
一个类友元(包含友元函数或者友元类的成员函数或者友元类的所有成员函数)可以访问该类的任何成员(包括成员变量及成员方法)。
除去友元外,
private
成员只有该类自身的成员函数可以访问,protected
成员只有该类及其派生类的成员函数可以访问,public
成员则该类及其派生类的成员函数和对象都可以访问。
- 我认为,在访问权限方面,友元与成员函数是一样的
- 派生类继承方式对访问的影响的影响
总体规则就是,private
必不可访问. 继承类型与访问控制类型取交集(即权限小的那个)
- 派生继承方式对派生类向基类转换的影响(设D继承B)
- 只有D公有继承B,D对象才可以用D向B转换
- 无论D以什么方式继承B,D的成员函数与友元都能使用D向B转换
- 若D公有或受保护继承B,则D的派生类(比如说F)F的成员和友元可以使用D向B的类型转换.若为私有继承,则不行.
即:继承,多次继承后,若基类的公有成员是可以访问的,则派生类向基类的类型转换就是可行的
- 友元不能继承
- 派生类智能为那些它可以访问的名字提供using声明,用来改变访问限定权
继承中的类作用域(15.6)
- 派生类成员将隐藏同名的基类成员,但可以使用作用域运算符来使用基类中被隐藏了的成员
- 由于名字查找先于类型检查,意味着编译器先寻找该函数名的声明,一旦找到,不会匹配形参,会直接使用该函数.
那么当派生类成员与基类某个成员函数同名,即使形参列表不同,基类成员也仍旧会被隐藏掉. - 如果只想覆盖重载的多个虚函数中的一些,那么利用using. using只指定名字而不指定形参列表.否则得覆盖所有的虚函数,不然其它的函数会被隐藏.
可见下面这个例子
class Base{public: virtual void fcn(){ cout << "Base::fcn()" << endl; } virtual void fcn(string){ cout << "Base::fcn(string)" << endl; }};class D1:public Base{public: //using Base::fcn;此时,可以利用指向D1的指针直接使用d1p->fcn(),即访问基类中的原来被隐藏了的函数 //隐藏基类fcn,这个fcn不是虚函数 //D1继承了Base::fcn()的定义 void fcn(int){ cout << "D1::fcn(int)" << endl; }//形参列表与Base中的fcn不一致 virtual void f2(){ cout << "D1::f2()" << endl; }//是一个新的虚函数,在Base中存在};class D2:public D1{public: void fcn(int){ cout << "D2::fcn(int)" << endl; }//是一个非虚函数,隐藏了D1::fcn(int) void fcn(){ cout << "D2::fcn()" << endl; }//覆盖了Base的虚函数 void f2(){ cout << "D2::f2()" << endl; }//覆盖了D1的虚函数f2};void name_search(){ Base bobj; D1 d1obj; D2 d2obj; Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; bp1->fcn();//正常调用Base::fcn() bp2->fcn();//调用Base::fcn() 自己的fcn()只是被隐藏,而不是覆盖,可以找到. bp3->fcn();//调用D2::fcn() 因为Base::fcn()被覆盖了 bp3->Base::fcn();//调用Base::fcn() D1*d1p = &d1obj; D2*d2p = &d2obj; //bp2->f2();//error Base没有f2成员 d1p->f2();//调用D1::f2() d2p->f2();//调用D2::f2() //d1p->fcn(); //error: D1自己没有fcn(viod)而基类中的被隐藏了 Base *p1 = &d2obj; D1 *p2 = &d2obj; D2 *p3 = &d2obj; //p1->fcn(42);//error Base没有fcn(int)成员 p2->fcn(42);//调用D1::fcn(int) p3->fcn(42);//调用D2::fcn(int),因为D1::fcn(int)被隐藏了 p3->D1::fcn(42);//调用D1::fcn(int)}
构造函数与拷贝控制(15.7)
- 基类析构函数应为虚的必要性:
由于当delete对象指针时执行析构函数,但是继承导致指针的静态类型与被删除动态类型不符合.例如delete一个Quote*指针,指针可能实际指向了一个Bulk_quote类型对象,这样的话,基类的虚构函数必须被覆盖 - 基类的虚构函数是一个不遵守 三法则(即定义了析构函数,必须定义拷贝与赋值操作) 的重要例外. 但是若定义了析构函数,即使通过=default形式使用了合成版本,编译器也不会为这个类合成移动操作.
- 派生类中删除拷贝控制与基类的关系
- 如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或析构函数是被删除的函数或者不可访问,则派生类中对应的成员将是被删除的,原因是编译器不能使用基类成员来执行派生类对象基类部分的构造、赋值或销毁操作。
- 如果在基类中有一个不可访问或删除掉的析构函数,则派生类中合成的默认和拷贝构造函数将是被删除的,因为编译器无法销毁派生类对象的基类部分。
- 和过去一样,编译器将不会合成一个删除掉的移动操作。当我们使用
=default
请求一个移动操作时,如果基类中的对应操作是删除的或不可访问的,那么派生类中该函数将是被删除的,原因是派生类对象的基类部分不可移动。同样,如果基类的析构函数是删除的或不可访问的,则派生类的移动构造函数也将是被删除的。
总的来说就是在派生类间接使用基类的函数时,基类函数无法调用,则派生类该函数也不会合成
- 需要移动操作则需要自己定义.一旦自己定义了移动操作,就必须显示地定义拷贝操作.
- 派生类构造函数,移动与拷贝构造函数,在拷贝和移动自有成员同时,也要操做基类部分成员.
但析构函数只负责销毁派生类自己分配的资源.对象成员是被隐式销毁的.
class Base{ };class D : public Base{public: D() = default; D(const D&d) :Base(d),val(d.val){} D& operator=(const D&rhs){ Base::operator=(rhs); val = rhs.val; return *this; } D(D&&d) :Base(std::move(d)),val(std::move(d.val)){} ~D(){ delete val; } int* val;};
- 构造函数时先构造基类.析构时,先销毁派生类部分再销毁基类部分.
- 利用
using
继承构造函数,不会改变该构造函数的访问级别 - 当基类构造函数含有默认实参,这些实参不会被继承,相反,派生类将获得多个集成的构造函数,每一个构造函数分别省略一个含有默认实参的形参.
0 0
- CppPrimer笔记 Chapter15 面对对象程序设计
- 面对对象程序设计1
- JavaScript高级程序设计笔记(四)面对对象
- CppPrimer笔记 Chapter6 函数
- CppPrimer笔记 Chapter7 类
- JAVA程序设计(07.3)-----面对对象设计 时钟
- JS中面对对象的程序设计
- CppPrimer笔记 Chapter8 IO库
- CppPrimer笔记 Chapter9 顺序容器
- CppPrimer笔记 Chapter11 关联容器
- CppPrimer笔记 Chapter12 动态内存
- CppPrimer笔记 Chapter13 拷贝控制
- 《c++primer》第15章面对对象程序设计习题解答
- JAVA程序设计(07.2)-----面对对象设计练习 猜拳
- VC++深入详解·chapter15·笔记
- Java面对对象编程---学习笔记(序目)
- 面试笔记9(面对对象编程)
- Ruby学习笔记(16)_面对对象
- List基础用法汇总
- 2017.03.14_File类和enum枚举类_小程序
- 派
- 选择排序
- 无常之无常
- CppPrimer笔记 Chapter15 面对对象程序设计
- 217. Contains Duplicate
- 时间换算
- windows cmd命令行显示下面的文件
- POJ 2955 Brackets(区间dp)
- 一次插入上万条数据遇到的问题
- lua认识(lua)中的变量
- map和结构体的简单运用Gym
- 7.Javascript语法-跳转语句