引用、指针、抽象类的多态 in C++ and C#

原创 2011年01月12日 11:28:00

一、C#的引用和C++的指针

    C++的对象是“值类型的”(相对于C#引用类型),我们在实例化一个C++对象时有两种方法,第一种方法是:Person person1; 这种方法创建的对象person1和创建普通值类型的变量一样,存放在栈中,代表一个完整的对象本身,这也是C++和C#不同的地方,C#的对象本身只能存放在堆中,但其对象的引用是存在栈中的。第二种方法是:Person* person1 = new Person(); 用这种方法创建了一个指向对象本身的指针,它存放的是对象本身的地址,而这个对象本身是存放在堆中的。

    现在来看看C#。C#的对象是引用类型(暂且这么说,以便区分)。由于C#的对象本身都是存放在堆中的,我们在实例化一个C#对象时,都会用到new来在对象中创建一个对象并且返回一个指向该对象的引用(其实就是指针),如Person person1 = new Person(); 这里的person1就是指向实际对象本身的一个指针,它只是一个地址而已,但是我们在访问或修改该对象本身的时候只需要把person1当做一个实际的对象来操作即可(这一点容易让人想到C++的引用或别名,实质上它更接近于C++的指针),但切记它只是一个地址,并且这个地址的值是可以改变的(从这一点就可以看出它不同于C++的引用,更像指针,因为它指向的对象是可以改变的)。为了搞清楚这个问题,我们来做几个测试程序。

  

这里我们创建了三个Person对象,通过姓名来区分。首先我们让person3和person1指向同一个堆中的对象,即person1的值和person2的值相等,都等于Person("Mkie")这个对象的地址,他们都引用一个相同的对象。然后我们让person1和person2指向同一个对象,即此时的person1的值发生了改变,它存放的是Person("Jack")这个堆中的对象的地址,但是这一步操作并没有改变person3的值,即person3的值依然是之前Person("Mike")这个对象的地址。最后在打印person1和person3的姓名时可以看到结果,分别为"Jack"和"Mike",也就是说最后通过person1引用到的对象是之前Person("Mike")这一步创建的,通过person3引用到的对象是之前Person("Jack")这一步创建的对象。

 

在上面程序的基础上继续考虑,如果我们要交换两个对象引用person1和person2的值,实际上就是交换两个地址的值,这样同样可以实现交换。不过假如我们要在子函数中去交换这个两个对象引用的值,就必须要加上ref关键字,如下

 

这里通过ref关键字,可以把 person1和person2两个对象引用的地址传给子函数,即指向指针的指针,在C#可称为引用的引用。子函数的函数堆栈中保存这两个对象引用的地址,然后通过引用的引用来改变主函数中person1和person2的值,从而达到交换两个对象引用的目的。如果这里不加ref关键字,则子函数堆栈得到的仅是主函数中person1和person2的值,即两个对象的地址,将这两个地址进行交换实际上只是将在子函数堆栈中的两个值交换,并不能改变主函数中person1和person2的值,记住这里我们要改变的只是实际对象引用的值而不是实际对象的值,如果要改变实际对象(堆中)的值,则不需要ref,直接通过传递对象引用,然后再子函数中通过引用来访问对象即可person1.Name=?,这就是引用的作用。

    我们在这里再来把C#和C++做个比较。如果是在C++中交换两个对象的值应该如何操作?

    首先,我们需要为待交换类重载赋值运算符,因为交换对象是需要赋值的。这个重载函数根据具体的类定义来实现。如下:

 

以上是Person类的定义。在主函数中对创建两个person对象,并在一个子函数中交换他们。这里我们可以用两种方法,

方法1:在堆中创建对象,通过改变指向对象的指针的值来交换对象。如下:

 

这里,我们把子函数的参数设置为指向指针的指针,目的是为了在子函数中改变主函数指针的值,即person1和person2的值,通过交换他们的值后,person1实际指向的对象就变成了之前person2指向的对象,而person2现在指向的对象变成了之前person1指向的对象。这样我们再通过对这两个指针进行解引用操作就可以得到交换后的对象。这种方法实际上是没有用到我们之前定义的赋值运算符重载函数的。

方法2:还是在堆中创建对象,这时我们的子函数参数是设置为指向Person对象的指针,我们要做的是在子函数中对指针进行解引用然后调用重载赋值运算符函数对实际的对象进行交换。这是最容易理解的方法,也是最直接的方法。代码如下:

 

这种方法即为我们用得最多的传地址调用函数的方法。

 

以上两种方法都可以实现对象的交换,实质上第一种方法只是交换了指向对象的指针的值,并非真正的对象交换,第二种方法才是将对象本身进行交换。在C++中我们可以如此灵活的去操作对象, 但是在C#中,由于“引用”这一概念的存在,我们在创建一个对象的时候必须要创建一个相应的引用,可是引用却又仅仅是对象的一个地址,它很像指针,但是我们却又不能对它进行解引用操作来表示实际对象的值,只能通过它去调用对象的公有成员,我想这也是C#中不允许重载赋值运算符的原因,因为我们对实际的对象进行赋值,我们能做的只是对对象的引用进行赋值操作。因此,C++在对象的操作比C#要灵活许多。不过C#也有C#的好处,我们有时候要对对象进行改变,不如实例化一个新的对象,旧的对象就不用去管它了,GC会帮我们清理掉。不过我更喜欢指针的灵活,它可以让程序员有种脚踏实地的感觉,额。。。

 

二、抽象类的多态

 

    再来谈谈多态吧。

 

    多态是将父类设置为和一个或多个子类相等的技术。通过多态,我们可以屏蔽子类之间的差异,把子类当做父类来使用,从而可以通过父类来调用子类中重写的方法,以实现通用性。

    抽象类是不能够实例化对象的,无论是在C++、C#还是Java中都是成立的。因为抽象类中含有抽象方法(在C++中叫做纯虚函数),这种方法没有实现代码,因此无法从一个抽象类创建这样一个不完整的对象。

    由于C++和C#在对象引用上的不同(这句话表达的很模糊,有待精炼。。。),具体应用的时候会有一些差异。

    上面几行说到,我们不能从抽象类实例化一个对象,也就是说我们不能用抽象类的对象来完成多态(这句话表达的也不够好)。不过我们可以用抽象类对象的引用或者指针来实现多态。

      还是用刚才那个Person类

 

这里我们将Customer对象的指针向上转型为Person对象的指针,完成了多态。

如果这里我们将main函数改成以下情况

 

这里,我们将Customer对象向上转型为Person对象,这样做是不对的,因为刚才提到抽象类不能创建对象,而这里经过想上转型后,实际上就创建了一个抽象类的对象,所以我们不能这么做。

 

好了,这是C++的做法。再来看看C#是怎么做的。

 

这里我们创建了两个类,ball为抽象类,有一个抽象方法Display(), football类继承与ball类,实现了Display(),这里要注意,在实现Display()时,必须要加上override关键字,这一点不像C++中直接覆盖就可以了。

主函数如下:

 

由于foot1是引用而不是具体的对象,因此我们可以将其向上转型为ball类对象,并实现多态。

以上就是对C++和C#在指针、引用、抽象类的多态方面的一点感想,很乱,想到哪写到哪,就当是笔记了。

今天就写这么多,噢啦~

 

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

关于基类、派生类、对象、指针和虚函数、多态、 静态绑定、 动态绑定 纯虚函数、抽象类

以下观点来源于《深入浅出MFC》到底调用到哪个函数,必须视指针的原始类型而定,于指针实际所指对象无关。1.如果你以一个“基类之指针”指向“派生类”,那么经由该指针你只能够调用基类所定义的函数。2.如果...

C#第06类,抽象类,多态

  • 2009-03-15 10:53
  • 350KB
  • 下载

虚函数-虚表-虚指针-多态性-如何实现多态-纯虚函数-抽象类-转

1. 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。 2. 存在虚函数的类都有一个一维的虚函数表叫做虚表。类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对...

抽象类多态接口

  • 2008-07-19 17:03
  • 1.70MB
  • 下载

抽象类、多态及接口.pdf

  • 2009-11-27 10:49
  • 350KB
  • 下载

虚函数-虚表-虚指针-多态性-如何实现多态-纯虚函数-抽象类(转呀转)

1. 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。2. 存在虚函数的类都有一个一维的虚函数表叫做虚表。类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)