当多态遇上数组 ... [C++, C++/CLI, C#]
When Polymorphism Meets Arrays ... [C++, C++/CLI, C#]
Written by Allen Lee
犹如星空与海鸥,漫画里根本你我一生永不会聚头,但我誓要共你牵手。
—— 古巨基,《美雪,美雪》
1. 能力测试
请回答下面的问题:
实现多态的效果,我们需要具备哪些条件?
上面的题目仅用于测试你是否具备阅读本文的必要条件。如果你对此题毫无头绪,那么我建议你尽快交叉本文。当然,要顺畅阅读本文,你还需要具备一些C++、C++/CLI和C#的基础知识。我想不必特别声明,建议当然是主观的。不过我愿意再说一次:建议是主观的,你有选择。
2. ... In C++,
好吧,既然你走到这一步,我建议你坚持下去。当然,你还是随时可以交叉本文,总之,你有选择。来,请先看看如下代码:
#include
#include
{
public:
virtual void Print()
{
std::cout << "A" << std::endl;
}
} ;
class B : public A
{
public:
virtual void Print() override
{
std::cout << "B" << std::endl;
}
} ;
class C : public A
{
public:
virtual void Print() override
{
std::cout << "C" << std::endl;
}
} ;
void Print(A arr[], int count)
{
for (int i = 0; i < count; ++i)
{
arr[i].Print();
}
}
int _tmain( int argc, _TCHAR* argv[])
{
const int COUNT = 11;
A a_arr[COUNT];
Print(a_arr, COUNT);
B b_arr[COUNT];
Print(b_arr, COUNT);
C c_arr[COUNT];
Print(c_arr, COUNT);
return 0;
}
- A:请告诉我,Code #01有没有问题?
- B:Easy job!把它编译并运行一下就知道了呗!
接着,阿B编译了这段代码,程序运行后,阿B发现输出结果正是我们所期望的。于是,阿B得出结论:这段代码没问题!
然而,你是否知道这段代码其实隐藏着一个计时炸弹,只要时机一到,它就会引爆并使你的程序完全瘫痪?不信?那我只好让你见识一下这个炸弹引爆是的情景了。现在,我分别为class B和class C加入一个field,并修改一下main()以便给class B和class C里的field赋值(注意,本赋值操作不是必须的!):
#include
#include
{
public:
virtual void Print()
{
std::cout << "A" << std::endl;
}
} ;
class B : public A
{
public:
virtual void Print() override
{
std::cout << "B" << std::endl;
}
long L;
} ;
class C : public A
{
public:
virtual void Print() override
{
std::cout << "C" << std::endl;
}
double D;
} ;
void Print(A arr[], int count)
{
for (int i = 0; i < count; ++i)
{
arr[i].Print();
}
}
int _tmain( int argc, _TCHAR* argv[])
{
const int COUNT = 11;
A a_arr[COUNT];
Print(a_arr, COUNT);
B b_arr[COUNT];
for (int i = 0; i < COUNT; ++i)
{
b_arr[i].L = 141214121412;
}
Print(b_arr, COUNT);
C c_arr[COUNT];
for (int i = 0; i < COUNT; ++i)
{
c_arr[i].D = 3.141592654;
}
Print(c_arr, COUNT);
return 0;
}
计时炸弹终于被引爆了:
可见程序无法找到b_arr[1]确切位置,其后果不会好过你在街上把一个背影看似相熟的女性误认为是你的女朋友时所产生的尴尬。至于产生这个问题的原因,Scott Meyers已经在他的《More Effective C++》作了详细的讲解[1],这里我就没必要重复劳动了。
显然,产生这个问题的根本原因是数组里的存放物长度不一致,如果能够让存放物的长度统一起来,问题就会迎刃而解了。问题男提出把指针放进数组,好吧,现在我们来看一下C++中多态与数组牵手的景象:
// See Code #02 for Class A, Class B and Class C.
void Print(A *arr[], int count)
{
for (int i = 0; i < count; ++i)
{
arr[i]->Print();
}
}
int _tmain( int argc, _TCHAR* argv[])
{
const int COUNT = 11;
A** a_arr = new A*[COUNT];
B** b_arr = new B*[COUNT];
C** c_arr = new C*[COUNT];
for (int i = 0; i < COUNT; ++i)
{
a_arr[i] = new A;
b_arr[i] = new B;
b_arr[i]->L = 141214121412;
c_arr[i] = new C;
c_arr[i]->D = 3.141592654;
}
Print(a_arr, COUNT);
Print(reinterpret_cast< A** >(b_arr), COUNT);
Print(reinterpret_cast< A** >(c_arr), COUNT);
return 0;
}
问题解决了,不过,我还是强烈建议你(当然,你有选择!):
在C++中,请尽量使用STL中的容器类来协助实现多态的效果。
3. ... In C++/CLI,
在C++/CLI的托管类型(Managed Type)中,只有ref class(或ref struct)[2]可用于建立继承体系。下面,我们来看看在C++/CLI中使用数组实现多态的情形:
using namespace System;
using namespace stdcli::language;
ref class A
{
public:
virtual void Print()
{
Console::WriteLine("A");
}
} ;
ref class B : public A
{
public:
virtual void Print() override
{
Console::WriteLine("B");
}
long L;
} ;
ref class C : public A
{
public:
virtual void Print() override
{
Console::WriteLine("C");
}
double D;
} ;
void Print(array< A^ >^ arr)
{
for (int i = 0; i < arr->Count; ++i)
{
arr[i]->Print();
}
}
int _tmain()
{
const int COUNT = 11;
array< A^ >^ a_arr = gcnew array< A^ >(COUNT);
for (int i = 0; i < a_arr->Count; ++i)
{
a_arr[i] = gcnew A;
}
Print(a_arr);
array< B^ >^ b_arr = gcnew array< B^ >(COUNT);
for (int i = 0; i < b_arr->Count; ++i)
{
b_arr[i] = gcnew B;
b_arr[i]->l = 141214121412;
}
Print(b_arr);
array< C^ >^ c_arr = gcnew array< C^ >(COUNT);
for (int i = 0; i < c_arr->Count; ++i)
{
c_arr[i] = gcnew C;
c_arr[i]->d = 3.141592654;
}
Print(c_arr);
}
4. ... In C#,
而在C#中,使用数组来实现多态又是如何的呢:
using System;
class A
{
public virtual void Print()
{
Console.WriteLine("A");
}
}
class B : A
{
public override void Print()
{
Console.WriteLine("B");
}
public long L;
}
class C : A
{
public override void Print()
{
Console.WriteLine("C") ;
}
public double D;
}
class Program
{
static void Main(string[] args)
{
const int COUNT = 11;
A[] a_arr = new A[COUNT];
for (int i = 0; i < a_arr.Length; i++)
{
a_arr[i] = new A();
}
Print(a_arr);
B[] b_arr = new B[COUNT];
for (int i = 0; i < b_arr.Length; i++)
{
b_arr[i] = new B();
b_arr[i].L = 141214121412;
}
Print(b_arr);
C[] c_arr = new C[COUNT];
for (int i = 0; i < c_arr.Length; i++)
{
c_arr[i] = new C();
c_arr[i].D = 3.141592654;
}
Print(c_arr);
}
public static void Print(A[] arr)
{
for (int i = 0; i < arr.Length; ++i)
arr[i].Print();
}
}
5. 牵手的条件
其实,多态能够牵上数组的手的条件很简单:平等。回顾上面所有的代码,我们不难发现,数组中的存放物等长是完成多态效果的必要条件,否则,编译器将被误导继而生成有问题的程序。
6. 关于舞台
Code #01、Code #02和Code #03都是使用Visual C++ 2005 Express Edition Beta 1来编辑和编译的,其中Code #01和Code #02是标准的Win32 Console Application,而Code #03是.NET Console Application。Code #04是使用Visual C# 2005 Express Edition Beta 1来编辑和编译的。
See also:
- Allen Lee,《今天你多态了吗?》
- [1] Scott Meyers 著;侯 捷 译;《More Effective C++中文版》P16;中国电力出版社,2003
- [2] C++/CLI中 ref class 和 ref struct 之间的区别相当于 Standard C++ 中class 和 struct 之间的区别。