class Parent
{
public:
Parent(int a) { this->a = a; }
//void print() { cout << "Parent print a: " << a << endl; }
virtual void print() { cout << "Parent print a: " << a << endl; }
//virtual 父类写了virtual,子类可写可不写
private:
int a;
};
class Child : public Parent
{
public:
Child(int b):Parent(20) { this->b = b; }
// 子类的和父类的函数名字一样
void print() { cout << "Child print b: " << b << endl; }
private:
int b;
};
void printFunc1(Parent* p) { p->print(); }
void printFunc2(Parent &p) { p.print(); }
void main()
{
Parent* p = NULL;
Parent p1(10);
Child c1(1);
{
// 指针
p = &p1;
p->print();
p = &c1;
p->print();
// 引用
Parent &p2 = p1;
p2.print();
Parent &p3 = c1;
p3.print();
// 指针做函数参数
printFunc1(&p1);
printFunc1(&c1);
// 引用做函数参数
printFunc2(p1);
printFunc2(c1);
}
// 以上均执行父类的打印函数
// 如何根据实际传入的对象类型,判断函数的调用,即
// --> 让一种调用语句 有多种表现形态
// --> 使用关键字 virtual
system("pause");
}
需求:(同样的调用语句有多种不同的表现形态)
? 根据实际的对象类型来判断重写函数的调用;
? 如果父类指针指向的是父类对象则调用父类中定义的函数;
? 如果父类指针指向的是子类对象则调用子类中定义的重写函数 ;
解决:通过 Virtual 关键字对多态进行支持
多态是设计模式的基础,多态是框架的基础
#include "iostream"
#include <string>
using namespace std;
class C
{
public:
virtual string code()
{
return "OPP:Procedure Oriented Programming";
}
};
class Cplusplus : public C
{
public:
virtual string code()
{
return "OOP:Object Oriented Programming";
}
};
string getcode(C* obj)
{
return obj->code();
}
void main()
{
C obj1;
Cplusplus obj2;
std::cout << getcode(&obj1) << std::endl;
std::cout << getcode(&obj2) << std::endl;
system("pause");
}
①继承;②函数重写(虚函数);③父类指针(引用)指向子类对象
联编:是指一个程序模块、代码之间互相关联的过程。
静态联编:是程序的匹配、连接在编译阶段实现,也称为早期匹配。例子:函数重载
动态联编:指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。 例子:switch、if、多态
虚析构函数用于指引 delete 运算符正确析构动态对象
class A
{
public:
A()
{
p = new char[20];
strcpy(p, "obja");
printf("A()\n");
}
virtual ~A()
{
delete[] p;
printf("~A()\n");
}
private:
char *p;
};
class B : public A
{
public:
B()
{
p = new char[20];
strcpy(p, "objb");
printf("B()\n");
}
virtual ~B()
{
delete[] p;
printf("~B()\n");
}
private:
char *p;
};
// 通过父类指针 释放所有的子类资源 --> 执行所以的析构函数
void todelete(A* obj)
{
delete obj;
}
void main()
{
// new
B* obj = new B;
// delete 直接通过子类对象释放资源
//delete obj;
todelete(obj);
//正常情况下,只执行了父类的析构函数
system("pause");
}
重载是在编译期间根据参数类型和个数决定函数调用;
必须在同一个类中进行;
子类无法重载父类的函数,父类同名函数将被名称覆盖;
必须发生于父类与子类之间;
并且父类与子类中的函数必须有完全相同的原型(参数类型和个数);
虚函数重写:将发生多态;非虚函数重写:重定义
当类中声明虚函数时,编译器会在类中生成一个虚函数表 ;
虚函数表是一个存储类成员函数指针的数据结构;
当存在虚函数时,每个对象中都有一个指向虚函数表的指针(VPTR,一般作为类对象的第一个成员 )
存在虚函数时,每个对象中都有一个指向虚函数表的指针(VPTR 指针)
Base base; ---> 找到VPTR指针 ---> 虚函数表 ---> 执行函数
通过虚函数表指针 VPTR 调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。
而普通成员函数是在编译时就确定了调用的函数。
// 基类
class Base
{
public:
Base() {};
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base::fun2()" << endl;
}
~Base() {};
private:
int a;
};
// 派生类
class Derived : public Base
{
public:
Derived() {};
void fun1()
{
cout << "Derived::fun1()" << endl;
}
~Derived() {};
private:
int b;
};
void main()
{
cout << "Base sizeof: " << sizeof(Base) << endl;
Base base;
//对象的前四个字节就是虚函数表
int vptr_addr = *(int*)&base;
printf("base 的虚函数表地址为:%08X\n", vptr_addr);
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)();
pFunction pFn;
// 通过vptr指针访问虚表,加上偏移量 取出单个函数地址
// 执行 Base::fun1()
int temp1 = *((int*)vptr_addr + 0);
pFn = (pFunction)temp1;
pFn();
// 执行 Base::fun2()
int temp2 = *((int*)vptr_addr + 1);
pFn = (pFunction)temp2;
pFn();
return;
}
VPTR,一般作为类对象的第一个成员;
所以 sizeof(Base)
其中有四个字节是 VPTR指针(32位)
对象在创建的时,由编译器对 VPTR 指针进行初始化;
只有当对象的构造完全结束后 VPTR 的指向才最终确定;
父类对象的 VPTR 指向父类虚函数表;子类对象的 VPTR 指向子类虚函数表;
Child c1;
// 初始化是分步的
// 先执行父类的构造函数时, c1的VPTR指向父类的虚函数表
// 当父类的构造函数指向完后,才会把c1的VPTR指向子类的虚函数表
// --> 子类对象构造时,在父类的构造函数调用虚函数,无法产生多态。
指针运算是按照指针所指的类型进行的。
如果子类中有新的成员,那么子类的类型大小和父类的类型大小不相同,也就是步长不同。
不要使用 父类指针++的方式操作数组,父类p++与子类p++的步长不同。
p++ --> p=p+1 --> p = (unsigned int)basep + sizeof(*p)
效果 ---> 实现 ---> 条件 ---> 理论 ---> 意义 ---> 原理
效果:多态使同样的调用语句有多种不同的表现形态;
实现:virtual 关键字,告诉编译器这个函数要支持多态;
条件:继承;virtual 重写、父类指针(引用)指向子类对象;
理论:动态联编
意义:设计模式的基础
原理:虚函数表,函数指针做函数参数
存在虚函数时,每个对象中都有一个指向虚函数表的指针(VPTR 指针)
Base base; ---> 找到VPTR指针 ---> 虚函数表 ---> 执行函数
谈谈你对重写,重载理解
是否可类的每个成员函数都声明为虚函数,为什么?
多态的实现原理,虚函数指向效率低
对象中的 VPTR 指针初始化(分步初始化);
子类对象构造时,在父类的构造函数调用虚函数,无法产生多态。
当存在虚函数时,每个对象中都有一个指向虚函数表的指针(VPTR,一般作为类对象的第一个成员 )
虚析构函数用于指引 delete 运算符正确析构动态对象
原文:https://www.cnblogs.com/2dx3906/p/13174998.html