三。多态
1.多态概念
(1)在基类指针指向派生类的情况下,根据外部传入的对象类型的不同,调用不同派生类自己的成员函数;
为了实现上述的功能,需要做一些额外的操作;
将基类的函数设为虚函数:在函数声明前加一个virtual关键字。
class Animal
{
public:
void eat()
...
virtual void sleep()
...
}
class Cat: public Animal
{
public:
void eat()
...
void sleep()
...
}
class Fish:public Animal
{
public:
void eat()
...
void sleep()
...
}
pa是Animal*类型的指针,所以只能调用Animal*的内部函数。
void func(Animal * pa)
{
1.pa是Animal*类型的指针
2.eat是一个普通成员函数
3.直接调用Animal::eat
void eat()
...
1.pa是Animal*类型的指针
2.sleep是一个虚函数,所以不能直接使用Animal的sleep函数
3.根据某些规则,去寻找真正需要执行的函数;如果是一个Animal对象,就执行Animal::sleep;如果是一个Cat的对象,就执行Cat::sellp。
void sleep()
...
}
根据传入的对象的判断具体需要执行的函数。
Animal *pa = new Animal;
Cat *pc = new Cat;
Fish *pf = new Fish;
(2) virtual关键字修饰的函数叫做虚函数。
多态:同样一条调用语句,可以有多种不同的形态。
函数重写:sleep在基类中是一个虚函数,在派生类中被重定义。
静态联编/早起联编:在编译期间就可以确定具体执行哪个函数,怎么调用怎么执行的。
动态联编/迟绑定:在编译期间无法确定具体调用哪个函数,在运行的时候才能确定具体调用哪一个函数。
(3) 多态的使用
1.发生在基类和派生类之间
2.必须要有虚函数
3.必须要有基类指针指向派生类对象
2.析构与多态
(1)~A(){}
void func(A *pa)
{
delete pa;
}
A *a1 = new A;
B *b1 = new B;
func(a1);
通过基类的指针释放派生类的对象。
1.pa是A的指针
2.delete调用析构函数,A的析构函数是一个普通函数
3.调用A的析构函数
4.沿着A的继承路径向上找它的父类,A没有父类
(2)virtual ~A(){}
void func(A *pa)
{
delete pa;
}
A *a1 = new A;
B *b1 = new B;
func(b1);
第一次调用B的析构:将基类的析构函数设为虚函数(可以防止内存泄露)
1.pa是A的指针
2.delete调用析构函数,A的析构是一个虚函数
3.通过某种方式,找到对象的析构函数进行调用
4.沿着对象的继承路径向上找它的父类
3.多态的实现
如何去确定具体调用的是哪一个类呢?
引用了一个虚函数表指针。
(1)class Animal
{
public:
void eat()
...
virtual void sleep()//有virtual时sizeof Animal为8
... //没有virtual时,sizeof Animal为4
virtual void func1()
...
virtual void func2()
...
virtual void func3()
...
private:
int a;
}
class Cat: public Animal
{
public:
void eat()
...
void sleep()
...
private:
int b;
}
class Fish:public Animal
{
public:
void eat()
...
void sleep()
...
private:
int c;
}
(2)虚函数表指针:放在对象的开始位置
C *pc = new C;
func(pc);
A A的虚函数表:类的虚函数地址
eat()时:
A *pa ---> vfptr ----> Animal::sleep()
a Animal::func1()
Animal::func2()
Animal::func3()
B B的虚函数表指针
sleep()时:
A *pa ---> vfptr ---> Cat::sleep()
a ...
b ...
4.虚函数与虚继承
class A
{
public:
virtual func1()
{
}
public:
int a;
};
class B
{
public:
virtual func2()
{
}
public:
int b;
};
A虚函数表 B虚函数表
vfptr -----> func2()
->(-4)
vbptr --->
->(8)
b
func1() <---- vfptr
a
5.多态使用
(1)在构造函数中是无法使用多态的。
class A
{
public:
A()
{
print();
printf("A 的构造函数");
}
void print()
{
printf("A 的打印函数");
}
private:
int a;
}
class B
{
public:
B()
{
print();
printf("B 的构造函数");
}
void print()
{
printf("B 的打印函数");
}
private:
int b;
}
B b1;//1.A的构造函数
//2.A的打印函数
//3.B的构造函数
//4.B的打印函数
无法做到选择,因为B的vfpt在不同的函数块中指向不同的地方。
(2)不要用基类指针去操作派生类的数组,基类指针和派生类指针有可能步长不一致。
B *pb = arrB;//pb[i] = *(pb+i)
//pb+i = (int)pb+sizeof(B)*i = (int)pb+12*i
A *pa = pb; //pa[i] = *(pa+i)
//pa+i = (int)pa+sizeof(A)*i = (int)pa+8*i
6.纯虚函数和抽象类
纯虚函数:虚函数只有函数声明,没有 函数实现,将函数体改为 =0。
virtual double Gets() = 0;
包含纯虚函数的类叫做抽象类:
1.抽象类不能实例化对象;
2.一个类如果继承自抽象类,则必须实现抽象类的所有纯虚函数;假如不实现,则该类将变为一个抽象类;
3.抽象类可以定义对象指针,用来操作派生类。
7.接口(抽象类:只有函数,没有参数)
多重继承接口是不会产生二义性的。
#include <iostream>
class A
{
public:
virtual void add() = 0;
virtual void print() = 0;
};
class B
{
public:
virtual void mul() = 0;
virtual void print() = 0;
};
class C:public A,public B
{
public:
C(int a,int b)
{
this->a = a;
this->b = b;
}
void add()
{
ret = a+b;
}
void mul()
{
ret = a*b;
}
void print()
{
printf("ret = %d\n",ret);
}
private:
int a;
int b;
int ret;
};
void add(A *pa)
{
pa->add();
pa->print();
}
void mul(A *pa)
{
pa->mul();
pa->print();
}
int main()
{
/*C c(2,5);
c.add();
c.print();
c.mul();
c.print();*/
C *pc = new C(3,4);
add(pc);
mul(pc);
return 0;
}
1.多态概念
(1)在基类指针指向派生类的情况下,根据外部传入的对象类型的不同,调用不同派生类自己的成员函数;
为了实现上述的功能,需要做一些额外的操作;
将基类的函数设为虚函数:在函数声明前加一个virtual关键字。
class Animal
{
public:
void eat()
...
virtual void sleep()
...
}
class Cat: public Animal
{
public:
void eat()
...
void sleep()
...
}
class Fish:public Animal
{
public:
void eat()
...
void sleep()
...
}
pa是Animal*类型的指针,所以只能调用Animal*的内部函数。
void func(Animal * pa)
{
1.pa是Animal*类型的指针
2.eat是一个普通成员函数
3.直接调用Animal::eat
void eat()
...
1.pa是Animal*类型的指针
2.sleep是一个虚函数,所以不能直接使用Animal的sleep函数
3.根据某些规则,去寻找真正需要执行的函数;如果是一个Animal对象,就执行Animal::sleep;如果是一个Cat的对象,就执行Cat::sellp。
void sleep()
...
}
根据传入的对象的判断具体需要执行的函数。
Animal *pa = new Animal;
Cat *pc = new Cat;
Fish *pf = new Fish;
(2) virtual关键字修饰的函数叫做虚函数。
多态:同样一条调用语句,可以有多种不同的形态。
函数重写:sleep在基类中是一个虚函数,在派生类中被重定义。
静态联编/早起联编:在编译期间就可以确定具体执行哪个函数,怎么调用怎么执行的。
动态联编/迟绑定:在编译期间无法确定具体调用哪个函数,在运行的时候才能确定具体调用哪一个函数。
(3) 多态的使用
1.发生在基类和派生类之间
2.必须要有虚函数
3.必须要有基类指针指向派生类对象
2.析构与多态
(1)~A(){}
void func(A *pa)
{
delete pa;
}
A *a1 = new A;
B *b1 = new B;
func(a1);
通过基类的指针释放派生类的对象。
1.pa是A的指针
2.delete调用析构函数,A的析构函数是一个普通函数
3.调用A的析构函数
4.沿着A的继承路径向上找它的父类,A没有父类
(2)virtual ~A(){}
void func(A *pa)
{
delete pa;
}
A *a1 = new A;
B *b1 = new B;
func(b1);
第一次调用B的析构:将基类的析构函数设为虚函数(可以防止内存泄露)
1.pa是A的指针
2.delete调用析构函数,A的析构是一个虚函数
3.通过某种方式,找到对象的析构函数进行调用
4.沿着对象的继承路径向上找它的父类
3.多态的实现
如何去确定具体调用的是哪一个类呢?
引用了一个虚函数表指针。
(1)class Animal
{
public:
void eat()
...
virtual void sleep()//有virtual时sizeof Animal为8
... //没有virtual时,sizeof Animal为4
virtual void func1()
...
virtual void func2()
...
virtual void func3()
...
private:
int a;
}
class Cat: public Animal
{
public:
void eat()
...
void sleep()
...
private:
int b;
}
class Fish:public Animal
{
public:
void eat()
...
void sleep()
...
private:
int c;
}
(2)虚函数表指针:放在对象的开始位置
C *pc = new C;
func(pc);
A A的虚函数表:类的虚函数地址
eat()时:
A *pa ---> vfptr ----> Animal::sleep()
a Animal::func1()
Animal::func2()
Animal::func3()
B B的虚函数表指针
sleep()时:
A *pa ---> vfptr ---> Cat::sleep()
a ...
b ...
4.虚函数与虚继承
class A
{
public:
virtual func1()
{
}
public:
int a;
};
class B
{
public:
virtual func2()
{
}
public:
int b;
};
A虚函数表 B虚函数表
vfptr -----> func2()
->(-4)
vbptr --->
->(8)
b
func1() <---- vfptr
a
5.多态使用
(1)在构造函数中是无法使用多态的。
class A
{
public:
A()
{
print();
printf("A 的构造函数");
}
void print()
{
printf("A 的打印函数");
}
private:
int a;
}
class B
{
public:
B()
{
print();
printf("B 的构造函数");
}
void print()
{
printf("B 的打印函数");
}
private:
int b;
}
B b1;//1.A的构造函数
//2.A的打印函数
//3.B的构造函数
//4.B的打印函数
无法做到选择,因为B的vfpt在不同的函数块中指向不同的地方。
(2)不要用基类指针去操作派生类的数组,基类指针和派生类指针有可能步长不一致。
B *pb = arrB;//pb[i] = *(pb+i)
//pb+i = (int)pb+sizeof(B)*i = (int)pb+12*i
A *pa = pb; //pa[i] = *(pa+i)
//pa+i = (int)pa+sizeof(A)*i = (int)pa+8*i
6.纯虚函数和抽象类
纯虚函数:虚函数只有函数声明,没有 函数实现,将函数体改为 =0。
virtual double Gets() = 0;
包含纯虚函数的类叫做抽象类:
1.抽象类不能实例化对象;
2.一个类如果继承自抽象类,则必须实现抽象类的所有纯虚函数;假如不实现,则该类将变为一个抽象类;
3.抽象类可以定义对象指针,用来操作派生类。
7.接口(抽象类:只有函数,没有参数)
多重继承接口是不会产生二义性的。
#include <iostream>
class A
{
public:
virtual void add() = 0;
virtual void print() = 0;
};
class B
{
public:
virtual void mul() = 0;
virtual void print() = 0;
};
class C:public A,public B
{
public:
C(int a,int b)
{
this->a = a;
this->b = b;
}
void add()
{
ret = a+b;
}
void mul()
{
ret = a*b;
}
void print()
{
printf("ret = %d\n",ret);
}
private:
int a;
int b;
int ret;
};
void add(A *pa)
{
pa->add();
pa->print();
}
void mul(A *pa)
{
pa->mul();
pa->print();
}
int main()
{
/*C c(2,5);
c.add();
c.print();
c.mul();
c.print();*/
C *pc = new C(3,4);
add(pc);
mul(pc);
return 0;
}