C++之多态

基础知识:

  多态指的是同样的消息被不同类型的对象接收时导致的不同行为,这里消息指的是对对象成员函数的调用,不同的行为指的是不同的实现,即调用了不同的函数。

多态的实现可以分为:

  编译时多态和运行时多态,编译时多态称为静态绑定,运行时多态称为动态绑定。

  静态绑定:通过函数重载 和 运算符重载 实现的(运算符重载实质上也是函数重载)

  多态绑定:通过虚函数实现的(在继承的基础上)


一. 静态绑定


1.函数重载

    什么是重载函数:

    ①、在相同的一个作用域内(同一文件内)同范围

    ②、函数名相同 

    ③、形参不同(个数、类型、顺序)

  重载函数的执行顺序:

    ㈠、先寻找一个严格匹配的函数,如果找到了则使用

    ㈡、通过内部的类型转换寻找一个匹配,如果找到了则使用

    ㈢、如果都没有,则报没有找到函数的出错信息

另外再说一下const重载函数:

class A 

public: 

  int x(){ ...... } 

  int x() const { ...... } 

};

这样的const重载是允许的 但是什么时候会调用 x()什么时候会调用x() const ? 他们又有什么不同呢?

const A a1; 

A a2; 

a1.x(); 

a2.x();

结果是:

   a1调用const版本,a2调用非const版本,所以const也可以当做函数重载的一个条件。

   

2.运算符重载

   不能重载的运算符有五个:

     类属关系运算符: .

    成员指针运算符: .*

    作用域分辨符号: ::

    运算符:       sizeof

    三目运算符号:   


运算符的重载有两种形式:


第一种:运算符重载为类的成员函数

函数类型 operator 运算符(形参表)

{

    函数体;

}

注意:函数体中形参的个数比原来的操作数个数少一,因为重载为成员函数,类自身的数据可以直接访问。

 

第二种:运算符重载为类的友元函数

friend 函数类型 operator 运算符(形参表)

{

    函数体;

}

注意:函数体中形参的个数等于原来的操作数的个数。

 

2.1.1运算符重载为成员函数的示例如下

#include <iostream>
using namespace std;
class complex
{
public:
    complex(double r=0.0, double 
i=0.0)
    {
       real = 
r;
       imag = 
i;
    }
    complex operator + (complex 
c2);
    complex operator  - 
(complex c2);
    void display();
private:
    double real;
    double imag;
};
 
complex complex::operator +(complex c2)
{
    complex c;
    c.real = c2.real + 
real;
    c.imag = c2.imag + 
imag;
    return c;
}
 
complex complex::operator -(complex c2)
{
    complex c;
    c.real = real - c2.real; 
 //注意是real在前
    c.imag = imag - 
c2.imag;
    return c;
}
 
void complex::display()
{
    cout << "(" << 
real << "," << imag << ")" << endl;
}
 
int main()
{
    complex c1(4,4), 
c2(2,9),c3;
    cout << 
"c1=";
    c1.display();
    
    cout << 
"c2=";
    c2.display();
    
    c3 = c1 - c2;
    cout << "c1-c2="; 
c3.display();
    c3 = c1 + c2;
    cout << "c1+c2="; 
c3.display();
    return 0;
}

2.1.2 运算符重载为友元函数的示例如下

#include <iostream>
using namespace std; 
class complex
{
public:
    complex(double r=0.0, double 
i=0.0)
    {
       real = 
r;
       imag = 
i;
    }
    friend complex operator + 
(complex c1, complex c2);
    friend complex operator - 
(complex c1, complex c2);
    void display();
private:
    double real;
    double imag;
};
 
complex operator + (complex c1, complex c2)
{
    return 
complex(c1.real+c2.real, c1.imag+c2.imag);
}
 
complex operator - (complex c1, complex c2)
{
    return 
complex(c1.real-c2.real, c1.imag+c2.imag);
}
 
void complex::display()
{
    cout << "(" << 
real << "," << imag << ")" << endl;
}
 
int main()
{
    complex c1(4,4), 
c2(2,9),c3;
    cout << 
"c1=";
    c1.display();
    
    cout << 
"c2=";
    c2.display();
    
    c3 = c1 - c2;
    cout << "c1-c2="; 
c3.display();
    c3 = c1 + c2;
    cout << "c1+c2="; 
c3.display();
    return 0;
}

一些规则:

  C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载。

  重载不能改变运算符运算对象的个数。

  重载不能改变运算符的优先级别。

  重载不能改变运算符的结合性。

  重载函数的参数至少有一个是用户自定义的类对象(或者类对象的引用)。

  一般将单目运算符重载为成员函数,将双目运算符重载为友元函数。

关于对"++"和"--"运算符的重载

由于"++"和"--"运算符有2种使用方式,前置运算和后置运算的不同的。针对这一特性,c++约定:如果在自增(自减)运算符重载函数中,若无参数就表示前置运算符函数,若加一个int型形参,就表示后置运算符函数。

对"<<"和">>"的重载后期再讲

二. 动态绑定


1.虚函数

   虚函数是动态绑定的基础,并且虚函数必须是非静态的成员函数;虚函数经过派生之后,在类族中就可以实现运行过程中的多态。

注意:

   根据赋值兼容原则,可以使用派生类的对象代替基类的对象,如果基类类型的指针指向派生类的对象,就可以使用这个指针来访问该对象,但是访问到得只是从基类继承到得同名成员,解决这一问题的办法就是在基类中将这个同名函数声明为虚函数。这样通过基类的指针就可以使不同派生类的不同对象产生不同的行为,从而实现了运行过程中的多态!

虚函数的声明格式:

virtual 函数类型 函数名(参数表)

{

    函数体;

}

使用虚函数的条件:

   首先类之间满足赋值兼容性原则;

   其次要声明虚函数;

   再次要有成员函数来调用,或者指针,引用来访问虚函数。

 

1.1未使用虚函数

代码:

#include <iostream>
using namespace std; 
class pet
{
public:
    void speak() 
//未使用虚函数
    {
       cout 
<< "How does a pet speak!" << endl;
    }
};
 
class cat:public pet
{
public:
    void speak()
    {
       cout 
<< "miao ! miao!" << endl;
    }
};
 
class dog:public pet
{
public:
    void speak()
    {
       cout 
<< "wang! wang!" << endl;
    }
};
 
int main()
{
    pet *p1, *p2, *p3, 
obj;
    dog dog1;
    cat cat1;
    //1.派生类的对象可以赋值给基类的对象
     obj = dog1; //派生类的对象可以复制给基类对象
    obj.speak(); //派生类作为基类使用,只能使用基类里成员函数
 
    //2.派生类对象的地址可以赋值给指向基类的指针
     p1 = &cat1; //派生类的对象的地址可以复制给基类对象指针
     p1->speak();//派生类对象作为基类使用,只能使用基类里的成员函数

    //3.派生类的对象可以初始化基类的引用
    pet &p4 = cat1; //派生类对象可以初始化基类的引用
     p4.speak();//派生类对象作为基类使用,只能使用基类里的成员函数
 
    dog1.speak();
    cat1.speak();
 
    return 0;
}

执行结果:

     How does a pet speak!

     How does a pet speak!

     How does a pet speak!

     wang! wang!

     miao ! miao!

1.2使用虚函数

代码:

#include 
<iostream>
using namespace std;
 
class pet
{
public:
    virtual void speak() 
//使用虚函数
    { 
      
 cout << "How does a pet speak!" 
<< endl;
    }
};
 
class cat:public pet
{
public:
    void speak()
    {
       cout 
<< "miao ! miao!" << endl;
    }
};
 
class dog:public pet
{
public:
    void speak()
    {
       cout 
<< "wang! wang!" << endl;
    }
};
 
int main()
{
    pet *p1, *p2, *p3, 
obj;
    dog dog1;
    cat cat1;
    //1.派生类的对象可以赋值给基类的对象
     obj = dog1; //派生类的对象可以复制给基类对象
    obj.speak(); //派生类作为基类使用,只能使用基类里成员函数
 
    //2.派生类对象的地址可以赋值给指向基类的指针
     p1 = 
&cat1; //派生类的对象的地址可以复制给基类对象指针
     p1->speak();//派生类对象作为基类使用,只能使用基类里的成员函数

    //3.派生类的对象可以初始化基类的引用
    pet &p4 = cat1; //派生类对象可以初始化基类的引用
     p4.speak();//派生类对象作为基类使用,只能使用基类里的成员函数
 
    dog1.speak();
    cat1.speak();
 
    return 0;
}

执行结果:

     wang! wang!

     miao ! miao!

     miao ! miao!

     wang! wang!

     miao ! miao!

函数实现机制:


2.纯虚函数

   纯虚函数是指在基类中声明该虚函数,但是在该基类中没有该函数的实现,各派生类可以根据具体的要求定义自己的函数。

声明格式:

class 类名

{

    virtual 函数类型函数名(参数表)=0;

}

作用:

    在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数,若要使派生类为非抽象类,则编译器要求在派生类中,必须对纯虚函数予以重载以实现多态性

 

3.抽象类

   带有纯虚函数的类称为抽象类,通过它可以为一个类族建立一个公共的接口,使得他们更有效的发挥其多态性。

注意:

   不能声明抽象类的对象,但是可以声明抽象类的指针和引用,通过指针和引用,就可以指向并访问派生类的对象,进而访问派生类的成员!

 

代码示例:

#include <iostream>
#include <string>
using namespace 
std;
 
class pet
{
    string 
m_strName;//默认为private
    int m_nAge;
    string 
m_strColor;
public:
    string 
m_strType;
    pet(string name, int age, 
string color);
    string GetName()
    {
       return 
m_strName;
    }
 
    int GetAge()
    {
       return 
m_nAge;
    }
 
    string 
GetColor()
    {
       return 
m_strColor;
    }
    virtual void speak() = 
0;//纯虚函数声明
    virtual void GetInfo() = 
0;
};
 
pet::pet(string name, int age, string 
color)
{
    m_strName = 
name;
    m_nAge = age;
    m_strColor = 
color;
    m_strType = 
"pet";
}
 
class cat:public pet
{
public:
    cat(string name, int age, 
string color):pet(name, age, color)
    {}
    void speak()
    {
       cout 
<< "miao! miao!" << endl << endl;
    }
 
    void GetInfo();
};
 
void cat::GetInfo()
{
    cout << "The cat's 
name:" << GetName() << endl;
    cout << "The cat's 
age:" << GetAge() << endl;
    cout << "The cat's 
color:" << GetColor << endl;
}
 
class dog:public pet
{
public:
    dog(string name, int age, 
string color):pet(name, age, color)
    {}
 
    void speak()
    {
       cout 
<< "wang! wang!" << endl << endl;
    }
    
    void GetInfo();
};
 
void dog::GetInfo()
{
    cout << "The dog's 
name:" << GetName() << endl;
    cout << "The dog's 
age:" <<  GetAge() << endl;
    cout << "The dog's 
color:" << GetColor << endl;
}
 
int main()
{
    pet *pl;
#if 1
    pl = new cat("Tom", 1, 
"white");
    pl->GetInfo();
    pl->speak();
    delete pl;
#endif
    pl = new dog("Jerry", 2, 
"Black");
    pl->GetInfo();
    pl->speak();
    delete pl;
 
    return 0;
}

执行结果:

       The dog's name:Tom

       The dog's age:1

       The dog's color:1

       miao! miao!


       wang! wang!