多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:
运算符重载将在下一章节讨论,接下来我们将讨论函数重载。
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。
重载函数额及我们可以理解为有一个公司,这个公司中有几个同名同姓的人,但是他们每个人的技能给有不同,我们现在要让这个公司的这些同名同姓的人里可以确定他们异地那个其中有i哟个可以肯定有一个可以完成这项任务,我们可以这样来理解这个吧。
public class Printdata
{
public void print(int i)
{
Console.WriteLine("Printing int: {0}", i);
}
public void print(double f)
{
Console.WriteLine("Printing float: {0}", f);
}
public void print(string s)
{
Console.WriteLine("Printing string: {0}", s);
}
public void studenrt()
{
Console.WriteLine("Hello World");
}
S on=new S();
on.printa("one");
on.printa(12);
}
我们为什么要使用函数重载
函数重载的调用方式
现在已经解决了重载函数命名冲突的问题,在定义完重载函数之后,用函数名调用的时候是如何去解析的?为了估计哪个重载函数最适合,需要依次按照下列规则来判断:
但是我们会来考虑一个问题,如果在最高层有多个匹配函数找到,调用将被拒绝(因为有歧义、模凌两可)。这时侯编译器就会报错,将错误抛给用户自己来处理:通过显示类型转换来调用等等(如f2(static_cast
您可以重定义或重载 C# 中内置的运算符。因此,程序员也可以使用用户自定义类型的运算符。重载运算符是具有特殊名称的函数,是通过关键字 operator 后跟运算符的符号来定义的。与其他函数一样,重载运算符有返回类型和参数列表。
**运算符重载及将运算符与类结合,产生新的含义。**目的为了实现类的多态性(多态是指一个函数名有多种含义)。
您可以重定义或重载大部分 C#内置的运算符。这样,您就能使用自定义类型的运算符。重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
public static Box operator+ (Box b, Box c)
{
Box box = new Box();
box.length = b.length + c.length;
box.breadth = b.breadth + c.breadth;
box.height = b.height + c.height;
return box;
}
下表描述了 C# 中运算符重载的能力:
运算符 | 描述 |
---|---|
+, -, !, ~, ++, – | 这些一元运算符只有一个操作数,且可以被重载。 |
+, -, *, /, % | 这些二元运算符带有两个操作数,且可以被重载。 |
==, !=, <, >, <=, >= | 这些比较运算符可以被重载。 |
&&, || | 这些条件逻辑运算符不能被直接重载。 |
+=, -=, *=, /=, %= | 这些赋值运算符不能被重载。 |
=, ., ?:, ->, new, is, sizeof, typeof | 这些运算符不能被重载。 |
这与c++类似,但是还是有点不同的
我们应该合理的使用运算符和函数的重载,这会是我们的代码更加的美观和简节。
C# 允许您使用关键字 abstract 创建抽象类,用于提供接口的部分类的实现。当一个派生类继承自该抽象类时,实现即完成。抽象类包含抽象方法,抽象方法可被派生类实现。派生类具有更专业的功能。
请注意,下面是有关抽象类的一些规则:
namespace ConsoleApp3
{
abstract class Shape
{
public abstract int num();
}
class one : Shape
{
public int lrnght { get; set; }
public int wight { get; set; }
public override int num()
{
return lrnght * wight;
}
}
}
在main中
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
one o=new one();
o.lrnght = 12;
o.wight = 12;
Console.WriteLine(o.num().ToString());
}
}
}
当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法。虚方法是使用关键字 virtual 声明的。虚方法可以在不同的继承类中有不同的实现。对虚方法的调用是在运行时发生的。
动态多态性是通过 抽象类 和 虚方法 实现的。
using System;
using System.Net;
using System.Net.NetworkInformation;
namespace ConsoleApp3
{
abstract class Shape
{
public abstract int num();
}
class one
{
public int lrnght { get; set; }
public int wight { get; set; }
public virtual void num1()
{
int a= lrnght + wight;
Console.WriteLine(a.ToString());
}
}
class onew : one
{
public override void num1()
{
int a= lrnght * wight;
Console.WriteLine(a.ToString());
}
}
}
main方法如下:
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
onew a =new onew();
a.lrnght = 12;
a.wight = 12;
a.num1();//144
one b= new one();
b.lrnght = a.lrnght;
b.wight = a.wight;
b.num1();//24
}
}
}
抽象方法和虚方法的区别
简单说,抽象方法是需要子类去实现的。虚方法是已经实现了的,可以被子类覆盖,也可以不覆盖,取决于需求。(及你可以理解为如果你继承了抽象类,那么你必须要给我实现我抽象类中的方法。而且抽象类中的方法不能写具体实现)
抽象方法和虚方法都可以供派生类重写。
接口定义了所有类继承接口时应遵循的语法合同。接口定义了语法合同 “是什么” 部分,派生类定义了语法合同 “怎么做” 部分。
接口定义了属性、方法和事件,这些都是接口的成员。接口只包含了成员的声明。成员的定义是派生类的责任。接口提供了派生类应遵循的标准结构。
抽象类在某种程度上与接口类似,但是,它们大多只是用在当只有少数方法由基类声明由派生类实现时。
接口使用 interface 关键字声明,它与类的声明类似。接口声明默认是 public 的。下面是一个接口声明的实例:
public interface ITransactions
{
// 接口成员
void showTransaction();
double getAmount();
}
下面的实例演示了上面接口的实现:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace InterfaceApplication
{
public interface ITransactions
{
// 接口成员
void showTransaction();
double getAmount();
}
public class Transaction : ITransactions
{
private string tCode;
private string date;
private double amount;
public Transaction()
{
tCode = " ";
date = " ";
amount = 0.0;
}
public Transaction(string c, string d, double a)
{
tCode = c;
date = d;
amount = a;
}
public double getAmount()
{
return amount;
}
public void showTransaction()
{
Console.WriteLine("Transaction: {0}", tCode);
Console.WriteLine("Date: {0}", date);
Console.WriteLine("Amount: {0}", getAmount());
}
}
class Tester
{
static void Main(string[] args)
{
Transaction t1 = new Transaction("001", "8/10/2012", 78900.00);
Transaction t2 = new Transaction("002", "9/10/2012", 451900.00);
t1.showTransaction();
t2.showTransaction();
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
Transaction: 001
Date: 8/10/2012
Amount: 78900
Transaction: 002
Date: 9/10/2012
Amount: 451900
接口使用的注意事项:
但是C#具有abstract关键字形成的抽象类,那么二者我们该如何选择呢!
简单说,抽象方法是需要子类去实现的。虚方法是已经实现了的,可以被子类覆盖,也可以不覆盖,取决于需求。(及你可以理解为如果你继承了抽象类,那么你必须要给我实现我抽象类中的方法。而且抽象类中的方法不能写具体实现)
抽象方法和虚方法都可以供派生类重写。
空间命名的设计目的的英文为了提供一种让一组名称与其他名称分隔开的方式。在一个命名空间中声明的类的名称与另一个命名空间中声明的相同的类的名称不冲突。
命名空间的定义以关键字namespace开始,后跟命名空间的名称,如下所示:
namespace namespace_name
{
// 代码声明
}
为了调用支持命名空间版本的函数或变量,会把命名空间的名称放在前面,如下所示:
namespace_name.item_name;
下面的程序演示了命名空间的用法:
using System;
namespace first_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
class TestClass
{
static void Main(string[] args)
{
first_space.namespace_cl fc = new first_space.namespace_cl();
second_space.namespace_cl sc = new second_space.namespace_cl();
fc.func();
sc.func();
Console.ReadKey();
}
}
当上面的代码被编译和执行时,它会产生以下结果:
Inside first_space
Inside second_space
使用关键字表明程序使用的是给定命名空间中的名称例如,我们在程序中使用。系统命名空间,其中定义了类控制台我们可以只写:
Console.WriteLine ("Hello there");
我们可以写完全限定名称,如下:
System.Console.WriteLine("Hello there");
您也可以使用使用命名空间指令,这样在使用的时候就不用在前面加上命名空间名称。该指令告诉编译器随后的代码使用了指定命名空间中的名称。下面的代码延时了命名空间的应用。
命名空间可以被嵌套,即您可以在一个命名空间内定义另一个命名空间,如下所示:
namespace namespace_name1
{
// 代码声明
namespace namespace_name2
{
// 代码声明
}
}
代码如下:
using System;
namespace first_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
namespace one
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
}
//first_space.namespace_cl one = new namespace_cl();
// one.func();
// second_space.one.namespace_cl two = new //second_space.one.namespace_cl();
// two.func();
预处理器指令指导编译器在实际编译开始之前对信息进行预处理。
所有的预处理器指令都是以 # 开始。且在一行上,只有空白字符可以出现在预处理器指令之前。预处理器指令不是语句,所以它们不以分号(;)结束。
C# 编译器没有一个单独的预处理器,但是,指令被处理时就像是有一个单独的预处理器一样。在 C# 中,预处理器指令用于在条件编译中起作用。与 C 和 C++ 不同指令不用,它们不是用来创建宏。一个预处理器指令必须是该行上的唯一指令。
下表列出了 C# 中可用的预处理器指令:
预处理器指令 | 描述 |
---|---|
#define | 它用于定义一系列成为符号的字符。 |
#undef | 它用于取消定义符号。 |
#if | 它用于测试符号是否为真。 |
#else | 它用于创建复合条件指令,与 #if 一起使用。 |
#elif | 它用于创建复合条件指令。 |
#endif | 指定一个条件指令的结束。 |
#line | 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。 |
#error | 它允许从代码的指定位置生成一个错误。 |
#warning | 它允许从代码的指定位置生成一级警告。 |
#region | 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。 |
#endregion | 它标识着 #region 块的结束。 |
#define 预处理器指令创建符号常量。
#define 允许您定义一个符号,这样,通过使用符号作为传递给 #if 指令的表达式,表达式将返回 true。它的语法如下:
#define symbol
下面的程序说明了这点:
#define PI
using System;
namespace PreprocessorDAppl
{
class Program
{
static void Main(string[] args)
{
#if (PI)
Console.WriteLine("PI is defined");
#else
Console.WriteLine("PI is not defined");
#endif
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
PI is defined
您可以使用 #if 指令来创建一个条件指令。条件指令用于测试符号是否为真。如果为真,编译器会执行 #if 和下一个指令之间的代码。
条件指令的语法:
#if symbol [operator symbol]...
其中,symbol 是要测试的符号名称。您也可以使用 true 和 false,或在符号前放置否定运算符。
运算符符号是用于评价符号的运算符。可以运算符可以是下列运算符之一:
您也可以用括号把符号和运算符进行分组。条件指令用于在调试版本或编译指定配置时编译代码。一个以 #if 指令开始的条件指令,必须显示地以一个 #endif 指令终止。
下面的程序演示了条件指令的用法:
#define DEBUG
#define VC_V10
using System;
public class TestClass
{
public static void Main()
{
#if (DEBUG && !VC_V10)
Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && VC_V10)
Console.WriteLine("VC_V10 is defined");
#elif (DEBUG && VC_V10)
Console.WriteLine("DEBUG and VC_V10 are defined");
#else
Console.WriteLine("DEBUG and VC_V10 are not defined");
#endif
Console.ReadKey();
}
}
当上面的代码被编译和执行时,它会产生下列结果:
DEBUG and VC_V10 are defined