您的位置:首页 > 编程语言 > C语言/C++

(钟豪原创) C#与C++中的方法的对比分析

2009-08-30 13:19 441 查看

第1章 方法与函数对比分析

1.1 函数与方法概念上的区别
虽然在C++中,大多数数据成为类的数据成员,而大多数对数据的操作放在了类的成员方法中,但是由于C++的向下兼容性,使得它仍然可以像面向过程的语言如C语言中一样把数据和对数据的操作分为两部分。所以通常在C++类中的对数据操作的方法成员仍然被称做为函数。
与C++相比,C#实现了完全意义上的面向对象:任何事物都必须封装在类中,或者作为类的实例成员。在C#中,没有全局常数、全局变量,也没有全局方法。由于这个特性,那些所有对数据进行操作的函数在C#中均被称为方法。
1.2 方法的申明与调用比较分析

1.2.1 C#和C++中类的方法申明的比较

(1) 通常C++类中的方法是声明在类的封装符“{ }”及类主体之外的,在类的封装符内一般只需给出该方法所需要的参数,以及返回值类型(如果有的话)。而该方法的具体实现是在类主体之外申明的。更加常用的做法是在建立C++程序的时候,每个类定义通常放在头文件中,类中的方法(成员函数)放在相同基本名字的源代码文件中。在使用每个文件中包含头文件。也可以把方法的具体实现放在类的主体内,这种情况在C++中称为隐式申明,也叫内联函数。但会降低执行效率。
(2) 而在C#中的方法的具体实现代码是放在类的封装符”{}”也就是类的主体内部的。

1.2.2 C#和C++中调用类的方法的比较

#include<iostream>
using namespace std;
class A
{public:
int i;
void foo();
};
void A::foo()
{
};

void main()
{
A simon,bill;
cout<<"The int's size is :"<<sizeof(int)<<endl;
cout<<"The simon's size is:"<<sizeof(simon)<<endl;
cout<<"The bill's size is:"<<sizeof(bill)<<endl;
}

程序运行结果:
The int's size is : 4
The simon's size is: 4
The bill's size is: 4
Press any key to continue

我们可以看到simon和bill对象的大小和它们所包含的int数据成员大小一致,所以我们可以得出这样一个结论:类中包括普通成员变量,但不包括普通成员函数(或方法)。这样做可以节约存储空间,每个类的每个成员函数只有一个副本,该类的每个对象都可以调用这个成员函数。另一方面每个对象又有自己的数据成员副本。
那么在C++中,编译器是如何分辨究竟向共享的成员函数传递哪个对象呢?事实上每个对象都可以通过this指针访问自己的地址。this指针在每个非static成员函数调用对象时作为第一个隐式参数传递给对象。This指针隐式引用对象的数据成员和成员函数。每个非static成员函数都能访问所调用成员所在对象的this指针。我们来看下面一段代码:
#include<iostream.h>
class Test
{public:
Test(int = 0);
void print() const;
private:
int x;
};
Test::Test(int a){ x = a ;}
void Test::print() const
{
cout<<" x = "<<x
<<"/n this->x = "<<this->x
<<"/n The this pointer's address is "<<this<<endl<<endl;
}
int main()
{
Test simon(12),Bill(10);
cout<<" simon's address is "<<&simon<<endl;
simon.print();
cout<<"/n Bill's address is "<<&Bill<<endl;
Bill.print();
return 0;
}

程序运行结果:
simon's address is 0x0013FF7C
x = 12
this->x = 12
The this pointer's address is 0x0013FF7C

Bill's address is 0x0013FF78
x = 10
this->x = 10
The this pointer's address is 0x0013FF78

Press any key to continue

可见在C++中,当调用对象的非static函数时,所调用的函数是去访问该被调用方法所在对象的this指针。然后通过this指针就可以访问该对象的数据了。
而在C#中,由于C#中的类都是接受垃圾回收收集器的控制,所以我们不能使用sizeof()函数来判定C#中的类实例是否是只包含数据成员。我们通过以下程序来判断在C#中同一类中的不同对象是如何分辨属于自己的方法的。
using System;
namespace ConsoleApplication2
{
class Class1
{
static void Main(string[] args)
{
SeeSharp bill,simon;
bill = new SeeSharp();
simon = new SeeSharp();
Console.WriteLine("Call bill's methord----foo:");
bill.foo();
Console.WriteLine("Call simon's methord----foo:");
simon.foo();
}
}
public class SeeSharp{
public int i;
public void foo()
{
int j;
unsafe {
Console.Write("j's address is:");
Console.WriteLine((uint)&j);
}
}
}

程序执行结果:
Call bill's methord----foo:
j's address is:1308288
Call simon's methord----foo:
j's address is:1308288

结论:可以看到bill,simon对象所调用的非static成员方法foo()的地址是一样的。所以可以断定在C#中每个类的每个方法只有一个副本,该类的每这样个对象都可以调用这个方法。显然这样做的目的还是为了节约内存,因为方法的代码对于所有的对象实例都是相同的。

1.2.3 C#中不能把方法声明为inline

C#中不能把方法显示的声明为inline。因为C#源程序会被编译成中间语言(IL)。任何内联将会在编译的第二个阶段发生。Just-in-time编译器将把IL转换成本地机器代码。JIT编译器将会访问IL中的所有信息,以决定哪些适合被内联,这一过程不需要程序员在源代码中作出说明。
1.3 静态函数以及静态方法比较分析
类的每个对象都有自己的所有数据成员的副本,有时类的所有对象应共享变量的一个副本,可以使用 Static类变量。Static类变量表示的是类范围中所有对象共享的信息。

1.3.1 静态函数的调用方式

在C++和C#中的Static成员的声明都是以Static关键字开始。例如:下面一段C++代码:
#include <iostream.h>
class Point
{
public:
Point(int xx=0, int yy=0) {X=xx;Y=yy;countP++;}
Point(Point &p);
int GetX() {return X;}
int GetY() {return Y;}
static void GetC() {cout<<" Object id="<<countP<<endl;}
private:
int X,Y;
static int countP;
};
Point::Point(Point &p)
{
X=p.X;
Y=p.Y;
countP++;
}

int Point::countP=0;

void main()
{
Point A(4,5);
cout<<"Point A,"<<A.GetX()<<","<<A.GetY();
A.GetC(); //(A)
Point B(A);
cout<<"Point B,"<<B.GetX()<<","<<B.GetY();
Point::GetC(); //(B)
Point C(5,6);
cout<<"Point C,"<<C.GetX()<<","<<C.GetY();
Point::GetC(); //(C)
}

程序运行结果:
Point A,4,5 Object id=1
Point B,4,5 Object id=2
Point C,5,6 Object id=3
Press any key to continue

注意这段程序的代码(A),(B)处,我们可以看到C++中访问静态数据成员函数可以直接通过对象名来访问,也可以通过“类名::静态成员函数”的方式来访问(如:代码段(C)处!)。但是在C#中,欲访问静态数据成员或者成员函数,必须采用“类名.静态方法”的方式。这种方式其实比C++中的通过对象名来访问静态共享数据更加合理,因为static方法没有this指针,它是独立于类的对象而存在的。所以若通过对象名来访问static方法将给人一种似乎static方法是与对象关联的误解。

1.3.2 静态函数访问非静态数据成员

首先,不管是在C++还是C#中的static成员函数或者方法,都不能访问非static类数据成员以及成员函数或者方法。但是也可以做些特殊的处理使静态函数访问非静态数据成员:通过参数传递方式得到对象名,然后通过对象名来访问。如下面一段C#代码:
using System;
namespace NoStatic
{
class Class1
{
static void Main(string[] args)
{
A a = new A(2);
A.foo(a);
}
}
class A
{
private int x;
public A(int a){x = a;}
public static void foo(A a)
{
Console.WriteLine(a.x);
}
}
}
经过以上对C++和C#中类成员的分析,我们可以得出类所封装的代码在内存中的形式如下图:

图1-1
经过分析可以得出以下结论:同种类的各个不同的对象拥有内存中的一段来存储自己的非静态数据成员,而对于静态数据成员则是采用所有对象共享一段特殊的程序静态数据区域。对于类中所有静态和非静态函数(方法)均是采用存储到专门的代码段内存空间,而同类的不同对象均共享这部分代码空间。
1.4 C++函数的缺省参数
如果我们在定义一个C++函数的时候就知道这个函数的其中一个参数将取某个确定的值,那么我们就可以在函数头部使用标准初始化语法嵌入这个值。例如,如果函数foo(int x,int y)的形式参数在绝大多情况下都将被设置为100,那么我们就可以使用下面的方式来定义这个函数:
int foo(int x, int y=100){ ………… }。
100这个值是这个函数的形参缺省的实参。经过这样的定义后,我们可以
对这个函数进行下列方式的调用:
foo(23); foo(34,200);
我们只能为一个函数中参数列表中缀尾参数提供缺省实参。举例说明,假如有一个带3个参数的函数int foo(int x,int y,int z),我们可以只为最后一个形参 z提供缺省实参,也可以只为y,z两个形参提供缺省实参,但是我们不能不为最后一个形参z不提供缺省实参,否则我们就不能为函数参数列表中的其他任何一个形参提供缺省参数。
在C#中不允许有默认参数说明符,如果要获得同样的效果,只能使用方法重载。
1.5 C#中的参数传递

1.5.1 按照传值模式传递一个基本类型的参数

我们通过一个例子来说明C#中的基本类型参数的传值调用,其实它和C++中基本类型参数的传值调用是没有任何区别的。
using System;
namespace PassByValue
{
class Class1
{
static void Main(string[] args)
{
int i = 100;
foo(i);
Console.WriteLine(i);
}
static void foo(int j){j+=10;}
}
}
此段代码和C++中的情况一样,程序在foo()函数体的对局部变量的修改并不会影响Main函数中的那个变量。因此,Main函数的打印拥语句将打印出i原来的数值。其中foo(int j)中的j得到的只是i的一份数据相同的拷贝,并不会影响到i中存储的数据。输出为:100

1.5.2 按照传递对象引用模式传递一个类类型的参数

我们通过下面的例子来说明C#中的根据传值方式传递一个类类型的参数的特点。
using System;
namespace PassClassTypeByValue
{
class Point
{
public int x,y;
public Point(int i,int j)
{
x=i;y=j;
}
}
class Class1
{
static void Main(string[] args)
{
Point myPoint = new Point(1,1); //(A)
Change(myPoint); //(B)
Console.WriteLine("myPoint.x = "+myPoint.x+",myPoint.y = "+myPoint.y);
}
static void Change(Point u)
{
u.x=100; //(C)
u.y=100; //(D)
}
}
}

程序运行结果:
myPoint.x = 100,myPoint.y = 100

这个程序首先在Test类的Main函数创建了一个Point对象。然后调用Change()方法,第A行所创建的那个对象的引用将作为参数传递给第B行的函数调用。在第C行的局部变量u所获得的是main函数中变量myPoint所保存的对象引用的一份拷贝。所以我们说它是通过传值方式传递了一个对象引用。我们知道,C#的对象引用可以看成是一个伪指针。所以,这种情形类似于在C++中通过指针模式传递一个类类型的参数。由于函数Change内部的变量u所保存的是myPoint引用的一份拷贝,所以u也指向和myPoint引用指向的那快内存,所以之后对u所做的修改其实就是对myPoint对象的字段的修改。
那是不是意味着C#的类类型的参数传递模式和C++中的类类型的传递引用模式是相同的呢?我们接着看下面一段程序:
using System;
namespace Swap
{
class Point
{
public int x,y;
public Point(int i,int j)
{
x=i;y=j;
}
}
class Class1
{
static void Main(string[] args)
{
Point x1,x2;
x1 = new Point(1,1);
x2 = new Point(2,2);
swap( x1, x2); //(A)
Console.WriteLine("x1 : x1.x = "+x1.x+" , x1.y = "+x1.y);
Console.WriteLine("x2 : x2.x = "+x2.x+" , x2.y = "+x2.y);
}
static void swap(Point x, Point y)
{
Point temp;
temp = x;
x = y;
y = temp;
}
}
}
如果C#的参数传递模式是传递引用的话,那么当程序在第A行调用swap函数应该导致两个参数所保存的对象引用进行交换。但事实上程序输出为:
x1 : x1.x = 1 , x1.y = 1
x2 : x2.x = 2 , x2.y = 2
上面的程序并,没有交换x1,x2对象引用,是因为程序执行第A行的swap函数时,局部变量x,y得到的只是x1,x2所保存的对象的引用的一份拷贝,虽然x,y所保存的对象引用在函数swap内部确实进行了交换,但Main函数内部x1,x2所保存的对象引用却并没有进行交换。
为了与C++做比较,我们来看看下面这个C++程序。在这个程序中,参数是根据引用方式来传递给swap函数的:
#include<iostream>
#include<string>
using namespace std;
class Point
{
public:
int x ; int y;
public:
Point(int xx,int yy)
{
x = xx; y = yy;
}
};
void swap(Point &x,Point &y)
{
Point temp = x;
x = y;
y = temp;
};
int main()
{
Point x1(1,1);
Point x2(2,2);
swap(x1,x2); //(A)
cout<<"x1 : x1.x = "<<x1.x<<" , x1.y = "<<x1.y<<endl;
cout<<"x2 : x2.x = "<<x2.x<<" , x2.y = "<<x2.y<<endl;
return 0;
}
当程序在第A 行调用swap函数时,函数swap中的引用x,y就成为了main函数的对象x1,x2的别名。当x,y在函数swap内部进行交换时,x1,x2也进行了交换。同样的结果也可以通过传递指针参数获得,只有我们在被调用函数内部对指针进行解引用,对指针所指向的内存地址的内容进行交换。

程序运行结果是:
x1 : x1.x = 2 , x1.y = 2
x2 : x2.x = 1 , x2.y = 1
Press any key to continue

1.5.3 方法引用传递到方法的同一个变量

那么在C# 中怎样实现可以交换对象的swap函数呢?答案是使用ref方法参数:ref方法参数关键字使方法引用传递到方法的同一个变量。当控制传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。要使用 ref 参数,必须将参数作为 ref 参数显式传递到方法。传递到 ref 参数的参数必须最先初始化。下面我们来看看修改后的swap程序:
using System;
namespace Swap
{
class Point
{
public int x,y;
public Point(int i,int j)
{
x=i;y=j;
}
}
class Class1
{
static void Main(string[] args)
{
Point x1,x2;
x1 = new Point(1,1);
x2 = new Point(2,2);
swap( ref x1, ref x2);//(A)
Console.WriteLine("x1 : x1.x = "+x1.x+" , x1.y = "+x1.y);
Console.WriteLine("x2 : x2.x = "+x2.x+" , x2.y = "+x2.y);
}
static void swap(ref Point x, ref Point y)
{
Point temp;
temp = x;
x = y;
y = temp;
}
}
}
当程序执行到第A行代码的时候,主程序调用swap函数。由于swap函数中的参数是按照引用方式传递的,引用参数是有效的输入/输出参数,如果参数的值在方法体内部修改,或者如果参数指向一个不同的对象,在方法外部的原始变量也将被更新并指向新的引用。所以当swap函数体内交换x,y对象时,Main函数中的x1,x2对象也同时交换了。

程序运行结果是:
x1 : x1.x = 2 , x1.y = 2
x2 : x2.x = 1 , x2.y = 1

在C#中,变量在使用前总是必须初始化。如果定义了一个方法,它将把一个值赋给一个输入参数,该输入变量在传递给方法前,仍必须被赋给一个值。使用out关键字可以避免这种无意义的步骤。使用out关键字修改的输入参数成为输出参数,能在初始化前传递给方法。Out关键字放在方法声明语句中变量类型的前面。方法调用中也必须使用out关键字。输出参数在方法返回前必须被赋给一个值。
1.6 C++中的Const成员函数
在C++中,如果某个成员函数的唯一用处就是提取可能位于对象私有部分的数据,那么我们可以把这个函数声明为const类型,如:
#include <iostream>
using namespace std;
class CCTest {
public:
void setNumber( int );
void printNumber() const;
private:
int number;
};
void CCTest::setNumber( int num ) { number = num; }
void CCTest::printNumber() const {
cout << "/nBefore: " << number;
//const_cast< CCTest * >( this )->number--; //(A)
//cout << "/nAfter: " << number; //(B)
}
int main() {
CCTest X;
X.setNumber( 8 );
X.printNumber();
}
我们看到通过把printNumber函数声明为const,编译器就可以保证这些函数的实现代码将不会意外的修改对象的状态。应该说C++中设计的const成员函数是非常合理的,但是在C#中,有一个限制,就是成员方法不能声明为const。我们看到代码第(A)行,如果我们去掉该注释,那么我们就可以通过const_cast操作来回避已经声明的const的方法;也就是某些情况下,我们虽然不改变类公共状态的方法,但通常会要改变私有成员变量的值。考虑到这些问题,Microsoft不允许在C#中使用const方法。
1.7 C#中的委托与C++中的函数指针
C#中的委托就是指将方法指针和一个对象引用直接到一个特定的类中。其中,对象引用指针对被调用的方法。它类似于应用程序中指向对象中的方法的指针。在C++中并没有委托这个概念,但是C++中的函数指针与C#中委托所做的工作是相同的。而在C#中又没有函数指针这一个概念,为什么C#会取消函数指针呢?

1.7.1 函数指针与委托的相同点

下面我们来构造一个类,这个类有两个数据成员;有一个接受函数指针、或者委托的方法,该方法用来根据函数指针、或者委托所指向的函数来对两个数据成员进行升序或者降序排序。
#include<iostream.h>
class SortClass
{
public:
int val1;
int val2;
public:
void DoSort( void(*p_SortPattern1)(int&,int&))
{
p_SortPattern1(val1,val2);
}

};
void Ascending(int &first ,int &second)
{
if(first > second)
{
int tmp = first;
first = second;
second = tmp;
}
};
void Descending(int &first ,int &second)
{
if(first < second )
{
int tmp = first;
first = second;
second = tmp;
}
};
void main()
{
void(*p_SortPattern)(int&,int&) = NULL;
SortClass howells;
howells.val1 = 20;
howells.val2 = 10;
cout<<"排 序 前:howells.val1 = "<<howells.val1<<"; howells.val2 = "<<howells.val2<<endl;
p_SortPattern = Ascending;//(A)把Ascending的函数地址赋给函数指针。
howells.DoSort(p_SortPattern);
cout<<"升序排序:howells.val1 = "<<howells.val1<<"; howells.val2 = "<<howells.val2<<endl;
p_SortPattern = Descending; //(B)把Descending的函数地址赋给指针。
howells.DoSort(p_SortPattern);
cout<<"降序排序:howells.val1 = "<<howells.val1<<"; howells.val2 = "<<howells.val2<<endl;
}

程序运行结果:
排 序 前:howells.val1 = 20; howells.val2 = 10
升序排序:howells.val1 = 10; howells.val2 = 20
降序排序:howells.val1 = 20; howells.val2 = 10
Press any key to continue

同样的功能在C#中实现是采用委托的概念,即建立一个委托即可实现代码如下:
using System;
public class SortClass
{
static public int val1;
static public int val2;
public delegate void Sort(ref int a, ref int b);
public void DoSort(Sort ar)
{
ar(ref val1, ref val2);
}
}
public class SortProgram
{
public static void Ascending( ref int first, ref int second )
{
if (first > second )
{
int tmp = first;
first = second;
second = tmp;
}
}
public static void Descending( ref int first, ref int second )
{
if (first < second )
{
int tmp = first;
first = second;
second = tmp;
}
}
public static void Main()
{
SortClass.Sort up = new SortClass.Sort(Ascending);
SortClass.Sort down = new SortClass.Sort(Descending);
SortClass doIT = new SortClass();
SortClass.val1 = 310;
SortClass.val2 = 220;
Console.WriteLine("排 序 前: val1 = {0}, val2 = {1}",
SortClass.val1, SortClass.val2);
doIT.DoSort(up);
Console.WriteLine("升序排序: val1 = {0}, val2 = {1}",
SortClass.val1, SortClass.val2);
doIT.DoSort(down);
Console.WriteLine("降序排序: val1 = {0}, val2 = {1}",
SortClass.val1, SortClass.val2);
}
}

程序运行输出为:
排 序 前: val1 = 310, val2 = 220
升序排序: val1 = 220, val2 = 310
降序排序: val1 = 310, val2 = 220

看来用C++中的函数指针和C#中的委托均能完成同样的工作,而且从上面的C++代码中并不能看出来有何不妥之处,那究竟是什么原因致使.NET Framework开发人员放弃了函数指针这一个概念呢?

1.7.2 C++中的函数指针不安全

C++中使用的函数指针不是类型安全的,函数指针其实就是内存地址,编译器并不知道它指向方法的签名。下面我通过两个程序来说明函数指针为什么是不安全的:
(1) 标准C++其中一条有趣的规则是:你可以在定义类之前声明它的成员函数指针。这将导致函数指针不安全!详细情况见下面程序代码:
#include<iostream.h>
class SomeClass;
typedef void(SomeClass::* SomeClassFunction)(void); //(A)
void Invoke(SomeClass *pClass, SomeClassFunction funcptr)
{
(pClass->*funcptr)();
};
class SomeClass
{
};
void main()
{
SomeClass *p = new SomeClass();
SomeClassFunction func;
Invoke(p,func);
}
注意到第A行代码处,我们声明了一个类成员指针类型,注意到该类成员指针类型声明是在类SomeClass之前,而此时编译器对SomeClass类一无所知。除非链接器进行了一些极端精细的优化措施,否则代码会忽视类的实际定义而能够正确地运行。
(2) 函数指针占用的空间是根据所指向的类的继承方式而变化的,并且允许其在子类和基类之间进行转化。详细情况见下面程序代码:
#include<iostream.h>
class A;
class B;
class C;
class D;
typedef int (A::* AFuncPointer)(void);
typedef int (B::* BFuncPointer)(void);
typedef int (C::* CFuncPointer)(void);
typedef int (D::* DFuncPointer)(void);
class A {
public:
int Afunc() { return 1; };
};
class B {
public:
int Bfunc() { return 2; };
};
class C: public A {
public:
int Cfunc() { return 3; };
};
class D: public A, public B {
public:
int Dfunc() { return 4; };
};
void main()
{
D howells;
AFuncPointer aFuncP;
CFuncPointer cFuncP;
DFuncPointer dFuncP;
cout<<"aFuncP指针的大小:"<<sizeof(aFuncP)<<endl;
cout<<"cFuncP指针的大小:"<<sizeof(cFuncP)<<endl;
cout<<"dFuncP指针的大小:"<<sizeof(dFuncP)<<endl<<endl;
dFuncP = D::Afunc;
cout<<"调用D的基类A中的Afunc函数:"<<(howells.*dFuncP)() <<endl;
dFuncP = D::Bfunc;
cout<<"调用D的基类B中的Bfunc函数:"<<(howells.*dFuncP)() <<endl;
dFuncP = D::Dfunc;
cout<<"调 用 D 中 的 Dfunc 函 数:"<<(howells.*dFuncP)() <<endl;
};

程序输出结果是:
aFuncP指针的大小:4
cFuncP指针的大小:4
dFuncP指针的大小:8
调用D的基类A中的Afunc函数:1
调用D的基类B中的Bfunc函数:2
调 用 D 中 的 Dfunc 函 数:4
Press any key to continue

可见在C++中,不同继承类型的类的成员函数指针大小是不同的,在Visual C++中,一个成员函数指针可以是4、8、12甚至16个字节长,这取决于它所相关的类的性质。
程序中建立了一个D类的成员函数指针,D类有两个不同的基类。在这种情况下, D类的一个成员函数指针不仅要说明要指明调用的是哪一个函数,还要指明使用哪一个this指针。编译器知道A类占用的空间有多大,所以它可以对Athis增加一个delta = sizeof(A)偏移量就可以将Athis指针转换为Bthis指针。将一个成员函数指针在子类和基类之间进行转化会改变指针的大小!因此,信息是会丢失的。所以也是不安全的。
(3) 标准C++中另一条有趣的规则是:允许成员函数指针之间进行转换,而不允许在转换完成后调用其中的函数。详细情况见下面程序:
#include<iostream.h>
class Plane
{
public:
void FlyingOff(void)
{
cout<<"飞机起飞了,大家注意系好安全带!"<<endl;
};
void Landing(int i)
{
cout<<"飞机"<<i<<"号航班降落了,大家注意系好安全带!"<<endl;
};
};
typedef void (Plane::*FlyingOff)(void);
typedef void (Plane::*Landing)(int);
void main()
{
Plane myPlane;
FlyingOff startPlane;
Landing planeLand;

startPlane = Plane::FlyingOff; //(A)
planeLand = (Landing)startPlane; //(B)
(myPlane.*planeLand)(8);
}

程序运行结果:
飞机起飞了,大家注意系好安全带!

该程序的第A行代码处把startPlane函数指针指向了类的FlyingOff函数。在第B行我将startPlane指针进行强制转换为Landing类型函数指针。注意看到Landing和FlyingOff函数指针的函数签名是不同的。但是在C++里面这是允许的,只不过不允许在转换完成后调用其中的函数。许转换而不允许调用是完全没有用处的,只有转换和调用都可行,才能方便而有效地实现委托。
由于以上的各种函数指针导致的不安全的原因,.NET开发人员并没有使用函数指针这一个概念。而基于事件编程是.NET Framework的基石,而模型利用委托来标识应用程序中的时间处理程序方法。除此之外,委托本身也有非常重要的作用。委托类似于应用应用程序中指向对象中方法的指针。在这一点上,它的功能与C++中的函数指针是相同的,但是C++中使用的函数指针不是类型安全的,因为编译器并不知道所调用的函数签名。所以,如果程序员出了错,就可能把错误的参数传送给函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: