多态(1),咕咕咕!
1.多态的概念
多态分为编译时多态(静态多态)和运行时多态(动态多态)。编译时多态主要是函数重载和函数模版,传不同的参数调用不同的函数,达到多种形态。实参传给形参的参数匹配是在编译时完成的。
运行时多态就是去完成某个函数,可以传不同的对象完成不同的行为,达到多种形态。
2.多态的定义和实现
多态是一个继承关系下的类对象,去调用同一函数,产生不同的行为。必须是基类的指针或者引用调用虚函数(只有基类的指针或者引用才能既指向基类对象又指向派生类对象)。被调用的函数必须是虚函数,并且完成了虚函数重写/覆盖(派生类必须对基类的虚函数完成了重写/覆盖,才能又不同的函数)。
class person
{
public:
virtual void gugu()
{
cout<<"gugu"<<endl;
}
};
class student:public person
{
public:
virtual void gugu()
{
cout<<"gugugu"<<endl;
}
};
void func(person& p)
{
p.gugu();
}
void test()
{
person q;
func(q);//gugu
student a;
func(a);//gugugu
}类成员函数前面加上virtual修饰,那么这个成员函数被称为虚函数,非成员函数不能加virtual修饰。
虚函数的重写/覆盖:派生类中有一个和基类完全相同的虚函数(派生类虚函数与基类虚函数的返回值类型,函数名字,参数列表完全相同),称派生类的虚函数重写了基类的虚函数。(在重写虚函数时,派生类的虚函数不加virtual关键字,也可以构成重写,因为继承后基类的虚函数被继承下来再派生类中依旧保持虚函数属性)
class person{
public:
virtual void gugu(){cout<<"gugu"<<endl;}
};
class student:public person
{
public:
virtual void gugu(){cout<<"gugugu"<<ednl;}
};
void func(person* ptr)
{
//虽然都是person指针ptr在调用gugu
//但是和ptr没关系,是由ptr指向的对象决定的
ptr->gugu();
}
int main()
{
person ps;
student st;
func(&ps);
func(&st);
return 0;
}
class A
{
public:
virtual void func(int val=1)
{
cout<<"A->"<<val<<ednl;
}
virtual void test()
{
func();
}
};
class B:public A
{
public:
void func(int val=0)
{
cout<<"B->"<<val<<endl;
}
};
int main()
{
B* p=new B;
p->test();
//B->1 调用了A的test函数,this->func()构成多态,多态时是重写虚函数的实现部分
//多态调用:父类虚函数声明+子类中实现部分,这里的this是A指针的
}B* p=newe B;
p->func();
//B->0 普通调用看指针或引用的类型,多态调用看指针或引用指向的对象虚函数重写会有一些问题:
协变:派生类重写基类虚函数时,与基类虚函数返回值不同,基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用,称为协变,实际意义不大,了解即可。
析构函数的重写:基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,统一处理成destructor
class A
{
public:
virtual ~A()
{
cout<<"~A"<<endl;
}
};
class B:public A
{
public:
~B()
{
cout<<"~B"<<endl;
delete p;
}
protected:
int* p=new int[10];
};
//只有派生类student的析构函数重写了person的析构函数,下面的delete对象调用析构函数
//才能构成多态,保证p1,p2指向的对象正确调用析构函数
int main()
{
A* p1=new A;
A* p2=new B;
delete p1;
delete p2;
return 0;
}
如果~A不加virtual,那么delete p2时调用A的析构函数,没有调用B的析构函数,会内存泄漏override和final关键字:c+=中虚函数重写的要求比较严格,但是有些情况由于疏忽,函数名写错参数写错等导致无法构成重写,在编译期间不会报出,运行时再debug才会发现,override可以帮助用户检测是否成功重写了。如果不想让派生类重写这个虚函数,可以用final修饰。
class car
{
public:
virtual void drive(){}
};
class gugu:public car
{
public;
virtual void drive()override{cout<<"gugu"<<endl;}
};
int main()
{
return 0;
}class car
{
public:
virtual void drive()final{}
};
class gugu:public car
{
public:
virtual void drive(){cout<<"gugu"<<endl;}
};
int main()
{
return 0;
}
