代码改变世界

C++Primer 第十五章

2016-06-28 20:44  szn好色仙人  阅读(459)  评论(0编辑  收藏  举报
//1.面向对象程序设计的核心思想是数据抽象,继承,动态绑定。
//  通过使用数据抽象,我们可以将类的接口和实现分离
//  使用继承,可以定义相似的类型并对其相似关系建模
//  使用动态绑定,可以在一定程度上忽视相似类型的区别,而以统一的方式使用它们的对象。

//2.对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明为虚函数。
//  使用基类的引用或指针调用一个虚函数的时候,将发生动态绑定:根据指针或引用指向的对象类型来调用基类或者派生类的虚函数。
//  基类通常定义一个虚析构函数,以便于对象的正常析构
//  任何构造函数之外的非静态函数都可以是虚函数。在基类中的虚函数,在派生类中隐式的也是虚函数。
//  成员函数如果没有被声明为虚函数,则其解析过程发生在编译时而非运行时。
//  每个类控制自己成员的初始化过程,派生类也最好使用基类的构造函数来初始化它的基类部分

//3.可以将基类的指针或引用绑定到派生类对象上有一层极为重要的含义:当使用基类类型的指针或引用的时候,实际上不清楚其所绑定对象的真实类型。
//  派生类向基类的转换只对指针和引用类型有效,在派生类类型和基类类型之间不存在这样的转换。

//4.智能指针类也支持派生类向基类的转换,这意味着可以将派生类的对象的指针存储在一个基类的智能指针内。

//5.一个派生类如果要覆盖而非隐藏某个继承的虚函数,则它的形参类型必须和被覆盖的虚函数完全一致。
//  同样的,它们的返回类型也必须一致。对于这个规则有一个例外:当此函数返回其类类型的指针或引用时,并且子类向父类类型转换时可访问的时候,它们可以返回各自的类类型。

//6.虚函数也可以有默认实参,但是如果某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定。所以虚函数最好不用默认实参,或者在子类和基类中使用相同的默认实参。
//  通过作用域运算符,可以实现对虚函数的调用不进行动态绑定。

//7.抽象基类:
//  A:和普通的虚函数不同,一个纯虚函数可以没有定义,通过在函数体的位置书写=0就可以将一个虚函数声明为纯虚函数,=0只能出现在类内部虚函数声明处。
//  B:也可以为纯虚函数提供定义,但是必须是在类的外部。
//  C:不能定义抽象基类的对象,当抽象基类的派生类没有覆盖纯虚函数时,此派生类也不能定义对象。

//8.派生类的构造函数只初始化它的直接基类。各个类控制其对象的初始化过程。

//9.派生类向基类转换的可访问性:
//  A:只有当D公有的继承B时,用户代码才能使用派生类向基类的转换。
//  B:不论D以任何方式继承B,D的成员函数和友员都能使用派生类向基类的转换。
//  C:如果D继承B的方式是公有的或受保护的,则D类的派生类的成员和友员可以使用D向B的类型转换。
//  总结:对于代码中的某个节点来说:如果基类的公有成员是可访问的,则派生类向基类的转换也是可使用的,反之则不行。

//10.就像友员关系不能传递一样,友员关系同样不能继承。基类的友员在访问派生类成员时不具有特殊性,类似的,派生类的友员也不能随意访问基类的成员。

//11.派生类可以为那些可访问的名字提供using声明,以此方法来改变被声明的名字的访问权限(具体权限取决于using声明的位置)。
class CA                {protected: int value;};
class CB : public CA    {public: using CA::value;};    //则value在CB类中的访问权限是public的

//12.struct和class的唯一区别在于:默认成员访问说明符及默认派生访问说明符的区别,前者均为公有,后者均为私有。

//13.每个类定义了自己的作用域。当存在继承关系时,派生类的作用域嵌套在其基类的作用域中。当一个名字在派生类的作用域中无法被解析,则编译器将在其外层的基类作用域中进行查找。
//   正是由于上述规定,所以在派生类中才能像使用自己的成员一样使用基类的成员。
//   一个对象、指针、引用的静态类型决定了该对象的哪些成员是可见的。即使静态类型和动态类型可能不一致,但是我们能使用哪些成员仍旧是由其静态类型决定的。

//14.和其它作用域一样,派生类也能重用定义在其直接或间接基类中的名字,此时将发生隐藏,派生类的成员将隐藏同名的基类成员,可以通过作用域运算符来访问被隐藏的成员。
//   由于上述隐藏的规则,派生类可以覆盖其基类的重载虚函数的0个或者全部版本,若非如此将发生隐藏。这样就会显得和繁琐。
//   对于上述问题,一个很好的解决方法是:为重载的成员提供一条using声明语句,这样我们就可以无需覆盖基类中的每一个重载版本。此时派生类只需要定义其特殊的函数语句即可。
class CA                
{
public:
    virtual void fun(){printf("A_0");}
    virtual void fun(int){printf("A_1");}
    virtual void fun(char*){printf("A_2");}
};
class CB : public CA    
{
public: 
    using CA::fun;                    //使基类中的所有fun函数被添加到派生类的作用域中。
    void fun(int i){printf("B_1");}    //重新定义派生类中特定的虚函数。
};    

//15.继承关系对基类拷贝控制最直接的影响就是基类通常应该定义一个虚析构函数来正确的释放对象。
//   基类或派生类的合成拷贝控制成员对类本身的成员依次进行初始化、赋值、销毁的操作,此外这些合成的成员还负责使用直接基类中对应的操作对一个对象的直接基类部分进行相应操作。//   在实际编程过程中,如果基类中没有默认、拷贝、移动构造函数,则派生类一般也不要定义相应的操作,其原因是派生类的基类部分将无法被正确操作。

//16.当我们用一个派生类对象为其基类对象初始化或赋值的时候,只有该派生类对象的基类部分会被拷贝、移动、赋值,它的派生类部分将被忽略掉。

//17.
        A(基类) B(派生类)
公有继承 公有成员 公有成员
        保护成员 保护成员
        私有成员 不可见


保护继承 公有成员 保护成员
        保护成员 保护成员
        私有成员 不可见


私有继承 公有成员 私有成员
        保护成员 私有成员
        私有成员 不可见


一般来说,公有继承时是Is A的关系,私有继承时是Has A的关系