如果类中存在虚函数,无论存在多少虚函数,都只有一个vfptr虚指针,vfptr指向一个vftable虚表,vftable存放的是本类中虚函数的入口地址。
代码示例
#include
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;
}
#include
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;
}
数组下标表示当前元素距离首地址的偏移量。
求此程序运行结果?
代码示例
#include
#include
using namespace std;
int main()
{
char a[5] = { 1,1,1,1,1 };
int* p = (int*)(a);
printf("%d\n", *p);
return 0;
}
代码示例(覆盖)
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;
}
代码示例
#include
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;
}
早绑定:编译时期就确定了函数的执行时期
静态联编:将函数调用和函数体结合起来的过程
通过名字粉碎技术实现
#include
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;
}
晚绑定:程序在运行过程中确定调用关系
通过虚函数实现
晚绑定条件:
① 公有继承
② 必须有virtual关键字:基类中同名同参必须是虚函数,同名函数派生类覆盖
③ 基类的引用或者指针指向基类对象或派生类对象
#include
#include
#include
#include
#include
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;
}
代码示例
#include
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;
}
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();
}
}
virtual函数系动态绑定,而缺省参数值确是静态绑定
公有继承+虚函数+引用/指针 产生了动态联编,调用的是子类的fn函数,有同名覆盖,但是函数重写参数并没有进行重写,如果没有在主函数进行传递参数,则参数不变。
代码示例
#include
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;
}
运行结果
多重继承也会产生动态联编;
非虚函数不需要重新定义
联编是指计算机程序自身彼此关联的过程,是把一个标识符和一个存储地址联系在一起的过程,也就是把一条消息和一个对象的操作相结合的过程。
如果使用虚基类指针或引用指明派生类对象并使用该指针调用虚函数(成员选择符用箭头号"->"),则程序动态地(运行时)选择该派生类代的虚函数,称为动态联编。动态联编亦称滞后联编。
如果使用对象名字和点成员选择运算符" . "引用特定的一个对象来调用虚函数,则被调用的虚函数是在编译时期确定的(称为静态联编)
代码示例
int main()
{
Base base;
Object* op = &base;
op->print;//静态联编
base.print();//动态联编
}
虚函数表的确认要考虑函数的属性
单指针,双指针,指针数组,数组指针,指针函数,函数指针
函数指针可以作为函数参数,也可以作为函数返回值
void(*signal(int, void(*func)(int)))(int);
右左法则——先找到标志符,以标志符为界限,右边找括号整体,然后找左边,然后继续右边,继续左边,直到分析结束。
int(*p[3](int, int));
代码示例
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;
}
运行结果
virtual 返回类型 函数名 (参数表)
成员函数应尽可能地设置为虚函数,但必须注意以下几条:
代码示例
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) {}
};
只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象
静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。
实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性。
内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。
析构函数可定义为虚函数,构造函数不能定义虚函数。 因为在调用构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性
函数执行速度要稍慢一些。为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总是要付出一定代价,但通用性是一个更高的目标。
如果定义放在类外virtual只能加在函数声明前面,不能加在函数定义前面。正确的定义必须不包括virtual。
因为在构建虚表之前要先查询构造函数,如果构造函数未定义,那么虚函数就不可以继续。
析构函数可定义为虚函数,构造函数不能定义虚函数。 因为在调用构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性。
如果当前类存在指针作为数据成员,必须写析构函数;如果当前类被继承,则析构函数必须为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;
}
程序员自己定义虚析构函数
在继承关系中,父对象有虚析构函数,子对象也必须有虚析构函数
用父指针指向子对象时,调动子类析构函数
在构造函数和析构函数中调用虚函数不会产生多态(父对象中的所有函数在子对象中都被重写)
纯虚函数是指被标明为不具体实现的虚拟成员函数。它用于这样的情况:定义一个基类时,会遇到无法定义基类中虚函数的具体实现,其实依赖于不同的派生类。
定义纯虚函数的一般格式为
virtual 返回类型 函数名(参数表)=0;
表明程序员将不定义该函数,函数声明是为派生类保留一个位置。“=0”本质上是将指向函数体的指针定义为NULL。
纯虚函数的基类是不能用来定义对象的。
纯虚函数没有实现部分,不能产生对象,所以含有纯虚函数的类是抽象类。
抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。
抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。
抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。
抽象类刻画一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。
抽象类只能作为基类来使用,其纯虚函数的实现也由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
代码示例
#include
#include
#include
#include
#include
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;
}