同名覆盖,函数重写,赋值兼容,多态、动态类型识别

目录

1、同名覆盖与函数重写

2、父子间的赋值兼容 

3、当函数重写遇上赋值兼容

4、多态、静态联编、动态联编

5、C++中的动态类型

5.1、静态类型与动态类型

5.2、动态类型识别

5.2.1 利用多态获得动态类型

5.2.2 类型识别关键字 

6、C++ 中的抽象类和接口

7、构造函数、析构函数与虚函数

8、小结


1、同名覆盖与函数重写

子类可以定义父类中的同名成员 (成员变量、成员函数),子类中的成员将隐藏父类中的同名成员 

父类中的同名成员依然存在于子类中 ,通过作用域分辨符访问父类中的同名成员  ,子类无法重载父类中的成员函数(作用域不同) 

#include <iostream>

using namespace std;

class Parent
{
public:
    int mi;

    void add(int v)
    {
        mi += v;
    }

    void add(int a, int b)
    {
        mi += (a + b);
    }
};

class Child : public Parent
{
public:
    int mi; // 同名覆盖

    // 子类任意一个add函数将隐藏父类的同名函数,子类无法直接调用父类同名函数(同名覆盖)
    // 也就是说当删除这里任意add()函数,子类无法再直接调用父类add(int),需要加上作用域分辨符
    void add(int v)
    {
        mi += v;
    }

    void add(int a, int b)
    {
        mi += (a + b);
    }

    void add(int x, int y, int z)
    {
        mi += (x + y + z);
    }
};

int main()
{
    Child c;

    c.mi = 100;  // 子类mi

    c.Parent::mi = 1000; // 父类mi

    c.add(1); // 调用子类的add函数
    c.add(2, 3);
    c.add(4, 5, 6);

    cout << "c.mi = " << c.mi << endl; // 121 = 100 + 1 + 2 + 3 + 4 + 5 + 6

    cout << "c.Parent::mi = " << c.Parent::mi << endl; // 1000

    c.Parent::add(1); // 调用父类add(int)

    cout << "c.Parent::mi = " << c.Parent::mi << endl; // 1001

    return 0;
}

子类中可以定义父类中已经存在的成员函数,这种重定义发生在继承中,叫做函数重写 ,函数重写是同名覆盖的一种特殊情况 

2、父子间的赋值兼容 

子类对象可以当作父类对象使用

子类对象可以直接赋值给父类对象 ,子类对象可以直接初始化父类对象 

父类指针可以直接指向子类对象 ,父类引用可以直接引用子类对象

当使用父类指针(引用)指向子类对象时 

子类对象退化为父类对象 ,只能访问父类中定义的成员 ,可以直接访问被子类覆盖的同名成员

#include <iostream>  
  
using namespace std;  
  
class Parent  
{  
public:  
    int mi;  
      
    void add(int i)  
    {  
        mi += i;  
    }  
      
    void add(int a, int b)  
    {  
        mi += (a + b);  
    }  
};  
  
class Child : public Parent  
{  
public:  
    int mv;  // 隐藏父类同名成员
      
    void add(int x, int y, int z)  // 隐藏父类同名成员
    {  
        mv += (x + y + z);  
    }  
};  
  
int main()  
{  
    Parent p;  
    Child c;  
      
    p = c;  // 赋值

    Parent p1(c);  // 初始化

    Parent& rp = c;  // 引用
    Parent* pp = &c; // 指针 
      
    rp.mi = 100;  
    rp.add(5);  // 调用了父类的add(int)          
    rp.add(10, 10);      

    // pp->mv = 1000;  // error
    // pp->add(1, 10, 100);  // error
      
    return 0;  
}  

3、当函数重写遇上赋值兼容

赋值兼容的问题     problem.cpp

#include <iostream>  

using namespace std;  
  
class Parent  
{  
public:  
    void print()  
    {  
        cout << "I'm Parent." << endl;  
    }  
};  
  
class Child : public Parent  
{  
public:   
    void print()  // 函数重写
    {  
        cout << "I'm Child." << endl;  
    }  
};  
  
void how_to_print(Parent* p) // 赋值兼容
{  
    p->print();  
}  
  
int main()  
{  
    Parent p;  
    Child c;  
      
    how_to_print(&p);    // Expected to print: I'm Parent.  
    how_to_print(&c);    // Expected to print: I'm Child.  
      
    return 0;  
}  
 

编译期间,编译器只能根据指针的类型判断所指向的对象 ,根据赋值兼容,编译器认为父类指针指向的是父类对象 ,因此,编译结果只可能是调用父类中定义的同名函数

在编译这个函数的时候,编译器不可能知道指针p究竟指向了什么, 但是编译器没有理由报错。于是,编译器认为最安全的做法是调用父类的print函数,因为父类和子类肯定都有相同的print函数。

编译器的这种处理显然不是我们所期望的,于是引入了多态的概念

4、多态、静态联编、动态联编

面向对象中期望的行为 

根据实际的对象类型判断如何调用重写函数 ,父类指针(引用)指向父类对象则调用父类中定义的函数 ,指向子类对象则调用子类中定义的重写函数 

面向对象中的多态的概念 

根据实际的对象类型决定函数调用的具体目标 ,同样的调用语句在实际运行时有多种不同的表现形态 

   

C++语言直接支持多态的概念

通过使用virtual关键字对多态进行支持 ,被virtual声明的函数被重写后具有多态特性 ,被virtual声明的函数叫做虚函数 

#include <iostream>  
  
using namespace std;  
  
class Parent  
{  
public:  
    virtual void print()  
    {  
        cout << "I'm Parent." << endl;  
    }  
};  
  
class Child : public Parent  
{  
public:  
    void print()  
    {  
        cout << "I'm Child." << endl;  
    }  
};  
  
void how_to_print(Parent* p)  
{  
    p->print();     // 展现多态的行为  
}  
  
int main()  
{  
    Parent p;  
    Child c;  
      
    how_to_print(&p);    // Expected to print: I'm Parent.  
    how_to_print(&c);    // Expected to print: I'm Child.  
      
    return 0;  
} 

  

多态的意义 

在程序运行过程中展现出动态的特性 ,函数重写必须多态实现,否则没有意义 (对比Java),多态是面向对象组件化程序设计的基础特性 

静态联编与动态联编

静态联编 

在程序的编译期间就能确定具体的函数调用  , 如:函数重载 :编译期间就能通过函数参数类型,个数判断具体函数调用

动态联编 

在程序实际运行后才能确定具体的函数调用 ,如:函数重写 ,引入多态后,到运行后才能确定具体调用函数

#include <iostream>  
  
using namespace std;  
  
class Parent  
{  
public:  
    virtual void func()  
    {  
        cout << "void func()" << endl;  
    }  
      
    virtual void func(int i)  
    {  
        cout << "void func(int i) : " << i << endl;  
    }  
      
    virtual void func(int i, int j)  
    {  
        cout << "void func(int i, int j) : " << "(" << i << ", " << j << ")" << endl;  
    }  
};  
  
class Child : public Parent  
{  
public:  
    void func(int i, int j)  
    {  
        cout << "void func(int i, int j) : " << i + j << endl;  
    }  
      
    void func(int i, int j, int k)  
    {  
        cout << "void func(int i, int j, int k) : " << i + j + k << endl;  
    }  
};  
  
void run(Parent* p)  
{  
    p->func(1, 2);     // 展现多态的特性  
                       // 动态联编  
}  
  
int main()  
{  
    Parent p;  
      
    p.func();         // 静态联编  
    p.func(1);        // 静态联编  
    p.func(1, 2);     // 静态联编  
      
    cout << endl;  
      
    Child c;  
      
    c.func(1, 2);     // 静态联编  
      
    cout << endl;  
      
    run(&p);  
    run(&c);  
      
    return 0;  
}  

 多态原理:C++ 对象模型分析

江湖恩怨     test.cpp 

#include <iostream>

using namespace std;

class Boss      // 逍遥王
{
public:
    int fight()         // 必杀技
    {
        int ret = 10;   // 伤害

        cout << "Boss::fight() : " << ret << endl;

        return ret;
    }
};

class Master    // 名剑山庄老庄主
{
public:
    virtual int eightSwordKill()    // 八剑齐飞---同时召唤8把名剑 (八个方向)
    {
        int ret = 8;  // 8个伤害

        cout << "Master::eightSwordKill() : " << ret << endl;

        return ret;
    }
};

class NewMaster : public Master    // 名剑山庄少庄主 ---报仇
{
public:
    int eightSwordKill()    // 重写八剑齐飞
    {
        int ret = Master::eightSwordKill() * 2;  // 在老庄主的基础上创新,左右互搏,伤害加倍

        cout << "NewMaster::eightSwordKill() : " << ret << endl;

        return ret;
    }
};

void field_pk(Master* master, Boss* boss)
{
    int k = master->eightSwordKill(); // 动态联编
    int b = boss->fight(); // 静态联编

    if( k < b )
    {
        cout << "Master is killed..." << endl;
    }
    else
    {
        cout << "Boss is killed..." << endl;
    }
}

int main()
{
    Master master;
    Boss boss;

    cout << "Master vs Boss" << endl;

    field_pk(&master, &boss);  // 老庄主弱于逍遥王,惨死

    cout << "NewMaster vs Boss" << endl;

    NewMaster newMaster;

    field_pk(&newMaster, &boss);  // 少庄主完胜

    return 0;
}

5、C++中的动态类型

5.1、静态类型与动态类型

  • 在面向对象中可能出现下面的情况
    • 基类指针指向子类对象
    • 基类引用成为子类对象的别名
  • 静态类型 :变量或对象自身的类型 (期望的类型)
  • 动态类型 :指针或引用所指向对象的实际类型
  • 基类指针是否可以强制类型转换为子类指针取决于动态类型
// 指针b期望指向一个父类对象,但代码运行时b不一定指向父类对象,b指向的实际对象类型是不确定的
void test(Base* b)  
{  
    /* 危险的转换方式 */  
    Derived* d = static_cast<Derived*>(b); // 指针b指向子类对象无问题,但若是父类对象就危险了 
}

5.2、动态类型识别

5.2.1 利用多态获得动态类型

利用多态

  • 在基类中定义虚函数返回具体的类型信息 ,所有的派生类都必须实现类型相关的虚函数 
  • 每个类中的类型虚函数都需要不同的实现

多态解决方案的缺陷 

  • 必须从基类开始提供类型虚函数,所有的派生类都必须重写类型虚函数 
  • 每个派生类的类型名必须唯—
#include <iostream>  
using namespace std;  
  
class Base  
{  
public:  
    virtual string type()  {  return "Base";  }  
};  
  
class Derived : public Base  
{  
public:  
    string type()  {  return "Derived";  }  
    
    void printf()  
    {  
        cout << "I'm a Derived." << endl;  
    }  
};  
  
class Child : public Base  
{  
public:  
    string type()  {  return "Child";  }  
};  
  
void test(Base* b)  
{  
    /* 危险的转换方式 */  
    // Derived* d = static_cast<Derived*>(b);  
      
    if( b->type() == "Derived" )  
    {  
        Derived* d = static_cast<Derived*>(b);  
          
        d->printf();  
    }  
      
    // cout << dynamic_cast<Derived*>(b) << endl; // 使用dynamic_cast,仅仅得知转换是否成功,不能得到动态类型
}  
  
  
int main(int argc, char *argv[])  
{  
    Base b;  
    Derived d;  
    Child c;  
      
    test(&b);  
    test(&d);  
    test(&c);  
      
    return 0;  
}

 

5.2.2 类型识别关键字 

typeid

  • C++提供了typeid操作符关键字用于获取类型信息
  • typeid关键字返回对应参数的类型信息,为 type_info类对象 ,包含类型信息
  • 当typeid的参数为NULL时将抛出异常
  • 当参数为类型时:返回静态类型信息
  • 当参数为变量时: 
    • 不存在虚函数表 ,返回静态类型信息
    • 存在虚函数表     ,返回动态类型信息
#include <iostream>
#include <typeinfo>

using namespace std;

class Base
{
public:
    virtual ~Base() {  }
};

class Derived : public Base
{
public:
    void printf()
    {
        cout << "I'm a Derived." << endl;
    }
};

void test(Base* b)
{
    // 有虚函数返回动态类型信息,若Base没有虚函数,b指向父类或子类结果一致都是输出父类类型信息
    const type_info& tb = typeid(*b);

    cout << tb.name() << endl;                  // 返回动态类型信息

    cout << typeid (b).name() << endl << endl;  // 返回静态类型信息, b就是一个指针变量,类型是指针
}

int main(int argc, char *argv[])
{
    int i = 0;

    const type_info& tiv = typeid(i);
    const type_info& tii = typeid(int);

    cout << (tiv == tii) << endl; // 1

    Base b;
    Derived d;

    test(&b);
    test(&d);

    return 0;
}

 

image.png

  • 不同编译器处理类型名字方式是不一样,所以在使用typeid不能做类型假设
  • 关于type_info的定义:C++标准没有确切定义type_info,但必须实现name(),before(),==,!= ,它与编译器相关
//Part of RTTI.
class type_info
{
protected:
    const char *__name;
    explicit type_info(const char *__n): __name(__n) { }
    
private:
    // 隐藏拷贝构造和赋值操作符,创建type_info对象只能使用typeid操作符
    type_info& operator=(const type_info&);
    type_info(const type_info&);
public:
    virtual ~type_info();
    // 返回C风格的字符串,表示类型名
    const char* name() const 
    { 
        return __name[0] == '*' ? __name + 1 : __name; 
    }
    
    //bool before(const type_info& __arg) const;
    //bool operator==(const type_info& __arg) const;   // 比较的是__name
    //bool operator!=(const type_info& __arg) const{ return !operator==(__arg); }
    //http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a01094_source.html
};
typeinfo与虚函数表的联系

#include <iostream>
#include <typeinfo>
using namespace std;

class Base
{
public:
    virtual void print(){}
};
class Derived : public Base
{
public:
    void print(){}
};
void test(Base* p)
{
    p->print();

    const type_info& info = typeid(*p);

    printf("%p\n", &info);

    return;
}

int main()
{
    Base* b = new Derived();

    test(b);

    return 0;
}

image.png

typeof

  • typeof 是GNU C编译器的特有关键字 
  • typeof 只在编译期生效,用于得到变量的类型
int main()
{
    int i = 100;
    typeof(i) j = i;         // int j = i;
    const typeof(i)* p = &j; // const int* p = &j;
    
    printf("sizeof(j) = %d\n", sizeof(j));
    printf("j = %d\n", j);
    printf("*p = %d\n", *p);
    
    return 0;
 }

dynamic_cast

  • dynamic_cast是与继承相关的类型转换关键字 
  • dynamic_cast要求相关的类中必须有虚函数 
  • 用于有直接或者间接继承关系的指针(引用)之间
    • 指针:转换成功:得到目标类型的指针 ,转换失败:得到一个空指针 
    • 引用: 转换成功:得到目标类型的引用 ,转换失败:得到一个异常操作信息(std::bad_cast)
#include <iostream>
using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }
    virtual ~Base()
    {
        cout << "Base::~Base()" << endl;
    }
};

class Derived : public Base
{
};

int main()
{
    Base* p = new Base;
    Derived* pd = dynamic_cast<Derived*>(p);  // 预计返回NULL

    if( pd != NULL )
    {
        cout << "pd = " << pd << endl;
    }
    else
    {
        cout << "Cast error!" << endl;
    }

    Base* p1 = new Derived();
    Derived* pd1 = dynamic_cast<Derived*>(p1); // 预计转换成功

    if( pd1 != NULL )
    {
        cout << "pd1 = " << pd1 << endl;
    }
    else
    {
        cout << "Cast error!" << endl;
    }

    delete p;
    delete p1;

    return 0;
}

 


6、C++ 中的抽象类和接口

面向对象中的抽象类 

可用于表示现实世界中的抽象概念,是一种只能定义类型,而不能产生对象的类 ,只能被继承并重写相关函数 ,直接特征是相关函数没有完整的实现

抽象类与纯虚函数

C++语言中没有抽象类的概念 ,C++中通过纯虚函数实现抽象类 ,纯虚函数是指只定义原型的成员函数 ,—个C++类中存在纯虚函数就成为了抽象类

抽象类只能用作父类被继承 ,子类必须实现纯虚函数的具体功能 ,纯虚函数被实现后成为虚函数 , 如果子类没有实现纯虚函数,则子类成为抽象类

#include <iostream>  
  
using namespace std;  
  
class Shape  
{  
public:  
    virtual double area() = 0;  // "= 0"用于告诉编译器当前是声明纯虚函数,因此不需要定义函数体
};  
  
class Rect : public Shape  
{  
    int ma;  
    int mb;  
public:  
    Rect(int a, int b)  
    {  
        ma = a;  
        mb = b;  
    }  
    double area()  
    {  
        return ma * mb;  
    }  
};  
  
class Circle : public Shape  
{  
    int mr;  
public:  
    Circle(int r)  
    {  
        mr = r;  
    }  
    double area()  
    {  
        return 3.14 * mr * mr;  
    }  
};  
  
void area(Shape* p)  // 不能定义对象,可以定义指针
{  
    double r = p->area();  
      
    cout << "r = " << r << endl;  
}  
  
int main()  
{  
    Rect rect(1, 2);  
    Circle circle(10);  
      
    area(&rect);  
    area(&circle);  
      
    return 0;  
}

  

C++中的接口

类中没有定义任何的成员变量 ,所有的成员函数都是公有的 ,所有的成员函数都是纯虚函数 ,接口是—种特殊的抽象类 (Java中严格区分接口和抽象类)

#include <iostream>  
  
using namespace std;  
  
class Channel  
{  
public:  
    virtual bool open() = 0;  
    virtual void close() = 0;  
    virtual bool send(char* buf, int len) = 0;  
    virtual int receive(char* buf, int len) = 0;  
};  
  
int main()  
{  
    return 0;  
}  

 

7、构造函数、析构函数与虚函数

构造函数是否可以成为虚函数? 析构函数是否可以成为虚函数?

构造函数不可能成为虚函数 ,在构造函数执行结束后,虚函数表指针才会被正确的初始化 

析构函数可以成为虚函数 ,建议在设计类时将析构函数声明为虚函数 

#include <iostream>    
  
using namespace std;  
  
class Base  
{  
public:  
    Base()  
    {  
        cout << "Base()" << endl;  
    }  
      
      
    ~Base()  
    {  
          
        cout << "~Base()" << endl;  
    }  
};  
  
  
class Derived : public Base  
{  
public:  
    Derived()  
    {  
        cout << "Derived()" << endl;  
             
    }  
      
    ~Derived()  
    {  
          
        cout << "~Derived()" << endl;  
    }  
};  
  
  
int main()  
{  
    Base* p = new Derived();  // 创建子类对象
      
    // ...  
      
    delete p;  // 只会析构父类部分
      
    return 0;  
}  

  

没调用子类的析构函数,编译器在delete p时只是单纯的根据指针的类型析构

我们需要的是根据p所指向实际对象判断如何调用析构函数(多态)

解决方案 : virtual ~Base()

 

构造函数是否可以发生多态? 析构函数是否可以发生多态?

构造函数不可能发生多态行为 ,在构造函数执行时,虚函数表指针未被正确初始化

析构函数不可能发生多态行为 ,在析构函数执行时,虚函数表指针已经被销毁 

#include <iostream>  

using namespace std;  
  
class Base  
{  
public:  
    Base()  
    {  
        cout << "Base()" << endl;  
          
        func();  
    }  
      
    virtual void func()   
    {  
        cout << "Base::func()" << endl;  
    }  
      
    virtual ~Base()  
    {  
        func();  
          
        cout << "~Base()" << endl;  
    }  
};  
  
  
class Derived : public Base  
{  
public:  
    Derived()  
    {  
        cout << "Derived()" << endl;  
          
        func();  
    }  
      
    virtual void func()  
    {  
        cout << "Derived::func()" << endl;  
    }  
      
    ~Derived()  
    {  
        func();  
          
        cout << "~Derived()" << endl;  
    }  
};  
  
  
int main()  
{  
    Base* p = new Derived();  
      
    // ...  
      
    delete p;  
      
    return 0;  
}  

 

构造函数和析构函数中不能发生多态行为, 只调用当前类中定义的函数版本!!

8、小结

子类可以定义父类中的同名成员 ,子类中的成员将隐藏父类中的同名成员 ,子类和父类中的函数不能构成重载关系 

子类可以定义父类中完全相同的成员函数 ,使用作用域分辨符访问父类中的同名成员

子类对象可以当作父类对象使用(赋值兼容) ,父类指针可以正确的指向子类对象 , 父类引用可以正确的代表子类对象 

子类中可以重写父类中的成员函数,函数重写只可能发生在父类与子类之间 ,根据实际对象的类型确定调用的具体函数 

virtual关键字是C++中支持多态的唯—方式 ,被重写的虚函数可表现出多态的特性

virtual就是告诉编译当调用虚函数时统一按间接调用的方式生成汇编代码

构造函数不能成为虚函数 ,析构函数可以成为虚函数 

构造函数和析构函数都无法产生多态行为 

C++中没有抽象类的概念 ,C++中通过纯虚函数实现抽象类 ,类中只存在纯虚函数的时成为接口 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值