目录
动态联编和静态联编
默认参数
类型转换问题
多态
什么是多态?
多态有什么作用?
多态的发生条件
多态的实现原理
纯虚函数和抽象类
抽象类的作用
虚析构函数和纯虚机构函数
重写,重载,重定义的条件
动态联编和静态联编的存在是为了支持C++的多态性。
静态联编:编译器在编译期就把对象与其在申明时采用的类型绑定起来,就确定函数的调用地址
class A
{
}
class B:public A
{
}
class D:public A
{
}
void test()
{
D* d=new D;//静态联编会把d绑定在D*类型上,因为D*是d的声明类型
B* b=d;//静态联编会把b绑定在B*类型上
}
动态联编:编译器在运行期才把对象与其最后一个指向的类型绑定起来,也叫晚绑定
class A
{
public:
virtual void test1() {}//被virtual修饰的函数,被叫做虚函数,具有动态联编的特性
void test2() {}
};
class B :public A
{
public:
void test1() {}
void test2() {}
};
void test()
{
B* b = new B();
A* a = b;
b->test2();
a->test2();
//提问1
b->test1();
a->test1();
//提问2
}
问题:
1.这里a->test2()和b->test2()调用的是不是同一个函数?
答: 不是,因为test2()函数不是虚函数,所以发生了静态联编,即在对象声明的那一刻起,其就已经与声明类型绑定了,比如与b绑定的是B*,a绑定的是A*,所以他们调用的分别是B的test2()函数,A的test2()函数
2.这里a->test1()和b->test1()调用的是不是同一个函数?
答:是的,因为test1()是一个虚函数,发生了动态联编,即在运行期与最后一个对象的类型绑定起来,这里a是与A绑定的,b通过B* b=d;也与A绑定了起来,他们都指向了A,所以调用的是同一个函数A的test1()函数。
当默认参数和虚函数一起出现的时候,则较为复杂些,因为虚函数是动态联编,默认参数是静态联编(为了执行效率),当他们混合在一起,就要好好分析了。
class A
{
public:
virtual void test1(int i = 100) { cout << i << endl; }//被virtual修饰的函数,被叫做虚函数,具有动态联编的特性
};
class B :public A
{
public:
void test1(int i = 200) { cout << i << endl; }
};
void test()
{
B* b = new B();
A* a = b;
b->test1();
a->test1();
}
由上面可知,d和b调用的test1()函数是同一个函数,那么他们此时的默认参数是多少呢?
解析:默认参数是静态绑定的,那么在你刚申明的时候就已经与各自的类型绑定了,D* d的时候,d的默认参数是100,B* b的时候,b的默认参数是200。
class A
{
}
class B:public A
{
}
class D:public A
{
}
子类转换为父类(向上转换):编译器认为指针的寻址范围缩小了,所以是安全的
void test()
{
A* a=new B;
}
父类转换为子类(向下转换):编译器认为指针的寻址范围扩大了,是不安全的
void test()
{
A* a=new A;
B* b=a;//err
}
同一个操作作用于不同的对象,可以有不同的解释,会产生不同的效果,这就是多态。
class People
{
public:
//虚函数
virtual void Mypro()
{
}
};
class xishi :public People
{
public:
//重写父类的虚函数
virtual void Mypro()
{
cout << "西施" << endl;
}
};
class wangzhaojun :public People
{
public:
//重写父类的虚函数
virtual void Mypro()
{
cout << "王昭君" << endl;
}
};
//同一个操作
void doLogin(People *pro)
{
pro->Mypro();//产生不同的效果,这就是多态
}
void test()
{
People* pro = NULL;
pro = new xishi;//不同的对象
doLogin(pro);
delete pro;
pro = new wangzhaojun;//不同的对象
doLogin(pro);
delete pro;
}
class Animal
{
public:
virtual void speak()
{
cout << "Animal speak()" << endl;
}
virtual void speak1()
{
cout << "Animal speak1()" << endl;
}
};
class Dog:public Animal
{
public:
void speak()
{
cout << "Dog speak()" << endl;
}
void speak1()
{
cout << "Dog speak1()" << endl;
}
}
void test()
{
cout << sizeof(Animal) << endl;
}
step1.编译器发现类中有虚函数,就会给类分配一个指针,这个指针也占类的内存,且指针指向一张表,这张表也由编译器创建, 表里放类中所有的虚函数的入口地址,这个指针的值就是表的地址,指针属于类,这个表不属于类。
step2.当子类继承这个类时,不仅仅继承了父类的变量,父类的指针也继承了下来,就是拷贝了一份存在自己的内存中,占4个字节,并且,父类的虚函数表也会一同拷贝一份过来,作为子类自己的虚函数表,但是指针的值现在是没有变的仍然指向的是父类的虚函数表而不是自己的,所以,编译器还在所有子类的构造函数中添加了初始化虚函数指针的代码,让从父类继承下来的指针,指向子类自己的虚函数表,此时虚函数表的内容是没有变的,还是父类写的虚函数。
step3.当编译器发现子类重写了父类的虚函数时,子类重写的虚函数就会覆盖掉虚函数表中父类的虚函数。
step4.当父类指针指向子类对象时,发生动态联编,编译器会找到子类的存储空间,通过指针,找到虚函数表,再通过表找到要执行的函数。
纯虚函数
能作为接口存在
virtual int getnum(int a, int b) = 0;
抽象类
类中所有函数都是纯虚函数的类叫抽象类
class rule
{
public:
//纯虚函数
virtual int getnum(int a, int b) = 0;
};
注意:
1.抽象类不能实例化对象
2.如果子类继承抽象类,子类必须实现抽象类的所有纯虚函数,不然子类也会变成抽象类
3.子类重写父类的函数时,必须按父类申明的顺序一样的顺序来重写!
抽象类可以构成抽象层,抽象层的作用:
1.依赖倒转:业务层依赖抽象层,实现层依赖抽象层
2.开闭原则:对修改源代码关闭,对扩展新功能开放
这样当你要修改业务层的代码时,不用去每个实现层都修改了,而只需要在抽象层修改相应的代码就可以了
问题:当基类指针指向派生类对象时,当基类指针释放派生类对象的时候,不会调用子类的析构函数,因为此时发生了静态联编。
class Animal
{
public:
Animal()
{
cout << "Animal的构造" << endl;
}
~Animal()
{
cout << "Animal的析构" << endl;
}
};
class Son :public Animal
{
public:
Son()
{
cout << "Son的构造" << endl;
pName = new char[64];
memset(pName, 0, 64);
strcpy_s(pName, strlen("如花")+1, "如花");
}
~Son()
{
cout << "Son的析构" << endl;
if (pName != NULL)
{
delete[] pName;
pName = NULL;
}
}
public:
char* pName;
};
void test()
{
Animal* animal = new Son;
delete animal;//我想要的是不仅仅释放掉Animal的空间,还有Son的空间,但发生了静态联编
//不会释放掉Son的空间,执行的是Animal的析构函数
}
解决:使用虚析构函数,使用虚析构函数时,就会调用子类的析构函数了,同时,如果你的类是抽象类,因为抽象类里必须都是纯虚函数,所以需析构函数也要改变为纯虚析构函数
虚析构函数
virtual ~Animal()
{
cout << "Animal的析构" << endl;
}
纯虚析构函数
virtual ~Animal() = 0;
纯虚析构函数需要初始化,只能且必须在类外初始化 ,要使用作用域
Animal::~Animal()
{
cout << "Animal的析构" << endl;
}
重载
1.同一个作用域
2.参数个数,参数顺序,参数类型不同
3.和函数返回值,没有关系
4.const也可以作为重载条件 //do(const Teacher& t){} do(Teacher& t)
重定义(隐藏)
1.有继承
2.子类(派生类)重新定义父类(基类)的同名成员(非virtual函数)
重写(覆盖)
1.有继承
2.子类(派生类)重写父类(基类)的virtual函数
3.函数返回值,函数名字,函数参数,必须和基类中的虚函数一致