关于派生类和派生类提及虚函数的一些小细节
- 在派生类中,如果其基类的同名成员函数已声明为虚函数(函数声明开头加了virtual),则派生类的同名成员函数声明/定义前不用加virtual
也就是说,当我们在派生类中,为了覆盖基类的某个虚函数而进行同名函数的声明时,可以不加关键字virtual,编译器会根据两个同名函数的原型声明,自动决定派生类的某个同名函数是否会覆盖其基类中的同名函数 - 定义派生类,要么覆盖基类的虚函数,要么原封不动地继承基类的成员函数。但是如果继承基类的纯虚函数,那么这个派生类会被视为抽象类。 你无法为抽象类定义实打实的类对象。
- 如果你要覆盖基类的虚函数,那么派生类对这个虚函数提供的新定义,其函数原型(声明)必须完全符合基类所声明的函数原型。(参表,返回类型,常量性必须完全符合!
- 关于基类虚函数和派生类对于虚函数的新定义,有一个常量性不符合的例子:
class Fibonacci:public num_sequence{
public:
virtual const char* what_am_i(){return "Fibonacci\n";}
//...
//...
};
class num_sequence{
public:
virtual const char*what_am_i()const{return "num_sequence\n";}
//。。。
};
编译器会给你警告,提示what_am_i()成员函数声明并非完全吻合基类中的声明。原因出在了常量性这里,关于const放在参表后代表什么的笔记,如果不懂去查阅一下
派生类的这个函数参表后没有const。
如果此时我想输出what_am_i()函数返回的内容,如下:
int main()
{
Fibonacci b;
num_sequence p;
num_sequence *pp=&b;
cout<<pp->what_am_i();
cout<<b.what_am_i();
return 0;
//因为基类指针pp指向了派生类类对象b,所以pp->what_am_i()
//我预想会输出"Fibonacci"
}
程序输出结果:
num_sequence
Fibonacci
为嘛?我们回头看一下警告信息:what_am_i()成员函数声明并非完全吻合基类中的声明。详细分析一下,就是派生类提供的同名成员函数并未被用来覆盖基类所提供的同名函数。
-
还有一种常量性不符合的例子,说明一下,就是基类的这个(what_am_i())虚函数的函数声明的返回类型是const
char*,而派生类的想要覆盖基类的这个虚函数的函数声明的返回类型是char*,这就造成了两类的同名成员虚函数返回类型不符,编译器直接报错 -
不过基类和派生类同名成员虚函数返回类型必须完全吻合的情况有个例外。就是当基类的虚函数返回某个基类形式(通常是基类指针/引用)时,比如:
class num_sequence{
public:
//我们想让派生类的clone()函数可返回一个指针,
//指向基类num_sequence的任何一个派生类
virtual num_sequence *clone()=0;
//...
};
class Fibonacci:public num_seuqence{
public:
//在派生类中,如果其基类的同名成员函数已声明为虚函数(函数声明开头加了virtual)
//则派生类的同名成员函数声明/定义前不用加virtual
Fibonacci *clone(){return new Fibonacci(*this);}
//...
};
你会发现派生类的同名成员函数clone()它的函数定义开头没加virtual,正是因为
在派生类中,如果其基类的同名成员函数已声明为虚函数(函数声明开头加了virtual),则派生类的同名成员函数声明/定义前不用加virtual
也就是说,当我们在派生类中,为了覆盖基类的某个虚函数而进行同名函数的声明时,可以不加关键字virtual,编译器会根据两个同名函数的原型声明,自动决定派生类的某个同名函数是否会覆盖其基类中的同名函数
这个机制,基类的虚函数和其派生类的同名成员函数的函数原型就可以不吻合。这是”基类的虚函数和其派生类的同名成员函数返回类型必须完全吻合“的一个例外。
虚函数的静态解析
- 虚函数机制在这两种情况下不起作用:
①基类的构造函数和析构函数里,②使用基类的类对象而非基类指针/引用 - 基类的构造函数中,派生类的虚函数绝对不会被调用。 基类的构造函数中只可以调用基类自身的和派生类的某(那)个虚函数重名的成员函数在基类的析构函数中,也是如此规则。
- 为了能够在“单一对象中展现多种类型”,多态需要一层间接性。
关于基类指针和引用的一些说明
-
C++里唯有用基类的指针/引用才能够支持面向对象编程概念
-
为基类声明一个实际的类对象,同时也就分配出足够容纳基类实际类对象的内存空间,
-
如果这个基类的实际的类对象作为某类继承体系里的虚函数的函数参数,然后你给人家传入的是派生类的类对象,那么后果就是基类子对象被复制到当初分配的足够容纳基类实际类对象(子对象)的内存空间,其他子对象(派生类子对象等)直接丢弃(切掉) 因为当初就分配了足够容纳基类实际类对象(子对象)的内存空间,其他的子对象没给考虑分配内存空间,对吧。那也就没有足够的内存放置派生类中的各个数据成员。
举个例子奥:
//LibMat是基类,其派生类是AudioBook,print函数在类继承体系是个虚函数
void print(LibMat object,const LibMat *pointer,const LibMat &reference)
{
//以下操作调用基类的print()函数
object.print();//因为object是基类LibMat的类对象
pointer->print();
reference.print();
//这两行操作,通过虚函数机制进行解析,因为pointer和reference事先也不知道
//是调用基类还是派生类的print()函数。只能等程序运行时进行解析调用哪个print()函数
}
int main()
{
AudioBook iWish("Her pride of 10","Stanley Lippman","Jeremy Irons");
print(iWish,iWish,iWish);
//...
}
这个例子中,iWish这个派生类类对象里的基类子对象(LibMat基类的object类对象“放入了当初分配的足够容纳基类实际类对象(子对象)的内存空间,
而派生类子对象(这里是AudioBook类的类对象,如果三层及以上的类继承体系,也是只保存派生类类对象里的基类子对象,使其被复制到分配的内存空间,基类的两个及以上的派生类子对象就被切掉(丢弃了))就被切掉(丢弃了)
而在print()虚函数的函数定义的两个参数(const LibMat *pointer,const LibMat &reference
)即基类指针/引用—pointer和reference,就被初始化为指向派生类类对象(iWish)所在的内存地址。这就是基类指针/引用能够指向完整的派生类类对象的原因。