#include <iostream>
using namespace std;
/*-----------------------
2012年11月10日 01:26:17
------------------------*/
/**************************************************************
虚函数:
1、在基类用【virtual声明】的成员函数即为虚函数
2、在派生类中的定义此函数,要求
函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同
并根据派生类的需要重新定义函数体
3、当一个成员函数声明为虚函数后,其派生类中的同名函数都自动成为虚函数,无论是否加virtual关键字
4、用基类的指针或引用指向派生类的对象
通过用基类的指针或引用调用虚函数
实际执行的将使派生类对象中定义的虚函数
虚函数表:
1、如果类中包含有虚成员函数,在用该类实例化对象时,
对象的第一个成员将是一个指向虚函数表的指针[pvftable]。
2、虚函数表记录运行过程中实际应该调用的虚函数的入口地址
3、类中所有的虚函数形成虚函数表
[可以通过调试查看:
cout<<sizeof(aa3)<<endl;]
虚析构函数: -- 防止内存泄漏
1、如果一个基类的指针指向派生类的对象,
并且想通过该指针delete派生类对象,
系统将只会执行基类的析构函数
而不会执行派生类的析构函数
2、为避免这种情况的发生,往往把析构函数声明为虚的,
此时,系统将 先执行派生类对象的析构函数
然后再执行基类的析构函数
3、如果基类的析构函数声明为虚的,
派生类的析构函数也将自动成为虚构函数
无论派生类析构函数声明中是否加virtual
4、防止内存泄漏
当派生类中有指针时,对象销毁时,
也需要释放指针所指向的地址
5、特别注意:
5.1、
基类名* 指针 = new 子类名();
只有当一个父类类型的指针 指向 子类对象[new 的对象]时
只有将 基类的析构函数 声明为 虚函数virtual
在 delete 指针时-->
才会:
先执行派生类对象的析构函数
然后再执行基类的析构函数
否则:
只执行基类的析构函数
5.2、
类名* 指针 = new 类名();
当指针的类型是对象类型的本身时
在 delete 指针时-->
基类的析构函数 【不是】虚函数virtual
也会:
先执行派生类对象的析构函数
然后再执行基类的析构函数
5.3、在栈中分配分配空间时【子类分配空间】
5.3.1、不需要 delete
在 该区域 执行完后 会自动释放 该栈空间
5.3.2、基类的析构函数 【不是】虚函数virtual
也会:
先执行派生类对象的析构函数
然后再执行基类的析构函数
纯虚函数和抽象类:
1、纯虚函数具有如下的一般形式:
virtual 返回类型 函数名(参数列表)=0;
2、纯虚函数没有函数体,即只有函数的声明而没有函数的定义
3、通常在基类中声明纯虚函数,在派生类中定义该虚函数
如果派生类中也没有定义该纯虚函数,则该函数在派生类中任然为纯虚函数
4、不能实例化对象的类称为[抽象类],
具有纯虚函数的类是不能被实例化对象的
所以具有纯虚函数的类是一种抽象类
5、虽然抽象类不能实例化对象,但是可以用抽象类的指针指向派生类对象
并调用派生类的虚函数的实际实现
6、子类在继承抽象类时:
必须全部实现抽象类的 纯虚函数,该派生类才能被实例化
如果只实现部分 纯虚函数,则该派生类还是 抽象类
7、当一个的所有函数都是 纯虚函数,
则该类就是 接口【C++没有接口】
接口是抽象类的一种特例
子类调用基类的函数:
基类类名::函数名();
多态:
1、父类的函数为 虚函数, 即在 返回类型前加 virtual
virtual void Print(){.....};
2、子类对该函数进行重写[返回类型 和 函数名 相同]
void Print(){......};
3、父类的[指针]指向父类对象或子类对象
//通过指针
void test1(a* temp){
temp->Print();
}
3.1、在 【栈中】 分配空间
父类名* 地址变量名 = &父类名[子类名]();
a* aa = &b();
//等价于
a* aa;
b bb;
aa=&bb;
test1(aa);
3.2、在 【堆中】 分配空间
父类名* 地址变量名 = new 父类名[子类名]();
a* cc = new c();
test1(cc);
注意点:
1、指针调用函数的方式:
指针名->函数名();
2、引用调用函数的方式:
引用名.函数名();
3、继承方式
class 子类名:public 父类名{....};
***************************************************************/
class Person{ //抽象类
public:
char* name;
public:
virtual void Eat()=0;
virtual void Sleep()=0;
};
class Stu:public Person{
public:
void Eat(){
cout<<"Eat......."<<endl;
}
void Sleep(){
cout<<"Sleep.........."<<endl;
}
};
void main(){
Person* person = new Stu();
person->Eat();
}
class a{
public:
virtual void Print(){
cout<<"a............."<<endl;
}
virtual void Prafint(){
cout<<"a............."<<endl;
}
public:
a(){
cout<<"new a............"<<endl;
}
public:
virtual ~a(){
cout<<"destory a............"<<endl;
}
};
//通过指针
void test1(a* temp){
temp->Print();
}
//通过引用
void test2(a& temp){
temp.Print();
}
class b:public a{
public:
void Print(){
cout<<"b............."<<endl;
}
public:
b(){
cout<<"new b............"<<endl;
}
public:
~b(){
cout<<"destory b............"<<endl;
}
};
class c:public a{
public:
void Print(){
cout<<"c............."<<endl;
}
public:
c(){
cout<<"new c............"<<endl;
}
public:
~c(){
cout<<"destory c............"<<endl;
}
};
void test22(){
/*
b bb;
注意:
在栈中实例化 派生类
1、先 调用父类的 构造函数 再调用派生类的构造函数
2、不需要手动释放内存
3、在 该 区域执行完后 会自动释放内存空间
new a............
new b............
destory b............
destory a............
*/
//a aa = bb;
//aa.Print();
a* aa = new b();
//a* aa = new b();
//aa->Print();
delete aa;
/*
a* aa = new b();
在堆中分配空间:
1、先 调用父类的 构造函数 再调用派生类的构造函数
2、先调用派生类的析构函数 再调用基类的析构函数
2.1、2的满足条件:需要把 基类的析构函数 声明 虚函数
3、如果基类的 析构函数 不是虚函数
3.1、在 delete 时只会调用基类的析构函数
*/
/*
new a............
new b............
b.............
destory b............
destory a............
*/
}
int test11(){
/*通过指针*/
a* aa1 = &b();
/*等价于
a* aa1;
b bb;
aa1=&bb;
*/
test1(aa1);
a* cc1 = new c(); //动态绑定 就是虚函数中的内容 在实例化的时候动态载入
test1(cc1);
/*通过引用*/
a& aa2 = b();
test2(aa2);
a* cc2 = new c();
test1(cc2);
a aa3;
cout<<sizeof(aa3)<<endl;
//cout<<&(a::Print)<<endl;
/*
//打印函数的地址
printf("a::Print=%x\n", &(a::Print));
printf("b::Print=%x\n", &(b::Print));
printf("c::Print=%x\n", &(c::Print));
打印结果:
a::Print=401271
b::Print=401271
c::Print=401271
分析:
a
/ \
b c
1、b继承a , c继承a
2、Print()在a类中是 虚函数
3、类b类c 重写了a的虚函数,所以该函数也为虚函数
4、三个类中的函数统一载入一个地址区:
意味着三个只能有一个存在
5、具体 401271 里面的内容就由调用的实例在实例化的时候动态载入
*/
return 0;
}