C++ 15:虚表,虚函数,多态,指针


1. 虚表

1.1 类中虚函数

如果类中存在虚函数,无论存在多少虚函数,都只有一个vfptr虚指针,vfptr指向一个vftable虚表,vftable存放的是本类中虚函数的入口地址。
在这里插入图片描述

1.2 类模型示例图

代码示例

#include<iostream>
using namespace std;

class A
{
public:
	virtual void fa() { cout << "A:fa" << endl; }
	virtual void ga() { cout << "A:ga" << endl; }
	virtual void ha() { cout << "A:ha" << endl; }
	void ff() { cout << "A:FF" << endl; }
private:
	int m_i;
};
class B
{
};
int main()
{
	cout << sizeof(A) << endl;
	A a;
	return 0;
}

代码图解
在这里插入图片描述

1.3 代码示例

取指针A类中的fa函数
在这里插入图片描述
用指针取出函数

#include<iostream>
using namespace std;

class A
{
public:
	virtual void fa() { cout << "A:fa" << endl; }
	virtual void ga() { cout << "A:ga" << endl; }
	virtual void ha() { cout << "A:ha" << endl; }
};
class B :public A
{
	virtual void fb() { cout << "B:fb" << endl; }
	virtual void gb() { cout << "B:gb" << endl; }
};
int main()
{
	cout << sizeof(B) << endl;
	B b;
	typedef void (*Fun)();
	Fun pf = (Fun) * ((int*)*(int*)(&b));
	pf();//A:fa();

	pf = (Fun) * ((int*)*(int*)(&b) + 1);
	pf();//A:ga();

	pf = (Fun) * ((int*)*(int*)(&b) + 2);
	pf();//A:ha();

	pf = (Fun) * ((int*)*(int*)(&b) + 3);
	pf();//B:fb();

	pf = (Fun) * ((int*)*(int*)(&b) + 4);
	pf();//B:gb();

	return 0;
}

代码图解
在这里插入图片描述
运行结果
在这里插入图片描述

1.4 数组下标为什么从0开始?

数组下标表示当前元素距离首地址的偏移量。

1.5 面试例题

求此程序运行结果?

代码示例

#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
	char a[5] = { 1,1,1,1,1 };

	int* p = (int*)(a);

	printf("%d\n", *p);
	return 0;
}

运行结果
在这里插入图片描述
代码图解
在这里插入图片描述

1.6 单继承

代码示例(覆盖)

class A
{
public:
	virtual void fa() { cout << "A::fa" << endl; }
	virtual void ga() { cout << "A::ga" << endl; }
	virtual void ha() { cout << "A::ha" << endl; }
};
class B :public A
{
public:
	virtual void fa() { cout << "B::fa" << endl; }
	virtual void ga() { cout << "B::hb" << endl; }
};
int main()
{
	cout << sizeof(B) << endl;
	B b;
	return 0;
}

代码图解
在这里插入图片描述

1.7 多继承

代码示例

#include<iostream>
using namespace std;

class A
{
public:
	virtual void fa() { cout << "A::fa" << endl; }
	virtual void ga() { cout << "A::ga" << endl; }
};
class B
{
public:
	virtual void fb() { cout << "B::fb" << endl; }
	virtual void gb() { cout << "B::gb" << endl; }
};
class C
{
public:
	virtual void fc() { cout << "C::fc" << endl; }
	virtual void gc() { cout << "C::gc" << endl; }
};
class D :public A, public B, public C
{
public:
	virtual void fd() { cout << "D::fd" << endl; }
	virtual void gd() { cout << "D::gd" << endl; }
};
int main()
{
	cout << sizeof(D) << endl;//12字节,3个虚表指针
	D d;
	return 0;
}

代码图解
在这里插入图片描述

2. 多态

2.1 多态分类

  • 重载多态——函数重载,运算符重载
  • 强制多态——4种强制类型转化,static_cast,const_cast…
  • 包含多态——virtual多态
  • 参数多态——类模板,函数模板(把类型作为参数传递)

2.2 早绑定和晚绑定

2.2.1 编译时期的多态

早绑定:编译时期就确定了函数的执行时期

静态联编:将函数调用和函数体结合起来的过程

  • 编译时的多态——通过函数的重载和运算符的重载来实现的

通过名字粉碎技术实现

#include<iostream>
using namespace std;

int max(int a, int b)
{
	cout << "int max" << endl;
	return a > b ? a : b;
}
double max(double a, double b)
{
	cout << "double max" << endl;
	return a > b ? a : b;
}
char max(char a, char b)
{
	cout << "char max" << endl;
	return a > b ? a : b;
}
int main()
{
	cout << max(4, 6) << endl;
	cout << max('a', 'z') << endl;
	cout << max(1.2, 2.3) << endl;
	return 0;
}

2.2.2 运行时期的多态

晚绑定:程序在运行过程中确定调用关系

  • 运行时的多态——运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据具体情况动态地来确定。它是通过类的继承关系public和虚函数来实现的。目的也是建立一种通用的程序。通用性是程序追求的主要目标之一。

通过虚函数实现

晚绑定条件:

① 公有继承
② 必须有virtual关键字:基类中同名同参必须是虚函数,同名函数派生类覆盖
③ 基类的引用或者指针指向基类对象或派生类对象

#include<iostream>
#include<list>
#include<type_traits>
#include<initializer_list>
#include<string>
using namespace std;
class Animal
{
private:
    string name;
public:
    Animal(const string& na) :name(na) {}
public:
    virtual void eat() {}
    virtual void walk() {}
    virtual void tail() {}
    virtual void PrintInfo() {}

    string& get_name() { return name; }
    const string& get_name() const { return name; }
};
class Dog :public Animal
{
private:
    string owner;
public:
    Dog(const string& ow, const string na) :Animal(na), owner(ow) {}
    virtual void eat()
    {
        cout << "Dog Eat:bone" << endl;
    }
    virtual void walk()
    {
        cout << "Dog Walk:run" << endl;
    }
    virtual void tail()
    {
        cout << "Dog Tail:wang wang" << endl;
    }
    virtual void PrintInfo()
    {
        cout << "Dog owner: " << owner << endl;
        cout << "Dog name: " << get_name() << endl;
    }
};
class Cat :public Animal
{
private:
    string owner;
public:
    Cat(const string& ow, const string na) :Animal(na), owner(ow) {}
    virtual void eat()
    {
        cout << "Dog Eat:fish" << endl;
    }
    virtual void walk()
    {
        cout << "Dog Walk:silent" << endl;
    }
    virtual void tail()
    {
        cout << "Dog Tail:miao miao" << endl;
    }
    virtual void PrintInfo()
    {
        cout << "Dog owner: " << owner << endl;
        cout << "Dog name: " << get_name() << endl;
    }
};
void fun(Animal& animal)
{
    animal.eat();
    animal.walk();
    animal.tail();
    animal.PrintInfo();
}
int main()
{
    Dog dog("yhping", "hashiqi");
    Cat cat("hm", "Persian");

    fun(dog);
    fun(cat);
    return 0;
}

运行结果
在这里插入图片描述
编译时期的多态
在这里插入图片描述

2.3 多态的产生原理(虚表的运行原理)

  1. 产生多态的条件:
    ① 公有继承
    ② 必须有virtual关键字:基类中同名同参必须是虚函数,同名函数派生类覆盖
    ③ 基类的引用或者指针指向基类对象或派生类对象
  2. 虚表是什么?

2.4 多态实现

2.4.1 代码示例

代码示例

#include<iostream>
using namespace std;
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)

class Shape
{
public:
	Shape(const char* name = "")
	{
		m_name = new char[strlen(name) + 1];
		strcpy(m_name, name);
	}
	virtual void Draw() {}
	virtual void Area() {}
	virtual void Grith() {}
private:
	char* m_name;
};

class Rectangle :public Shape
{
public:
	Rectangle() :Shape("Rectangle") {}
	virtual void Draw()
	{
		cout << "Rectangle Drew" << endl;
	}
private:
	int m_length;
	int m_width;
};

class Circle :public Shape
{
public:
	Circle() :Shape("Circle") {}
	virtual void Draw()
	{
		cout << "Circle Draw" << endl;
	}
};

class Triangle :public Shape
{
public:
	Triangle() :Shape("Triangle") {}
	virtual void Draw()
	{
		cout << "Triangle Draw" << endl;
	}
};

主函数1

int main()
{
	Shape* pa = NULL;
	Circle cc;
	pa = &cc;
	pa->Draw();
	Rectangle r;
	pa = &r;
	pa->Draw();
	Triangle t;
	pa = &t;
	pa->Draw();
	return 0;
}

运行结果1
在这里插入图片描述
主函数2

int main()
{
	//指针数组
	Shape* pa[3];
	pa[0] = new Rectangle;
	pa[1] = new Circle;
	pa[2] = new Triangle;
	for (int i = 0; i < 3; ++i)
	{
		pa[i]->Draw();
		pa[i]->Area();
		pa[i]->Grith();
	}
}

运行结果2
在这里插入图片描述

2.5 绝不重新定义继承而来的缺省参数值

virtual函数系动态绑定,而缺省参数值确是静态绑定

公有继承+虚函数+引用/指针 产生了动态联编,调用的是子类的fn函数,有同名覆盖,但是函数重写参数并没有进行重写,如果没有在主函数进行传递参数,则参数不变。

代码示例

#include<iostream>
using namespace std;

class Parent
{
public:
	virtual void fn(int a = 10)
	{
		cout << "Parent fn a =" << a << endl;
	}
};
class Child :public Parent
{
public:
	virtual void fn(int b = 20)
	{
		cout << "Child fn b=" << b << endl;
	}
};
int main()
{
	Child c;
	Parent* p = &c;
	p->fn();
	return 0;
}

运行结果
在这里插入图片描述
多重继承也会产生动态联编;
非虚函数不需要重新定义

2.6 动态联编

  1. 联编是指计算机程序自身彼此关联的过程,是把一个标识符和一个存储地址联系在一起的过程,也就是把一条消息和一个对象的操作相结合的过程。

  2. 如果使用虚基类指针或引用指明派生类对象并使用该指针调用虚函数(成员选择符用箭头号"->"),则程序动态地(运行时)选择该派生类代的虚函数,称为动态联编。动态联编亦称滞后联编。

  3. 如果使用对象名字和点成员选择运算符" . "引用特定的一个对象来调用虚函数,则被调用的虚函数是在编译时期确定的(称为静态联编)

代码示例

int main()
{
    Base base;
    Object* op = &base;
    op->print;//静态联编
    base.print();//动态联编
}

虚函数表的确认要考虑函数的属性

3. 指针

单指针,双指针,指针数组,数组指针,指针函数,函数指针

函数指针可以作为函数参数,也可以作为函数返回值

3.1 例1:函数指针函数

void(*signal(int, void(*func)(int)))(int);

在这里插入图片描述
右左法则——先找到标志符,以标志符为界限,右边找括号整体,然后找左边,然后继续右边,继续左边,直到分析结束。

3.2 例2:函数指针数组

int(*p[3](int, int));

在这里插入图片描述
p是个数组,数组里面存放指针,指针指向函数。

3.3 指针函数指针数组——转移表

代码示例

int Max(int a, int b)
{
	return a > b ? a : b;
}
int Min(int a, int b)
{
	return a < b ? a : b;
}
int Add(int a, int b)
{
	return a + b;
}
int Sub(int a, int b)
{
	return a / b;
}
int main()
{
	int(*fp[])(int, int) = { Max,Min,Add,Sub };
	int n = sizeof(fp) / sizeof(fp[0]);
	for (int i = 0; i < n; ++i)
	{
		cout << fp[i](4, 7) << endl;
	}
	return 0;
}

运行结果

在这里插入图片描述

3.4 下列定义的含义

在这里插入图片描述

4. 虚函数

4.1 定义

  • 虚函数是一个类的成员函数:

virtual 返回类型 函数名 (参数表)

  • 关键字virtual 指明该成员函数为虚函数。virtual 仅用于类定义中,如虚函数在类外定义,可不加virtual。
  • 当一个类的成员函数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特性。

4.2 成员函数与虚函数

成员函数应尽可能地设置为虚函数,但必须注意以下几条:

  1. 派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是重载,而不是虚函数。

代码示例

class Object
{
public:
    virtual void fun(){}
};
class Base :public Object
{
public:
    virtual void fun(int x) {}
};

如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个例外。

代码示例

class Object
{
public:
    virtual Object* fun() {}
};
class Base :public Object
{
public:
    virtual Base* fun(int x) {}
};
  1. 只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象

  2. 静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。

  3. 实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性。

  4. 内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。

  5. 析构函数可定义为虚函数,构造函数不能定义虚函数。 因为在调用构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性

  6. 函数执行速度要稍慢一些。为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总是要付出一定代价,但通用性是一个更高的目标。

  7. 如果定义放在类外virtual只能加在函数声明前面,不能加在函数定义前面。正确的定义必须不包括virtual。

4.3 为什么构造函数(移动构造函数,拷贝构造函数)不可以定义为虚函数?

  • 因为在构建虚表之前要先查询构造函数,如果构造函数未定义,那么虚函数就不可以继续。

  • 析构函数可定义为虚函数,构造函数不能定义虚函数。 因为在调用构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性。

4.4 为什么析构函数多数为虚函数?

  • 如果当前类存在指针作为数据成员,必须写析构函数;如果当前类被继承,则析构函数必须为virtual,为实现多态,防止内存泄漏。

  • 因为将析构函数设置为虚函数是为了防止存在指针没有释放,产生内存泄漏。

  • 虚析构——特殊多态

代码示例

class A
{
public:
	A()
	{
		cout << "A" << endl;
		m_i = new int;
	}
	virtual ~A()
	{
		cout << "~B" << endl;
		delete m_i;
	}
private:
	int* m_i;
};
class B :public A
{
public:
	B()
	{
		cout << "B" << endl;
	}
	~B()
	{
		cout << "~B" << endl;
	}
};
int main()
{
	A* pb = new B;
	delete pb;
	return 0;
}

代码图解
在这里插入图片描述
在这里插入图片描述

4.5 虚析构函数什么时候存在?

  1. 程序员自己定义虚析构函数

  2. 在继承关系中,父对象有虚析构函数,子对象也必须有虚析构函数

  3. 用父指针指向子对象时,调动子类析构函数

  4. 在构造函数和析构函数中调用虚函数不会产生多态(父对象中的所有函数在子对象中都被重写)

4.6 纯虚函数

4.6.1 定义和特点

  • 纯虚函数是指被标明为不具体实现的虚拟成员函数。它用于这样的情况:定义一个基类时,会遇到无法定义基类中虚函数的具体实现,其实依赖于不同的派生类。

  • 定义纯虚函数的一般格式为
    virtual 返回类型 函数名(参数表)=0;

  • 表明程序员将不定义该函数,函数声明是为派生类保留一个位置。“=0”本质上是将指向函数体的指针定义为NULL。

4.6.2 抽象类特点

  1. 纯虚函数的基类是不能用来定义对象的。

  2. 纯虚函数没有实现部分,不能产生对象,所以含有纯虚函数的类是抽象类。

  3. 抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。

  4. 抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。

  5. 抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。

  6. 抽象类刻画一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。

  7. 抽象类只能作为基类来使用,其纯虚函数的实现也由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。

4.6.3抽象类规定

  • 抽象类只能用作其他类的基类,不能建立抽象类对象
  • 抽象类不能用作参数类型、函数返回类型或显式转换的类型
  • 可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性

4.7 虚表调动

代码示例

#include<iostream>
#include<list>
#include<type_traits>
#include<initializer_list>
#include<string>
using namespace std;
class Object
{
private:
    int value;
public:
    Object(int x = 0) :value(x) {}
    virtual void add()
    {
        cout << "Object::add" << endl;
    }
    virtual void fun()
    {
        cout << "Obect::fun" << endl;
    }
    virtual void print() const
    {
        cout << "Object::print" << endl;
    }
};
class Base :public Object
{
private:
    int sum;
public:
    Base(int x = 0) :Object(x + 10),sum(x) 
    {

    }
    virtual void add()
    {
        cout << "Base::add" << endl;
    }
    virtual void fun()
    {
        cout << "Base::fun" << endl;
    }
    virtual void print() const
    {
        cout << "Base::print" << endl;
    }
};
int main()
{
    Base base(10);
    Object* op = &base;

    return 0;
}

代码图解
在这里插入图片描述


  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值