首页 > 其他分享 >多态与虚函数1

多态与虚函数1

时间:2023-04-05 09:34:07浏览次数:27  
标签:调用 虚表 函数 派生类 多态 基类 构造函数

多态

虚函数

定义

成员函数前面加上virtual关键字的函数,称为虚函数。

重定义(重写)的要求

  1. 基类与派生类中函数名要相同
  2. 函数的参数列表也要相同(参数的个数、类型、顺序)
  3. 函数的返回类型一致

小结:基类与派生类中的同名虚函数,除了函数体可以不一样之外,其他的全部都要保持一致。(函数名,函数返回类型,函数的参数列表)

虚函数的实现原理(重要)

当基类定义了虚函数的时候,就会在基类对象的存储布局的前面多一个虚函数指针,该虚函数指针会指向虚函数表(虚表),虚表中存放的是虚函数的入口地址。当派生类继承基类的时候,会吸收基类的虚函数,同样会在派生类对象的存储布局的前面多一个虚函数指针,该虚函数指针指向派生类自己的虚表,该虚表里面存放的是从基类吸收的虚函数的入口地址,当派生类对该虚函数进行重写时,会用派生类自己的虚函数的入口地址进行覆盖,存放的就是派生类自己的虚函数的入口地址

虚函数被激活的五个条件(重要)

  1. 基类要定义虚函数
  2. 派生类要重写(重定义、覆盖)该虚函数
  3. 创建派生类的对象
  4. 要用基类的指针(引用绑定)指向派生类的对象
  5. 要用基类的指针(引用)调用虚函数

那些函数不能设置为虚函数?

  1. 普通函数(全局函数,非成员函数,自由函数):因为虚函数定义中必须是成员函数
  2. 静态成员函数:静态成员函数发生的时机在编译的时候,而虚函数要体现多态,必须在运行的时候;静态成员函数没有this指针,被该类的所有对象共享,对该类而言是唯一的
  3. 内联函数:同样发生的时机在编译的时候;内联函数如果写成虚函数,那么就失去了内联的含义(内联函数目的就是在编译时发生,而虚函数是在运行时发生的)
  4. 友元函数:如果友元函数本身是一个普通函数,那么友元函数不能被设置为虚函数;但是如果友元函数本身是成员函数,那么该友元函数是可以被设置为虚函数的
  5. 构造函数:同样发生的时机在编译的时候;构造函数是不可以被继承的,而虚函数是可以被继承的;如果将构造函数设置为虚函数,那么就需要通过虚表找到虚函数的入口地址,则需要虚函数指针指向虚表,而虚函数指针存储在对象存储布局的最前面,而如果构造函数不调用,那么对象就不一定是完整的,那么对象的存储布局的前面就不一定有虚函数指针,但是如果没有虚函数指针就不能指向虚表来找到函数入口,前后矛盾

虚函数的访问

  1. 使用指针进行访问:可以体现动态多态
  2. 使用引用进行访问:可以体现动态多态
  3. 使用对象进行访问:虚函数体现的就是普通成员函数的特性,在编译的时候就已经确定了函数调用
  4. 使用其他普通成员函数进行访问
  5. 使用特殊成员函数(构造函数、析构函数):在调用Father的构造函数的时候,调用func1()的时候,派生类Son的对象还没有构建完成,所以就看不到虚表这套逻辑,所以只能看到Father自己的func1()。而当派生类Son销毁时,虚表已经被销毁了,所以在调用Father的func2()的时候,已经看不到Son这套逻辑,所以就只会调用到Father自己的func2函数。都没有体现动态多态
#include <iostream>

using std::cout;
using std::endl;

class Grandpa
{
public:
    Grandpa()
    {
        cout << "Grandpa()" << endl;
    }

    ~Grandpa()
    {
        cout << "~Grandpa()" << endl;
    }

    virtual
    void func1()
    {
        cout << "void Grandpa::func1()" << endl;
    }

    virtual
    void func2()
    {
        cout << "void Grandpa::func2()" << endl;
    }
};

class Father
: public Grandpa
{
public:
    Father()
    : Grandpa()
    {
        cout << "Father()" << endl;
        func1();
    }

    ~Father()
    {
        cout << "~Father()" << endl;
        func2();
    }

    virtual
    void func1()
    {
        cout << "void Father::func1()" << endl;
    }

    virtual
    void func2()
    {
        cout << "void Father::func2()" << endl;
    }
};

class Son
: public Father
{
public:
    Son()
    : Father()
    {
        cout << "Son()" << endl;
    }

    ~Son()
    {
        cout << "~Son()" << endl;
    }

    virtual
    void func1()
    {
        cout << "void Son::func1()" << endl;
    }

    virtual
    void func2()
    {
        cout << "void Son::func2()" << endl;
    }
};

int main()
{
    Son son;//栈对象
    return 0;
}

运行结果:

两个加深理解的代码

1

#include <iostream>

using std::cout;
using std::cin;
using std::endl;

class A
{
public:
    A() 
    { 
        cout << "A's cons." << endl; 
    }


    virtual 
        ~A() 
        { 
            cout << "A's des." << endl; 
        }

    virtual 
        void f() 
        { 
            cout<<"A's f()."<<endl; 
        }

    void g() 
    { 
        f();
    }

};

class B 
: public A
{
public:
    B() 
    { 
        f(); 
        cout << "B's cons." << endl; 
    }

    ~B() 
    { 
        cout << "B's des." << endl; 
    }
};

class C 
: public B
{
public:
    C() 
    { 
        cout<<"C's cons."<<endl; 
    }

    ~C()
    { 
        cout<<"C's des."<<endl;
    }

    void f() 
    { 
        cout<<"C's f()."<<endl; 
    }

};
int  main(void)
{  
    A *pa=new C();
    pa->g();

    delete pa;

    return 0;

}

运行结果:

1:在调用B类的构造函数时会调用f函数,此时C对象还没有构建完整,且B类里面并没有重写f函数,并没有满足激活虚函数的五个条件,只能看到A类里面的f函数,最后也只能调用A类里的f函数。

2:对于pa->g(),这里调用g函数,再通过this指针调用g里面的f函数,且这个指针是指向派生类(C)的基类(A)类型的指针,满足了激活虚函数的五个条件,所以会调用C类里面的f函数。

2

#include <iostream>

using std::cout;
using std::cin;
using std::endl;

class A
{ 
public:
    virtual
        void func(int val = 1)
        {
            cout << "A->" << val << endl;
        }
    virtual void test()
    {
        func();
    }
private:
    long _a;
};

class B
: public A
{ 
public:
    virtual
        void func(int val = 10)
        {
            cout << "B->" << val << endl;
        }
private:
    long _b;
};

class C
: public B
{
    virtual
        void func(int val = 10)
        {
            cout << "C->" << val << endl;
        }   

};

int main(void)
{
    B b;
    A *p1 = (A*)&b;
    B *p2 = &b;
    p1->func();
    p2->func();

    return 0;
}

运行结果:

注意:为什么用 p1调用func函数时会打印 B->1?

显然,这里是满足虚函数激活的五个条件的,但是虚函数是在运行时起作用的,故在编译阶段已经确定了func里面的参数val的值,而p1是A类型的指针,编译阶段会走A类里面的func函数,故在编译阶段将val的值确定为1了,然后在运行阶段调用B里面的func函数。

标签:调用,虚表,函数,派生类,多态,基类,构造函数
From: https://www.cnblogs.com/MyXjil/p/17288838.html

相关文章

  • linux下c语言的crypt函数怎么用?
    linux的crypt最近学校布置了一个网安的小作业,要用到linux里面的这个crypt函数,写一篇总结一下。首先我们要了解这个函数是用来做什么的。密码影子文件中存储了每一个用户的用户明文和其单向哈希过的秘文cipher="$1$C68vnJ27$1ttFZ1/Rylq/xi350A0NI0";密码字段用\(id\)salt$......
  • 函数模板
    一:基本范例 a)模板的定义是以template关键字开头的 b)类型模板参数T前面用typename来修饰,遇到typename就该知道其后面跟的是一个类型。typename可以被class取代 c)类型模板参数T(代表一个类型),前面的修饰符typename/class都用<>括起来 d)T这个名字可以换成任意其他标识符  二:实......
  • 函数
    '''函数的意义'''#求每个字符串的长度str1="fqs123"str2="doudou"count=0foriinstr1:count+=1print(str(count))count=0foriinstr2:count+=1print(str(count))'''函数def函数名(传入参数):......
  • Kotlin中函数式编程的详解
     一、函数式编程理解我们一直在学习面向对象编程范式,另个一个较知名的编程范式是诞生于20世纪50年代,基于抽象数学的λ(lambda)演算发展而来的函数式编程,尽管函数式编程更常用在学术而非软件领域,但它的一些原则适用于任何编程语言。函数式编程范式主要依赖于高阶函数(以函数为参数或......
  • 如何编写高质量的 JS 函数(3) --函数式编程[理论篇]
    作者:杨昆 【编写高质量函数系列】中,《如何编写高质量的JS函数(1)--敲山震虎篇》介绍了函数的执行机制,此篇将会从函数的命名、注释和鲁棒性方面,阐述如何通过JavaScript编写高质量的函数。 《如何编写高质量的JS函数(2)--命名/注释/鲁棒篇》从函数的命名、注释和鲁棒性方面,阐......
  • C++多态
    多态是C++面向对象最主要的特性之一,多态即多种形态,多个不同对象对同一事物会产生不同状态多态的实现1.虚函数重写实现多态的必要条件是虚函数,父类中有虚函数,子类重写虚函数,实现同一对象的不同表达方式。2.多态的要求 (1、被调用的函数必须是虚函数,子类对父类的虚函......
  • 析构函数
    析构函数c++primerp444 1.构造函数初始化对象的非静态数据成员。2.析构函数释放对象使用的资源,并销毁对象的非静态数据成员。3.一个类只有唯一的析构函数。析构函数没有参数,不接受重载。  1.构造函数中,对象的成员初始化是在函数体执行之前完成的,并且按照它们在类中......
  • Python 内置函数map()
    内置函数mapmap(func,可迭代对象1,可迭代对象2...)map函数可以接收一个函数和一个或多个可迭代对象。接收的func函数有几个参数,就需要接收几个可迭代对象。map接收的函数会作用于可迭代对象的每个元素,并且返回一个值。map将可迭代对象的每个元素经过func函数处理返回的值组成......
  • Python3内置函数之R系列
    1、range()在Python中,range()函数用于创建一系列数字的序列,常用于for循环中,可以接受1到3个参数,具体形式如下:range(stop):表示生成从0开始到stop-1结束的整数序列,步长为1。range(start,stop):表示生成从start开始到stop-1结束的整数序列,步长为1。range(start,stop,step)......
  • Python3内置函数之P系列
    1、pow()pow()函数是Python内置函数之一,用于计算一个数的幂。它接受两个参数,第一个参数为底数,第二个参数为指数,如果提供第三个参数,则表示对结果取模。 2、print()print()函数是Python内置函数之一,用于输出指定的对象。它可以接受多个参数,用逗号分隔,它们将被依次输出,并且......