C++语言学习(九)——多态

C++语言学习(九)——多态

C++中所谓的多态(polymorphism)是指由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应。
    多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性。可以减轻系统升级,维护,调试的工作量和复杂度。

多态是一种不同层次分类下的重要联系,是一种跨层操作。

一、多态实现的前提

赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。
赋值兼容是一种默认行为,不需要任何的显式的转化步骤,只能发生在public继承方式中,是多态实现的前提条件。
    赋值兼容规则中所指的替代包括以下的情况:
    A、子类对象可以直接赋值给父类对象

B、子类对象可以直接初始化父类对象
    C、父类引用可以直接引用子类对象
    D、父类指针可以直接指向子类对象

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

#include <iostream>
using namespace std;
 
class Parent
{
public:
    int m;
    Parent(int a)
    {
        m = a;
    }
    void print()
    {
        cout << "Parent m = " << m << endl;
    }
};
 
class Child : public Parent
{
public:
    int m;
    Child(int a):Parent(a)
    {
        m = a;
    }
    void print()
    {
        cout << "Child m = " << m << endl;
    }
};
int main()
{
    Parent p(0);
    Child c(0);
    Parent p1(c);
    Parent* pp = &c;//指向子类对象的父类指针退化为父类对象
    Parent& rp = c;//指向子类对象的父类引用退化为父类对象
    pp->print(); //Parent m = 0
    rp.print(); //Parent m = 0
    //Child cc = static_cast<Child>(p);//需要转换构造函数支持
    Child* pc = static_cast<Child*>(&p);
    pc->print();//Child m = xxxx
    return 0;
}

在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。

父类也可以通过强转的方式转化为子类,但存在访问越界的风险。

子类中可以重定义父类中已经存在的成员函数,即函数重写。

当函数重写遇到赋值兼容时,编译器只能根据指针的类型判断所指向的对象,根据赋值兼容原则,编译器认为父类指针指向的是父类对象,只能调用父类中定义的同名函数。

二、多态形成的条件

根据父类指针指向的实际对象类型决定调用的函数,即多态。多态中,父类指针(引用)指向父类对象则调用父类中定义的函数,父类指针(引用)指向子类对象时调用子类对象的函数。

C++通过virtual关键字对多态进行支持,被virtual声明的函数被重写后具有多态属性。

多态形成的条件:
    A、父类中有虚函数。
    B、子类override(覆写)父类中的虚函数。
    C、通过己被子类对象赋值的父类指针,调用共用接口。

1、虚函数

定义

class 类名
{
virtual 函数声明;
}

A、在基类中用 virual 声明成员函数为虚函数。类外实现虚函数时,不必再加 virtual。
    B、在派生类中重新定义此函数称为覆写,要求函数名,返值类型,函数参数个数及类型全部匹配。并根据派生类的需要重新定义函数体。
    C、当一个成员函数被声明为虚函数后,其派生类中完全相同的函数(显示的写出)也为虚函数。 可以在其前加 virtual 以示清晰。
    D、定义一个指向基类对象的指针,并使其指向其子类的对象,通过该指针调用虚函数,此时调用的就是指针变量指向对象的同名函数。

E、构造函数不能为虚函数,在构造函数执行完毕后虚函数表指针才能被正确初始化。析构函数可以为虚函数,定义一个父类指针并使用new创建的子类对象初始化,使用delete释放父类指针的堆空间时,只会调用父类的析构函数,不会调用子类的析构函数,会造成内存泄漏,父类析构函数声明为虚函数可以避免这个问题。一般来说需要将析构函数声明为虚函数。构造函数执行时,虚函数表指针未被正确初始化,因此构造函数不可能发生多态;析构函数函数执行时,虚函数表指针已经被销毁,因此析构函数也不可能发生多态。构造函数和析构函数中只能调用当前类中定义的函数版本。

2、纯虚函数

定义

class 类名
{
virtual 函数声明 = 0;
}

A、含有纯虚函数的类,称为抽象基类,不可实列化。即不能创建对象,存在的意义就是被继承,提供族类的公共接口,java 中称为 interface。
    B、纯虚函数只有声明,没有实现,被“初始化”为 0。
    C、如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数,派生类仍然为纯虚基类。

3、虚函数的限制

A、只有类的成员函数才能声明为虚函数。
    虚函数仅适用于有继承关系的类对象,所以普通函数不能声明为虚函数。
    B、静态成员函数不能是虚函数
    静态成员函数不受对象的捆绑,只有类的信息。
    C、内联函数不能是虚函数
    D、构造函数不能是虚函数
    构造时,对象的创建尚未完成。构造完成后,才能算一个名符其实的对象。
    E、析构函数可以是虚函数且通常声明为虚函数

F、含有虚函数的类,析构函数也必须声明为虚函数。在 delete父类指针的时候,会调用子类的析构函数。

三、多态应用实例

#include <iostream>
using namespace std;
 
class Parent
{
public:
    int m;
    Parent(int a)
    {
        m = a;
    }
    virtual void print()
    {
        cout << "Parent m = " << m << endl;
    }
};
 
class Child : public Parent
{
public:
    int m;
    Child(int a):Parent(a)
    {
        m = a;
    }
    void print()
    {
        cout << "Child m = " << m << endl;
    }
};
int main()
{
    Parent p(0);
    Child c(0);
    Parent p1(c);
    Parent* pp = &c;//指向子类对象
    Parent& rp = c;//指向子类对象
    pp->print(); //Child m = 0
    rp.print(); //Child m = 0
    Child* pc = static_cast<Child*>(&p);//指向父类对象
    pc->print();//Parent m = 0
    return 0;
}

四、C++对象模型分析

class是一种特殊的struct,class中的成员函数与成员变量是分开存放的,每个对象拥有独立的成员变量,所有的对象共享类中的成员函数。

1、类对象模型的内存排布

运行时对象退化为结构体的形式:

A、所有成员变量在内存中依次排布

B、由于内存对齐的存在,成员变量间可能存在内存间隙

C、可以通过内存地址访问成员变量

D、访问权限关键字在运行时失效

代码实例:

#include <iostream>
#include <string>
 
using namespace std;
 
class A
{
    int i;
    int j;
    char c;
    double d;
public:
    void print()
    {
        cout << "i = " << i << ", "
             << "j = " << j << ", "
             << "c = " << c << ", "
             << "d = " << d << endl;
    }
};
 
struct B
{
    int i;
    int j;
    char c;
    double d;
};
 
int main()
{
    A a;
    
    cout << "sizeof(A) = " << sizeof(A) << endl;    // 20 bytes
    cout << "sizeof(a) = " << sizeof(a) << endl;
    cout << "sizeof(B) = " << sizeof(B) << endl;    // 20 bytes
    
    a.print();
    
    B* p = reinterpret_cast<B*>(&a);
    
    p->i = 1;
    p->j = 2;
    p->c = ‘c‘;
    p->d = 3;
    
    a.print();
    
    p->i = 100;
    p->j = 200;
    p->c = ‘C‘;
    p->d = 3.14;
    
    a.print();
    
    return 0;
}

2、类对象对成员函数的调用分析

类中的成员函数存在于代码段,调用成员函数时对象地址作为参数隐式传递给成员函数,成员函数通过对象地址隐式访问成员变量,C++语法隐藏了对象地址的传递过程。

3、继承对象模型

子类是由父类成员叠加子类成员得到的。

C++多态的实现原理:

当类中声明虚函数时,编译器会在类中生成一个虚函数表,用于存储virtual成员函数地址,虚函数表由编译器自动生成与维护,存在虚函数时每个对象中都有一个指向虚函数表的指针。

由于对象调用虚函数时会查询虚函数表,因此虚函数的调用效率比普通成员函数低。

对象中虚函数表指针存在对象开始空间中。

代码实例:

#include <iostream>
#include <string>
 
using namespace std;
 
class Demo
{
protected:
    int mi;
    int mj;
public:
    virtual void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << endl;
    }
};
 
class Derived : public Demo
{
    int mk;
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << ", "
             << "mk = " << mk << endl;
    }
};
 
struct Test
{
    void* p;
    int mi;
    int mj;
    int mk;
};
 
int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;         
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;  
    
    Derived d(1, 2, 3);
    Test* p = reinterpret_cast<Test*>(&d);
    
    cout << "Before changing ..." << endl;
    
    d.print();
    
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    
    cout << "After changing ..." << endl;
    
    d.print();
    
    return 0;
}

五、抽象类与接口

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

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

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

抽象类不可以定义对象,但是可以定义指针,指针指向子类对象,当子类中实现了子类纯虚函数,可以实现多态。

C++中满足下列条件的类称为接口:

A、类中没有定义任何的成员变量

B、所有的成员函数都是公有的

C、所有的成员函数都是纯虚函数

从以上条件可以知道,接口是一种特殊的抽象类。

C++的接口代码实例如下:

#include <iostream>
#include <string>
 
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;
}
时间: 2024-08-08 09:37:21

C++语言学习(九)——多态的相关文章

20-黑马程序员------OC 语言学习笔记---多态

多态 1.没有继承就没有多态 2.代码的体现:父类类型的指针指向子类对象 3.好处:如果函数\方法参数中使用的是父类类型,可以传入父类.子类对象 4.局限性: 1> 父类类型的变量 不能 直接调用子类特有的方法.必须强转为子类类型变量后,才能直接调用子类特有的方法 @interface Animal : NSObject - (void)eat; @end @implementation Animal - (void)eat { NSLog(@"Animal-吃东西----");

go 语言学习九 - String()

package main import "fmt" func main() { /* 一个类型如果定义了指针接收者的String方法: func (p *Type) String() string {} 打印这个类型的指针时会调用, 打印这个类型的值时不会调用. */ var x Xint = 123 fmt.Println(x) // 123 fmt.Println(&x) // can not print Xint point. /* 一个类型如果定义了值接收者的Strin

C++语言学习(十九)——C++类型识别

C++语言学习(十九)--C++类型识别 一.C++类型识别简介 1.C++类型识别简介 C++是静态类型语言,其数据类型是在编译期就确定的,不能在运行时更改.C++语言中,静态类型是对象自身的类型,动态类型是指针(引用)所指向对象的实际类型.RTTI(Run-Time Type Information)即运行时类型识别,C++通过RTTI实现对多态的支持.为了支持RTTI,C++提供了一个type_info类和typeid与dynamic_cast两个关键字. 2.type_info结构体 t

C++语言学习(十一)——多态

C++语言学习(十一)--多态 一.多态简介 C++中的多态(polymorphism)是指由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应.多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性.可以减轻系统升级,维护,调试的工作量和复杂度.多态是一种不同层次分类下的重要联系,是一种跨层操作. 二.多态实现的前提 赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代.赋值兼容是一种默认行为,不需要任何的显式的转化步骤,只能发生在public继承方式中,是多

go语言学习(五)——面向对象编程

主要讲的是"类"和接口&和其他传统语言不一样的地方挺多的,断断续续看了好几天 下面是我的练习代码 // GoStudy0219 project main.go /* go语言学习--面向对象编程(1) go中类型的值语义和引用语义 结构体(类)的定义和初始化 */ package main import ( "fmt" ) func main() { //几种"类"的初始化 v1 := &character{"Tom&q

【转】朱兆祺教你如何攻破C语言学习、笔试与机试的难点(连载)

原文网址:http://bbs.elecfans.com/jishu_354666_1_1.html 再过1个月又是一年应届毕业生应聘的高峰期了,为了方便应届毕业生应聘,笔者将大学四年C语言知识及去年本人C语言笔试难点进行梳理,希望能对今年应届毕业生的应聘有所帮助. 2013年10月18日更新-->    攻破C语言这个帖子更新到这里,我不仅仅是为了补充大学学生遗漏的知识,我更重要的是希望通过我的经验,你们实际项目中的C语言写得漂亮,写出属于你的风格.“朱兆祺STM32手记”(http://bb

R语言学习笔记

參考:W.N. Venables, D.M. Smith and the R DCT: Introduction to R -- Notes on R: A Programming Environment for Data Analysis and Graphics,2003. http://bayes.math.montana.edu/Rweb/Rnotes/R.html 前言:关于R 在R的官方教程里是这么给R下注解的:一个数据分析和图形显示的程序设计环境(A system for data

Java语言中的----多态和异常处理

day13 Java语言中的----多态和异常处理 一.概述: 学习Java语言就要理解Java中的面向对象的四大特征:分别是封装.抽象.继承.多态.前面三个我们已经学完了,下面我们来学一下多态.什么是多态?不明思议就是具有两种形态性,一个方法可以定义两种形态,这就是方法的多态性.同时我们还会学习到什么是方法重载.和方法签名. 异常处理就是在编译的时候需要我们手动的去处理一下编译都不能够通过的程序.使用try {} catch{}结构就可以处理异常. 二.多态: 1.所有类的基类是object,

C++语言学习(二)——C++对C语言基础语法的扩展

C++语言学习(二)--C++对C语言基础语法的扩展 C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面. 一.实用性增强 C语言中变量的定义必须在作用域开始的位置进行定义. #include <stdio.h> int main(int argc, char *argv[]) { int i;//定义变量 int j; //使用变量 for(i = 0; i < 10; i++) { for(j = 0; j < 10; j++)