[C++ Primer] 面向对象编程

来源:互联网 发布:淘宝查看新品标签 编辑:程序博客网 时间:2024/05/01 00:31

 

1.要注意区分类和类对象,对象只能访问public成员,而类和友元可以访问private成员。而对于基类的protected成员,派生类可以访问其基类的protected成员(友元中也可以访问protected成员)不能通过派生类对象类外访问protected成员(这点与private类似)(值得注意的是:在派生类中定义一个函数接受派生类对象的引用(指针)或者直接定义一个派生类对象<前向声明只限制定义类的成员时只能定义为指针>,此时可以通过此引用(指针)或对象来访问其基类的protected成员和派生类的private成员,原因在于此函数在类中)。---》所以可以重新定义结论:pubic成员在类外和类中皆可访问,protected成员只能在派生类类中访问,private只能在类中访问(友元可以访问类的private和protected成员,当然也是在友元中--比如某个友元类的成员函数中)。

2.尽管不是必须这样做,派生类一般会重定义所继承的虚函数,如果没有重定义则使用基类中定义的版本。派生类中虚函数的声明必须与基类中的完全一致,但有一个例外,基类中返回对基类型的引用(指针)的虚函数。派生类中的虚函数可以返回对基类或派生类的引用(指针)。一旦函数在基类中声明为虚函数,则一直为虚函数,派生类无法改变该函数为虚函数这一事实,派生类重定义虚函数时,virtual可用可不用(只能在类定义体中使用)。

3.多态的实质是动态绑定,要触发动态绑定必须满足两个条件:一,只有虚函数的调用才能进行动态绑定(如果调用非虚函数,无论对象是什么类型,都执行基类类型定义的函数,前提是通过基类的引用或指针来调用)。二,必须通过对基类类型的引用或指针进行函数调用(对象是非多态的,调用的函数,无论是虚函数或非虚函数,是由对象的类型定义。只有通过引用或指针调用,虚函数才在运行时确定)。

4.公用,私有和受保护的继承:

公用继承(public),基类成员保持自己的访问级别,基类的public成员为派生类的public成员,基类的protected成员为派生类的protected成员。

受保护继承(protected),基类的public和protected成员在派生类中为protected成员。(注意基类中的public成员虽然在派生类中为protected但是也可以访问,猜测此时视派生类为其派生类)

私有继承(private),基类所有的成员在派生类中为private成员。

注意:派生类无法访问基类的private成员,无论是什么继承都不能访问。使用class保留字定义的派生类默认private继承(没有声明是什么继承时),而struct定义的派生类为public继承,class Base{...};

struct D1:Base{};public继承

class D2:Base{};private继承

5.派生类恢复继承成员的访问级别,即在基类中的访问级别,使用using来实现:

class Base{

public:

    std::size_t size() const {return n;}

protected:

    std::size_t n;

}

class Derived : private Base{

public:

    using Base::size;

protected:

    using Base::n;

};

这样定义之后,size成员在Derived访问级别从原来的private恢复为public,n在Derived中访问级别从private恢复为protected。

6.友元关系不能继承,基类的友元对派生类的成员没有特殊访问权限。如果基类定义了static成员,整个继承层次中只有这样一个成员,无论基类派生多少个派生类,每个static成员只有一个实例,并且static遵循访问控制,如果在基类中为private则派生类不能访问。

7.因为派生类包含一个基类部分,意味着可以像使用基类对象一样在派生类对象上执行操作,即派生类对象也是基类对象,存在从派生类引用(指针)到基类引用(指针)的自动转换。而反过来,一个基类对象可能只是派生类对象的一部分,结果,没有从基类引用(指针)到派生类引用(指针)的(自动)转换。相对于指针或引用,对象转换的情况更为复杂,虽然一般可以使用派生类型的对象对基类类型的对象进行初始化或赋值,但是没有从派生类类型对象到基类类型对象的直接转换(即对象不能转换,只能赋值或初始化)。-----比如将派生类引用传递给一个接受基类类型引用的函数,引用直接绑定到该对象,对象本身并未复制,仍然是派生类对象。而如果函数接受的是基类类型对象,那么该派生类对象的基类部分被复制到形参,即先转换为基类对象,然后复制初始化。

注意 :只有在public继承时,派生类(包括对象,指针,引用)才能转换为基类(对象,指针,引用)--PS:书上说protected继承时,后续派生类可以转换为基类,但是这点自己测试时还没有通过??

           当派生类转换为基类后,则按照基类的视角来访问其成员(不能再访问派生类的成员了,在基类中可以访问在派生类中无法访问的基类的private成员)。

8.从基类到派生类的自动转换是不存在的,原因在于基类只能是基类,不能包含派生类的成员。编译器甚至限制当基类指针或引用绑定到派生类对象后,从基类到派生类的转换也是被限制的,理论上是安全的,但是编译器只是根据类型来判断有误。

Bulk_item bulk;

Item_base * itemP = &bulk;

Bulk_item  *bulkP  = itemp;//error,

不能将派生类转换为基类后,再将基类转换为派生类,即使我们知道这种转换是安全的。但是在转换安全的前提下,我们也可以使用static_cast强制编译器进行转换,或者可以使用dynamic_cast申请在运行时检查。

9.构造函数和复制控制成员不能继承(自己觉得书上这样说是有问题,个人认为不是不能继承,只是基类的构造函数和复制成员操作的对象是基类而不是派生类,派生类在实例化时首先调用基类的构造函数初始化基类部分,如果初始化列表为空则调用基类默认构造函数,当然也可以通过初始化列表传参给基类的构造函数),每个类必须定义自己的构造函数和复制成员,不定义的话就使用合成版本。

10.如果类不包含指针这样的成员,那么复制控制可以使用合成的版本。但是如果包含指针则应定义自己的复制控制。复制构造函数合成版本中:派生类对象复制时,先调用合成的基类复制构造函数,再复制派生部分。赋值操作符和析构函数类似处理。但是如果派生类定义了自己的复制构造函数时,该复制构造函数一般应显式的使用基类复制构造函数初始化基类部分,与构造函数类似,如果复制构造函数没有向基类传递参数(此时的参数应为派生类对象)则调用的是基类的默认构造函数。

赋值操作符通常与复制构造函数类似,如果派生类定义了自己的复制操作符,则必须对基类部分进行显式赋值。如下:

//Base::operator==(const Base &)

Derived &Derived ::operator=(constDerived  & rhs)

{

    if(this != &ths) { //防止自身赋值

        Base::operator=(rhs); //显式调用Base类赋值操作符给基类部分赋值(此操作符可以由基类自己定义,也可是合成版本,但是需要在派生类中显示调用)

    }

    return *this;

}

析构函数与复制构造函数,赋值操作符不同。派生类析构函数不负责撤销基类部分的成员,编译器总是显示的调用基类的析构函数,每个析构函数只负责清除自己的成员。对象的撤销顺序与构造顺序相反,先运行派生类析构函数,在按照继承层次依次向上调用基类析构函数(编译器显示调用)。

注意:删除基类指针则运行基类析构函数并清除基类成员,但是如果对象实际是派生类对象,则没有定义该行为,所以为了保证运行适当的析构函数,基类中的析构函数必须为虚函数,这样一来,通过指针调用时,运行哪个析构函数将由指针所指的对象类型决定。即使析构函数没有什么工作要做,继承层此的根类应定义析构函数为虚函数,由于虚函数的性质将被继承,所以派生类及后续派生类的析构函数也将是虚函数。

11.构造函数和析构函数中的虚函数:派生类对象实例化时首先运行基类构造函数初始化对象基类部分,在执行基类构造函数时,对象派生类部分是没有初始化的,此时对象还是不是一个派生类对象。而撤销派生类对象时,先运行派生类的析构函数,然后按照继承层次逆序调用基类的析构函数。在这两种情况下,对象都是不完整的,所以编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类对象对待。如果不这样的话,在基类构造函数或析构函数中调用虚函数时,则调用的是派生类版本从而可能访问派生类对象成员,而此时对象的派生类部分成员还没有初始化,这样的访问可能会造成程序崩溃。

12.继承情况下的类作用域:

在继承情况下,派生类的作用域嵌套在基类的作用域中。派生类作用域相当于局部作用域而基类作用域相当于全局作用域,通过派生类类型的指针或引用访问成员时,首先在派生类中查找,找不到的情况才在基类中查找(对于函数一旦找到了名字,编译器就不再继续查找了(当然如果有多个函数,还是会进行参数匹配),此时如果实参与形参不匹配,调用就会出错)。而通过基类类型的指针或引用访问时,将在基类中查找而忽略派生类。与基类同名的派生类成员将屏蔽对基类成员的直接访问(对于函数而言,只要函数同名即使函数原型不同,基类成员也会被屏蔽--类似局部作用域中函数也会屏蔽全局作用域中同名函数,即使函数原型不同),当然也可以使用作用域操作符访问被屏蔽的基类成员。例如: Base::mem。

类的多态性是属于C++屏蔽机制中的一种特例,通过基类类型的引用或指针调用函数,理论上是只在基类中查找而忽略派生类,但是由于函数是虚函数再加上通过基类类型的引用或指针调用从而触发了动态绑定,所以才有可能调用到派生类中的同名函数(注意此时虚函数在基类和派生类中必须是同一原型(返回派生类型的对象等例外),这样才符合多多态的定义,即同样的代码调用不同的函数)。

13.继承与重载:

像其他函数一样,成员函数(无论是虚还是非虚)也可以重载,由于屏蔽机制,如果派生类通过自身类型使用所有的重载版本,那么派生类必须要么重定义所有的重载版本,要么一个也不定义。但是有时候,类需要仅仅重定义一个重载版本,并且想能使用所继承的其他版本,显然重定义所有基类版本不是一个好选择,此时可以引入using声明。using声明可以恢复基类成员的访问级别(但是不能比原来的严格或宽松,即原来如果在public成员,则只能在派生类的public标号下使用using声明),从将其作用域加入派生类中。同时由于using只能指定一个名字,不能指定形参表,因此声明的函数所有版本都会加到派生类的作用域中,这样的话派生类只需要重定义本类型需要的版本函数名,其他版本可以使用从基类所继承的。

14.含有(或继承)一个或多个纯虚函数的类是抽象基类,不能创建抽象基类的对象。从抽象基类派生出的类必须实现全部的纯虚函数,这样就可以创建相应的对象了。否则还是抽象基类,不能创建对象。

15.覆盖虚函数机制:想调用基类的虚函数版本,可以使用作用域操作符,Base::fun();


原创粉丝点击