C++ Primer 学习笔记_69_面向对象编程 --继承情况下的类作用域(二)

2014-11-24 12:52:57 · 作者: · 浏览: 4
编译器上死活编译不过, *因为在Derivd中的string bar处编译器提示说:与前面的声明冲突了! *的确,在Derivd中,bar既有数据成员又有成员函数!!! */

三、作用域与成员函数

在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽:

struct Base
{
    int memfuc();
};

struct Derived : Base
{
    int memfuc(int);
};

int main()
{
    Derived d;
    Base b;

    b.memfuc(); 		//调用Base::memfuc()
    d.memfuc(10);   	//调用Derived::memfuc()
    d.memfuc();			//Error
    d.Base::memfuc();//调用Base::memfuc()
}

Derived中的memfuc声明隐藏了Base中的声明。在确定下面一条语句时:

    d.memfuc();	

编译器查找名字memfuc,并在Derived类中找到。一旦找到了名字,编译器要就不再继续查找了。

【小心地雷】

局部作用域中声明的函数不会重载全局作用域中定义的函数,同样,派生类中定义的函数也不会重载基类中定义的成员。通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类中根本没有定义该函数时,才考虑基类函数。如:

struct Base
{
    int memfuc();
};

struct Derived : Base
{
    int memfuc(int);
};

Derived d;
d.memfuc();	//Error

如果将Derived中的intmemfuc(int)注释掉,则:

d.memfuc();	//OK

重载函数

像其他任意函数一样,成员函数(无论虚还是非虚)也可以重载。派生类可以重定义所继承的0个或多个版本。

[注意] 如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员

struct Derived : Base
{
    int memfuc();
    int memfuc(int);
    double memfuc(double);
};

int main()
{
    Derived d;
    d.memfuc();     //Derived::memfuc()
    d.memfuc(10);   //Derived::memfuc(int)
}

如果派生类想通过自身类型使用所有的重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重定义。

有时类需要仅仅重定义一个重载版本,并且想要继承其他版本的含义,在这种情况下,派生类不用重定义所继承的每一个基类版本,它可以为重载成员提供using声明。一个using声明只能指定一个名字,不能指定形参表,因此:using声明会将该函数的所有重载实例加到派生类的作用域。将所有名字加入作用域之后,派生类只需要重定义本类型确实必须定义的那些函数,对其他版本可以使用继承的定义。

struct Base
{
    int memfuc();
    int memfuc(int);
    int memfuc(double);
};

struct Derived : Base
{
    using Base::memfuc;
    int memfuc();   //重定义
};

int main()
{
    Derived d;
    d.memfuc();     //Derived::memfuc()
    d.memfuc(10);   //Base::memfuc(int)
}

四、虚函数与作用域

虚函数:如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数:

class Base
{
public:
    virtual int fcn();
};

class D1 : public Base
{
public:
    //该fcn屏蔽了Base类中的虚函数fun
    int fcn(int);
    /**此时有两个名为 fcn 的函数:
    *类从 Base 继承的一个名为 fcn 的虚函数
    *类定义的名为 fcn 的非虚成员函数,该函数接受一个 int 形参
    */
};

class D2 : public D1
{
public:
    /**重定义了它继承的两个函数:
    *1.重定义了 Base 中定义的 fcn 的原始版本
    *2.重定义了 D1 中定义的非虚版本。
    */
    int fcn();
    int fcn(int);
};


通过基类调用被屏蔽的虚函数

通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类:

    Base bobj;
    D1 d1obj;
    D2 d2obj;

    Base *bp1 = &bobj,*bp2 = &d1obj,*bp3 = &d2obj;

    bp1 -> fcn();	//调用Base::fcn()
    bp2 -> fcn();	//调用Base::fcn()
    bp3 -> fcn();	//调用D2::fcn()

【关键概念:名字查找与继承】

理解 C++中继承层次的关键在于理解如何确定函数调用。确定函数调用遵循以下四个步骤:

1)首先确定进行函数调用的对象、引用或指针的静态类型

2)在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。

3)一旦找到了该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用是否合法。

4)假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数


//P502 习题15.24
    Bulk_item bulk;
    Item_base item(bulk);
    Item_base *p = &bulk;

    /**由于net_price为虚函数
    *对虚函数而言,只能通过指针或引用进行动态绑定
    *而通过对象调用虚函数,所调用到的总是该对象所属类型中定义的函数
    */
    p -> net_price(10);	//调用Bulk_item版本的net_price
    item.net_price(10);	//调用Item_base版本的net_price