1.虚函数的一些特性
- 虚函数也有重载
#include"stdafx.h"
#include"stdlib.h"
#include<iostream>
using namespace std;
class Base
{
public:
virtual void f(float x){ cout <<"Base::f(float) "<< x << endl; }
virtual void f(int x){ cout <<"Base::f(int) "<< x << endl; }
void g(float x){ cout <<"Base::g(float) "<< x << endl; }
void h(float x){ cout <<"Base::h(float) "<< x << endl; }
};
class Derived : public Base
{
public:
virtual void f(float x){ cout <<"Derived::f(float) "<< x << endl; }
void g(int x){ cout <<"Derived::g(int) "<< x << endl; }
void h(float x){ cout <<"Derived::h(float) "<< x << endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Derived d;
Base *pb = &d;
Derived *fd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); //Derived::f(float) 3.14
fd->f(3.14f); //Derived::f(float) 3.14
Base b;
pb=&b;
pb->f(3);//Base::f<int> 3,基类虚函数的重载
system("pause");
return 0;
}
- 只要基类有virtual修饰,那么它的派生类和派生类的派生类即使没有virtual修饰,也是虚函数。
#include"stdafx.h"
#include"stdlib.h"
#include<iostream>
using namespace std;
class Base
{
public:
virtual void f(float x){ cout <<"Base::f(float) "<< x << endl; }
virtual void f(int x){ cout <<"Base::f(int) "<< x << endl; }
void g(float x){ cout <<"Base::g(float) "<< x << endl; }
void h(float x){ cout <<"Base::h(float) "<< x << endl; }
};
class Derived : public Base
{
public:
void f(float x){ cout <<"Derived::f(float) "<< x << endl; }
void g(int x){ cout <<"Derived::g(int) "<< x << endl; }
void h(float x){ cout <<"Derived::h(float) "<< x << endl; }
};
class second_Derived:public Derived
{
public:
void f(float x){ cout <<"second_Derived::f(float) "<< x << endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Derived d;
second_Derived c;
Base *pb = &d;
Derived *fd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); //Derived::f(float) 3.14
fd->f(3.14f); //Derived::f(float) 3.14
pb=&c;
pb->f(3.14f);//second_Derived::f(float) 3.14,派生类的派生类也能实现virtual调用
Base b;
pb=&b;
pb->f(3);//"Base::f(int) "
return 0;
}
- 派生类中只有函数名、形参、返回值都和基类的虚函数一致才会认为是虚函数
#include"stdafx.h"
#include"stdlib.h"
#include<iostream>
using namespace std;
class Base
{
public:
virtual void f(float x){ cout <<"Base::f(float) "<< x << endl; }
void f(int x){ cout <<"Base::f(int) "<< x << endl; }
void g(float x){ cout <<"Base::g(float) "<< x << endl; }
void h(float x){ cout <<"Base::h(float) "<< x << endl; }
};
class Derived : public Base
{
public:
void f(float x){ cout <<"Derived::f(float) "<< x << endl; }
void f(int x){ cout <<"Derived::f(int) "<< x << endl; }
void g(int x){ cout <<"Derived::g(int) "<< x << endl; }
void h(float x){ cout <<"Derived::h(float) "<< x << endl; }
};
class second_Derived:public Derived
{
public:
void f(float x){ cout <<"second_Derived::f(float) "<< x << endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Derived d;
second_Derived c;
Base *pb = &d;
Derived *fd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); //Derived::f(float) 3.14
pb->f(3); //Base::f(int) 3,此处调用的仍然是基类的函数,而不是虚函数
fd->f(3.14f); //Derived::f(float) 3.14
fd->f(3); //Derived::f(int) 3
pb=&c;
pb->f(3.14f);//second_Derived::f(float) 3.14
Base b;
pb=&b;
pb->f(3);//"Base::f(int) 3"
pb->f(3.14f);//"Base::f(float) 3.14"
system("pause");
return 0;
}
系统按一下规则来判断派生类的一个函数成员是不是虚函数
- 该函数是否与基类的虚函数有相同的名称
- 该函数是否与基类的虚函数有相同的参数个数及相同的对应参数的类型
- 该函数是否与基类的虚函数有相同的返回值或者满足类型兼容原则的指针、引用型的返回值
如果派生类的函数满足了上述条件,就会自动确认为虚函数。这时,派生类的虚函数便覆盖了基类的虚函数。不仅如此,派生类中的虚函数还会隐藏基类中同名函数的所有其它重载形式。
在VS2010中并没有隐藏基类中的同名函数。
2.构造函数调用虚函数
当基类构造函数调用虚函数时,不会调用派生类的虚函数。假设有基类Base和派生类Derived。两个类中有虚成员函数virt(),如果Base::Base()调用了虚函数virt(),则被调用的是
Base::virt(),而不是Derived::virt()。这时因为当基类被构造时,对象还不是一个派生类的对象。
同样,被基类析构时,对象已经不再是一个派生类的对象了,所以如果Base::~Base()调用的是Base::virt(),而不是Derived::virt()。
只有虚函数时动态绑定的,如果派生类需要修改基类的行为(即重写与基类函数同名的函数),就应该在基类中将相应的函数声明为虚函数。而基类中声明的非虚函数,通常代表那些不希望被派生类改变的功能,也是不能实现多态的。因此一般不要重写继承而来的非虚函数。
在重写继承来的虚函数时,如果函数有默认形参值,千万不要重新定义不同的值。原因是虽然虚函数是动态绑定的,但默认形参值是静态绑定的。也就是说,通过一个指向派生类对象的基类指针,可以访问到派生类的虚函数,但默认形参值却只能来自基类的定义。
3.纯虚函数
纯虚函数的定义为:
virtual 函数类型 函数名(参数表)=0
带有纯虚函数的类就是抽象类。抽象类派生出新的类后,如果派生类给出纯虚函数的函数实现,这个派生类就可以定义自己的对象,因而不再是抽象类。反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类。
抽象类不能实例化,既不能定义一个抽象类的对象。但是,我们可以声明一个抽象类的的指针和引用。通过指针或引用,我们就可以访问派生类的对象了
4.友元函数
形式
friend 类型名 友元函数名(形参表);
然后在类体外对友元函数进行定义,定义的格式和普通函数相同,但可以通过对象作为参数直接访问对象的私有成员
说明如下:
1)必须在类的说明中说明友元函数,说明时以关键字friend开头,后跟友元函数的函数原型,友元函数的说明可以出现在类的任何地方,包括在private和public部分;
2)注意友元函数不是类的成员函数,所以友元函数的实现和普通函数一样,在实现时不用"::“指示属于哪个类,只有成员函数才使用”::"作用域符号;
3)友元函数不能直接访问类的成员,只能访问对象成员,
4)友元函数可以访问对象的私有成员,但普通函数不行;
5)调用友元函数时,在实际参数中需要指出要访问的对象,
6)类与类之间的友元关系不能继承。
7)一个类的成员函数也可以作为另一个类的友元,但必须先定义这个类。
#include<iostream>
#include<string>
using namespace std;
class A;
class B
{
public:
void put(const A& a);
};
class A
{
public:
A()
{
m_str="";
}
A(const char *str): m_str(str) {}
A(string str): m_str(str) {}
friend class B;
friend void B::put(const A&);
private:
string m_str;
};
void B::put(const A& a)
{
cout<<a.m_str<<endl;
}
int main()
{
A a="friend";
B b;
b.put(a);
return 0;
}