对消息、消息传递和多态的理解

本文探讨了C++中的消息传递概念,详细解释了多态的不同形式,包括参数多态、包含多态、重载多态和强制多态。此外,还介绍了通过类型转换构造函数和类型转换运算符在C++中实现的强制多态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        以前学习面向对象的时候,对这消息的概念一直很不解,对多态也是一知半解。最近在看书的过程中,逐渐明白了这两个概念。现将对两者的理解整理如下:

  • 消息和消息传递 
         对象之间进行通信的一种构造叫做消息。当一个消息发送给某个对象时,包含要求接收对象去执行某些活动的信息,接受到信息的对象经过解释,然后予以响应。这种通信机制叫做消息传递。发送消息的对象不需要知道接收消息的对象如何对请求予以响应。
        OOP使得对象具有交互能力的主要模型就是消息传递模型。对象被看成用传递消息的方式互相联系的通信实体,它们既可以接受、也可以拒绝外界发来的消息。一般情况下,对消接收它能够识别的消息,拒绝它不能识别的消息。对于一个对象而言,任何外部代码都不能以任何不可预知或事先不运行的方式与这个对象进行交互。
        发送一条消息至少应给出一个接收 对象的名字和要发给这个对象的那条 消息的名字。通常,消息的名字就是这个对象中外界可知的某个方法的名字。在消息中,经常还有 一组参数(也就是那个方法所要求的参数),将外界的信息传给这个对象。
       对于一个类来说,它关于方法界面的定义规定了实例的消息传递协议。
       通俗地说,消息就是类中声明为public的方法(接口)。而消息传递就是在一个对象的某个方法中调用另一个对象中的某个方法。

  • 多态
                在程序设计领域,一个广泛认可的定义是“一种将不同的特殊行为和单个泛化记号相关联的能力”。
                Cardelli and Wegner把多态从语言设计的角度分为通用多态( universal )和特定多态( ad-hoc)。
        通用多态又分为参数( Parametric )多态和包含( Inclusion)多态;特定多态包含重载(overloading )
        多态和强制(coercion )多态。       
                具体分类如下图所示:
                                                                             |-- parametric
                                                    |-- universal --|
                                                    |                        |-- inclusion
                      polymorphism --|
                                                    |                        |-- overloading
                                                    |-- ad hoc     --|
                                                                             |-- coercion

        对于通用多态,相同的代码应用于不同的类型;而在特定多态里,不同的代码可由不同的类型执行。
  • 参数多态
        当函数在不同的类间以相同的方式运作时,是参数多态。它需要类型参数,比如C++中的模版(templates)。例子(C++)如下:
我们编写彼此无关的具体类Car和Airplane(它们都有一个run()成员函数)。
// static_poly.h

#include <iostream>

//具体类Car
class Car
{
public:
    void run() const
    {
        std::cout << "run a car/n";
    }
};

//具体类Airplane
class Airplane
{
public:
    void run() const
    {
        std::cout << "run a airplane/n";
    }
};

// static_poly_1.cpp

#include <iostream>
#include <vector>
#include "static_poly.h"

// 通过引用而run任何vehicle
template <typename Vehicle>
void run_vehicle(const Vehicle& vehicle)
{
    vehicle.run();            // 根据vehicle的具体类型调用对应的run()
}
 
int main()
{
    Car car;
    Airplane airplane;
    run_vehicle(car);         // 调用Car::run()
    run_vehicle(airplane);    // 调用Airplane::run()
}
现在Vehicle用作模板参数而非公共基类对象(事实上,这里的Vehicle只是一个符合直觉的记号而已,此外别无它意)。经过编译器处理后,我们最终会得到run_vehicle<Car>()和 run_vehicle<Airplane>()两个不同的函数。

  • 包含多态
        一个对象可以被视为属于许多有某种联系的类时,存在包含多态,也就是说,存在类之间的包含关系。包含多态依赖于继承( inheritance)和子类型(subtyping)。 C++中包含多态的实现需要继承和虚函数的支持。 如果说参数多态是通过“彼此单独定义但支持共同操作的具体类”来表达共同性的话(换句话说,必须存在必需的同名成员函数),那么包含多态则是通过虚函数来表达共同接口。 例子( C++)如下:
我们可以定义一个抽象基类Vehicle和两个派生于Vehicle的具体类Car和Airplane:

// dynamic_poly.h

#include <iostream>

// 公共抽象基类Vehicle
class Vehicle
{
public:
    virtual void run() const = 0;
};

// 派生于Vehicle的具体类Car
class Car: public Vehicle
{
public:
    virtual void run() const
    {
        std::cout << "run a car/n";
    }
};

// 派生于Vehicle的具体类Airplane
class Airplane: public Vehicle
{
public:
    virtual void run() const
    {
        std::cout << "run a airplane/n";
    }
};
客户程序可以通过指向基类Vehicle的指针(或引用)来操纵具体对象。通过指向基类对象的指针(或引用)来调用一个虚函数,会导致对被指向的具体对象之相应成员的调用:

// dynamic_poly_1.cpp

#include <iostream>
#include <vector>
#include "dynamic_poly.h"

// 通过指针run任何vehicle
void run_vehicle(const Vehicle* vehicle)
{
    vehicle->run();            // 根据vehicle的具体类型调用对应的run()
}

int main()
{
    Car car;
    Airplane airplane;
    run_vehicle(&car);         // 调用Car::run()
    run_vehicle(&airplane);    // 调用Airplane::run()
}

此例中,关键的多态接口元素为虚函数run()。由于run_vehicle()的参数为指向基类Vehicle的指针,因而无法在编译期决定使用哪一个版本的run()。在运行期,为了分派函数调用,虚函数被调用的那个对象的完整动态类型将被访问。这样一来,对一个Car对象调用run_vehicle (),实际上将调用Car::run(),而对于Airplane对象而言将调用Airplane::run()。
  • 重载多态
        重载多态简单地理解就是函数重载。函数重载即基于不同的参数列表,同一个函数名字可以指向不同的函数定义。它与类的继承无关。例子(C++)如下:
 
// overload_poly.cpp

#include <iostream>
#include <string>

// 定义两个重载函数

int my_add(int a, int b)
{
    return a + b;
}

int my_add(int a, std::string b)
{
    return a + atoi(b.c_str());
}

int main()
{
    int i = my_add(1, 2);                // 两个整数相加
    int s = my_add(1, "2");              // 一个整数和一个字符串相加
    std::cout << "i = " << i << "/n";
    std::cout << "s = " << s << "/n";
}

根据参数列表的不同(类型、个数或兼而有之),my_add(1, 2)和my_add(1, "2")被分别编译为对my_add(int, int)和my_add(int, std::string)的调用。实现原理在于编译器根据不同的参数列表对同名函数进行名字重整,而后这些同名函数就变成了彼此不同的函数。比方说,也许某个编译器会将my_add()函数名字分别重整为my_add_int_int()和my_add_int_str()。
  • 强制多态
        强制多态简单地理解就是,编译程序通过语义操作,把操作对象的类型强行加以变换,以符合函数或操作符的要求。程序设计语言中基本类型的大多数操作符,在发生不同类型的数据进行混合运算时,编译程序一般都会进行强制多态。程序员也可以显示地进行强制多态的操作(Casting)。
        要注意的是并不是任意两个类型之间都可以进行强制多态。在不同类型之间实现强制多态,通常需要执行不同的转换操作。强制多态的原则是:将值集较小(即占用 存储空间较小)的类型,变换成值集包含了前者(即占用存储空间较大)的类型,反之,应当注意可能发生的对值的损伤(特别是在使用Casting时)。
        有时,强制多态与重载多态(操作符重载)是混合出现的。例如,
        对于表达式
                            1+2;
                            1.0+2;
                            1+2.0;
                            1.0+2.0;
中出现的多态,就会有多种解释:
·操作符+有四种过载多态;
·操作符+只有一种:double * double -> double, 要将参与运算的整数强制变换成浮点数;
·操作符+有两种过载多态:int * int -> int 和 double * double -> double,要将混合运算中的整数强制变换成浮点数。   
         以上的强制多态主要是通过操作符重载所表现出来的多态,另外,还有两种不同的方法实现强制多态,它们分别是,1)通过类型转换构造函数(type conversion constructor)(类A的构造函数接收一个类B的对象),2)通过类型转换操作符(type conversion operator)(类B中的类型转换操作符返回一个类A的对象)。
例子(C++)如下:

Coercion in C++ using a type conversion constructor

class B 
{ public:
void hello() const
{ cout << "Exemplar of B" << endl;
}
};

class A
{ public:
A(const B&) {} // type conversion constructor
A() {} // standard constructor
void hello() const
{ cout << "Exemplar of A" << endl;
}
};

void test(const A& v)
{ v.hello();
};

// ...
A a;
B b;
test(a);
test(b); // b "changes" to an A here

Coercion in C++ using a type conversion operator

class A
{ public:
void hello() const
{ cout << "Exemplar of A" << endl;
}
};

class B
{ public:
void hello() const
{ cout << "Exemplar of B" << endl;
}
operator A() const
{ return A(); // return an A
}
};

void test(const A& v)
{ v.hello();
};

// ...

A a;
B b;
test(a);
test(b); // b "changes" to an A here

有一种说法是,通用多态是真正的多态,而特定多态却不是,它是某种表面上的多态,因为它的多态特征
会在某种情况下消失(?disappears at close range)。

注:JAVA目前提供了跟C++完全一样的包含多态机制。因为JAVA里每个方法默认都是虚的(函数)。现在也
有人提议在Java里加入参数多态。

另外,对于多态,根据多态发生时间的不同可以分为静态多态和动态多态。其中,通过类继承和虚函数
机制生效于运行期的多态,称为动态多态(dynamic polymorphism);模板也允许将不同的特殊行为和单个泛化
记号相关联,由于这种关联处理于编译期而非运行期,因此被称为静态多态(static polymorphism)。
动态多态只需要一个多态函数,生成的可执行代码尺寸较小,静态多态必须针对不同的类型产生不同的模板实体,
尺寸会大一些,但生成的代码会更快,因为无需通过指针进行间接操作。静态多态比动态多态更加类型安全,
因为全部绑定都被检查于编译期。
容易知道,前面的包含多态属于动态多态,而参数多态和重载多态和强制多态则属于静态多态。

参考资料:
[1] C++多态技术 http://www.vckbase.com/document/viewdoc/?id=948
[2] OBJECT ORIENTED PROGRAMMING POLYMORPHISM Sanchit Karve http://www.dreamincode.net/forums/showtopic9872.htm
[3] Polymorphism In Object_Oriented Languages W. Eisenecker http://accu.org/index.php/journals/538


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值