1、 构造基类和派生类
其中A类是基类,B类是派生类。
继承关系中,每个类必须控制它自己的成员初始化过程。即B类必须使用A的构造函数来初始化基类部分。也就是B的构造函数必须是重写过的(不是继承A的)。
派生类可以覆盖基类的虚函数,但是也可以不覆盖(而使用基类的)。
基类的静态成员:基类定义一个静态成员,那么基类与所有派生类都共同拥有这仅有的一个静态成员。
#include<bits/stdc++.h>
using namespace std;
class A{
public:
A(int v):v(v){}
virtual void print(){
cout << v << endl;
}
virtual void print1(){
cout << v << endl;
}
int v;
static int x;
};
int A::x=1;
class B:public A
{
public:
B(int v):A(v){}//调用A的构造函数
void print1()override{
cout << v*10 << endl;
}
};
int main()
{
B b(10);
b.print1();//100
b.print();//10,并没有重写覆盖。
A *p = &b;//p是A类指针,动态绑定。虽然静态类型是A,但是指向b的
p->print1();//100
cout << b.x<<endl;//B类可以直接使用基类的静态成员
return 0;
}
2、深入理解动态绑定。
静态绑定:对象在声明时采用的类型,是在编译期确定的。
动态绑定:目前所指对象的类型。是在运行期决定的。对象的动态类型可以更改,但是静态类型不可以更改。
#include<bits/stdc++.h>
using namespace std;
class A{
public:
A(int v):v(v){}
void print(){
cout << v << endl;
}
virtual void print1(){
cout << v << endl;
}
int v;
};
class B:public A
{
public:
B(int v):A(v){}//调用A的构造函数
void print(){//子类重新定义了no—virture 函数,这是一个不好的设计,会导致名称遮掩。这里只是解释动态与静态绑定。
cout << v * 10 << endl;
}
void print1()override{
cout << v*10 << endl;
}
};
int main()
{
B *pb = new B(10);
A *pa = pb;
pb->print();//100 静态
pa->print();//10
pa->print1();//100 动态
pb->print1();//100
}
print函数不是虚函数,所以pb调用时对应的是B类的,pa调用的是A类的,也就是静态绑定。
而print1函数是虚函数,所以两个调用都是同一个函数(B类的),也就是动态绑定。
PS:绝不要重写no——virture 函数(违背is-a,“不变性凌驾于特异性之上”)
一句话总结:只有虚函数才是用动态绑定,其他的全部都是静态绑定。
3、虚函数与默认实参
虚函数是动态绑定的,但是为了执行效率,缺省参数是静态绑定的。
如果缺省值也是动态绑定的,那么编译期就必须要有办法在运行期间为virtue函数决定适当的缺省值,如果是这样的话,就要比目前的在编译期决定的机制更慢而且更复杂。
话说每次派生类都提供缺省值好麻烦,并且修改不容易。怎么解决呢?
NVI,non-virtual绝对不会被重新改写。
#include<bits/stdc++.h>
using namespace std;
class A{
public:
A(int v):v(v){}
virtual void print(int x = 100){
cout << x << endl;
}
int v;
};
class B:public A
{
public:
B(int v):A(v){}//调用A的构造函数
void print(int x = 10)override{
cout << x + 111 << endl;
}
};
int main()
{
B b(10);
A &p = b;
p.print();//100 + 111 = 211,缺省参数静态绑定。
}
4、三种访问权限与三种继承方式。
三种访问权限:
public:对本类,子类,外部(调用方) 均可见
protect:对外部调用不可见
private:对外部、子类不可见
三种继承方式:
不影响子类对父类的访问权限,子类对父类只看父类的访问控制权。
继承方式为了控制子类调用方对父类的访问权限。
相当于把父类的public的权限在子类中变成了相应的权限。
5、派生类可以访问基类中成员的访问权限。
但是仅限于那些可访问的基类成员(基类访问权限是private不可以)。使用using声明即可。
class A{
public:
A(int v):v(v){}
protected://如果此时是private的,子类也不可用using改变了(子类本身不能访问)
int v;
};
class B:private A{//private派生
public:
using A::v;//A::v变成public了(取决于using在哪个访问权限下面) B的用户可使用v了
B(int v):A(v){}//调用基类的构造函数
};
int main(){
B b(10);
cout << b.v << endl;//B的用户可访问了
return 0;
}
6、只有在派生类中才可以通过访问派生类对象访问基类的protected成员。而基类对象不可以。
class A{
protected:
void f(){cout << 1 << endl;}
};
class B:public A{
public:
void get(){
f();
B b;
b.f();//派生类对象为什么可以访问基类protected?
// A a;
// a.f();//基类对象不可以访问protected
}
};
int main(){
B b;
b.get();
}
7、除了重新定义虚函数外,派生类的成员一定不要和基类的成员同名。且一定要在派生类重新定义的虚函数面前加上virtual关键字(或者override)。
如果基类有序函数virtual void p();的话,那么派生类如果定义一个函数void p();由于该函数与基类的虚函数同名且调用形式相同,所以void p()也是虚函数。
如果派生类中的函数是 void p(int x = 1)的话,那么这个函数不是虚函数并且掩盖了基类虚函数名称p()。
class A{
public:
virtual void f(){cout << 1 << endl;}
};
class B:public A{
public:
void f(int x = 1){ cout << 2 << endl;}
};
int main(){
A a;
B b;
A *pa = &b;
pa->f();//1,
pa = &a;
pa->f();//1
b.f();//2
a.f();//1
}
分析:第一个pa->f(); 此时pa = &b,因为pa是一个A类指针,所以编译器在类A中寻找f函数,但是它发现f函数是virtual的,所以动态绑定B类的virtual函数,但是B类的虚函数f是A的默认实现,所以最终还是调用A::f();
第二个pa->f()类似第一个,绑定A类直接调用。
第三个第四个都是静态绑定。
8、多态基类的析构函数应该为虚函数。
class A{
public:
A(){cout << "A constructor" << endl;}
~A(){cout << "A destructor" << endl;}
};
class B:public A{
public:
B(){cout << "B constructor" << endl;}
~B(){cout << "B destructor" << endl;}
};
int main(){
A *p = new B();
delete p;
}
// A constructor
// B constructor
// A destructor
可以看到:当一个基类指针是非虚的,用它去删除一个派生类对象时,c++将不会调用整个析构函数链,结果是未定义的。只调用了A的析构函数,对象的派生部分并没有被销毁。“局部销毁”。造成资源泄露。
class A{
public:
A(){cout << "A constructor" << endl;}
virtual ~A(){cout << "A destructor" << endl;}
};
class B:public A{
public:
B(){cout << "B constructor" << endl;}
virtual ~B(){cout << "B destructor" << endl;}
};
int main(){
A *p = new B();
delete p;
}
// A constructor
// B constructor
// B destructor
// A destructor
9、关于虚函数表:
当我们创建一个对象b,&b是这个对象的地址。
(int *)&b 强制转化成int *,为了取对象地址的前4个字节,前四个字节是虚表指针。
*(int *)&b 虚表地址
*((int )(int *)&b) 第一个虚函数地址
*((int )(int *)(&b) + 1) )第二个虚函数地址
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void f() { cout << "Base1::f" << endl; }
virtual void g() { cout << "Base1::g" << endl; }
virtual void h() { cout << "Base1::h" << endl; }
};
class Base2 {
public:
virtual void f() { cout << "Base2::f" << endl; }
virtual void g() { cout << "Base2::g" << endl; }
virtual void h() { cout << "Base2::h" << endl; }
};
class Base3 {
public:
virtual void f() { cout << "Base3::f" << endl; }
virtual void g() { cout << "Base3::g" << endl; }
virtual void h() { cout << "Base3::h" << endl; }
};
class Derive : public Base1, public Base2, public Base3 {
public:
virtual void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
};
typedef void(*Fun)(void);
int main()
{
Fun pFun = NULL;
Derive d;
int** pVtab = (int**)&d;
//Base1's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);
pFun = (Fun)pVtab[0][0];
pFun();
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);
pFun = (Fun)pVtab[0][1];
pFun();
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);
pFun = (Fun)pVtab[0][2];
pFun();
//Derive's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);
pFun = (Fun)pVtab[0][3];
pFun();
//The tail of the vtable
pFun = (Fun)pVtab[0][4];
cout<<pFun<<endl;
//Base2's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
pFun = (Fun)pVtab[1][0];
pFun();
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
pFun = (Fun)pVtab[1][1];
pFun();
pFun = (Fun)pVtab[1][2];
pFun();
//The tail of the vtable
pFun = (Fun)pVtab[1][3];
cout<<pFun<<endl;
//Base3's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
pFun = (Fun)pVtab[2][0];
pFun();
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
pFun = (Fun)pVtab[2][1];
pFun();
pFun = (Fun)pVtab[2][2];
pFun();
//The tail of the vtable
pFun = (Fun)pVtab[2][3];
cout<<pFun<<endl;
return 0;
}
// Derive::f
// Base1::g
// Base1::h
// Derive::g1
// 1
// Derive::f
// Base2::g
// Base2::h
// 1
// Derive::f
// Base3::g
// Base3::h
// 0
哪些函数不能使虚函数?
1、构造函数不可以。
构造函数用来初始化对象,假如子类可以继承基类构造函数(使用虚函数),那么基类不知道子类有哪些成员。
从存储:对象未创建,vtable不存在,构造函数如果是虚的,那么需要vtable来调用,对象都还没实例化,怎么构造?
2、普通函数,友元函数
3、内联函数:编译时展开
4、静态成员函数:编译时确定