1、虚拟基类
在一个虚函数的声明语句的分号前加上 =0;就可以将一个虚函数变成纯虚函数,其中,=0只能出现在类内部的虚函数声明语句处。纯虚函数只用声明,而不用定义,其存在就是为了提供接口,含有纯虚函数的类是抽象基类。我们不能直接创建一个抽象基类的对象,但可以创建其指针或者引用。值得注意的是,你也可以为纯虚函数提供定义,不过函数体必须定义在类的外部。但此时哪怕在外部定义了,也是纯虚函数,不能构建对象。纯虚函数应该在派生类中重写,否则派生类也是抽象类,不能实例化。
Class SiBianXing //不能实例化哟
{
public:
virtual int Area()= 0;
};
2、虚函数
虚函数是一种在基类定义为virtual的函数,并在一个或多个派生类中再定义的函数。虚函数的特点是,只要定义一个基类的指针,就可以指向派生类的对象。
注:无虚函数时,遵循以下规则:C++规定,定义为基类的指针(引用),也能作指向派生类的指针(引用)使用,并可以用这个指向派生类对象的指针访问继承来的基类成员;但不能用它访问派生类的成员。
l 使用虚函数实现运行时的多态性的关键在于:必须通过基类指针访问这些函数。
l 一旦一个函数定义为虚函数,无论它传下去多少层,一直保持为虚函数。
l 把虚函数的再定义称为重写(overriding)而不叫重载(overloading)。
Class A{ virtual void Pirint(){cout<<”基类”;} }
Class B:public A{ virtual void Print(){“子类”;} }
A* p = new B;p->Print();//结果 “子类”;
虚函数表
在子类成员函数中调用函数情况:
上面的 改为如此 调用:
A* pA = new B;
pA->TestFun();
打印结果一样的。所以说,无论是子类对象还是父类指针,只要是子类构造的,在父类成员函数里,或者直接调用虚函数,都会调用子类的虚函数。但是,在父类构造函数里调用虚函数,调用的却是父类的虚函数,因为在构造父类时,子类还未构造,怎么可能调用子类的函数呢。
3、重载,重定义,重写
重载(overload):函数名字相同,但它的形参个数或者顺序(或者类型不同)注意不能靠返回类型来判断(意思就是比如有两个函数参数相同,返回类型却不同,会有重定义的编译错误)。函数重载必然发生在同一个作用域中(这里应该在同一个类中)。
重写(override):派生类重定义基类的虚函数(即会覆盖基类的虚函 数)。基类子类参数必须相同,否则还是重定义。返回值也必须相同或协变,否则会有 重写虚函数返回类型有差异且不是协变 的编译错误。
重定义(redefine):派生类对基类的成员函数重新定义(即派生类定义了某个函数)该函数的名字与基类中的函数名字一样。不管这个函数的参数列表是不是与基类中的函数相同,返回类型是否相同,则这个同名的函数就会把基类中的所有这个同名的函数的所有重载版本都隐藏了,这时并不是在派生类中重载基类的同名成员函数,而是隐藏(重定义造成了隐藏)。
比如你的基类中有一个成员函数:void func(int i);而子类中又定义了一个void func();那么此时,基类中的void func(int i)就被自动隐藏了,子类对象不能直接调用它。
如果派生类覆盖了基类中的成员函数或成员变量,则当派生类的对象调用该函数或变量时是调用的派生类中的版本,若想在派生类中调用基类的成员函数或成员变量,加一个 基类:: 即可。如b.A::m_Int;b.A::fun()。
4、虚基类
名字来源于虚拟(virtual)继承?
如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。
在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性。
C++提供虚基类(virtual base class )的方法,使得在继承间接共同基类时只保留一份成员。
注意: 虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。
经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。
一个类A中的变量会汇集所有基类中的变量,若多重继承导致多份相同的变量,除非最早拥有此变量int a来自只有一个类Base,并且Base的子类都是virtual继承,此A 中只有一个 int a,Base为虚基类;若int a 由两个基类Base1和Base2创造,即使Base1是虚基类,A中也至少有两份int a,一个来自Base1,其他的来自Base2或它的子类。
虚基类的初始化如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。
5、友元(友元函数、友元类和友元成员函数)
友元函数
友原类
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
关于友元类的注意事项:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明。
友元成员函数
使类B中的成员函数成为类A的友元函数,这样类B的该成员函数就可以访问类A的所有成员了。
当用到友元成员函数时,需注意友元声明和友元定义之间的相互依赖,在该例子中,类B必须先定义,否则类A就不能将一个B的函数指定为友元。然而,只有在定义了类A之后,才能定义类B的该成员函数。更一般的讲,必须先定义包含成员函数的类,才能将成员函数设为友元。另一方面,不必预先声明类和非成员函数来将它们设为友元。
6、编译器默认定义的构造函数
定义一个(无论参数有无,几个,类型)构造函数,编译器默认生成的构造函数便不存在了。比如:
class C{
public:
C(int){}
};
void main()
{
C c; // error C2512: “C”: 没有合适的默认构造函数可用
}
但是虽然定义一个(无论参数有无,几个,类型)构造函数,编译器默认生成的copy构造函数,copy assignment构造函数还是存在的。比如:
class C{
public:
C(int){}
};
void main()
{
C c1(1);
C c2(c1); //ok
C c3(2);
c3 = c2; //ok
}
但是,构造函数加了explicit关键字后,同参数类型的编译器生成的asignment copy构造函数,便不可用,这叫做“没有可用于隐式转换的非显式构造函数”。比如
explicit C(int){}之后,C c4 = 33;编译报错。explicit C(C&){}之后,C c5 = c4;编译报错
7、仿函数
函数指针指向类成员函数
8、函数指针指向类成员函数
class B:public A
{
public:
void Print()
{
printf("这是class A 的打印函数\n"");
}
};
typedef void(B::* AFUN)();
AFUN pfun = &B::Print;
void main()
{
B b;
(b.*pfun)();//打印 这是class A 的打印函数
}
上面这一部分原理就应用到了mfc消息映射机制里。
我们都知道static成员函数不可以调用非static成员函数,但是static成员变量是可以保存非static成员函数地址的:
class A;
typedef void(A::* AFUN)();
class A
{
public:
static AFUN FunArray[10];
public:
void Print(){
cout<<"非static类成员函数\n";
}
static void StaticFun(){
FunArray[0]();//编译错误 error C2064: 项不会计算为接受 0 个参数的函数
}
};
AFUN A::FunArray[10];
void main()
{
A a;
a.TestFun();
A::StaticFun();
system("pause");
}
虽然我们可以用static成员变量保存非static成员函数,但是想在static成员函数里,调用非static成员函数,还是不可以滴。
9、类的任何一个对象都可以访问同类其他对象的私有成员变量
class D{
private:
int m_Int;
public:
void F(D& other){
//这里没有 编译错误
int a = other.m_Int;
}
};