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必不可访问. 继承类型与访问控制类型取交集(即权限小的那个)
继承类型\访问控制类型 public protected private public继承 public protected 不可用 protected继承 protected protected 不可用 private继承 private 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
原创粉丝点击