C#面向对象(OOP)入门—第一天—多态和继承(方法重载)
面向对象是什么
面向对象是一种基于对象的编程方法,它取代了仅仅依靠方法和流程的编程方式。面向对象的编程语言中,对象(object)其实就是指特定类型、或某个类的实例。面向对象使得编程人员更容易组织和管理整个软件的程序。对象之间的独立性使得我们更容易更新和更改程序。对于大型程序则更加易读和易管理。
面向对象包含哪些内容
- 数据抽象(Data Absraction):数据抽象的概念是逻辑实现的内部和多余的细节对用户隐藏。用户可以使用一个类所允许使用的任何数据和方法,而不必要明白它是如何创建或者它背后有多么复杂。举一个现实的例子,就是开车过程中,我们可以通过换挡杆来换档,然而我们比不要知道他是如何换挡的,比如换挡过程中齿轮箱等部件是如何动作的,我们只需要根据自己的需要来切换档位就行。
- 继承(Inheritance):继承是面向对象中最受欢迎的概念,继承让开发者可以实现代码重用。例如,我们在一个类里面实现了一个有特定逻辑功能的函数,我们可以用这个类派生一个新的类,然后我们不必要重新写这个函数,在派生类中可以直接使用。
- 数据封装(Data Encapsulation):将类里面的成员函数和成员数据封装进一个单独的模块叫做封装,数据或函数的可见性可以由访问修饰符来控制。
- 消息通讯(Message Communication):指当一个对象调用一个类的方法去执行的时候,消息的通讯手段。
方法重载(编译时多态)
public class Overload { public void DisplayOverload(int a){ System.Console.WriteLine("DisplayOverload " + a); } public void DisplayOverload(string a){ System.Console.WriteLine("DisplayOverload " + a); } public void DisplayOverload(string a, int b){ System.Console.WriteLine("DisplayOverload " + a + b); } }
类似上面的代码,3个函数拥有相同的名字,但是参数类型不同。这就是方法重载。
知识点:在C#中,函数签名(识别一个函数的依据)包括函数的名称以及函数的参数(包括参数数量和参数数据类型,参数的类型)。函数的返回值不是,函数的修饰关键字(比如static)也不是。参数的类型指的是ref或者out的参数。(例如 int a 和 ref int a不同,但是注意 ref int a 和 out int a 相同)。
参数数组参数在多态中的作用
一个方法被调用过程中,参数的类型有以下几种:
- 传递值
- 传递引用(ref)
- 作为输出参数(out)(其实也是一种引用的传递)
- 采用参数数组(params)
当我们传递N个(未知)参数给某个方法的时候,我们可以用params关键字。
知识点:params关键字只能在方法的最后一个参数,并且数组不能是多维的。
例如如下定义是错误的:
private void DisplayOverload(int a, params string[] parameterArray, int b) { }
知识点:当有个params的参数在最后一个,但是倒数第二个参数的类型和最后一个相同,则传递参数中,符合类型的第一个会给值,剩余的给后面的参数数组(例如 []和[][]可以,而[,] 不可以),并且params关键字不能和ref或out组合使用。
例如:
public class Overload { public void Display() { DisplayOverload(100, 200, 300); //100会传递给参数a,200和300传递给parameterArray DisplayOverload(200, 100); //200传递给参数a,100传递给parameterArray DisplayOverload(200); //200传递给参数a } private void DisplayOverload(int a, params int[] parameterArray) { foreach (var i in parameterArray) Console.WriteLine(i + " " + a); } }
这种情况也是错误的
public class Overload { public void Display() { string [] names = {"Akhil","Arsh"}; DisplayOverload(2, names, "Ekta"); //当调用函数时,编译器会试图将names和 "Ekta"组合为一个字符串数组,但是names不是string而是string[]类型,所以会报错 } private void DisplayOverload(int a, params string[] parameterArray) { foreach (var str in parameterArray) Console.WriteLine(str + " " + a); } }
对于在传递数组参数后,改变其值会发生什么举如下两个例子:
例子1:
public class Overload { public void Display() { int[] numbers = {10, 20, 30}; DisplayOverload(40, numbers); Console.WriteLine(numbers[1]); } private void DisplayOverload(int a, params int[] parameterArray) { parameterArray[1] = 1000; } } class Program { static void Main(string[] args) { Overload overload = new Overload(); overload.Display(); Console.ReadKey(); } } //输出结果是1000
例子2:
public class Overload { public void Display() { int number = 102; DisplayOverload(200, 1000, number, 200); Console.WriteLine(number); } private void DisplayOverload(int a, params int[] parameterArray) { parameterArray[1] = 3000; } } class Program { static void Main(string[] args) { Overload overload = new Overload(); overload.Display(); Console.ReadKey(); } } //输出结果是102
例子1中的数组传递到函数里面,在函数更改值对应也更改了原数组的值。(我认为是传递的数组的地址);但是对于例子2,不得不新建一个数组来存放各个参数组成的新数组,然后传递到调用的函数里,当对其更改值时,其实更改的是新数组对应的值,而这个函数对number的值一无所知,跟别提去更改了。
另一种重载的情况
public class Overload { public void Display() { DisplayOverload(200); DisplayOverload(200, 300); DisplayOverload(200, 300, 500, 600); } private void DisplayOverload(int x, int y) { Console.WriteLine("The two integers " + x + " " + y); } private void DisplayOverload(params int[] parameterArray) { Console.WriteLine("parameterArray"); } } class Program { static void Main(string[] args) { Overload overload = new Overload(); overload.Display(); Console.ReadKey(); } } /* 输出是: parameterArray The two integers 200 300 parameterArray */
第一个和第三个调用没有问题,只能调用带数组的参数。但是对于第二种我不太理解,原文作者解释说,C#很聪明,他对params参数不太认可,就像不是自己的亲生孩子一样,所以作为第二选择,两个int参数正好有匹配的函数来调用,所以这样输出。作者虽然感情化的描述了,但是可以理解,因为确实就是这样的机制。要想真正的明白缘由,或许还要查阅一些资料才能明白,暂时搁置这个问题吧。
好吧,一个更有趣的例子:
public class Overload { public static void Display(params object[] objectParamArray) { foreach (object obj in objectParamArray) { Console.Write(obj.GetType().FullName + " "); } Console.WriteLine(); } } class Program { static void Main(string[] args) { object[] objArray = { 100, "Akhil", 200.300 }; object obj = objArray; Overload.Display(objArray); Overload.Display((object)objArray); Overload.Display(obj); Overload.Display((object[])obj); Console.ReadKey(); } } /* 输出结果是: System.Int32 System.String System.Double System.Object[] System.Object[] System.Int32 System.String System.Double */
Display函数是输出数组内每个成员的类型。第一个调用没有问题,第二个调用与第三个调用等价,传递过去的其实是一个object对象(注意这不是装箱,装箱时值类型转换为object,而数组是引用类型),这时,首先是将object转换为object[]但是不存在这样的默认转换,所以只能创建一个object[]来存放obj,而obj的类型就是object[],所以输出System.Object[]。第四个调用,做了强制的转换,从object到object[],所以跟第一个相同。
总结:
这篇文章主要讲了编译时多态,也叫作前期绑定或者方法重载。并列举了方法重载的各种情形,以及params参数在重载中的特殊用途。
- C# 通过其参数而不是由它的名字来区别一个方法。
- 方法返回值和参数类型绝不是方法签名的一部分,如果方法的名称相同。所以这不是多态性。
- 修饰符(如static)不是方法签名的一部分。
- 方法的签名包括其名称、 形参的数量和类型。一个函数的返回类型不是签名的一部分。两种方法不能具有相同的签名,并且成员也不能有同名的成员。
- 参数名称应是唯一的。我们也可以不有一个参数名称和声明的变量名称相同的功能相同。
- 如果通过值传递,变量的值传递过去,如果通过 ref 和 out,引用的地址传递过去。
- 这 params 关键字只能应用于方法的最后一个参数。因此n 个数量的参数只可以在最后。
- C# 是很聪明地承认如果倒数第二个参数,参数有相同的数据类型。
- 参数数组必须是一维数组。
注明:原文地址:https://codeteddy.com/2014/05/11/diving-in-oop-part-1-polymorphism-and-inheritanceearly-bindingcompile-time-polymorphism/
我只是在作者基础上进行了翻译以及总结,并加了一点自己的理解,希望对大家有帮助。
如有错误,敬请指证。