c++(七)、继承与多态

一、继承与构造

1、继承(inheritance)

所谓继承是在一个基类或父类基础上形成的子类或派生类。

如上图中在bike中延伸出Tandem Bike、racing bike和mountain bike这三种子类或派生类,即子继承父;而泛华则是父泛华子。

当要避免一个类被继承时,c++11引入final特殊标识符,可以使得类不能被继承。

class B{};//B可以被继承;
class C : public B {};//C继承类B;
class D final {};//类D不能被继承。

2、c++11中继承中的构造函数

并不是类中所有的成员函数都可以被继承,c++11中规定析构函数和友元函数是不能够被继承的。

当要继承基类中的构造函数时,可以使用using A::A,此时继承类A中所有的ctor,此时不能仅继承指定的某个基类ctor。

//调用继承的构造函数
class A{
public:
    A(int i){};
    A(double d, int i){};
};

class B : A{
    using A::A;//继承基类A中的所有构造函数;
    int d{0};
};

int main(){
    B b(1);//调用A的ctor,A(int i);
}

当派生类成员也需要初始化,则可以在派生类ctor中调用基类构造函数;

class A{
public:
    A(int i){}
    A(double d, int i){}
};

class B : A{
    using A::A;//此时不继承A(int i);
    int d{0};
    B(int i) : A{i}, d{i}{}
};

int main(){
    B b(1);//调用B(int i);
    return 1;
}

3、继承中的默认构造函数

若基类中的ctor未被显式调用,则基类的默认构造函数就会被调用。

//基类A
class A{
public:
    A() = defalt;//默认构造函数;
    A(int i){}
};

class B :A{
public:
    B() {}//等价于B() : A{} {}
    B(int i){}//等价于B(int i) : A{} {}
};

因此要考虑给基类提供默认构造函数。

4、构造链和析构链

构造链是构造函数链,构造类实例会沿着继承链调用所有的基类ctor,调用次序:base first,derive next(父先子后);

析构链是析构函数链,与构造链的顺序刚好相反,是子先父后。

//基类
class A{
public:
    A(){
        std::cout << "A()" << std::endl;
    }

    ~A(){std::cout << "~A()" << std::endl;}

};

//派生类
class B : A{
public:
    B(){std::cout << "B()" << std::endl;}

    ~B(){std::cout << "~B()" << std::endl;}

};


//测试用例
int main(){
    {
        B b;//创建对象b
    }
    return 1;
}

output:
A()
B()
~B()
~A()

二、名字隐藏与重定义

1、继承中的名字隐藏

class P{
public:
    void f(){}
};

class C : public P{
public:
    void f(int x){}
};

int main(){
    C c;
    c.f();
    return 1;
}

上述代码在编译时会提示找不到f()函数,这是因为派生类视作内部作用域,基类视作外部作用域,而内部作用域的名字会隐藏外部作用域的同名名字。解决办法如下所示:

class P{
public:
    void f(){}
};

class C : public P{
public:
    using P ::f;//用using声明基类中的成员函数;
    void f(int x){}
};

int main(){
    C c;
    c.f();
    return 1;
}

2、重定义函数(Redefining functions)

//基类
class A
{
public:
    string toString()
    {
        return "A";
    }
};

//继承类
class B : public A
{
public:
    string toString()
    {
        return "B";
    }

    void g()
    {
        std::cout << toString() << std::endl;//仅能调用类B中的toString函数;
    }
};


int main()
{
    B b;
    std::cout << b.toString() << std::endl;//调用类B中的toString()函数;
    b.A::toString();//调用A类中的toString()函数;
    return 0;
}

在上述例子中,派生类B中声明了一个与基类A的toString()同名函数,在派生类中声明了一个与基类成员同名的新成员即为重定义。

如果仅在类A中声明toString()函数,类B中没有toString()函数时,当类B继承类A后,B对象可以访问基类A中的toString()函数,但此时的toString()函数只能输出基类的对象信息,无法输出继承类B的对象信息,但当在继承类B中也重定义toString()函数后,B对象b调用的toString()函数就可以输出类B的信息。

重定义和重载有区别,重载函数特点:a、函数名相同;b、至少一个特征不同(参数类型、参数数量或参数顺序不同)。

低于重定义函数,函数特征相同(同名、同参数和相同的返回类型),唯一区别在于定义的位置不同(在基类和继承类中分别定义)。

三、覆写与运行时多态

1、多态(polymorphism)的概念

广义的多态指不同类型的实体/对象对于同一消息有不同的响应。

目前多态性有两种表现的方式:重载多态和子类型多态。

//重载多态
class A
{
public:
    int f(int x);
    int f();
};

//子类型多态
class B
{
    virtual int f()
    {
        return 1;
    }
};

class C : public B
{
    virtual int f()
    {
        return 0;
    }
};



A a;
B b;
A* P = &b;
a.f();//调用A::f();
b.f();//调用B::f();
p->f();//调用B::f();

对于重载和重定义函数在程序运行时的不同在于联编不同,重载函数属于静态联编,是在程序编译时确定调用哪个函数;子类型多态属于动态联编,即在程序运行时才能确定调用哪个函数。动态联编实现的多态也成为运行时多态。

2、实现运行时多态

    实现运行时多态有两个要素:a、虚函数(virtual function);b、覆写(override)(覆写即为在派生类中重定义一个虚函数)。

为何要使用运行时多态?

//利用重载函数(静态联编),不利用运行时多态;
class A
{
public:
    string toString()
    {
        return "A";
    }
};

class B : public A
{
public:
    string toString()
    {
        return "B";
    }
};

class C : public A
{
public:
    string toString()
    {
        return "C";
    }
};

void print(A* p)
{
    std::cout << p->toString() << std::endl;
}

//当如果要打印类B和类C时,需重载print函数
void print(B* p)
{
    std::cout << p->toString() << std::endl;
}

void print(C* p)
{
    std::cout << p->toString() << std::endl;
}


int main()
{
    A a{};
    B b{};
    C c{};
    print(&a);//调用A::toString();
    print(&b);//调用B::toString();
    print(&c);//调用C::toString();
    return 0;
}

当使用运行时多态时,上述例子如下所示:

//利用运行时多态;
class A
{
public:
    virtual string toString()//将基类中同名函数声明为virtual
    {
        return "A";
    }
};

class B : public A
{
public:
    string toString()//此处virtual可以省略;
    {
        return "B";
    }
};

class C : public A
{
public:
    string toString()//此处virtual可以省略;
    {
        return "C";
    }
};

void print(A* p)
{
    std::cout << p->toString() << std::endl;
}

//当如果要打印类B和类C时,不需重载print函数
int main()
{
    A a{};
    B b{};
    C c{};
    print(&a);//调用A::toString();
    print(&b);//调用B::toString();
    print(&c);//调用C::toString();
    return 0;
}

上述应用静态联编和动态联编的例子中,可以看到虚函数的传递性:当基类中定义了虚同名函数,那么派生类中的同名函数自动变为虚函数。

其实当使用动态联编时,类中保存了一个virtual函数表,此时程序运行时比非虚函数开销大,代码编译效率会有所降低。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值