如果子类定义了与父类中原型相同的函数会发生什么?在子类中定义与父类中原型相同的函数叫做函数重写。函数重写只发生在父类与子类之间。
#include <cstdlib>
#include <iostream>
using namespace std;
class Parent
{
public:
void print()
{
cout<<"I'm Parent..."<<endl;
}
};
class Child : public Parent
{
public:
void print()
{
cout<<"I'm Child..."<<endl;
}
};
int main(int argc, char *argv[])
{
Child child;
child.print();
child.Parent::print();
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
1. 父类中被重写的函数依然会继承给子类;
2. 默认情况下子类中重写的函数将隐藏父类中的函数;
3. 通过作用域分辨符::可以访问到父类中被隐藏的函数;
当函数重写遇上赋值兼容性原则。你期望下面的程序输出什么?
#include <cstdlib>
#include <iostream>
using namespace std;
class Parent
{
public:
void print()
{
cout<<"I'm Parent..."<<endl;
}
};
class Child : public Parent
{
public:
void print()
{
cout<<"I'm Child..."<<endl;
}
};
void howToPrint(Parent* p)
{
p->print();
}
void run()
{
Child child;
Parent* pp = &child;
Parent& rp = child;
child.print();
pp->print();
rp.print();
}
int main(int argc, char *argv[])
{
run();
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
1. C++与C相同,是静态编译型语言;
2. 在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;
3. 所以编译器认为父类指针指向的是父类对象(根据赋值兼容性原则,这个假设合理);
4. 由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象;
5. 从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数;
下面函数调用后的输出是什么?
在编译这个函数的时候,编译器不可能知道指针 p 究竟指向了什么。但是编译器没有理由报错。于是,编译器认为最安全的做法是编译到父类的print函数,因为父类和子类肯定都有相同的print函数。
面向对象的新需求
1. 根据实际的对象类型来判断重写函数的调用;
2. 如果父类指针指向的是父类对象则调用父类中定义的函数;
3. 如果父类指针指向的是子类对象则调用子类中定义的重写函数;
多态:同样的调用语句有多种不同的表现形态。
C++中的多态支持
C++中通过virtual关键字对多态进行支持,使用virtual声明的函数被重写后即可展现多态特性。
#include <cstdlib>
#include <iostream>
using namespace std;
class Boss
{
private:
static Boss* cInstance;
Boss()
{
}
public:
static Boss* GetInstance()
{
if( cInstance == NULL )
{
cInstance = new Boss();
}
return cInstance;
}
int fight()
{
cout<<"Boss::fight()"<<endl;
return 10;
}
};
Boss* Boss::cInstance = NULL;
class Master
{
public:
virtual int eightSwordKill()
{
cout<<"Master::eightSwordKill()"<<endl;
return 8;
}
};
class NewMaster : public Master
{
public:
virtual int eightSwordKill()
{
cout<<"NewMaster::eightSwordKill()"<<endl;
return Master::eightSwordKill() * 2;
}
};
void fieldPK(Master* master, Boss* boss)
{
int k = master->eightSwordKill();
int b = boss->fight();
if( k < b )
{
cout<<"Master is killed..."<<endl;
}
else
{
cout<<"Boss is killed..."<<endl;
}
}
int main(int argc, char *argv[])
{
Boss* boss = Boss::GetInstance();
cout<<"Master vs Boss"<<endl;
Master master;
fieldPK(&master, boss);
cout<<"New Master vs Boss"<<endl;
NewMaster newMaster;
fieldPK(&newMaster, boss);
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
重载和重写有什么区别?什么时候是重载,什么时候是重写?那种效率更高?
函数重载
1. 必须在同一个类中进行;
2. 子类无法重载父类的函数,父类同名函数将被覆盖;
3. 重载是在编译期间根据参数类型和个数决定调用函数;
函数重写
1. 必须发生于父类与子类之间;
2. 并且父类与子类中的函数必须有完全相同的原型;
3. 使用virtual声明之后能够产生多态;
4. 多态是在运行期间根据具体对象的类型决定调用函数;
注:函数重载效率更高。
#include <cstdlib>
#include <iostream>
using namespace std;
class Parent
{
public:
virtual void func()
{
cout<<"void func()"<<endl;
}
virtual void func(int i)
{
cout<<"void func(int i)"<<endl;
}
virtual void func(int i, int j)
{
cout<<"void func(int i, int j)"<<endl;
}
};
class Child : public Parent
{
public:
void func(int i, int j)
{
cout<<"void func(int i, int j)"<<" "<<i + j<<endl;
}
void func(int i, int j, int k)
{
cout<<"void func(int i, int j, int k)"<<" "<<i + j + k<<endl;
}
};
void run(Parent* p)
{
p->func(1, 2);
}
int main(int argc, char *argv[])
{
Parent p;
p.func();
p.func(1);
p.func(1, 2);
Child c;
//c.fun(); //Error
c.func(1, 2);
run(&p);
run(&c);
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
是否可以将类的每个成员函数都声明为虚函数?
C++中多态的实现原理
1. 当类中声明虚函数时,编译器会在类中生成一个虚函数表;
2. 虚函数表是一个存储类成员函数指针的数据结构;
3. 虚函数表是由编译器自动生成与维护的,并且虚函数表会编译到最终的可执行文件中;
4. virtual成员函数会被编译器放入虚函数表中;
5. 存在虚函数时,每个对象中都有一个指向虚函数表的指针;
注:出于效率考虑,没有必要将所有成员函数都声明为虚函数。
对象中的VPTR指针什么时候被初始化?
1. 对象在创建的时候由编译器对VPTR指针进行初始化;
2. 只有当对象的构造完全结束后VPTR的指向才最终确定;
3. 父类对象的VPTR指向父类虚函数表;
4. 子类对象的VPTR指向子类虚函数表;
注:构造函数中调用虚函数无法实现多态。
#include <cstdlib>
#include <iostream>
using namespace std;
class Parent
{
public:
Parent()
{
this->func();
}
virtual void func()
{
cout<<"void Parent::func()"<<endl;
}
};
class Child : public Parent
{
public:
void func()
{
cout<<"void Child::func()"<<endl;
}
};
int main(int argc, char *argv[])
{
Child c;
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
执行Child c;创建对象的时候。首先调用父类的构造函数,然后C++编译器对VPTR指针进行初始化,即VPTR ---> parent::VTABLE;然后构造函数有调用了fun();C++编译发现fun()是虚函数,然后就到虚函数表中查找,然后就找到了父类中的fun();所以结果就是我们看到的样子。即构造函数中调用虚函数无法实现多态。
纯虚函数
在现实中需要知道具体的图形类型才能求面积,所以对概念上的“图形”求面积是没有意义的!
这个类的设计完全脱离实际,没有任何的意义!
用Shape作为基类进行继承
#include <cstdlib>
#include <iostream>
using namespace std;
class Shape
{
public:
virtual double area() = 0;
};
class Rectangle : public Shape
{
double m_a;
double m_b;
public:
Rectangle(double a, double b)
{
m_a = a;
m_b = b;
}
double area()
{
return m_a * m_b;
}
};
class Circle : public Shape
{
double m_r;
public:
Circle(double r)
{
m_r = r;
}
double area()
{
return 3.14 * m_r * m_r;
}
};
void area(Shape* s)
{
cout<<s->area()<<endl;
}
int main(int argc, char *argv[])
{
Rectangle rect(2, 3);
Circle circle(4);
area(&rect);
area(&circle);
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
面向对象中的抽象类
1. 抽象类可用于表示现实世界中的抽象概念;
2. 抽象类是一种只能定义类型,而不能产生对象的类;
3. 抽象类只能被继承并重写相关函数;
4. 抽象类的直接特征是纯虚函数;
注:纯虚函数是只声明函数原型,而故意不定义函数体的虚函数。
抽象类与纯虚函数
1. 抽象类不能用于定义对象;
2. 抽象类只能用于定义指针和引用;
3. 抽象中的纯虚函数必须被子类重写;
小结:
1. 函数重载与函数重写不同;
2. 多态是通过虚函数表实现的;
3. 虚函数在效率上会受到影响;
4. 抽象类可用于表示现实世界中的抽象概念;
5. 抽象类是通过纯虚函数实现的;
参考:https://www.runoob.com/w3cnote/cpp-virtual-functions.html