美文网首页
C++中继承与多态

C++中继承与多态

作者: nethanhan | 来源:发表于2017-10-14 10:55 被阅读0次

父子间的同名冲突

首先来看一段代码:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    int mi;
};

class Child : public Parent
{
public:
    int mi;
};

int main()
{
    Child c;
    
    //这里的mi是Parent中的还是Child中的呢?
    c.mi = 100;
    
    return 0;
}

编译通过,说明子类可以定义和父类相同的同名成员。

  • 子类可以定义父类中的同名成员
  • 子类中的成员将隐藏父类中的同名成员
  • 父类中的同名成员依然存在于子类中
  • 通过作用域分辨符( : : )访问父类中的同名成员
  • 访问父类中的同名成员
Child c;

//子类中的mi
c.mi = 100;

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

再来看一个例子:

#include <iostream>
#include <string>

using namespace std;

//定义一个命名空间A
namespace A
{
    int g_i = 0;
}
//定义一个命名空间B
namespace B
{
    int g_i = 1;
}

class Parent
{
public:
    int mi;
    
    Parent()
    {
        cout << "Parent() : " << "&mi = " << &mi << endl;
    }
};

class Child : public Parent
{
public:
    int mi;
    
    Child()
    {
        cout << "Child() : " << "&mi = " << &mi << endl;
    }
};

int main()
{
    Child c;
    
    //向子类的mi成员赋值100
    c.mi = 100;    
    //通过作用域向父类的mi成员赋值1000
    c.Parent::mi = 1000;
    
    //打印子类中mi的地址
    cout << "&c.mi = " << &c.mi << endl;
    //打印子类中mi的内容
    cout << "c.mi = " << c.mi << endl;
    
    //打印父类中mi的地址
    cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
    //打印父类中mi的内容
    cout << "c.Parent::mi = " << c.Parent::mi << endl;
    
    return 0;
}

输出结果为:

Parent() : &mi = 0x7fff57f57a90
Child() : &mi = 0x7fff57f57a94
&c.mi = 0x7fff57f57a94
c.mi = 100
&c.Parent::mi = 0x7fff57f57a90
c.Parent::mi = 1000
  • 类中的成员函数可以进行重载
    • 重载函数的本质为多个不同的函数
    • 函数名和参数列表是唯一的标识
    • 函数重载必须发生在同一个作用域中
    • 所以父子之间的同名成员不构成重载

比如像这样:

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;
    
    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);
    }
};

代码 Parent类 和 Child类 中有同名的函数add ,但是两个类之间不构成重载,只有Parent类中多个add函数构成重载。

  • 子类中的函数将隐藏父类的同名函数
  • 子类无法重载父类中的成员函数
  • 使用作用域分辨符访问父类中的同名函数
  • 子类可以定义父类中完全相同的成员函数

父子间的赋值兼容

  • 子类对象可以当作父类对象使用(兼容性)
    • 子类对象可以直接赋值给父类对象
    • 子类对象可以直接初始化父类对象
    • 父类指针可以直接指向子类对象
    • 父类引用可以直接引用子类对象

举个例子:

#include <iostream>
#include <string>

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;

    return 0;
}

在main函数中进行上述几条的操作都没有出现编译出错。现在进行这样操作:

rp.mi = 100;
rp.add(5);        
rp.add(10, 10);  

发现可以编译通过,并没有出现同名覆盖的问题。但是如果这样操作:

pp->mv = 1000;
pp->add(1, 10, 100);

运行以后就会报错,报错信息如下:

48-1.cpp:51:10: error: no member named 'mv' in 'Parent'
     pp->mv = 1000;
     ~~  ^
48-1.cpp:52:10: error: no matching member function for call to 'add'
     pp->add(1, 10, 100);
     ~~~~^~~
48-1.cpp:16:10: note: candidate function not viable: requires 2 arguments, but 3
      were provided
    void add(int a, int b)
         ^
48-1.cpp:11:10: note: candidate function not viable: requires single argument
      'i', but 3 arguments were provided
    void add(int i)
         ^
2 errors generated.

信息提示没有找到带有3个参数的add函数。为什么呢?

  • 当使用父类指针(引用)指向子类对象时
    • 子类对象退化为父类对象
    • 只能访问父类中定义的成员
    • 可以直接访问被子类覆盖的同名成员

特殊的同名函数

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

例如:

class Parent
{
    public:
        void print()
        {
            cout << "I'm Parent." << endl;
        }
};

//函数重写

class Child : public Parent
{
    public:
        void print()
        {
            cout << "I'm Child" << endl;
        }
};

假如函数重写和赋值兼容同时出现呢? 就像这样:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    int mi;
    
    void add(int i)
    {
        mi += i;
    }
    
    void add(int a, int b)
    {
        mi += (a + b);
    }
    
    void print()
    {
        cout << "I'm Parent." << endl;
    }
};

class Child : public Parent
{
public:
    int mv;
    
    void add(int x, int y, int z)
    {
        mv += (x + y + z);
    }
    
    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;
}

预期输出是 I'm Parent.I'm Child. 。但是实际输出:

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

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

多态的概念和意义

  • 函数重写回顾
    • 父类中被重写的函数依然会继承给子类
    • 子类中重写的函数将覆盖父类中的函数
    • 通过作用域分辨符( : : )可以访问到父类中的函数

就像这样:

Child c;
Parent* p = &c;

c.Parent::print();  //从父类中继承
c.print();          //从子类中重写

p->print();         //父类中定义

虽然程序逻辑是这样,但并不是我们所期望的。面向对象中期望的行为:

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

这里就引出了面向对象中的 多态 的概念:

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

例如:

p->print();

p指向父类对象时,会执行

void print()
{
    cout << "I'm Parent" << end;
}

p指向子类对象时,会执行

void print()
{
    cout << "I'm Child" << endl;
}
  • C++语言直接支持多态的概念
    • 通过使用 virtual 关键字对多态进行支持
    • virtual 声明的函数被重写后具有多态特性
    • virtual 声明的函数叫做虚函数

举个例子:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    //用 virtual 关键字修饰,则具有多态特性
    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;
}

执行结果为:

I'm Parent.
I'm Child.
  • 多态的意义
    • 在程序运行过程中展现出动态的特性
    • 函数重写必须多态实现,否则没有意义
    • 多态是面向对象组件化程序设计的基础特性

静态联编和动态联编

  • 理论中的概念
    • 静态联编
      • 在程序的编译期间就能确定具体的函数调用。 如:函数重载
    • 动态联编
      • 在程序实际运行后才能确定具体的函数调用。如:函数重写

举个例子:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    //函数重载  并且用 virtual 关键字修饰
    virtual void func()
    {
        cout << "void func()" << endl;
    }
    //函数重载  并且用 virtual 关键字修饰
    virtual void func(int i)
    {
        cout << "void func(int i) : " << i << endl;
    }
    //函数重载  并且用 virtual 关键字修饰
    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;
}

运行结果为:

void func()
void func(int i) : 1
void func(int i, int j) : (1, 2)

void func(int i, int j) : 3

void func(int i, int j) : (1, 2)
void func(int i, int j) : 3

小结

  • 子类可以定义父类的 同名成员 ,定义时子类中的成员将 隐藏 父类中的 同名成员
  • 子类和父类 中的函数 不能构成重载关系
  • 使用 作用域分辨符 可以访问父类中的 同名成员
  • 子类对象 可以当做 父类对象 使用
  • 父类指针 可以正确的指向 子类对象
  • 父类引用 可以正确的代表 子类对象
  • 子类中可以重写父类中的 成员函数
  • 函数重写只可能发生在 父类与子类 之间
  • 多态是根据 实际对象的类型 确定调用的 具体函数
  • virtual关键字 是C++中支持 多态 的唯一方式
  • 被重写的 虚函数 可表现出多态的特性

相关文章

  • C++学习笔记(面向对象)

    C++是一门面向对象的语言 类定义 & 对象 C++ 中的继承,多态,抽象

  • 深刻剖析之c++博客文章

    三大特性 封装、继承、多态 多态 C++ 虚函数表解析C++多态的实现原理 介绍了类的多态(虚函数和动态/迟绑定)...

  • 2020-07-06----《C++类的学习》

    函数重载:同名不同参。 C++类的特点:封装、继承、多态。 //多态与函数重载是啥关系? 虚函数:和软件架构相关 ...

  • C++的多态

    C++三大特性:封装、继承和多态。其中最好理解的就是封装了,继承作为C++面向对象的特征也不难理解,那么多态,应该...

  • 类的继承笔记

    特征:封装,继承,多态 对象的方法 self 参数 (相当于c++中的this指针) >>> class Ball...

  • (四)C++中的继承、多态和模板函数

    C++中的继承、多态和模板函数 一、继承 1、属性和方法的继承 继承可以更好的实现代码的重用性 2、通过子类给父类...

  • C++中继承与多态

    父子间的同名冲突 首先来看一段代码: 编译通过,说明子类可以定义和父类相同的同名成员。 子类可以定义父类中的同名成...

  • C++继承多态

    首先声明,这是我的C++学习笔记 继承 这里我们的Dog继承了Anima,我们创建了一条狗,给狗取了个名字叫啊黄,...

  • C++继承多态

    https://blog.csdn.net/qq_39755395/article/details/79751362

  • 02汇编角度理解虚函数的实现

    面向对象语言的三大特性 封装继承和多态。c++ 中的多态是通过虚函数实现的。我们声明一个Person类,然后 编写...

网友评论

      本文标题:C++中继承与多态

      本文链接:https://www.haomeiwen.com/subject/ujsxuxtx.html