☀️ 学会编程入门必备 C# 最基础知识介绍(五)——方法、封装、继承、多态

        • 前言
    • 方法❄️
      • C# 中定义方法
      • 实例
      • C# 中调用方法
      • 递归方法调用
      • 参数传递
      • 按值传递参数
      • 按引用传递参数
      • 按输出传递参数
    • C# 封装⛄️
      • Public 访问修饰符
      • Private 访问修饰符
      • Protected 访问修饰符
      • Internal 访问修饰符
      • Protected Internal 访问修饰符
    • C# 继承⚡️
      • 基类和派生类
      • 基类的初始化
      • C# 多重继承
    • C# 多态性
      • 静态多态性
      • 函数重载
      • C# 运算符重载
        • 运算符重载的实现
        • 可重载和不可重载运算符
      • 动态多态性
    • 总结

前言

学过编程的人都知道有个名词叫 " O O P 思 想 " \color{FF66FF}{"OOP思想"} "OOP" —— " 面 向 对 象 编 程 " \color{FF66FF}{"面向对象编程"} ""(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息
核心思想:封装,继承,多态.

前面几篇博客介绍了C#的一些基础知识,包括基本语法、运算符、数组等。
在本篇博客就来介绍在C#中最常用的几种概念,即: 方 法 \color{0000ff}{方法} 继 承 \color{00ff00}{继承} 封 装 \color{00ff00}{封装} 多 态 \color{00ff00}{多态}


方法❄️

一个方法是把一些相关的语句组织在一起,用来执行一个任务的语句块。每一个 C# 程序至少有一个带有 Main 方法的类。
要使用一个方法,您需要:

  • 定义方法
  • 调用方法

C# 中定义方法

当定义一个方法时,从根本上说是在声明它的结构的元素。在 C# 中,定义方法的语法如下:

<Access Specifier> <Return Type> <Method Name>(Parameter List)
{
     
   Method Body
}

下面是方法的各个元素:

  • Access Specifier:访问修饰符,这个决定了变量或方法对于另一个类的可见性。
  • Return type:返回类型,一个方法可以返回一个值。返回类型是方法返回的值的数据类型。如果方法不返回任何值,则返回类型为 void。
  • Method name:方法名称,是一个唯一的标识符,且是大小写敏感的。它不能与类中声明的其他标识符相同。
  • Parameter
    list:参数列表,使用圆括号括起来,该参数是用来传递和接收方法的数据。参数列表是指方法的参数类型、顺序和数量。参数是可选的,也就是说,一个方法可能不包含参数。
  • Method body:方法主体,包含了完成任务所需的指令集。

实例

下面的代码片段显示一个函数 FindMax,它接受两个整数值,并返回两个中的较大值。它有 public 访问修饰符,所以它可以使用类的实例从类的外部进行访问。

实例
class NumberManipulator
{
     
   public int FindMax(int num1, int num2)
   {
     
      /* 局部变量声明 */
      int result;

      if (num1 > num2)
         result = num1;
      else
         result = num2;

      return result;
   }
}

C# 中调用方法

您可以使用方法名调用方法。下面的实例演示了这点:

实例
using System;

namespace CalculatorApplication
{
     
   class NumberManipulator
   {
     
      public int FindMax(int num1, int num2)
      {
     
         /* 局部变量声明 */
         int result;

         if (num1 > num2)
            result = num1;
         else
            result = num2;

         return result;
      }
      static void Main(string[] args)
      {
     
         /* 局部变量定义 */
         int a = 100;
         int b = 200;
         int ret;
         NumberManipulator n = new NumberManipulator();

         //调用 FindMax 方法
         ret = n.FindMax(a, b);
         Console.WriteLine("最大值是: {0}", ret );
         Console.ReadLine();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

最大值是: 200

也可以使用类的实例从另一个类中调用其他类的公有方法。例如,方法 FindMax 属于 NumberManipulator 类,可以从另一个类 Test 中调用它。

using System;

namespace CalculatorApplication
{
     
    class NumberManipulator
    {
     
        public int FindMax(int num1, int num2)
        {
     
            /* 局部变量声明 */
            int result;

            if (num1 > num2)
                result = num1;
            else
                result = num2;

            return result;
        }
    }
    class Test
    {
     
        static void Main(string[] args)
        {
     
            /* 局部变量定义 */
            int a = 100;
            int b = 200;
            int ret;
            NumberManipulator n = new NumberManipulator();
            //调用 FindMax 方法
            ret = n.FindMax(a, b);
            Console.WriteLine("最大值是: {0}", ret );
            Console.ReadLine();

        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

最大值是: 200


递归方法调用

一个方法可以自我调用。这就是所谓的 递归。下面的实例使用递归函数计算一个数的阶乘:

实例
using System;

namespace CalculatorApplication
{
     
    class NumberManipulator
    {
     
        public int factorial(int num)
        {
     
            /* 局部变量定义 */
            int result;

            if (num == 1)
            {
     
                return 1;
            }
            else
            {
     
                result = factorial(num - 1) * num;
                return result;
            }
        }
   
        static void Main(string[] args)
        {
     
            NumberManipulator n = new NumberManipulator();
            //调用 factorial 方法
            Console.WriteLine("6 的阶乘是: {0}", n.factorial(6));
            Console.WriteLine("7 的阶乘是: {0}", n.factorial(7));
            Console.WriteLine("8 的阶乘是: {0}", n.factorial(8));
            Console.ReadLine();

        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

6 的阶乘是: 720
7 的阶乘是: 5040
8 的阶乘是: 40320


参数传递

当调用带有参数的方法时,您需要向方法传递参数。在 C# 中,有三种向方法传递参数的方式

方式 描述
值参数 这种方式复制参数的实际值给函数的形式参数,实参和形参使用的是两个不同内存中的值。在这种情况下,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全。
引用参数 这种方式复制参数的内存位置的引用给形式参数。这意味着,当形参的值发生改变时,同时也改变实参的值。
输出参数 这种方式可以返回多个值。

按值传递参数

这是参数传递的默认方式。在这种方式下,当调用一个方法时,会为每个值参数创建一个新的存储位置。

实际参数的值会复制给形参,实参和形参使用的是两个不同内存中的值。所以,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全。下面的实例演示了这个概念:

实例
using System;
namespace CalculatorApplication
{
     
   class NumberManipulator
   {
     
      public void swap(int x, int y)
      {
     
         int temp;
         
         temp = x; /* 保存 x 的值 */
         x = y;    /* 把 y 赋值给 x */
         y = temp; /* 把 temp 赋值给 y */
      }
     
      static void Main(string[] args)
      {
     
         NumberManipulator n = new NumberManipulator();
         /* 局部变量定义 */
         int a = 100;
         int b = 200;
         
         Console.WriteLine("在交换之前,a 的值: {0}", a);
         Console.WriteLine("在交换之前,b 的值: {0}", b);
         
         /* 调用函数来交换值 */
         n.swap(a, b);
         
         Console.WriteLine("在交换之后,a 的值: {0}", a);
         Console.WriteLine("在交换之后,b 的值: {0}", b);
         
         Console.ReadLine();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

在交换之前,a 的值:100
在交换之前,b 的值:200
在交换之后,a 的值:100
在交换之后,b 的值:200

结果表明,即使在函数内改变了值,值也没有发生任何的变化。

按引用传递参数

引用参数是一个对变量的内存位置的引用。当按引用传递参数时,与值参数不同的是,它不会为这些参数创建一个新的存储位置。引用参数表示与提供给方法的实际参数具有相同的内存位置。

在 C# 中,使用 按引用传递参数
引用参数是一个对变量的内存位置的引用。当按引用传递参数时,与值参数不同的是,它不会为这些参数创建一个新的存储位置。引用参数表示与提供给方法的实际参数具有相同的内存位置。

在 C# 中,使用 ref 关键字声明引用参数。下面的实例演示了这点:

实例
using System;
namespace CalculatorApplication
{
     
   class NumberManipulator
   {
     
      public void swap(ref int x, ref int y)
      {
     
         int temp;

         temp = x; /* 保存 x 的值 */
         x = y;    /* 把 y 赋值给 x */
         y = temp; /* 把 temp 赋值给 y */
       }
   
      static void Main(string[] args)
      {
     
         NumberManipulator n = new NumberManipulator();
         /* 局部变量定义 */
         int a = 100;
         int b = 200;

         Console.WriteLine("在交换之前,a 的值: {0}", a);
         Console.WriteLine("在交换之前,b 的值: {0}", b);

         /* 调用函数来交换值 */
         n.swap(ref a, ref b);

         Console.WriteLine("在交换之后,a 的值: {0}", a);
         Console.WriteLine("在交换之后,b 的值: {0}", b);
 
         Console.ReadLine();

      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

在交换之前,a 的值:100
在交换之前,b 的值:200
在交换之后,a 的值:200
在交换之后,b 的值:100

结果表明,swap 函数内的值改变了,且这个改变可以在 Main 函数中反映出来。

按输出传递参数

return 语句可用于只从函数中返回一个值。但是,可以使用 输出参数 来从函数中返回两个值。输出参数会把方法输出的数据赋给自己,其他方面与引用参数相似。

下面的实例演示了这点:

实例
using System;

namespace CalculatorApplication
{
     
   class NumberManipulator
   {
     
      public void getValue(out int x )
      {
     
         int temp = 5;
         x = temp;
      }
   
      static void Main(string[] args)
      {
     
         NumberManipulator n = new NumberManipulator();
         /* 局部变量定义 */
         int a = 100;
         
         Console.WriteLine("在方法调用之前,a 的值: {0}", a);
         
         /* 调用函数来获取值 */
         n.getValue(out a);

         Console.WriteLine("在方法调用之后,a 的值: {0}", a);
         Console.ReadLine();

      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

在方法调用之前,a 的值: 100
在方法调用之后,a 的值: 5

提供给输出参数的变量不需要赋值。当需要从一个参数没有指定初始值的方法中返回值时,输出参数特别有用。请看下面的实例,来理解这一点:

实例
using System;

namespace CalculatorApplication
{
     
   class NumberManipulator
   {
     
      public void getValues(out int x, out int y )
      {
     
          Console.WriteLine("请输入第一个值: ");
          x = Convert.ToInt32(Console.ReadLine());
          Console.WriteLine("请输入第二个值: ");
          y = Convert.ToInt32(Console.ReadLine());
      }
   
      static void Main(string[] args)
      {
     
         NumberManipulator n = new NumberManipulator();
         /* 局部变量定义 */
         int a , b;
         
         /* 调用函数来获取值 */
         n.getValues(out a, out b);

         Console.WriteLine("在方法调用之后,a 的值: {0}", a);
         Console.WriteLine("在方法调用之后,b 的值: {0}", b);
         Console.ReadLine();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果(取决于用户输入):

请输入第一个值:
7
请输入第二个值:
8
在方法调用之后,a 的值: 7
在方法调用之后,b 的值: 8


C# 封装⛄️

封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。

抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象。

C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。

一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:

  • public:所有对象都可以访问;
  • private:对象本身在对象内部可以访问;
  • protected:只有该类对象及其子类对象可以访问
  • internal:同一个程序集的对象可以访问;
  • protected internal:访问限于当前程序集或派生自包含类的类型。

Public 访问修饰符

Public 访问修饰符允许一个类将其成员变量和成员函数暴露给其他的函数和对象。任何公有成员可以被外部的类访问。

下面的实例说明了这点:

实例
using System;

namespace RectangleApplication
{
     
    class Rectangle
    {
     
        //成员变量
        public double length;
        public double width;

        public double GetArea()
        {
     
            return length * width;
        }
        public void Display()
        {
     
            Console.WriteLine("长度: {0}", length);
            Console.WriteLine("宽度: {0}", width);
            Console.WriteLine("面积: {0}", GetArea());
        }
    }// Rectangle 结束

    class ExecuteRectangle
    {
     
        static void Main(string[] args)
        {
     
            Rectangle r = new Rectangle();
            r.length = 4.5;
            r.width = 3.5;
            r.Display();
            Console.ReadLine();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

长度: 4.5
宽度: 3.5
面积: 15.75

在上面的实例中,成员变量 length 和 width 被声明为 public,所以它们可以被函数 Main() 使用 Rectangle 类的实例 r 访问。
成员函数 Display() 和 GetArea() 可以直接访问这些变量。
成员函数 Display() 也被声明为 public,所以它也能被 Main() 使用 Rectangle 类的实例 r 访问。


Private 访问修饰符

Private 访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。

下面的实例说明了这点:

实例
using System;

namespace RectangleApplication
{
     
    class Rectangle
    {
     
        //成员变量
        private double length;
        private double width;

        public void Acceptdetails()
        {
     
            Console.WriteLine("请输入长度:");
            length = Convert.ToDouble(Console.ReadLine());
            Console.WriteLine("请输入宽度:");
            width = Convert.ToDouble(Console.ReadLine());
        }
        public double GetArea()
        {
     
            return length * width;
        }
        public void Display()
        {
     
            Console.WriteLine("长度: {0}", length);
            Console.WriteLine("宽度: {0}", width);
            Console.WriteLine("面积: {0}", GetArea());
        }
    }//end class Rectangle    
    class ExecuteRectangle
    {
     
        static void Main(string[] args)
        {
     
            Rectangle r = new Rectangle();
            r.Acceptdetails();
            r.Display();
            Console.ReadLine();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

请输入长度:
4.4
请输入宽度:
3.3

长度: 4.4
宽度: 3.3
面积: 14.52

在上面的实例中,成员变量 length 和 width 被声明为 private,所以它们不能被函数 Main() 访问。
成员函数 AcceptDetails() 和 Display() 可以访问这些变量。
由于成员函数 AcceptDetails() 和 Display() 被声明为 public,所以它们可以被 Main() 使用 Rectangle 类的实例 r 访问。


Protected 访问修饰符

Protected 访问修饰符允许子类访问它的基类的成员变量和成员函数。这样有助于实现继承。将在继承的内容部分详细讨论这个。


Internal 访问修饰符

Internal 访问说明符允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。换句话说,带有 internal 访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类或方法访问。

下面的实例说明了这点:

实例
using System;

namespace RectangleApplication
{
     
    class Rectangle
    {
     
        //成员变量
        internal double length;
        internal double width;
       
        double GetArea()
        {
     
            return length * width;
        }
       public void Display()
        {
     
            Console.WriteLine("长度: {0}", length);
            Console.WriteLine("宽度: {0}", width);
            Console.WriteLine("面积: {0}", GetArea());
        }
    }//end class Rectangle    
    class ExecuteRectangle
    {
     
        static void Main(string[] args)
        {
     
            Rectangle r = new Rectangle();
            r.length = 4.5;
            r.width = 3.5;
            r.Display();
            Console.ReadLine();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

长度: 4.5
宽度: 3.5
面积: 15.75

在上面的实例中,请注意成员函数 GetArea() 声明的时候不带有任何访问修饰符。如果没有指定访问修饰符,则使用类成员的默认访问修饰符,即为 private


Protected Internal 访问修饰符

Protected Internal 访问修饰符允许在本类,派生类或者包含该类的程序集中访问。这也被用于实现继承。


C# 继承⚡️

继承是面向对象程序设计中最重要的概念之一。继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易。同时也有利于重用代码和节省开发时间。

当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承了已有的类的成员即可。这个已有的类被称为的基类,这个新的类被称为派生类

继承的思想实现了 属于 (IS-A) 关系。例如,哺乳动物 属于 (IS-A) 动物,狗 属于 (IS-A) 哺乳动物,因此狗 属于 (IS-A) 动物。

基类和派生类

一个类可以派生自多个类或接口,这意味着它可以从多个基类或接口继承数据和函数。

C# 中创建派生类的语法如下:

<访问修饰符符> class <基类>
{
     
 ...
}
class <派生类> : <基类>
{
     
 ...
}

假设,有一个基类 Shape,它的派生类是 Rectangle:

实例
using System;
namespace InheritanceApplication
{
     
   class Shape
   {
     
      public void setWidth(int w)
      {
     
         width = w;
      }
      public void setHeight(int h)
      {
     
         height = h;
      }
      protected int width;
      protected int height;
   }

   // 派生类
   class Rectangle: Shape
   {
     
      public int getArea()
      {
     
         return (width * height);
      }
   }
   
   class RectangleTester
   {
     
      static void Main(string[] args)
      {
     
         Rectangle Rect = new Rectangle();

         Rect.setWidth(5);
         Rect.setHeight(7);

         // 打印对象的面积
         Console.WriteLine("总面积: {0}",  Rect.getArea());
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

总面积: 35


基类的初始化

派生类继承了基类的成员变量和成员方法。因此父类对象应在子类对象创建之前被创建。您可以在成员初始化列表中进行父类的初始化。

下面的程序演示了这点:

实例
using System;
namespace RectangleApplication
{
     
   class Rectangle
   {
     
      // 成员变量
      protected double length;
      protected double width;
      public Rectangle(double l, double w)
      {
     
         length = l;
         width = w;
      }
      public double GetArea()
      {
     
         return length * width;
      }
      public void Display()
      {
     
         Console.WriteLine("长度: {0}", length);
         Console.WriteLine("宽度: {0}", width);
         Console.WriteLine("面积: {0}", GetArea());
      }
   }//end class Rectangle  
   class Tabletop : Rectangle
   {
     
      private double cost;
      public Tabletop(double l, double w) : base(l, w)
      {
      }
      public double GetCost()
      {
     
         double cost;
         cost = GetArea() * 70;
         return cost;
      }
      public void Display()
      {
     
         base.Display();
         Console.WriteLine("成本: {0}", GetCost());
      }
   }
   class ExecuteRectangle
   {
     
      static void Main(string[] args)
      {
     
         Tabletop t = new Tabletop(4.5, 7.5);
         t.Display();
         Console.ReadLine();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

长度: 4.5
宽度: 7.5
面积: 33.75
成本: 2362.5


C# 多重继承

多重继承指的是一个类别可以同时从多于一个父类继承行为与特征的功能。与单一继承相对,单一继承指一个类别只可以继承自一个父类。

C# 不支持多重继承。但是,您可以使用接口来实现多重继承。下面的程序演示了这点:

实例
using System;
namespace InheritanceApplication
{
     
   class Shape
   {
     
      public void setWidth(int w)
      {
     
         width = w;
      }
      public void setHeight(int h)
      {
     
         height = h;
      }
      protected int width;
      protected int height;
   }

   // 基类 PaintCost
   public interface PaintCost
   {
     
      int getCost(int area);

   }
   // 派生类
   class Rectangle : Shape, PaintCost
   {
     
      public int getArea()
      {
     
         return (width * height);
      }
      public int getCost(int area)
      {
     
         return area * 70;
      }
   }
   class RectangleTester
   {
     
      static void Main(string[] args)
      {
     
         Rectangle Rect = new Rectangle();
         int area;
         Rect.setWidth(5);
         Rect.setHeight(7);
         area = Rect.getArea();
         // 打印对象的面积
         Console.WriteLine("总面积: {0}",  Rect.getArea());
         Console.WriteLine("油漆总成本: ${0}" , Rect.getCost(area));
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

总面积: 35
油漆总成本: $2450


C# 多态性

多态是同一个行为具有多个不同表现形式或形态的能力。

多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。
在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
☀️ 学会编程入门必备 C# 最基础知识介绍(五)——方法、封装、继承、多态_第1张图片

现实中,比如我们按下 F1 键这个动作:

  • 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
  • 如果当前在 Word 下弹出的就是 Word 帮助;
  • 在 Windows 下弹出的就是 Windows 帮助和支持。
  • 同一个事件发生在不同的对象上会产生不同的结果。

静态多态性

在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:

  • 函数重载
  • 运算符重载

函数重载

可以在同一个范围内对相同的函数名有多个定义。函数的定义必须彼此不同,可以是参数列表中的参数类型不同,也可以是参数个数不同。不能重载只有返回类型不同的函数声明。

下面的实例演示了几个相同的函数 Add(),用于对不同个数参数进行相加处理:

实例
using System;
namespace PolymorphismApplication
{
     
    public class TestData  
    {
       
        public int Add(int a, int b, int c)  
        {
       
            return a + b + c;  
        }  
        public int Add(int a, int b)  
        {
       
            return a + b;  
        }  
    }  
    class Program  
    {
       
        static void Main(string[] args)  
        {
       
            TestData dataClass = new TestData();
            int add1 = dataClass.Add(1, 2);  
            int add2 = dataClass.Add(1, 2, 3);

            Console.WriteLine("add1 :" + add1);
            Console.WriteLine("add2 :" + add2);  
        }  
    }  
}

下面的实例演示了几个相同的函数 print(),用于打印不同的数据类型:

实例
using System;
namespace PolymorphismApplication
{
     
   class Printdata
   {
     
      void print(int i)
      {
     
         Console.WriteLine("输出整型: {0}", i );
      }

      void print(double f)
      {
     
         Console.WriteLine("输出浮点型: {0}" , f);
      }

      void print(string s)
      {
     
         Console.WriteLine("输出字符串: {0}", s);
      }
      static void Main(string[] args)
      {
     
         Printdata p = new Printdata();
         // 调用 print 来打印整数
         p.print(1);
         // 调用 print 来打印浮点数
         p.print(1.23);
         // 调用 print 来打印字符串
         p.print("Hello Runoob");
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

输出整型: 1
输出浮点型: 1.23
输出字符串: Hello Runoob


C# 运算符重载

您可以重定义或重载 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;
}

上面的函数为用户自定义的类 Box 实现了加法运算符(+)。它把两个 Box 对象的属性相加,并返回相加后的 Box 对象。


运算符重载的实现

下面的程序演示了完整的实现:

实例
using System;

namespace OperatorOvlApplication
{
     
   class Box
   {
     
      private double length;      // 长度
      private double breadth;     // 宽度
      private double height;      // 高度

      public double getVolume()
      {
     
         return length * breadth * height;
      }
      public void setLength( double len )
      {
     
         length = len;
      }

      public void setBreadth( double bre )
      {
     
         breadth = bre;
      }

      public void setHeight( double hei )
      {
     
         height = hei;
      }
      // 重载 + 运算符来把两个 Box 对象相加
      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;
      }

   }

   class Tester
   {
     
      static void Main(string[] args)
      {
     
         Box Box1 = new Box();         // 声明 Box1,类型为 Box
         Box Box2 = new Box();         // 声明 Box2,类型为 Box
         Box Box3 = new Box();         // 声明 Box3,类型为 Box
         double volume = 0.0;          // 体积

         // Box1 详述
         Box1.setLength(6.0);
         Box1.setBreadth(7.0);
         Box1.setHeight(5.0);

         // Box2 详述
         Box2.setLength(12.0);
         Box2.setBreadth(13.0);
         Box2.setHeight(10.0);

         // Box1 的体积
         volume = Box1.getVolume();
         Console.WriteLine("Box1 的体积: {0}", volume);

         // Box2 的体积
         volume = Box2.getVolume();
         Console.WriteLine("Box2 的体积: {0}", volume);

         // 把两个对象相加
         Box3 = Box1 + Box2;

         // Box3 的体积
         volume = Box3.getVolume();
         Console.WriteLine("Box3 的体积: {0}", volume);
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

Box1 的体积: 210
Box2 的体积: 1560
Box3 的体积: 5400


可重载和不可重载运算符

下表描述了 C# 中运算符重载的能力:

运算符 描述
+, -, !, ~, ++, – 这些一元运算符只有一个操作数,且可以被重载
+, -, *, /, % 这些二元运算符带有两个操作数,且可以被重载
==, !=, <, >, <=, >= 这些比较运算符可以被重载
&&, 这些条件逻辑运算符不能被直接重载
+=, -=, *=, /=, %= 这些赋值运算符不能被重载
=, ., ?:, ->, new, is, sizeof, typeof 这些运算符不能被重载

实例
针对上述讨论,让我们扩展上面的实例,重载更多的运算符:

实例
using System;

namespace OperatorOvlApplication
{
     
    class Box
    {
     
       private double length;      // 长度
       private double breadth;     // 宽度
       private double height;      // 高度
     
       public double getVolume()
       {
     
         return length * breadth * height;
       }
      public void setLength( double len )
      {
     
          length = len;
      }

      public void setBreadth( double bre )
      {
     
          breadth = bre;
      }

      public void setHeight( double hei )
      {
     
          height = hei;
      }
      // 重载 + 运算符来把两个 Box 对象相加
      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;
      }
     
      public static bool operator == (Box lhs, Box rhs)
      {
     
          bool status = false;
          if (lhs.length == rhs.length && lhs.height == rhs.height
             && lhs.breadth == rhs.breadth)
          {
     
              status = true;
          }
          return status;
      }
      public static bool operator !=(Box lhs, Box rhs)
      {
     
          bool status = false;
          if (lhs.length != rhs.length || lhs.height != rhs.height
              || lhs.breadth != rhs.breadth)
          {
     
              status = true;
          }
          return status;
      }
      public static bool operator <(Box lhs, Box rhs)
      {
     
          bool status = false;
          if (lhs.length < rhs.length && lhs.height
              < rhs.height && lhs.breadth < rhs.breadth)
          {
     
              status = true;
          }
          return status;
      }

      public static bool operator >(Box lhs, Box rhs)
      {
     
          bool status = false;
          if (lhs.length > rhs.length && lhs.height
              > rhs.height && lhs.breadth > rhs.breadth)
          {
     
              status = true;
          }
          return status;
      }

      public static bool operator <=(Box lhs, Box rhs)
      {
     
          bool status = false;
          if (lhs.length <= rhs.length && lhs.height
              <= rhs.height && lhs.breadth <= rhs.breadth)
          {
     
              status = true;
          }
          return status;
      }

      public static bool operator >=(Box lhs, Box rhs)
      {
     
          bool status = false;
          if (lhs.length >= rhs.length && lhs.height
             >= rhs.height && lhs.breadth >= rhs.breadth)
          {
     
              status = true;
          }
          return status;
      }
      public override string ToString()
      {
     
          return String.Format("({0}, {1}, {2})", length, breadth, height);
      }
   
   }
   
   class Tester
   {
     
      static void Main(string[] args)
      {
     
        Box Box1 = new Box();          // 声明 Box1,类型为 Box
        Box Box2 = new Box();          // 声明 Box2,类型为 Box
        Box Box3 = new Box();          // 声明 Box3,类型为 Box
        Box Box4 = new Box();
        double volume = 0.0;   // 体积

        // Box1 详述
        Box1.setLength(6.0);
        Box1.setBreadth(7.0);
        Box1.setHeight(5.0);

        // Box2 详述
        Box2.setLength(12.0);
        Box2.setBreadth(13.0);
        Box2.setHeight(10.0);

       // 使用重载的 ToString() 显示两个盒子
        Console.WriteLine("Box1: {0}", Box1.ToString());
        Console.WriteLine("Box2: {0}", Box2.ToString());
       
        // Box1 的体积
        volume = Box1.getVolume();
        Console.WriteLine("Box1 的体积: {0}", volume);

        // Box2 的体积
        volume = Box2.getVolume();
        Console.WriteLine("Box2 的体积: {0}", volume);

        // 把两个对象相加
        Box3 = Box1 + Box2;
        Console.WriteLine("Box3: {0}", Box3.ToString());
        // Box3 的体积
        volume = Box3.getVolume();
        Console.WriteLine("Box3 的体积: {0}", volume);

        //comparing the boxes
        if (Box1 > Box2)
          Console.WriteLine("Box1 大于 Box2");
        else
          Console.WriteLine("Box1 不大于 Box2");
        if (Box1 < Box2)
          Console.WriteLine("Box1 小于 Box2");
        else
          Console.WriteLine("Box1 不小于 Box2");
        if (Box1 >= Box2)
          Console.WriteLine("Box1 大于等于 Box2");
        else
          Console.WriteLine("Box1 不大于等于 Box2");
        if (Box1 <= Box2)
          Console.WriteLine("Box1 小于等于 Box2");
        else
          Console.WriteLine("Box1 不小于等于 Box2");
        if (Box1 != Box2)
          Console.WriteLine("Box1 不等于 Box2");
        else
          Console.WriteLine("Box1 等于 Box2");
        Box4 = Box3;
        if (Box3 == Box4)
          Console.WriteLine("Box3 等于 Box4");
        else
          Console.WriteLine("Box3 不等于 Box4");

        Console.ReadKey();
      }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

Box1: (6, 7, 5)
Box2: (12, 13, 10)
Box1 的体积: 210
Box2 的体积: 1560
Box3:(18, 20, 15)
Box3 的体积: 5400
Box1 不大于 Box2
Box1 小于 Box2
Box1 不大于等于 Box2
Box1 小于等于 Box2
Box1 不等于 Box2
Box3 等于 Box4


动态多态性

C# 允许您使用关键字 abstract 创建抽象类,用于提供接口的部分类的实现。当一个派生类继承自该抽象类时,实现即完成。抽象类包含抽象方法,抽象方法可被派生类实现。派生类具有更专业的功能。

请注意,下面是有关抽象类的一些规则:

  • 不能创建一个抽象类的实例。
  • 不能在一个抽象类外部声明一个抽象方法。
  • 通过在类定义前面放置关键字 sealed,可以将类声明为密封类。当一个类被声明为 sealed 时,它不能被继承。抽象类不能被声明为
    sealed。

下面的程序演示了一个抽象类:

实例
using System;
namespace PolymorphismApplication
{
     
   abstract class Shape
   {
     
       abstract public int area();
   }
   class Rectangle:  Shape
   {
     
      private int length;
      private int width;
      public Rectangle( int a=0, int b=0)
      {
     
         length = a;
         width = b;
      }
      public override int area ()
      {
     
         Console.WriteLine("Rectangle 类的面积:");
         return (width * length);
      }
   }

   class RectangleTester
   {
     
      static void Main(string[] args)
      {
     
         Rectangle r = new Rectangle(10, 7);
         double a = r.area();
         Console.WriteLine("面积: {0}",a);
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

Rectangle 类的面积:
面积: 70

当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法

虚方法是使用关键字 virtual 声明的。

虚方法可以在不同的继承类中有不同的实现。

对虚方法的调用是在运行时发生的。

动态多态性是通过 抽象类虚方法 实现的。

以下实例创建了 Shape 基类,并创建派生类 Circle、 Rectangle、Triangle, Shape 类提供一个名为 Draw 的虚拟方法,在每个派生类中重写该方法以绘制该类的指定形状。

实例
using System;
using System.Collections.Generic;

public class Shape
{
     
    public int X {
      get; private set; }
    public int Y {
      get; private set; }
    public int Height {
      get; set; }
    public int Width {
      get; set; }
   
    // 虚方法
    public virtual void Draw()
    {
     
        Console.WriteLine("执行基类的画图任务");
    }
}

class Circle : Shape
{
     
    public override void Draw()
    {
     
        Console.WriteLine("画一个圆形");
        base.Draw();
    }
}
class Rectangle : Shape
{
     
    public override void Draw()
    {
     
        Console.WriteLine("画一个长方形");
        base.Draw();
    }
}
class Triangle : Shape
{
     
    public override void Draw()
    {
     
        Console.WriteLine("画一个三角形");
        base.Draw();
    }
}

class Program
{
     
    static void Main(string[] args)
    {
     
        // 创建一个 List 对象,并向该对象添加 Circle、Triangle 和 Rectangle
        var shapes = new List<Shape>
        {
     
            new Rectangle(),
            new Triangle(),
            new Circle()
        };

        // 使用 foreach 循环对该列表的派生类进行循环访问,并对其中的每个 Shape 对象调用 Draw 方法
        foreach (var shape in shapes)
        {
     
            shape.Draw();
        }

        Console.WriteLine("按下任意键退出。");
        Console.ReadKey();
    }

}

当上面的代码被编译和执行时,它会产生下列结果:

  • 画一个长方形
  • 执行基类的画图任务
  • 画一个三角形
  • 执行基类的画图任务
  • 画一个圆形
  • 执行基类的画图任务
  • 按下任意键退出。

下面的程序演示通过虚方法 area() 来计算不同形状图像的面积:

实例
using System;
namespace PolymorphismApplication
{
     
   class Shape
   {
     
      protected int width, height;
      public Shape( int a=0, int b=0)
      {
     
         width = a;
         height = b;
      }
      public virtual int area()
      {
     
         Console.WriteLine("父类的面积:");
         return 0;
      }
   }
   class Rectangle: Shape
   {
     
      public Rectangle( int a=0, int b=0): base(a, b)
      {
     

      }
      public override int area ()
      {
     
         Console.WriteLine("Rectangle 类的面积:");
         return (width * height);
      }
   }
   class Triangle: Shape
   {
     
      public Triangle(int a = 0, int b = 0): base(a, b)
      {
     
     
      }
      public override int area()
      {
     
         Console.WriteLine("Triangle 类的面积:");
         return (width * height / 2);
      }
   }
   class Caller
   {
     
      public void CallArea(Shape sh)
      {
     
         int a;
         a = sh.area();
         Console.WriteLine("面积: {0}", a);
      }
   }  
   class Tester
   {
     
     
      static void Main(string[] args)
      {
     
         Caller c = new Caller();
         Rectangle r = new Rectangle(10, 7);
         Triangle t = new Triangle(10, 5);
         c.CallArea(r);
         c.CallArea(t);
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

Rectangle 类的面积:
面积:70
Triangle 类的面积:
面积:25


总结

本篇文章介绍了C#中一些基础知识,是接着上一篇博客写的
主要介绍了C#中的方法封装继承多态

可能有些地方写的不是很全,大概就是这样啦。有空会继续更新

传送门

上 一 篇 \color{00ff90}{上一篇 } ☀️ 学会编程入门必备 C# 最基础知识介绍(四)——数组、字符串、结构体、枚举、类
下 一 篇 \color{00ff90}{下一篇} ☀️ 学会编程入门必备 C# 最基础知识介绍(六)——接口、命名空间、预处理指令、正则表达式、异常处理、文件的输入与输出

你可能感兴趣的:(C#基础知识,编程语言,多态,类)