标题:C++多态性基本概念 包括虚函数和纯虚函数
取消只看楼主
lateraware
Rank: 1
等 级:新手上路
帖 子:36
专家分:0
注 册:2012-2-9
结帖率:66.67%
 问题点数:0 回复次数:3 
C++多态性基本概念 包括虚函数和纯虚函数
C++编程语言是一款应用广泛,支持多种程序设计的计算机编程语言。我们今天就会为大家详细介绍其中C++多态性的一些基本知识,以方便大家在学习过程中对此能够有一个充分的掌握。

  前几天笔试的时候碰到考C++多态性的题目,因为不是自己的专业不是纯做软件开发,C++学习不是很好,做得有点混乱。回来以后立刻查了相关资料,大概明白了一点,可能以后解题的时候不会乱了。

  先摘下一些网上的书上的基本概念。

  多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphisn),字面意思多种形状。

  C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。

  多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。

  那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

  最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。

笔试的题目
#include <stdio.h>
class A  
{  
    public:  void foo()  
    {  
        printf("1");
    }
    virtual void fuu()  
    {  
        printf("2");  
    }  
};  
class B:public A  
{
    public:  void foo()  
    {
        printf("3");  
    }
    void fuu()  
    {  
        printf("4");  
    }
};
int main()
{  
    A a;  
    B b;  
    A *p = &a;
    p->foo();
    p->fuu();  
    p = &b;
    p->foo();
    p->fuu();
    return 0;
}
    第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。
  第二个输出结果就是1、4。p->foo()和p->fuu()则是基类指针指向子类对象,正式体现多态的用法,p->foo()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的foo()函数的代码了,因此输出的结果还是1。而p->fuu()指针是基类指针,指向的fuu是一个虚函数,由于每个虚函数都有一个虚函数列表,此时p调用fuu()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fuu()函数的地址,因此输出的结果也会是子类的结果4.

  笔试的题目中还有一个另类测试方法。即

  B *ptr = (B *)&a;  ptr->foo();  ptr->fuu();

  问这两调用的输出结果。这是一个用子类的指针去指向一个强制转换为子类地址的基类对象。结果,这两句调用的输出结果是3,2。

  并不是很理解这种用法,从原理上来解释,由于B是子类指针,虽然被赋予了基类对象地址,但是ptr->foo()在调用的时候,由于地址偏移量固定,偏移量是子类对象的偏移量,于是即使在指向了一个基类对象的情况下,还是调用到了子类的函数,虽然可能从始到终都没有子类对象的实例化出现。

  而ptr->fuu()的调用,可能还是因为C++多态性的原因,由于指向的是一个基类对象,通过虚函数列表的引用,找到了基类中foo()函数的地址,因此调用了基类的函数。由此可见多态性的强大,可以适应各种变化,不论指针是基类的还是子类的,都能找到正确的实现方法。

//小结:1.有virtual才可能发生多态现象2.不发生多态(无virtual)调用就按原类型调用
#include <iostream>
using namespace std;

class Base
{
    public:
        virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
        void g(float x){ cout << "Base::g(float) " << x << endl; }
        void h(float x){ cout << "Base::h(float) " << x << endl; }
};
 
class Derived : public Base
{
    public:
        virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }//多态
        void g(int x){ cout << "Derived::g(int) " << x << endl; }//覆盖
        void h(float x){ cout << "Derived::h(float) " << x << endl; }//复制
};
 
void main(void)
{
    Derived d;
    Base *pb = &d;
    Derived *pd = &d;
    // Good : behavior depends solely on type of the object
    pb->f(3.14f); // Derived::f(float) 3.14
    pd->f(3.14f); // Derived::f(float) 3.14
    // Bad : behavior depends on type of the pointer
    pb->g(3.14f); // Base::g(float) 3.14
    pd->g(3.14f); // Derived::g(int) 3 (surprise!)
    // Bad : behavior depends on type of the pointer
    pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
    pd->h(3.14f); // Derived::h(float) 3.14
    system("pause");
}
//---------------------------------------------------------

C++纯虚函数
一、定义
  纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
  virtual void funtion1()=0
二、引入原因
  1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
  2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
  为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
三、相似概念
  1、多态性
  指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
  a.编译时多态性:通过重载函数实现
  b 运行时多态性:通过虚函数实现。
  2、虚函数
  虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载
  3、抽象类
  包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
[编辑本段]
程序举例
  基类:
  class A
  {
  public:
  A();
  void f1();
  virtual void f2();
  virtual void f3()=0;
  virtual ~A();
  };
  子类:
  class B : public A
  {
  public:
  B();
  void f1();
  void f2();
  void f3();
  virtual ~B();
  };
  主函数:
  int main(int argc, char* argv[])
  {
  A *m_j=new B();
  m_j->f1();
  m_j->f2();
  m_j->f3();
  delete m_j;
  return 0;
  }
  f1()是一个普通的重载.
  调用m_j->f1();会去调用A类中的f1(),它是在我们写好代码的时候就会定好的.
  也就是根据它是由A类定义的,这样就调用这个类的函数.
  f2()是虚函数.
  调用m_j->f2();会调用m_j中到底保存的对象中,对应的这个函数.这是由于new的B对象.
  f3()与f2()一样,只是在基类中不需要写函数实现.

虚函数和纯虚函数

在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。比如在微软的MFC类库中,你会发现很多函数都有virtual关键字,也就是说,它们都是虚函数。难怪有人甚至称虚函数是C++语言的精髓。

那么,什么是虚函数呢,我们先来看看微软的解释:

虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。

——摘自MSDN

这个定义说得不是很明白。MSDN中还给出了一个例子,但是它的例子也并不能很好的说明问题。我们自己编写这样一个例子:

#include "stdio.h"
#include "conio.h"
class Parent
{
public:
char data[20];
void Function1();
virtual void Function2(); // 这里声明Function2是虚函数
}parent;
void Parent::Function1()
{
printf("This is parent,function1\n");
}
void Parent::Function2()
{
printf("This is parent,function2\n");
}

class Child:public Parent
{
void Function1();
void Function2();
} child;
void Child::Function1()
{
printf("This is child,function1\n");
}
void Child::Function2()
{
printf("This is child,function2\n");
}
int main(int argc, char* argv[])
{
Parent *p; // 定义一个基类指针
if(_getch()=='c') // 如果输入一个小写字母c
p=&child; // 指向继承类对象
else
p=&parent; // 否则指向基类对象
p->Function1(); // 这里在编译时会直接给出Parent::Function1()的
入口地址。
p->Function2(); // 注意这里,执行的是哪一个Function2?
return 0;
}
用任意版本的Visual C++或Borland C++编译并运行,输入一个小写字母c,得到下面的结果:
This is parent,function1
This is child,function2
为什么会有第一行的结果呢?因为我们是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以我们看到了第一行的结果。

那么第二行的结果又是怎么回事呢?我们注意到,Function2()函数在基类中被virtual关键字修饰,也就是说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。如果我们在运行上面的程序时任意输入一个非c的字符,结果如下:
This is parent,function1
This is parent,function2
请注意看第二行,它的结果出现了变化。程序中仅仅调用了一个Function2()函数,却可以根据用户的输入自动决定到底调用基类中的Function2还是继承类中的Function2,这就是虚函数的作用。我们知道,在MFC中,很多类都是需要你继承的,它们的成员函数很多都要重载,比如编写MFC应用程序最常用的CView::OnDraw(CDC*)函数,就必须重载使用。把它定义为虚函数(实际上,在MFC中OnDraw不仅是虚函数,还是纯虚函数),可以保证时刻调用的是用户自己编写的OnDraw。虚函数的重要用途在这里可见一斑。

再看下面的
-----------------------------------------------------------

摘自:C++中虚函数和纯虚函数的概念,差别和分别存在的原因


首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数
定义一个函数为纯虚函数,才代表函数没有被实现
定义他是为了实现一个接口,起到一个规范的作用,规范继承这个
类的程序员必须实现这个函数。

对继承的影响:
普通的类(没有虚函数,纯虚函数)就可以被继承,而且工作的相当好

关于这个问题有以下疑问:
纯虚函数难道就是为了实现接口?接口存在的意义?
我实在弄不懂,我干嘛要预先定义好?未来的事情本难料
就等有一天我的类中需要使用某个函数,在添加一个函数
不久可以?

关于实例化一个类:
有纯虚函数的类是不可能生成类对象的,如果没有纯虚函数则可以。比如:
class CA
{
public:
virtual void fun() = 0; // 说明fun函数为纯虚函数
virtual void fun1();
};

class CB
{
public:
virtual void fun();
virtual void fun1();
};

// CA,CB类的实现
...

void main()
{
CA a; // 不允许,因为类CA中有纯虚函数
CB b; // 可以,因为类CB中没有纯虚函数
...
}

---------------------------------------------------------------
虚函数在多态中间的使用:
多态一般就是通过指向基类的指针来实现的。

dog mydogwangwang;
mydogwangwang.born();
一定是返回“dog”

那么
horse myhorsepipi;
myhorsepipi.born();
一定是返回“horse”

也是多态呀?
/////////////////////////////////////////////////
有一点你必须明白,就是用父类的指针在运行时刻来调用子类:
例如,有个函数是这样的:
void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}

参数maybedog_maybehorse在编译时刻并不知道传进来的是dog类还是horse类,所以就把它设定为animal类,具体到运行时决定了才决定用那个函数。
也就是说用父类指针通过虚函数来决定运行时刻到底是谁而指向谁的函数。
////////////////////////////////////////////////////////////////////
//用虚函数
#include <iostream.h>

class animal
{
public:
animal();
~animal();
void fun1(animal *maybedog_maybehorse);
virtual void born();

};

void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}




animal::animal()
{
}
animal::~animal()
{
}
void animal::born()
{
cout<< "animal";
}

class dog: public animal
{
public:
dog();
~dog();
virtual void born();
};

dog::dog()
{
}
dog::~dog()
{
}
void dog::born(){
cout<<"dog";
}

class horse:public animal
{
public:
horse();
~horse();
virtual void born();
};

horse::horse()
{
}

horse::~horse()
{
}
void horse::born(){
cout<<"horse";
}

void main()
{
animal a;
dog b;
horse c;
a.fun1(&c);
}

//output: horse

/////////////////////////////////////////////////////////////////
//不用虚函数

#include <iostream.h>

class animal
{
public:
animal();
~animal();
void fun1(animal *maybedog_maybehorse);
void born();

};

void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}




animal::animal()
{
}
animal::~animal()
{
}
void animal::born()
{
cout<< "animal";
}

class dog: public animal
{
public:
dog();
~dog();
void born();
};

dog::dog()
{
}
dog::~dog()
{
}
void dog::born(){
cout<<"dog";
}

class horse:public animal
{
public:
horse();
~horse();
void born();
};

horse::horse()
{
}

horse::~horse()
{
}
void horse::born(){
cout<<"horse";
}

void main()
{
animal a;
dog b;
horse c;
a.fun1(&c);
}


output: animal


[ 本帖最后由 lateraware 于 2012-2-22 22:53 编辑 ]
收到的鲜花
  • 小鱼儿c2012-02-23 10:30 送鲜花  3朵   附言:我很赞同
搜索更多相关主题的帖子: 计算机编程 编程语言 多态性 虚函数 
2012-02-22 20:10
lateraware
Rank: 1
等 级:新手上路
帖 子:36
专家分:0
注 册:2012-2-9
得分:0 
回复 2楼 小鱼儿c
共同学习相互进步 呵呵!
2012-02-23 20:00
lateraware
Rank: 1
等 级:新手上路
帖 子:36
专家分:0
注 册:2012-2-9
得分:0 
回复 3楼 Devil_W
鄙人确实是初学者,以后还望仁兄请多多指教 呵呵!
2012-02-23 20:04
lateraware
Rank: 1
等 级:新手上路
帖 子:36
专家分:0
注 册:2012-2-9
得分:0 
回复 7楼 lateraware
没错 呵呵 我忘了 下次记得了
2012-02-23 20:30



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-361400-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.292939 second(s), 9 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved