03、C#面向对象

面向对象


对象包括属性和方法,属性是指对象固有的特征,方法则是对象的行为。

面向对象语言的三大特征分别是封装、继承、多态。

封装:封装的好处就是能让用户只关心对象的用法而不用关心对象的实现。
继承:继承关系主要体现在类之间的继承,这样既能减少开发时的代码量又方便了程序的复用。
多态:多态是通过类的继承或接口的实现来体现的,提高复用性和可移植性。

类的定义

在C# 语言中创建的任何项目都有类的存在,通过类能体现面向对象语言中封装、继承、多态的特性。

类的访问修饰符 修饰符 类名 {
类的成员
}

类的访问修饰符:public(任何项目中访问类)、internal或者不写(只能在当前项目中访问类)。

修饰符:修饰符是对类的描述,包括abstract(抽象类,不能被实例化)、sealed(密封类,不能被继承)和static(静态类,不能被实例化)等。

其中类目是自定义的,而类的成员也是自定义的元素,主要包括字段、属性和方法。

访问修饰符

类的访问修饰符主要有两个,即 internal 和 public,如果省略了访问修饰符,即为 internal。

类中成员的访问修饰符有 4 个,具体用法如下。

属性 说明
public 公共的、全局的,表示不限制对该类成员的访问
private 私有的,表示只有该类本身可以访问
protected 受保护的,表示只有该类本身和他的子类可以访问
internal 内部的,表示程序集内可访问

在修饰字段时通常用两个修饰符,即readonly (只读)和static (静态的)。

readonly 修饰字段意味着只能读取该字段的值而不能给字段赋值。
static 修饰的字段是静态字段,可以直接通过类名访问该字段。

【实例】在 Test 类中分别定义使用不同修饰符的字段。

public class Test {
    private int id;
    public readonly string name;
    internal static int age;
    private const string major = "计算机";
}

字段在类中定义完成后,在类加载时,会自动为字段赋值,不同数据类型的字段默认值不同, 如下表所示。

数据类型 默认值
整数类型 0
浮点型 0
字符串类型 空值
字符型 a
布尔型 False
其他引用类型 空值

方法的定义

方法是将完成同一功能的内容放到一起,方便书写和调用,也体现了面向对象语言中封装的特性。

访问修饰符 修饰符 返回值类型 方法名(参数列表) {
语句块;
}

访问修饰符:所有类成员访问修饰符都可以使用,如果省略访问修饰符,默认是 private。

修饰符:修饰符包括 virtual(虚拟的)、abstract(抽象的)、override(重写的)、static(静态的)、sealed(密封的)。override 是在类之间继承时使用的。

返回值类型:用于在调用方法后得到返回结果,返回值可以是任意的数据类型,如果指定了返回值类型。

其中方法名是自定义的,而参数列表允许0到多个参数,每个参数使用逗号隔开。

public class Computer {
    private double add(double a, double b) {
        return a + b;
    }

    private double minus(double a, double b) {
        return a - b;
    }

    private double multiply(double a, double b) {
        return a * b;
    }

    private double divide(double a, double b) {
        return a / b;
    }
}

方法的重载

函数重载的意思就是方法名称相同,而参数列表不同。参数列表不同主要体现在参数个数或参数的数据类型不同。

下面是一个函数重载的示例代码:

public class SumUtils {
    public int sum(int a, int b) {
        return a + b;
    }

    public double sum(double a, double b) {
        return a + b;
    }

    public string sum(string a, string b) {
        return a + b;
    }

    public int sum(int a) {
        int sum = 0;
        for (int i = 0; i < a; i++) {
            sum += i;
        }
        return sum;
    }
}

从上面的程序可以看出在该类中定义的方法名称都是 Sum,仅是参数的类型或个数不同而已。在 Main 方法中调用上述定义的方法,代码如下。

class Program {
    static void Main(string[] args) {
        SumUtils sumUtils = new SumUtils();
        Console.WriteLine("两个整数的和为:" + sumUtils.sum(3, 5));
        Console.WriteLine("两个小数的和为:" + sumUtils.sum(3.1, 5.2));
        Console.WriteLine("两个字符串的连接为:" + sumUtils.sum("Hello", "World"));
        Console.WriteLine("1~10的和为:" + sumUtils.sum(10));
    }
}

执行上面的代码,结构如下所示:

两个整数的和为:8
两个小数的和为:8.3
两个字符串的连接为:HelloWorld
1~10的和为:45

方法的参数

方法中的参数分为实际参数和形式参数,实际参数被称为实参,是在调用方法时传递的参数;形式参数被称为形参,是在方法定义中所写的参数。

public int Add(int a,int b) {
    return a+b;
}

在上面的方法定义中,a 和 b 是形式参数。在 Print 调用方法时使用如下代码:

public void Print() {
    Add(3,4);
}

在调用 Add 方法时传递的参数 3 和 4 即为实际参数。

在 C# 语言中,方法中的参数除了定义数据类型外,还可以定义引用参数和输出参数。

引用参数使用 ref 关键字定义
输出参数使用 out 关键字定义。

ref

引用参数在方法中使用时必须为其赋值,并且必须是由变量赋予的值,不能是常量或表达式。

public class RefClass {
    public bool jude(ref int num) {
        if (num % 5 == 0) {
            return true;
        }
        return false;
    }
}

在 Main 方法中调用 judge 方法,代码如下。

class Program {
    static void Main(string[] args) {
        RefClass refClass = new RefClass();
        int a = 20;
        bool result = refClass.jude(ref a);
        Console.WriteLine("验证结果是:{0}", result);
    }
}

注意:传入的方法参数列表必须是ref + 变量的方式,否则出错。

out

输出参数相当于返回值,即在方法调用完成后可以将返回的结果存放到输出参数中。

输出参数多用于一个方法需要返回多个值的情况。需要注意的是,在使用输出参数时,必须在方法调用完成前为输出参数赋值。

public class OutClass {
    public void jude(int num, out bool result) {
        if (num % 5 == 0) {
            result = true;
        } else {
            result = false;
        }
    }
}

在 Main 中调用该方法,代码如下:

class Program {
    static void Main(string[] args) {
        OutClass outClass = new OutClass();
        bool result;
        outClass.jude(20, out result);
        Console.WriteLine(result);
    }
}

方法递归

递归是一种特殊的执行程序,它是用方法调用自身的形式实现的,让程序代码循环执行。

使用递归实现计算所输入数的阶乘。

public static int factorial(int n) {
    if (n == 0) {
        return 1;
    }
    return n * factorial(n - 1);
}

在 Main 方法中调用该静态方法,代码如下:

static void Main(string[] args) {
    int result = factorial(5);
    Console.WriteLine("result = {0}", result);
}

Get & Set

属性经常与字段连用,并提供了 get 访问器和 set 访问器,分别用于获取或设置字段的值。

下面的实例定义图书信息类,然后字段包含图书的信息:

public class Book {
    private int id;
    private string name;
    private double price;

    public int Id {
        get => id;
        set => id = value;
    }

    public string Name {
        get => name;
        set => name = value;
    }

    public double Price {
        get => price;
        set => price = value;
    }
}

在 C# 语言中可以将属性的定义简化成如下写法。

public class Book {
    private int id;
    private string name;
    private double price;
    public int Id { get; set;}
    public string Name { get; set;}
    public double Price { get; set;}

    public void printMessage() {
        Console.WriteLine("图书编号:{0}", id);
        Console.WriteLine("图书名称:{0}", name);
        Console.WriteLine("图书价格:{0}", price);
    }
}

一般类的成员调用都是通过new关键字创建对象,然后通过.操作符来调用成员。静态成员通过类名调用。

class Program {
    static void Main(string[] args) {
        Book book = new Book();
        book.Id = 001;
        book.Name = "计算机基础";
        book.Price = 69.5;
        book.printMessage();
    }
}

构造函数 & 析构函数

构造函数

构造函数和类名相同,在对象创建的时候执行,如果没有编写构造函数,系统会默认构造一个空参的构造函数。在对属性赋值的时候,还可以创建当前类的构造函数来进行赋值操作:

public class Book {
    private int id;
    private string name;
    private double price;

    public Book(int id, string name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public int Id { get; set;}
    public string Name { get; set;}
    public double Price { get; set;}

    public void printMessage() {
        Console.WriteLine("图书编号:{0}", id);
        Console.WriteLine("图书名称:{0}", name);
        Console.WriteLine("图书价格:{0}", price);
    }
}

在调用的时候就可以通过构造函数赋值:

class Program {
    static void Main(string[] args) {
        Book book = new Book(001, "计算机基础", 69.5);
        book.printMessage();
    }
}

析构函数

析构方法则是在垃圾回收、释放资源时使用的。它和类名一样,但是在前面使用~修饰符。

public class Book {
    private int id;
    private string name;
    private double price;

    public Book(int id, string name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public int Id { get; set;}
    public string Name { get; set;}
    public double Price { get; set;}

    public void printMessage() {
        Console.WriteLine("图书编号:{0}", id);
        Console.WriteLine("图书名称:{0}", name);
        Console.WriteLine("图书价格:{0}", price);
    }
	// 析构函数
    ~Book() {
        Console.WriteLine("调用了析构方法");
    }
}

lambda表达式

在 C# 语言中提供了 Lambda 表达式,给编写程序带来了很多的便利。

访问修饰符 修饰符 返回值类型 方法名(参数列表) => 表达式;

如果在方法定义中定义了返回值类型,在表达式中不必使用 return 关键字,只需要计算值即可

假设有如下方法需要转换为lambda表达式:

public static int add(int a, int b) {
    return a + b;
}

转换成lambda表达式的写法如下:

public static int add(int a, int b) => a + b;

在main方法中调用的结果如下:

class Program {
    public static int add(int a, int b) => a + b;
    static void Main(string[] args) {
        Console.WriteLine(add(100, 200));
    }
}

如果将 Add 方法中的返回值更改成 void,则 Add 方法的定义语句如下。

public static void add(int a, int b) => Console.WriteLine(a + b);

嵌套类

在类中除了能编写前面提到的类成员以外,还能直接定义类。将一个类定义在另一个类的内部,即可将在类内部定义的类称为嵌套类。

嵌套类相当于类中成员,能使用类成员的访问修饰符和修饰符。但是,在访问嵌套类中的成员时必须加上外层类的名称。

public class OuterClass {
    public class InnerClass {
        public string name;
        public int age;
        public void printMessage() {
            Console.WriteLine("名称是:{0}", name);
            Console.WriteLine("年龄是:{0}", age);
        }
    }
}

在 Main 方法中调用嵌套类的成员,代码如下:

class Program {
    static void Main(string[] args) {
        OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
        innerClass.name = "legend";
        innerClass.age = 31;
        innerClass.printMessage();
    }
}

如果内部类是静态成员,则使用"外部类.内部类.静态成员"的方式调用。

部分类

在 C# 语言中提供了一个部分类,正如字面上的意思,它用于表示一个类中的一部分。

一个类可以由多个部分类构成,定义部分类的语法形式 如下:

访问修饰符 修饰符 partial class 类名{……}

partial 即为定义部分类的关键字。部分类主要用于当一个类中的内容较多时将相似类中的内容拆分到不同的类中,并且部分类的名称必须相同。

定义名为 Course 的类,分别使用两个部分类实现定义课程属性并输出的操作。在一个部分类中设定课程的属性,在一个部分类中定义方法输出课程的属性。

public partial class Course {
    private int id;
    private string name;
    private double points;

    public int Id {
        get => id;
        set => id = value;
    }

    public string Name {
        get => name;
        set => name = value;
    }

    public double Points {
        get => points;
        set => points = value;
    }
}

public partial class Course {
    public void printCourses() {
        Console.WriteLine("课程编号:{0}", id);
        Console.WriteLine("课程名称:{0}", name);
        Console.WriteLine("课程学分:{0}", points);
    }
}

在 Main 方法中为属性赋值并调用 PrintCourse 方法,代码如下:

class Program {
    static void Main(string[] args) {
        Course course = new Course();
        course.Id = 1001;
        course.Name = "C#入门";
        course.Points = 3;
        course.printCourses();
    }
}

从该实例可以看出,在不同的部分类中可以直接互相访问其成员,相当于所有的代码都写到一个类中。

部分方法

部分方法的使用需要注意如下三点:

  • 部分方法必须是私有的,并且不能使用 virtual、abstract、override、new、sealed、extern 等修饰符。
  • 部分方法不能有返回值。
  • 在部分方法中不能使用 out 类型的参数。

由于部分方法使用的情况非常的少,这里不再详细叙述。

public partial class Course {
    private int id;
    private string name;
    private double points;

    public int Id {
        get => id;
        set => id = value;
    }

    public string Name {
        get => name;
        set => name = value;
    }

    public double Points {
        get => points;
        set => points = value;
    }

    partial void printCourses();
    public void printMessage() {
        printCourses();
    }
}

public partial class Course {
    partial void printCourses() {
        Console.WriteLine("课程编号:{0}", id);
        Console.WriteLine("课程名称:{0}", name);
        Console.WriteLine("课程学分:{0}", points);
    }
}

在 Main 方法中调用 printMessage方法的代码如下:

class Program {
    static void Main(string[] args) {
        Course course = new Course();
        course.Id = 1001;
        course.Name = "C#入门";
        course.Points = 3;
        course.printMessage();
    }
}

常用类

Console类

Console 类主要用于控制台应用程序的输入和输岀操作。

方法 描述
Write 向控制台输出内容后不换行
WriteLine 向控制台输出内容后换行
Read 从控制台上读取一个字符
ReadLine 从控制台上读取一行字符

向控制台中输出内容时也可以对输出的内容进行格式化,格式化时使用的是占位符的方法,语法形式如下。

Console.Write(格式化字符串, 输出项, 输出项2);

在格式化字符串中使用{索引号}的形式,索引号从 0 开始。输出项 1 填充 {0} 位置的内容,依此类推。

【实例】从控制台依次输入姓名和所在学校,并在输出时组成一句话“xx 同学在 xx 学习”。

class Program {
    static void Main(string[] args) {
        Console.WriteLine("请输入学生姓名:");
        string name = Console.ReadLine();
        Console.WriteLine("请输入所在学校:");
        string school = Console.ReadLine();
        Console.WriteLine("{0}同学在{1}学习", name, school);
    }
}

Math类

Math 类主要用于一些与数学相关的计算,并提供了很多静态方法方便访问,常用的方法如下表所示。

方法 描述
Abs 取绝对值
Ceiling 返回大于或等于指定的双精度浮点数的最小整数值
Floor 返回小于或等于指定的双精度浮点数的最大整数值
Equals 返回指定的对象实例是否相等
Max 返回两个数中较大数的值
Min 返回两个数中较小数的值
Sqrt 返回指定数字的平方根
Round 返回四舍五入后的值

【实例】从控制台输入两个数,分别使用 Max 和 Min 方法输出其中的最大值和最小值。

class Program {
    static void Main(string[] args) {
        Console.WriteLine("请输入第一个数:");
        double num = Double.Parse(Console.ReadLine());
        Console.WriteLine("请输入第二个数:");
        double num1 = Double.Parse(Console.ReadLine());
        Console.WriteLine("两个数较大的数为:{0}", Math.Max(num, num1));
        Console.WriteLine("两个数较小的数为:{0}", Math.Min(num, num1));
    }
}

Random类

Random 类是一个产生伪随机数字的类,它的构造函数有两种。

New Random()
New Random(Int32)

前者是根据触发那刻的系统时间做为种子,来产生一个随机数字,后者可以自己设定触发的种子,一般都是用 UnCheck((Int)DateTime.Now.Ticks) 做为参数种子。

Random 类中提供的 Next、NextBytes 以及 NextDouble 方法可以生成整数类型、byte 数组类型以及双精度浮点型的随机数,详细说明如下表所示。

方法 描述
Next() 每次产生一个不同的随机正整数
Next(int max Value) 产生一个比 max Value 小的正整数
Next(int min Value,int max Value) 产生一个 minValue~maxValue 的正整数,但不包含 maxValue
NextDouble() 产生一个0.0~1.0的浮点数
NextBytes(byte[] buffer) 用随机数填充指定字节数的数组

【实例】分别使用 Next、NextDouble 以及 NextBytes 方法生成随机数。

class Program {
    static void Main(string[] args) {
        Random random = new Random();
        Console.WriteLine("产生一个10以内的数:{0}", random.Next(0, 10));
        Console.WriteLine("产生一个0到1之间的浮点数:{0}", random.NextDouble());
        byte[] b = new byte[5];
        random.NextBytes(b);
        Console.WriteLine("产生的byte类型的值为:");
        foreach (byte var in b) {
            Console.Write(var + " ");
        }
        Console.WriteLine();
    }
}

DateTime类

DateTime 类用于表示时间,所表示的范围是从 0001 年 1 月 1 日 0 点到 9999 年 12 月 31 日 24 点。

在 DateTime 类中提供了静态属性 Now,用于获取当前的日期和时间,如下所示:

DateTime.Now

在 DateTime 类中提供了常用的属性和方 法用于获取或设置日期和时间,如下表所示:

方法 描述
Date 获取实例的日期部分
Day 获取该实例所表示的日期是一个月的第几天
DayOfWeek 获取该实例所表示的日期是一周的星期几
DayOfYear 获取该实例所表示的日期是一年的第几天
Add(Timespan value) 在指定的日期实例上添加时间间隔值 value
AddDays(double value) 在指定的日期实例上添加指定天数 value
AddHours(double value) 在指定的日期实例上添加指定的小时数 value
AddMinutes(double value) 在指定的日期实例上添加指定的分钟数 value
AddSeconds(double value) 在指定的日期实例上添加指定的秒数 value
AddMonths(int value) 在指定的日期实例上添加指定的月份 value
AddYears (int value) 在指定的日期实例上添加指定的年份 value

实例】使用 DateTime 类获取当前时间,分别输出该日是当月的第几天、星期几以 及一年中的第几天,并计算 30 天后的日期。

class Program{
    static void Main(string[] args) {
        DateTime dt = DateTime.Now;
        Console.WriteLine("当前日期为:{0}", dt);
        Console.WriteLine("当前时本月的第{0}天", dt.Day);
        Console.WriteLine("当前是:{0}", dt.DayOfWeek);
        Console.WriteLine("当前是本年度第{0}天", dt.DayOfYear);
        Console.WriteLine("30 天后的日期是{0}", dt.AddDays(30));
    }
}

继承

在 C# 语言中仅支持单重继承,主要用于解决代码的重用问题。

Object类

Object 类是 C# 语言中最原始、最重要的类,是所有类的“祖先”,每个 C# 类都是它的子类,它实现了每个类都必须具有的基本方法。

Object 类中的属性和方法可以被用到任何类。

在 Object 类中提供了 4 个常用的方法,即 Equals、GetHashCode、GetType 以及 ToString 方法。

Equals方法

Equals 方法主要用于比较两个对象是否相等,如果相等则返回 True,否则返回 False。

在 C# 语言中,Equals 方法提供了两个,一个是静态的,一个是非静态的,具体的定义如下。

Equals (object ol, object o2); //静态方法
Equals (object o); //非静态方法

1)创建一个Student类,可以不编写任何代码

class Student{};

2)创建两个 Student 类的对象,并使用 Equals 方法比较类的对象,代码如下

class Program {
    static void Main(string[] args) {
        Student stu1 = new Student();
        Student stu2 = new Student();
        bool flag = Equals(stu1, stu2);
        Console.WriteLine("stu1 和 stu2 比较的结果为,{0}", flag);
    }
}

GetHashCode方法

GetHashCode 方法返回当前 System.Object 的哈希代码,每个对象的哈希值都是固定的。

该方法不含有任何参数,并且不是静态方法,因此需要使用实例来调用该方法。

创建两个 Student 类的对象,并分别计算其哈希值。

class Program {
    static void Main(string[] args) {
        Student stu1 = new Student();
        Student stu2 = new Student();
        Console.WriteLine(stu1.GetHashCode());
        Console.WriteLine(stu2.GetHashCode());
    }
}

执行上面的代码输出如下所示:

58225482
54267293

从上面的执行效果可以看出,不同实例的哈希值是不同的,因此也可以通过该方法比较对象是否相等。

GetType方法

GetType 方法用于获取当前实例的类型,返回值为 System.Type 类型。不包含参数,非静态方法。

下面通过实例来演示该方法的使用。

class Program {
    static void Main(string[] args) {
        int i = 100;
        string str = "abc";
        Student stu = new Student();
        Console.WriteLine(i.GetType());
        Console.WriteLine(str.GetType());
        Console.WriteLine(stu.GetType());
    }
}

执行上面的代码,效果如下所示:

System.Int32
System.String
Demo01.Student

ToString方法

ToString 方法返回一个对象实例的字符串,在默认情况下将返回类类型的限定名。

如果当前类型没有重写 ToString() 方法的情况下,调用 ToString() 方法,默认返回当前类型的名称。

class Program {
    static void Main(string[] args) {
        Int32 a = 1;
        Object b = new Object();
        Console.WriteLine("值类型(Int32类型)的字符串的表现形式:{0}", a.ToString());
        Console.WriteLine("引用类型字符串的表现形式:{0}", b.ToString());
    }
}

执行上面的代码,效果如下:

值类型(Int32类型)的字符串的表现形式:1
引用类型字符串的表现形式:System.Object

base关键字

在 C# 语言中子类中定义的同名方法相当于在子类中重新定义了一个方法,在子类中的对象是调用不到父类中的同名方法的,调用的是子类中的方法。可以使用base关键字调用父类方法。

class Program {
    static void Main(string[] args) {
        Person person = new Person();
        person.print();
        Teacher teacher = new Teacher();
        teacher.print();
        Student student = new Student();
        student.print();
    }
}

class Person {
    public void print() {
        Console.WriteLine("Person Print");
    }
}

class Teacher : Person {
    public void print() {
        base.print();
        Console.WriteLine("Teacher Print");
    }
}

class Student : Person {
    public void print() {
        base.print();
        Console.WriteLine("Student Print");
    }
}

运行上面的示例,输出结果如下:

Person Print
Person Print
Teacher Print
Person Print
Student Print

从上面的执行效果可以看出,通过 base 关键字调用 Print 方法即可调用在父类中定义的语句。

注意:this 关键字代表的是当前类的对象,而 base 关键字代表的是父类中的对象。

虚方法

virtual 关键字能修饰方法、属性、索引器以及事件等,用到父类的成员中。

使用 virtual 关键字修饰属性和方法的语法形式如下。

//修饰属性
public virtual 数据类型 属性名{get; set; }

//修饰方法
访问修饰符 virtual 返回值类型方法名 {
语句块;
}

需要注意的是,virtual 关键字不能修饰使用 static 修饰的成员。

使用virtual修饰的方法会有如下特性:

情况1:在基类中定义了virtual方法,但在派生类中没有重写该虚方法。那么在对派生类实例的调用中,该虚方法使用的是基类定义的方法。

情况2:在基类中定义了virtual方法,然后在派生类中使用override重写该方法。那么在对派生类实例的调用中,该虚方法使用的是派生重写的方法。

下面通过一个实例来演示虚方法的使用:

class Program {
    static void Main(string[] args) {
        Person person = new Person();
        person.print();
        Teacher teacher = new Teacher();
        teacher.print();
        Student student = new Student();
        student.print();
    }
}

class Person {
    public virtual void print() {
        Console.WriteLine("Person Print");
    }
}

// Teach重写了父类的虚方法
class Teacher : Person {
    public override void print() {
        base.print();
        Console.WriteLine("Teacher Print");
    }
}

// Student没有实现父类的虚方法
class Student : Person {
}

打印结果如下所示:

Person Print
Person Print
Teacher Print
Person Print

从结果可以看出,即使重写了该虚方法,父类的虚方法也会调用一次,再执行子类的虚方法。

抽象类

abstract 关键字代表的是抽象的,使用该关键字能修饰类和方法,修饰的方法被称为抽象方法、修饰的类被称为抽象类。

在 C# 语言中抽象方法是一种不带方法体的方法,仅包含方法的定义,语法形式如下。

访问修饰符 abstract 方法返回值类型 方法名(参数列表);

在定义抽象类时,若使用 abstract 修饰类,将其放到 class 关键字的前面,语法形式如下:

访问修饰符 abstract class 类名 {
//类成员
}

在抽象类中可以定义抽象方法,也可以定义非抽象方法。可以被继承,但是抽象类不能被实例化。

class Program {
    static void Main(string[] args) {
        MathMajor mathMajor = new MathMajor();
        mathMajor.Id = 1;
        mathMajor.English = 80;
        mathMajor.Math = 90;
        mathMajor.total();
        EnglishMajor englishMajor = new EnglishMajor();
        englishMajor.Id = 2;
        englishMajor.English = 80;
        englishMajor.Math = 90;
        englishMajor.total();
    }
}

abstract class ExamResult {
    private int id;
    private double math;
    private double english;

    public int Id {
        get => id;
        set => id = value;
    }

    public double Math {
        get => math;
        set => math = value;
    }

    public double English {
        get => english;
        set => english = value;
    }
    // 计算总成绩
    public abstract void total();
}

class MathMajor : ExamResult {
    public override void total() {
        double total = Math * 0.6 + English * 0.4;
        Console.WriteLine("学号为{0}的数学专业成绩为:{1}", Id, total);
    }
}

class EnglishMajor : ExamResult {
    public override void total() {
        double total = Math * 0.4 + English * 0.6;
        Console.WriteLine("学号为{0}的英语专业成绩为:{1}", Id, total);
    }
}

密封类

sealed 关键字的含义是密封的,使用该关键字能修饰类或者类中的方法,修饰的类被称为密封类、修饰的方法被称为密封方法。

密封方法必须出现在子类中,并且是子类重写的父类方法,即 sealed 关键字必须与 override 关键字一起使用。

密封类不能被继承,密封方法不能被重写。

创建一个计算面积的抽象类 AreaAbstract ,并定义抽象方法计算面积。

public abstract class AreaAbstract {
    public abstract void area();
}

class Rectangle : AreaAbstract {
    private double width;
    private double length;

    public double Width {
        get => width;
        set => width = value;
    }

    public double Length {
        get => length;
        set => length = value;
    }

    public override void area() {
        Console.WriteLine("矩形的面积是:" + Width * Length);
    }
}

sealed class Circle : AreaAbstract {
    private double r;

    public double R {
        get => r;
        set => r = value;
    }

    public override void area() {
        Console.WriteLine("圆的面积是:" + r * 3.14 * 3.14);
    }
}

在上面的实例中,Circle 类不能被继承,Rectangle 类中的 Area 方法不能被重写。

多态

多态称为运行时多态,也就是在程序运行时自动让父类的实例调用子类中重写的 方法,它并不是在程序编译阶段完成的。

使用继承实现多态,实际上是指子类在继承父类后,重写了父类的虚方法或抽象方法。

总而言之,使用继承实现多态必须满足以下两个条件:

  • 子类在继承父类时必须有重写的父类的方法。
  • 在调用重写的方法时,必须创建父类的对象指向子类(即子类转换成父类)。

下面看一个示例:

class Program {
    static void Main(string[] args) {
        Major major1 = new Undergraduate();
        major1.Id = 1;
        major1.Name = "张晓";
        Console.WriteLine("本科生信息:");
        Console.WriteLine("学号:" + major1.Id + " 姓名:" + major1.Name);
        major1.Requirement();
        Major major2 = new Graduate();
        major2.Id = 2;
        major2.Name = "李明";
        Console.WriteLine("研究生信息:");
        Console.WriteLine("学号:" + major2.Id + " 姓名:" + major2.Name);
        major2.Requirement();
    }
}

abstract class Major {
    public int Id { get; set; }
    public string Name { get; set; }
    public abstract void Requirement();
}
class Undergraduate :Major {
    public override void Requirement() {
        Console.WriteLine("本科生学制4年,必须修满48学分");
    }
}

class Graduate : Major {
    public override void Requirement() {
        Console.WriteLine("研究生学制3年,必须修满32学分");
    }
}

执行上面的代码,效果如下:

本科生信息:
学号:1 姓名:张晓
本科生学制4年,必须修满48学分
研究生信息:
学号:2 姓名:李明
研究生学制3年,必须修满32学分

接口

在 C# 语言中,类之间的继承关系仅支持单重继承,而接口是为了实现多重继承关系设计的。

一个类能同时实现多个接口,还能在实现接口的同时再继承其他类,并且接口之间也可以继承。

无论是表示类之间的继承还是类实现接口、接口之间的继承,都使用“:”来表示。

接口定义

接口定义的语法形式如下:

interface 接口名称 {
接口成员;
}

下面的一个简单的接口定义的示例:

interface ICompute {
    int Id { get; set; }
    string Name { get; set; }
    void Total();
    void Avg();
}

接口实现

在 C# 语言中实现接口的具体语法形式如下:

class 类名 : 接口名 {
//类中的成员以及实现接口中的成员
}

以抽象方式实现接口中的成员是指将接口中未实现的成员定义为抽象成员,示例代码如下。

// 这样的接口等同于下面的抽象类
interface ITest {
    string name { get; set}
    void Print();
}
abstract class Test : ITest {
    public abstract string name { get; set; }
    public abstract void Print();
}

接口和抽象类的区别如下:

接口 抽象类
在接口中仅能定义成员,但不能有具体的实现。 抽象类除了抽象成员以外,其他成员允许有具体的实现。
在接口中不能声明字段,并且不能声明任何私有成员,成员不能包含任何修饰符。 在抽象类中能声明任意成员,并能使用任何修饰符来修饰。
接口能使用类或者结构体来继承。 抽象类仅能使用类继承。
在使用类来实现接口时,必须隐式或显式地实现接口中的所有成员,否则需要将实现类定义为抽象类,并将接口中未实现的成员以抽象的方式实现。 在使用类来继承抽象 类时允许实现全部或部分成员,但仅实现其中的部分成员,其实现类必须也定义为抽象类。
一个接口允许继承多个接口。 一个类只能有一个父类。

在实现接口的成员时有两种方式,一种是隐式实现接口成员,一种是显式实现接口成员。

1)下面创建并实现一个接口示例,主要使用隐式实现接口成员的方式:

interface ICompute {
    public int Id { get; set; }     //隐式的实现接口中的属性
    public string Name { get; set; }    //隐式实现接口中的属性
    public double English { get; set; }
    public double Programming { get; set; }
    public double Database { get; set; }
    void Total();
    void Avg();
}

class ComputerMajor : ICompute {
    public int Id { get; set; }     //隐式的实现接口中的属性
    public string Name { get; set; }    //隐式实现接口中的属性
    public double English { get; set; }
    public double Programming { get; set; }
    public double Database { get; set; }

    public void Total() {
        double sum = English + Programming + Database;
        Console.WriteLine("总分为:" + sum);
    }

    public void Avg() {
        double avg = (English + Programming + Database) / 3;
        Console.WriteLine("平均分:" + avg);
    }
}

在 Main 方法中调用该实现类的成员,代码如下:

class Program {
    static void Main(string[] args) {
        ComputerMajor computerMajor = new ComputerMajor();
        computerMajor.Id = 1;
        computerMajor.Name = "李明";
        computerMajor.English = 80;
        computerMajor.Programming = 90;
        computerMajor.Database = 85;
        Console.WriteLine("学号:" + computerMajor.Id);
        Console.WriteLine("姓名:" + computerMajor.Name);
        Console.WriteLine("成绩信息如下:");
        computerMajor.Total();
        computerMajor.Avg();
    }
}

2)显式实现接口是指在实现接口时所实现的成员名称前含有接口名称作为前缀。不常用,不再演示。

interface ICompute {
    public int Id { get; set; }     //隐式的实现接口中的属性
    public string Name { get; set; }    //隐式实现接口中的属性
    public double English { get; set; }
    public double Programming { get; set; }
    public double Database { get; set; }
    void Total();
    void Avg();
}

class ComputerMajor : ICompute {
    public double English { get; set; }
    public double Programming { get; set; }
    public double Database { get; set; }
    int ICompute.Id { get; set; }           //显示实现接口中的属性
    string ICompute.Name { get; set; }      //显示实现接口中的属性
    void ICompute.Total()  {        //显示实现接口中的方法
        double sum = English + Programming + Database;
        Console.WriteLine("总分数:" + sum);
    }
    void ICompute.Avg() {
        double avg = (English + Programming + Database) / 3;
        Console.WriteLine("平均分为:" + avg);
    }
}

在Main方法中调用实现类中的成员,代码如下

class Program {
    static void Main(string[] args) {
        ComputerMajor computerMajor = new ComputerMajor();
        ICompute compute = computerMajor;       //创建接口的实例
        compute.Id = 1;
        compute.Name = "李明";
        computerMajor.English = 80;
        computerMajor.Programming = 90;
        computerMajor.Database = 85;
        Console.WriteLine("学号:" + compute.Id);
        Console.WriteLine("姓名:" + compute.Name);
        Console.WriteLine("成绩信息如下:");
        compute.Total();
        compute.Avg();
    }
}
原文地址:https://www.cnblogs.com/pengjingya/p/14401410.html