C++中关于多态实现和使用方法

目录
  • 赋值兼容
    • 实例
  • 多态
    • 静多态
    • 动多态
    • 格式
    • 实例
    • override
  • 纯虚函数
    • 含有虚函数的析构函数
    • 注意事项
  • RTTI
    • typeid
    • typecast
  • 多态实现
    • 虚函数
    • 一般继承(no override)
    • 一般继承(override)
    • 过程推断

都说 C++ 是面向对象的语言,其中的面向对象主要包括三部分:继承,封装,多态。继承和封装我们之前就简单介绍过,这里主要对多态的使用方法做一个简单说明。

赋值兼容

赋值兼容说的是在使用基类对象的地方可以使用公有继承类的对象来代替。赋值兼容是一种默认的行为,不需要进行显式转换就能够实现。

就比如在派生类拷贝构造函数的参数初始化列表中,我们会直接使用派生类对象作为基类拷贝构造函数的参数,而不会报错,这就是赋值兼容的表现。赋值兼容主要表现在:

  • 派生类的对象可以赋值给基类对象
  • 派生类的对象可以初始化基类的引用
  • 派生类对象的地址可以赋值给指向基类的指针
  • 但,发生赋值兼容之后,只能使用从基类继承的成员

实例

#include <iostream>
 
using namespace std;
 
class PERSON
{
public:
    PERSON(char *name_ = "***",char sex_ = '*')
        :name(name_),sex(sex_){}
    void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
    }
protected:
    char *name;
    char sex;
};
 
class STUDENT:public PERSON
{
public:
    STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "100")
        :PERSON(name_,sex_),num(num_){}
    void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
        cout<<"The num is "<<num<<endl;
    }
private:
    char *num;
};
 
int main()
{
    STUDENT st("zhangsan",'x',"100");
    st.display();
    PERSON per = st;
    per.display();
    PERSON &per2 = st;
    per2.display();
    PERSON *per3 = &st;
    per3->display();
 
    return 0;
}

结果为:

The name is zhangsan
The sex is x
The num is 100
The name is zhangsan
The sex is x
The name is zhangsan
The sex is x
The name is zhangsan
The sex is x

上边的程序可以看出,基类对象,引用和指针都可以使用派生类对象或者指针进行赋值,从而进行访问。

其实也可以将基类指针强制转换为派生类指针,进行访问,但这种形式绝不是赋值兼容:

int main()
{
    PERSON per("zhangsan",'x');
    per.display();
 
    STUDENT *st = static_cast<STUDENT *>(&per);
    st->display();
 
    return 0;
}

结果为:

The name is zhangsan
The sex is x
The name is zhangsan
The sex is x
The num is 夽@

上边的程序中,是将基类的指针强制转换派生类的指针,从而调用派生类的对象。

  • 从实际上来说,该过程只是将以基类地址其实的一段内存交给了派生类的指针,因为类对象只存储数据成员,因此能够对应访问到从基类继承到的数据成员。
  • 但同时不确定原来基类成员后边的空间有什么东西,结果为出现部分乱码。
  • 如果将基类和派生类位置对调就是赋值兼容了。

多态

C++ 中的多态主要说的是,在面向对象中,接口的多种不同的实现方式。

静多态

C++ 中的多态是接口多种不同的实现方式。而我们之前提到过的函数重载也是接口的多种不同的实现方式,因此也可以称之为多态,只是函数重载是在编译阶段通过 name mangling 实现的,所以叫做静多态。

动多态

而不在编译阶段而是在运行阶段决定的多态就称为动多态。动多态的形成条件为:

  • 父类中有虚函数
  • 子类 override 父类中的的虚函数
  • 通过已被子类对象赋值的父类指针或引用,调用公有接口

格式

class classname
{
    virtual datatype func(argu);
}

实例

#include <iostream>
 
using namespace std;
 
class PERSON
{
public:
    PERSON(char *name_ = "***",char sex_ = '*')
        :name(name_),sex(sex_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
    }
protected:
    char *name;
    char sex;
};
 
class STUDENT:public PERSON
{
public:
    STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "100")
        :PERSON(name_,sex_),num(num_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
        cout<<"The num is "<<num<<endl;
    }
protected:
    char *num;
};
 
class POSTGRADUATE:public STUDENT
{
public:
    POSTGRADUATE(char *name_ = "***",char sex_ = '*',char *num_ = "***",char *job_ = "***")
        :STUDENT(name_,sex_,num_),job(job_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
        cout<<"The num is "<<num<<endl;
        cout<<"The job is "<<job<<endl;
    }
protected:
    char *job;
};
 
int main()
{
    POSTGRADUATE po("zhsangsan",'x',"100","paper");
    po.display();
 
    PERSON *per = &po;
    per->display();
    STUDENT *st = &po;
    st->display();
 
    return 0;
}

结果为:

The name is zhsangsan
The sex is x
The num is 100
The job is paper
The name is zhsangsan
The sex is x
The num is 100
The job is paper
The name is zhsangsan
The sex is x
The num is 100
The job is paper

在基类中声明虚函数时需要使用 virtual 关键字,在类外实现虚函数时,不用再加 virtual

在派生类中重新定义此函数的过程称为 override,此过程要求函数的要素全都不能发生改变,包括函数名,返回值类型,形参个数和类型,只有函数体可以改变

当基类中的函数成员被声明为 virtual 时,其派生类中完全相同的函数都会变为虚函数,原则上派生类中的虚函数不用使用 virtual 关键字,但是为了程序的可读性,可以在派生类的对应函数前加上 virtual

定义一个指向基类的指针,并使其指向其子类对象的地址,通过该指针调用虚函数,此时调用的就是指针变量指向的对象

子类中 override 的函数,可以为任意访问类型

通过多态就避免了赋值兼容的问题

override

在虚函数的使用中,需要在派生类中 override 基类中的虚函数,表明该函数是从基类 override 得到的,override 的含义表明:

  • override 的函数要素全都不能发生改变
  • 包括函数名,返回值类型,形参个数和类型
  • 只有函数体可以改变

而有时为了可读性,也为了防止编写时出错,可以通过在函数后添加 override 关键字表明这是 override 得到的。如上边的例子中:

virtual void display() override

使用上边的形式可以严格语法书写。

纯虚函数

对于一些抽象基类来说,我们并不需要在其中的虚函数中编写什么语句,因此可以将之写成纯虚函数。

class classname
{
    virtual datatype func(argu) = 0;
}

如上例所示,可以将 STUDENT 中的 display 函数定义为纯虚函数:

virtual void display() = 0;

只是此时不能够调用 STUDENT 中的该函数了。

对于纯虚函数而言:

  • 含有纯虚函数的类,称为抽象基类,不能够创建该类对象,该类只能被继承,提供公共接口
  • 纯虚函数的声明形式就包含了声明和实现
  • 如果一个类中声明了纯虚函数,而在派生类中没有定义该函数,则该虚函数在派生类中仍然是纯虚函数,派生类仍然为抽象基类,这意味着在第一次继承的时候一定要定义该函数
  • 从这个角度看,才算是虚函数的正确用法,直接用来声明为纯虚基类,从而被继承

含有虚函数的析构函数

含有虚函数的类,析构函数也应该声明为虚函数。

#include <iostream>
 
using namespace std;
 
class PERSON
{
public:
    PERSON(char *name_ = "***",char sex_ = '*')
        :name(name_),sex(sex_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
    }
    ~PERSON(){cout<<"PERSON"<<endl;}
protected:
    char *name;
    char sex;
};
 
class STUDENT:public PERSON
{
public:
    STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "100")
        :PERSON(name_,sex_),num(num_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
        cout<<"The num is "<<num<<endl;
    }
    ~STUDENT(){cout<<"STUDENT"<<endl;}
protected:
    char *num;
};
 
int main()
{
    {
    STUDENT st("zhsangsan",'x',"100");
    st.display();
    }
 
    cout<<"****************"<<endl;
 
    PERSON *p = new STUDENT("zhsangsan",'x',"100");
    p->display();
    delete p;
 
    return 0;
}

结果为:

The name is zhsangsan
The sex is x
The num is 100
STUDENT
PERSON
****************
The name is zhsangsan
The sex is x
The num is 100
PERSON

此时如果将析构函数声明为 virtual:

virtual ~PERSON(){cout<<"PERSON"<<endl;}

结果为:

The name is zhsangsan
The sex is x
The num is 100
STUDENT
PERSON
****************
The name is zhsangsan
The sex is x
The num is 100
STUDENT
PERSON

可以看出,对于堆对象来说,含有虚函数的类对象析构与栈对象析构是有所差别的。为了防止这种情况出现,最好是将含有虚函数的析构函数声明为 virtual。

注意事项

  • 因为虚函数是用在继承中的,因此只有类成员函数才能声明为虚函数
  • 静态成员函数不能是虚函数
  • 内联函数不能是虚函数
  • 构造函数不能是虚函数
  • 析构函数可以是虚函数且通常声明为虚函数

RTTI

(Run Time Type Identification,RTTI) 也叫运行时类型信息,也是通过多态实现的。

typeid

typeid 返回包含操作数数据类型信息的 type_info 对象的一个引用,信息中包括数据类型的名称。要使用 typeid,需要在函数中包含:

#include <typeinfo>
  • type_info 重载了操作符 ==,!= 用来进行比较
  • 函数 name() 返回类型名称
  • type_info 的拷贝和赋值都是私有的,因此不可拷贝和赋值
#include <iostream>
#include <typeinfo>
 
using namespace std;
 
typedef void (*Func)();
 
class Base1
{
};
 
class Base2
{
public:
    virtual ~Base2(){}
};
 
class Derive1:public Base1
{
};
 
class Derive2:public Base2
{
};
 
int main()
{
    cout<<typeid(int).name()<<endl;
    cout<<typeid(double).name()<<endl;
    cout<<typeid(char *).name()<<endl;
    cout<<typeid(char **).name()<<endl;
    cout<<typeid(const char *).name()<<endl;
    cout<<typeid(const char * const ).name()<<endl;
    cout<<"********************"<<endl;
 
    cout<<typeid(Func).name()<<endl;
    cout<<typeid(Base1).name()<<endl;
    cout<<typeid(Base2).name()<<endl;
    cout<<typeid(Derive1).name()<<endl;
    cout<<typeid(Derive2).name()<<endl;
    cout<<"********************"<<endl;
 
    Derive1 d;
    Base1 &b = d;
    cout<<typeid(b).name()<<endl;
    cout<<typeid(d).name()<<endl;
    cout<<"********************"<<endl;
 
    Derive2 dd;
    Base2 &bb = dd;
    cout<<typeid(bb).name()<<endl;
    cout<<typeid(dd).name()<<endl;
    cout<<"********************"<<endl;
 
    Base1 *p = &d;
    cout<<typeid(p).name()<<endl;
    cout<<typeid(*p).name()<<endl;
    cout<<typeid(d).name()<<endl;
    cout<<boolalpha<<(typeid(*p)== typeid(d))<<endl;
    cout<<"********************"<<endl;
 
    Base2 *pp = &dd;
    cout<<typeid(pp).name()<<endl;
    cout<<typeid(*pp).name()<<endl;
    cout<<typeid(dd).name()<<endl;
    cout<<boolalpha<<(typeid(*pp)== typeid(dd))<<endl;
    cout<<"********************"<<endl;
 
    return 0;
}

结果为:

i
d
Pc
PPc
PKc
PKc
********************
PFvvE
5Base1
5Base2
7Derive1
7Derive2
********************
5Base1
7Derive1
********************
7Derive2
7Derive2
********************
P5Base1
5Base1
7Derive1
false
********************
P5Base2
7Derive2
7Derive2
true
********************

从上边可以看出,在 typeid 涉及到虚函数时,利用指针得到的结果就可能出现差别,因此在使用 typeid 时需要注意:

  • 确保基类中至少定义了一个虚函数(虚析构也可)
  • 在涉及到虚函数时,尽量不要将 typeid 应用于指针,而是应用于引用,或者解引用的指针
  • typeid 是一个运算符,而不是函数
  • typeid 运算符返回的 type_info 类型,其拷贝构造函数和赋值运算函数都声明为 private,因此不能用于 stl 容器。也因此我们一般不直接保存 type_info,而是保存 type_info 的 name 信息

Notice how the type that typeid considers for pointers is the pointer type itself(both a and b are of type class Base *). However, when typeid is applied to objects(like *a and *b) typeid yields their dynamic type (i.e. the type of their most derived complete object).

If the type typeid evaluates is a pointer preceded by the dereference operator (*), and this pointer has a null value, typeid throws a bad_typeid exception.

typecast

在之前的文章中,我们简单介绍过 static_cast,reininterpreter_cast,const_cast 的用法,当时还剩下一个 dynamic_cast。

dynamic_cast 是一种运行时的类型转换方式,因此用于运行时的转换判断。该转换能够检查指针所指向的类型,然后判断这一类型与转换的目标类型是否相同,如果是返回对象地址,如果不是返回 NULL。

dynamic_cast 常用于多态继承中,来判断父类指针的真实指向。

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class A
{
public:
    virtual ~A(){}
};
 
class B:public A
{
};
 
class C:public A
{
};
 
class D
{
};
 
int main()
{
    B b;
    A *pa = &b;
 
    B *pb = dynamic_cast<B*>(pa); //成功
    cout<<pb<<endl;
 
    C *pc = dynamic_cast<C*>(pa); //成功 安全
    cout<<pc<<endl;
 
    D *pd = dynamic_cast<D*>(pa); //成功 安全
    cout<<pd<<endl;
 
    pb = static_cast<B*>(pa); //成功
    cout<<pb<<endl;
 
    pc = static_cast<C*>(pa); //成功 不安全
    cout<<pc<<endl;
 
    pb = reinterpret_cast<B*>(pa); //成功 不安全
    cout<<pb<<endl;
 
    pc = reinterpret_cast<C*>(pa); //成功 不安全
    cout<<pc<<endl;
 
    pd = reinterpret_cast<D*>(pa); //成功 不安全
    cout<<pd<<endl;
 
    return 0;
}

结果为:

0x61fe8c
0
0
0x61fe8c
0x61fe8c
0x61fe8c
0x61fe8c
0x61fe8c

在上述几种类型转换中,dynamic_cast 的转换用法算是比较安全的,因为这种转换方式是先比较再返回的,而 reininterpreter_cast 则是最不安全的,因为这种转换方式不做类型检查直接将源类型重新解释为目标类型,容易出错。

但 dynamic_cast 的目标类型必须是类的指针或者引用。

多态实现

虚函数

之前介绍函数重载,也就是静多态是通过 name mangling 实现的,而 C++ 的动多态则是通过虚函数表(virtual table)实现的。这个表中主要是一个类的虚函数的地址表,这张表包含了继承,override 的情况。在实际使用中,在含有虚函数的类对象中,该表会被分配到该对象的内存中,用于指明实际所要调用的函数。

C++ 编译器保证虚函数表的指针存在于实例对象的最前面,这表示实例对象的地址就是该虚函数表的位置,然后就可以遍历其中的函数指针,进行调用。

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    void f() { cout << "Base::f" << endl; }
    void g() { cout << "Base::g" << endl; }
    void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
int main()
{
    Base b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    return 0;
}

结果为:

sizeof(Base) = 4
sizeof(b) = 4

如果基类中存在虚函数,则为:

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
int main()
{
    Base b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    return 0;
}

结果为:

sizeof(Base) = 8
sizeof(b) = 8

可以看出有虚函数的基类的大小会比没有虚函数的基类大小多出一个指针的大小。这个多出来的指针就是虚函数表的位置。

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
typedef void (*FUNC)(void);
 
int main()
{
    Base b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    cout<<&b<<endl;
    cout<<*((int **)*(int *)(&b)+0)<<endl;
    cout<<*((int **)*(int *)(&b)+1)<<endl;
    cout<<*((int **)*(int *)(&b)+2)<<endl;
    cout<<*((int **)*(int *)(&b)+3)<<endl;
 
    FUNC pf = NULL;
    pf = (FUNC)*((int **)*(int *)(&b)+0);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+1);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+2);
    pf();
 
    return 0;
}

结果为:

sizeof(Base) = 8
sizeof(b) = 8
0x61fe94
0x4029f0
0x402a24
0x402a58
0x3a434347
Base::f
Base::g
Base::h

上面的程序中:

  • 先将 &b 转换为 int *(这样能够保证 +1 一次增加一个指针的大小),取得虚函数表的地址
  • 然后,再次取址就得到了第一个虚函数的地址,也就是 Base::f
  • 最后再转换为 int **,通过 +1,+2 后取址,就能够得到 Base::g,Base::h

一般继承(no override)

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
class Derive:public Base
{
    virtual void f1() { cout << "Base::f1" << endl; }
    virtual void g1() { cout << "Base::g1" << endl; }
    virtual void h1() { cout << "Base::h1" << endl; }
};
 
typedef void (*FUNC)(void);
 
int main()
{
    Derive b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(Derive) = "<<sizeof(Derive)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    cout<<&b<<endl;
    cout<<*((int **)*(int *)(&b)+0)<<endl;
    cout<<*((int **)*(int *)(&b)+1)<<endl;
    cout<<*((int **)*(int *)(&b)+2)<<endl;
    cout<<*((int **)*(int *)(&b)+3)<<endl;
    cout<<*((int **)*(int *)(&b)+4)<<endl;
    cout<<*((int **)*(int *)(&b)+5)<<endl;
    cout<<*((int **)*(int *)(&b)+6)<<endl;
 
    FUNC pf = NULL;
    pf = (FUNC)*((int **)*(int *)(&b)+0);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+1);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+2);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+3);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+4);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+5);
    pf();
 
    return 0;
}

结果为:

sizeof(Base) = 8
sizeof(Derive) = 8
sizeof(b) = 8
0x61fe94
0x402ae0
0x402b14
0x402b48
0x402b94
0x402bc8
0x402bfc
0x3a434347
Base::f
Base::g
Base::h
Base::f1
Base::g1
Base::h1

在上边的例子中,派生类没有 override 任何父类的函数,并又重新定义了几个虚函数,因此对于派生类来说:

  • 虚函数按照其声明顺序在表中存放
  • 父类的虚函数在子类的虚函数前边

一般继承(override)

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
class Derive:public Base
{
    virtual void f() { cout << "Base::f1" << endl; }
    virtual void g1() { cout << "Base::g1" << endl; }
    virtual void h1() { cout << "Base::h1" << endl; }
};
 
typedef void (*FUNC)(void);
 
int main()
{
    Derive b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(Derive) = "<<sizeof(Derive)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    cout<<&b<<endl;
    cout<<*((int **)*(int *)(&b)+0)<<endl;
    cout<<*((int **)*(int *)(&b)+1)<<endl;
    cout<<*((int **)*(int *)(&b)+2)<<endl;
    cout<<*((int **)*(int *)(&b)+3)<<endl;
    cout<<*((int **)*(int *)(&b)+4)<<endl;
    cout<<*((int **)*(int *)(&b)+5)<<endl;
 
    FUNC pf = NULL;
    pf = (FUNC)*((int **)*(int *)(&b)+0);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+1);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+2);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+3);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+4);
    pf();
 
    return 0;
}

结果为:

sizeof(Base) = 8
sizeof(Derive) = 8
sizeof(b) = 8
0x61fe94
0x402b54
0x402ad4
0x402b08
0x402b88
0x402bbc
0x3a434347
Base::f1
Base::g
Base::h
Base::g1
Base::h1

在上边的例子中,派生类 override 了父类的 f 函数,并又重新定义了几个虚函数,因此对于派生类来说:

  • override 的 f 函数被放到了虚函数表中原来基类虚函数的位置
  • 没有 override 的函数不变

过程推断

Base *b = new Derive();
b->f();

这段代码的实际过程为:

  • 明确 b 类型
  • 通过指向虚函数表的指针和偏移量,来匹配虚函数的地址
  • 根据地址调用虚函数

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • C/C++多态深入探究原理

    目录 多态 虚表和虚表指针 多态 面向对象编程有三大特性:继承.封装和多态. 其中,多态又分为编译时多态和运行时多态.编译多态是通过重载函数体现的,运行多态是通过虚函数体现的. 多态是如何实现的呢?下面举个例子: #include <iostream> using namespace std; class Base { public: virtual void fun() { cout << " Base::func()" << endl; } vo

  • C++多态的全面讲解

    目录 1.多态的定义和实现 多态的浅层理解 多态的构成条件 2.虚函数 虚函数的重写规则 虚函数重写条件的两个例外 1.协变(返回值不同) 2.析构函数的重写(函数名不同) 3.C++11 override 和 final override final 重载对比重定义(隐藏)与重写(覆盖) 4.抽象类 接口继承与实现继承 1.多态的定义和实现 多态的浅层理解 多态,即多种形态,也就是说,不同的对象在完成某个行为时会产生不同的状态. 举个例子,买火车票时,普通人正常买票,学生半价买票,军人优先买票

  • C++多态的示例详解

    目录 案例一:计算器 案例要求 代码实现 运行效果 案例二:制作饮品 案例要求 代码实现 运行效果 案例三:电脑组装 案例要求 代码实现 运行效果 今天就以三个案例来把C++多态的内容结束.第一个案例就是用多态写一个计算器并实现简单的加减乘除操作:第二个案例就是一个饮品制作,主要就是抽象类重写方法:第三个是比较综合的电脑组装案例,正文会详细介绍:那么就开始上手操作吧! 案例一:计算器 案例要求 使用多态实现计算器的加减乘除操作 代码实现 class AbstractCalculator { pu

  • C++学习之多态的使用详解

    目录 前言 多态 向上转型 向下转型 作用域 前言 最近为了完成数据库系统的实验,又复习起了<C++ Primer>,上一次看这本巨著也是大二下的六月份,那时看面向对象程序编程这一章还云里雾里的,没有领会多态的奥妙,学完 Java 之后回头再看这一章发现对多态有了更好的理解.好记性不如烂笔头,本篇博客将会对 C++ 的多态机制做一个不太详细的总结,希望下一次不需要从头再看一遍<C++ Primer>了 _(:з」∠)_. 多态 多态离不开继承,首先来定义一个基类 Animal,里面

  • C++利用多态实现职工管理系统(项目开发)

    分析 首先看一下这个项目的文件: 主要分为两部分: 主体部分: main.cpp和workManager.h,workManager.cpp职工部分(这里采用多态的方式编写): 主要是worker.h和worker.cpp 三种职位:boss,employee,manager 经过分析是否大概知道了其中各部分的意思呢? 看起来这里面有很多,但是正是这种多个文件编写才时代码更加简洁. 所以在正式写项目之前一定要先考虑好整体架构,在进行编写. 项目整体架构: 这个项目的难度并不大,主要是要学会这个项

  • C++多态实现方式详情

    注:文章转自公众号:Coder梁(ID:Coder_LT) 在我们之前介绍的继承的情况当中,派生类调用基类的方法都是不做任何改动的调用. 但有的时候会有一些特殊的情况,我们会希望同一个方法在不同的派生类当中的行为是不同的.举个简单的例子,比如speak方法,在不同的类当中的实现肯定是不同的.如果是Human类,就是正常的说话,如果是Dog类可能是汪汪,而Cat类则是喵喵. 在这种情况下只是简单地使用继承是无法满足我们的要求的,最好能够有一个机制可以让方法有多种形态,不同的对象去调用的逻辑不同.这

  • PHP5中实现多态的两种方法实例分享

    在PHP5中,变量的类型是不确定的,一个变量可以指向任何类型的数值.字符串.对象.资源等.我们无法说PHP5中多态的是变量. 我们只能说在PHP5中,多态应用在方法参数的类型提示位置.一个类的任何子类对象都可以满足以当前类型作为类型提示的类型要求.所有实现这个接口的类,都可以满足以接口类型作为类型提示的方法参数要求.简单的说,一个类拥有其父类.和已实现接口的身份. 通过实现接口实现多态 复制代码 代码如下: <?phpinterface User{ // User接口    public fun

  • C++中关于多态实现和使用方法

    目录 赋值兼容 实例 多态 静多态 动多态 格式 实例 override 纯虚函数 含有虚函数的析构函数 注意事项 RTTI typeid typecast 多态实现 虚函数 一般继承(no override) 一般继承(override) 过程推断 都说 C++ 是面向对象的语言,其中的面向对象主要包括三部分:继承,封装,多态.继承和封装我们之前就简单介绍过,这里主要对多态的使用方法做一个简单说明. 赋值兼容 赋值兼容说的是在使用基类对象的地方可以使用公有继承类的对象来代替.赋值兼容是一种默认

  • java 对象实例化过程中的多态特性解析

    目录 java 对象实例化过程中的多态特性 通过案例说明 通过上述代码 java对象的三个特性(封装.继承.多态) 1.封装 2.继承 3.多态 java 对象实例化过程中的多态特性 执行对象实例化过程中遵循多态特性 ==> 调用的方法都是实例化的子类中的重写方法,只有明确调用了super.xxx关键词或者是子类中没有该方法时,才会去调用父类相同的同名方法. 通过案例说明 package com.njau.test1; class Test { public static void main(S

  • C++中的多态与虚函数的内部实现方法

    1.什么是多态 多态性可以简单概括为"一个接口,多种行为". 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和模板来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.

  • C# 中的多态底层虚方法调用详情

    目录 一.C# 中的多态玩法 1. 一个简单的 C# 例子 2. 汇编代码分析 (1)eax,dword ptr [ebp-8] (2)eax,dword ptr [eax] (3)eax,dword ptr [eax+28h] (4)call dword ptr [eax+10h] 三.总结 前言: 本质上来说,CoreCLR 也是 C++ 写的,所以也逃不过用 虚表 来实现多态的玩法, 不过玩法也稍微复杂了一些,希望本篇对大家有帮助. 一.C# 中的多态玩法 1. 一个简单的 C# 例子 为

  • 进一步理解Java中的多态概念

    多态性有两种: 1)编译时多态性 对于多个同名方法,如果在编译时能够确定执行同名方法中的哪一个,则称为编译时多态性. 2)运行时多态性 如果在编译时不能确定,只能在运行时才能确定执行多个同名方法中的哪一个,则称为运行时多态性. 方法覆盖表现出两种多态性,当对象获得本类实例时,为编译时多态性,否则为运行时多态性,例如: XXXX x1 = new XXXX(参数列表); //对象获得本类实例,对象与其引用的实例类型一致 XXX xx1 = new XXX(参数列表); x1.toString();

  • Java中的多态用法实例分析

    本文实例讲述了Java中的多态用法.分享给大家供大家参考.具体分析如下: 多态,是面向对象的程序设计语言最核心的特征.封装性.继承性都比较简单,所以这里只对多态做一个小小的笔记... 1.什么是多态? 多态意味着一个对象可以多重特征,可以在特定的情况下,表现出不同的状态,从而应对不同的属性和方法.在Java中,多态的实现指的是使用同一个实现接口,以实现不同的对象实例. 例如,我们定义一个Parent类,再定义一个getName()方法返回一个字符串,定义一个形参为Parent类型的成员方法doS

  • Java编程—在测试中考虑多态

    面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承是为了重用父类代码.两个类若存在IS-A的关系就可以使用继承.,同时继承也为实现多态做了铺垫.那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开: 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底

  • C#中的多态深入理解

    封装.继承.多态,面向对象的三大特性,前两项理解相对容易,但要理解多态,特别是深入的了解,对于初学者而言可能就会有一定困难了.我一直认为学习OO的最好方法就是结合实践,封装.继承在实际工作中的应用随处可见,但多态呢?也许未必,可能不经意间用到也不会把它跟"多态"这个词对应起来.在此抛砖引玉,大家讨论,个人能力有限,不足之处还请指正. 之前看到过类似的问题:如果面试时主考官要求你用一句话来描述多态,尽可能的精炼,你会怎么回答?当然答案有很多,每个人的理解和表达不尽相同,但我比较趋向这样描

  • JS中的多态实例详解

    多态在面向对象编程语言中是十分重要的.在JAVA中是通过继承来得到多态的效果.如下: public abstract class Animal { abstract void makeSound(); // 抽象方法 } public class Chicken extends Animal{ public void makeSound(){ System.out.println( "咯咯咯" ); } } public class Duck extends Animal{ publi

随机推荐