1,C++多态性,虚函数,重载,纯虚函数,比较有意思,,,,,,
在面向对象的程序中,当不同的对象接受到相同的消息产生不同的动作,这种性质称为多态性。
简单的来说:就是指用一个名字定义不同的函数,这些函数执行不同但有类似的操作,即所谓的同样的接口访问功能不同的函数,从而实现“一个接口,多种方法”
例如:比较两个数的大小,我们可以针对不同的数据类型(整型,浮点型,双精度型),写出多个不同名称的函数来
实现。但是,事实上,由于它们的功能完全相同,所以可以利用多态性来完成这些功能。。。。
int Max(int i, int j)
{
return i>j?i:j;
}
float Max(float i, float j)
{
return i>j?i:j;
}
鉴于此:如果3,4或着1.2,3.4比较大小的时候,那么Max(3,4)和Max(1.2,3.4)被调用时,编译器会自判断调用哪一个函数。。。。。。
多态性与联编(编译)
在面向对象的语言中,多态性的实现和联编密不可分。联编(binding,绑定或装配)是将一个标识符名和一个
存储地址联系在一起。。。。一个源程序经过编译,连接,最后生成可执行代码,就是将部分的执行代码联编
在一起的过程。。。。
一般而言,联编方式有两种:静态联编(static binding)和动态联编(dynamic binding)。静态联编是联编在一
个源程序经过编译,连接,成为可执行文件这个过程中的编译阶段完成的,即联编过程是在运行之前完成的,,
动态联编:是指联编在一个源程序经过编译,连接,成为可执行文件的这个过程中的程序执行阶段完成的,即
联编过程是在程序运行时才动态完成的,,,,
C++实现多态性有以下两种情况
1,编译时多态行:通过函数重载,运算符重载,模板重载
2,运行时多态:借助虚函数来实现
虚函数声明:
virtual 函数类型 函数名称 (形式参数表);
虚函数的声明只能出现在类声明的函数原型声明中,,,在派生类中可以不显示的声明为虚函数,当调用此成员
函数时,C++系统会自动判断调用哪一个对象的成员函数。。。。
虚函数:单界面多实现
基类中用虚函数定义了一个函数的原型(提供一个界面),那么就提供了公有继承对象的一个公共的界面,而
多个派生类重新定义虚函数的函数体内容(多个实现版本),将基类的指针指向派生类对象,从而达到虚函数
不同实现的目的。。。。。。。。
代码1:
#include <iostream>
using namespace std;
class shape
{
public:
float area()
{ return -1;}
};
class circle:public shape
{
float radius;
public:
circle(float r)
{ radius = r;}
float area()
{ return 3.14*radius*radius;}
};
int main()
{
shape obj, *ptr;
circle c(3.4);
ptr = &obj;
cout<<ptr->area()<<endl;
ptr = &c;
cout<<ptr->area()<<endl;
}
程序运行结果:
结果为什么会是这样的呢???我们希望的是求圆的面积啊,,,,,
这是因为在这里只是用到了静态编译,,,,所以前后两次ptr->area()都调用了基类中的area函数,,,,
但是,,,值得注意的是:对于我们的变量,如果有相同的话,那么就会调用派生类中的值。。。。。。。。。
而如果,我们把代码改成了这样:
#include <iostream>
using namespace std;
class shape
{
public:
virtual float area()//跟上面的程序相比,只有这里不一样
{ return -1;} //关键字virtual说明此函数为虚函数
};
class circle:public shape
{
float radius;
public:
circle(float r)
{ radius = r;}
float area() //在派生类中,虚函数被重新定义以实现不同的操作。
{ return 3.14*radius*radius;} //这种方式称为:函数超越,或者函数覆盖。。。。
};
int main()
{
shape obj, *ptr;
circle c(3.4);
ptr = &obj;
cout<<ptr->area()<<endl;
ptr = &c;
cout<<ptr->area()<<endl;
}
运行结果:
概述:
1,virtual关键字:指示C++编译器对该函数的调用进行动态联编
2,尽管可以用对象名和点运算符的方式调用虚函数,即向对象发送消息tri.area()或者rect.area()。这同样是静态联编
3,只有当访问虚函数是通过基类指针s(或者引用)时才可获得运行时的多态行。。。。
4,在派生类中,虚函数被重新定义时,其函数的原型必须与基类中的函数原型完全相同,,,,,,,,,,,,,
也就是说:什么都一样,就是函数体的实现完全不同
5,必须在继承的体系下实现。。。。。,必须是类的成员函数,普通函数不可能是虚函数。。。。。。
6,虚函数不能是静态成员函数,,,,因为静态成员函数的存储位置说明了它不受限与某个具体的对象。。。。。
7,根据特性,运行时确定,所以内联函数自然而然也就不是虚函数。。。。。。
8,构造函数不能是虚函数,析构函数可以是:因为构造时,对象还是一片未定型的空间。。。。只有在构造完成后,
对象才能成为一个类的名副其实的实例,,,,,、
由此,我们再次简单的总结一下:
虚函数,声明定义一定在基类中,其他的实现可能在派生类中(前提是:函数原型基本一样),这样,通过基类的指
针(引用)跟派生类的对象挂钩,那么调用的就是派生类中的函数了,,,实现了多态。。。。
这里可能跟函数重载有一定关系::
#include <iostream>
using namespace std;
class base
{
public:
virtual void func1();
virtual void func2();
virtual void func3();
void fun4();
};
class derived:public base
{
public:
virtual void func1(); //virtual ,虚函数的再次实现
void func2(int x);
char func3(); //错误,返回类型不同
void func4(); //普通函数的重载,不是虚函数,,,
};
void main()
{
base d1,*bp;
derived d2;
bp = &d2;
bp->func1(); //调用父类func1()
bp->func2(); //调用基类func2()
bp->func3(); //调用基类func3()
bp->func4(); //调用基类func4()
}
虚函数:只能是成员函数,,,,,重载函数:没有要求
虚函数:不同调用通过不同的对象指向或引用
重载函数:根据参数的类型或个数来进行不同的调用。。。。。。
什么函数不能声明为虚函数?
普通函数(非成员函数)、静态成员函数、内联成员函数、构造函数、友元函数
虚函数通过继承方式来体现出多态作用,它必须是基类的非静态成员函数,其访问权限可
以是protected或public,在基类的类定义中定义虚函数的一般形式是:
virtual 函数返回值类型虚函数名(形参表){ 函数体 }
常见的不能声明为虚函数的有:普通函数(非成员函数)、静态成员函数、内联成员函数、构造函数、友元函数。
(1)普通函数不能声明为虚函数。普通函数(非成员函数)只能被重载(overload),不能被重写(override),声明为虚函数也没有什么意思,因此编译器会在编译时绑定函数。
(2) 构造函数不能声明为虚函数。构造函数一般用来初始化对象,只有在一个对象生成之后,才能发挥多态作用。如果将构造函数声明为虚函数,则表现为在对象还没有生成的时候来定义它的多态,这两点是不统一的。另外,构造函数不能被继承,因而不能声明为虚函数。
(3) 静态成员函数不能声明为虚函数。静态成员函数对于每个类来说只有一份代码,所有的对象都共享这份代码,它不归某个对象所有,所以也没有动态绑定的必要性。
(4) 内联(inline)成员函数不能声明为虚函数。内联函数就是为了在代码中直接展开,减少函数调用开销的代价。虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。另外,内联函数在编译时被展开,虚函数在运行时才能动态的绑定函数。
(5) 友元函数不能声明为虚函数。友元函数不属于类的成员函数,不能被继承。
设置虚函数时须注意以下几点:
-
只有类的成员函数才能说明为虚函数;
-
静态成员函数不能是虚函数;
-
内联函数不能为虚函数;
-
构造函数不能是虚函数;
-
析构函数可以是虚函数,而且通常声明为虚函数。
纯虚函数:
纯虚函数是在基类中说明的虚函数,它在该基类中没有定义具体的操作内容,而在各个派生类中根据实际需要定义
自己的实现。。。。。。
virtual 函数类型 函数名称(函数参数表) = 0;
从声明格式看:纯虚函数是在虚函数成员的后面加上“= 0”,纯虚函数是根本没有函数体的,当声明为纯虚函数,那么
为一个类的名副其实的实例,,,,,、
由此,我们再次简单的总结一下:
虚函数,声明定义一定在基类中,其他的实现可能在派生类中(前提是:函数原型基本一样),这样,通过基类的指
针(引用)跟派生类的对象挂钩,那么调用的就是派生类中的函数了,,,实现了多态。。。。
这里可能跟函数重载有一定关系:
基类中就不能给出函数的实现部分,其函数体是由基类的派生类给出。。。。。。。。。。
可以这样说:
有一个动物类,,,,继承有狮子,老虎等,当没有具体的动物时,我们探究它的是不是哺乳动物(作为一个函数)
就没有一点价值
了,所以,此刻的动物类就是一个抽象类,,,,也就是说:动物类这个基类里面没有这个函数的实现,也不能有
那么什么是抽象类(含有纯虚函数的类,抽象类)
class animal
{
public:
virtual void ISLEG_NUMBER() = 0;
};
int main()
{
animal pig; //错误,抽象类中不能存在对象
animal *p; //正确,可以声明指向动物类(抽象类)的指针
animal f(); //错误,抽象类不能作为函数的返回类型
g(animal); //错误,抽象类不能作为函数的参数类型
}
抽象类,一定不能有对象,,,,,,,,,,,,,,,,,,,,,,,,,,