一、继承是为了实现代码复用。有三种继承方式:公有继承(public)、保护继承(protected)、私有继承(private)。
class Base
{
public:
int ma;
protected:
int mb;
private:
int mc;
};
class Derive:public Base
{
public:
int md;
protected:
int me;
private:
int mf;
};
没有访问限定符默认私有继承。Base称为基类/父类,Derive称为派生类/子类,派生类继承基类。在派生类对象内存布局,显示基类继承过来的,然后才是自己的,如上边的Derive的内存布局为:
Base::
ma
mb
mc
Derive::
md
me
mf
二、继承除构造函数和析构函数以外的所有方法和数据,包括作用域,但是友元关系不能继承。
继承来的访问限定是什么?如下图所示:
基类中的私有成员继承到派生类但是不可见,也就是不能访问。一个类的私有成员除了自己和友元其他都不可见。
三、派生类对象的构造方式:最先构造基类的成员对象,再构造继承部分,然后构造派生类的成员对象,最后构造派生类部分,析构则相反。
class Base
{
public:
Base(int a):ma(a)
{
cout<< "Base()" <<endl;
}
~Base()
{
cout<< "~Base()" <<endl;
}
void show()
{
cout<< "Base::show()" <<endl;
}
void show(int i)
{
cout<< "Base::show(int)" <<endl;
}
protected:
int ma;
};
class Derive:public Base
{
public:
Derive(int data):mb(data),Base(data)//初始化列表中调用基类的构造函数
{
cout << "Derive()" <<endl;
}
~Derive()
{
cout << "~Derive()" <<endl;
}
void show()
{
cout<< "Derive::show()" <<endl;
}
private:
int mb;
};
四、基类和派生类中同名成员方法的关系:
重载关系(overload):同作用域、同名、参数列表不同
隐藏关系(overhide):继承关系下隐藏了基类所有同名函数,加作用域可以访问被隐藏的函数
覆盖关系(override):虚函数表上派生类中的函数覆盖掉基类中的同名且参数列表相同的虚函数
五、C++支持的四种类型强转:
const_cast:去掉对象的const属性
static_cast:编译器认为可以支持的强转,安全性更高
reinterpret_cast:与C语言方式相同
dynamic_cast:RTTI(run-time type information)强制转换
六、基类和派生类的相互指向或引用
派生类对象可以赋给基类对象,编译器支持从下到上的转换。
派生类指针(引用)不能指向基类对象,基类指针(引用)可以指向派生类对象。
Base *p = &d;
p->show();
//调用Base中的show,因为指针类型是Base
//编译期间就已经确定
//Derive *q = &b;//error
七、虚函数
虚函数指针vfptr,指向虚函数表vftable,虚函数表中存放虚函数的入口地址。
基类中有虚函数时,派生类中的同名函数自动变成虚函数。基类和派生类中的同名且参数列表相同的虚函数,在虚函数表上会覆盖掉基类中的同名函数。每个对象都有一个vfptr,但是同一类只有一个vftable。编译期间形成vftable,存放在只读数据段,生命周期为从程序开始到程序结束。
由此可见,成为虚函数有2个条件:
1、对象调用(虚表通过虚函数指针找到,虚函数指针在对象中)
2、能取地址(入口地址放在虚函数表中)
构造函数、inline函数、static成员函数不能写成虚函数。析构函数可以写成虚函数,即虚析构。析构基类指针指向堆内存上的对象时,delete会导致派生类的析构函数无法调用,造成资源浪费。对于继承和派生来说,虚析构相当于一个特殊的同名函数。基类的析构函数写成虚函数,派生类的析构函数自动变成虚函数,调用派生类虚析构也会析构积累资源。
show函数不是虚函数时,汇编时call Base::show,也就是说在编译期间已经确定,称为静态绑定(早绑定);当show函数为虚函数时,call eax,也就是说在运行时才知道调用函数的地址,称为动态的绑定(晚绑定)。这个过程称为运行时的多态,也就是说可以通过一个指针调用多个函数。(指针指向或引用虚函数时才会产生运行时多态,对象本身调用不会产生多态)
静态的多态(编译期的绑定):模板、函数重载
动态的多态(运行时的绑定):虚函数
Base b(2);
Derive d(5);
b.show();//静态绑定
d.show();//静态绑定
Base *pb1 = &b;
pb1->show();
Base *pb2 = &d;
pb2->show();
Base &rb1 = b;
rb1.show();
Base &rb2 = d;
rb2.show();
Derive *pd1 = &d;
pd1->show();
Derive &rd1 = d;
rd1.show();
八、纯虚函数
class Animal
{
public:
Animal(string name):_name(name){}
virtual void bark() = 0;//纯虚函数
protected:
string _name;
};
拥有纯虚函数的类称为抽象类,不能定义对象,但可以写成指针或引用。
class Animal
{
public:
Animal(string name):_name(name){}
virtual void bark() = 0;//纯虚函数
protected:
string _name;
};
class Cat:public Animal
{
public:
Cat(string name):Animal(name){}
virtual void bark()
{
cout << _name << " miaomiao" <<endl;
}
};
class Dog:public Animal
{
public:
Dog(string name):Animal(name){}
virtual void bark()
{
cout << _name << " wangwang" <<endl;
}
};
void showbark(Animal *p)
{
p->bark();
}
int main()
{
Animal *p1 = new Cat("cat");
Animal *p2 = new Dog("dog");
int *p11 = (int*)p1;
int *p22 = (int*)p2;
int tmp = p11[0];
p11[0] = p22[0];
p22[0] = tmp;
//交换了虚函数指针
showbark(p1);
showbark(p2);
return 0;
}
class Base
{
public:
Base(int a):ma(a)
{
clear();
}
void clear()
{
memset(this,0,sizeof(*this));
}
virtual void show()
{
cout << "ma:" << ma <<endl;
}
protected:
int ma;
};
class Derive:public Base
{
public:
Derive(int b):Base(b),mb(b)
{}
void show()
{
cout << "Derive::ma:" << ma << " mb:" << mb <<endl;
}
private:
int mb;
};
int main()
{
Base *p = new Derive(10);
p->show();
delete p;
//结果显示ma=0;mb=10;
return 0;
}
1、生成虚函数表的时机:构造函数之前。
每一层都要用vfptr指向vftable,构造Base将vfptr和ma内存都置成0,接着构造Derive将vfptr再指向vftable。
2、构造函数中调用虚函数:静态调用(对象还没有生成)
3、析构函数中调用虚函数:静态调用(析构是为释放对象做准备,对象可能不完整)
class Base
{
public:
Base(int a):ma(a){}
virtual void show(int i = 10)
{
cout << "Base::show i=" << i <<endl;
}
protected:
int ma;
};
class Derive:public Base
{
public:
Derive(int a):Base(a){}
void show(int i = 20)
{
cout << "Derive::show i=" << i <<endl;
}
};
int main()
{
Base *p = new Derive(10);
p->show();
delete p;
//结果为Derive::show i=10
return 0;
}
首先编译时压参i=10,call eax,运行时确定Derive::show,但是参数为10。也就是说,成员能不能访问,权限是否正确,函数的默认值用哪个,是在编译期间就确定的。最终调用那个对象的方法取决于运行时虚函数表取谁的地址。
九、RTTI
class Person
{
public:
Person(string name,int age,string sex)
:_name(name),_age(age),_sex(sex)
{}
virtual void showScore() = 0;
protected:
string _name;
int _age;
string _sex;
};
class Student:public Person
{
public:
Student(string name,int age,string sex,double score)
:Person(name,age,sex),_score(score)
{}
void showScore()
{
cout<<"name:"<<_name<<endl;
cout<<"age:"<<_age<<endl;
cout<<"sex:"<<_sex<<endl;
cout<<"score:"<<_score<<endl;
}
private:
double _score;
};
class Teacher:public Person
{
public:
Teacher(string name,int age,string sex,string level)
:Person(name,age,sex),_level(level)
{}
void showScore()
{
cout<<"name:"<<_name<<endl;
cout<<"age:"<<_age<<endl;
cout<<"sex:"<<_sex<<endl;
cout<<"level:"<<_level<<endl;
}
void showTeacherScore()
{
cout<<"name:"<<_name<<endl;
cout<<"level:"<<_level<<endl;
}
private:
string _level;
};
void showSchoolPersonScore(Person *p)
{
//if(typeid(*p) == typeid(Teacher))
Teacher *t = dynamic_cast<Teacher*>(p);
//如果类型形参与实参里面RTTI所指的类型相同,转换成功,否则返回NULL
if(t != NULL)//转化成功
{
t->showTeacherScore();
}
else
{
p->showScore();
}
}
int main()
{
Student stu1("小明",20,"男",80);
Teacher tea1("张老师",30,"女","讲师");
showSchoolPersonScore(&stu1);
showSchoolPersonScore(&tea1);
return 0;
}
十、虚继承
class A
{
protected:
int ma;
};
class B:public A
{
protected:
int mb;
};
class C:public A
{
protected:
int mc;
};
class D:public B,public C
{
protected:
int md;
};
其继承关系如下图:
1、间接基类A被继承了2次;
2、如果访问ma,会提示不明确,也就是说不知道调用那个基类。
这里就用到了虚继承。
class A
{};
class B:virtual public A
{};
class C:virtual public A
{};
class D:public B,public C
{};