{ }codelper

学习 › C#

各语言特有 · 37

类与对象

把数据(字段)和行为(方法)放进同一个模板,再用它创建多个对象。

本单元适用于 Python · JavaScript · TypeScript · Java · C# · C++。
C 没有类,用结构体(struct)加函数完成同样的事(见结构体一课)。
Craykon 没有自定义类,而是把这类数据放进 字典(),如 学生["姓名"]。

类是设计图,对象是按设计图造出来的实物。
下面的示例用带有姓名和分数的 Student 类创建几个学生,按分数从高到低排序,并连同等级一起输出。

using System;using System.Collections.Generic; class Student{    public string Name { get; }    public int Score { get; }     public Student(string name, int score)    {        Name = name;        Score = score;    }     public string Grade()    {        if (Score >= 90) return "A";        if (Score >= 80) return "B";        return "C";    }     public string Describe() => $"{Name} {Score} {Grade()}";} class Program{    static void Main()    {        int n = int.Parse(Console.ReadLine());        var students = new List<Student>();        for (int i = 0; i < n; i++)        {            string[] parts = Console.ReadLine().Split(' ');            students.Add(new Student(parts[0], int.Parse(parts[1])));        }         students.Sort((a, b) => b.Score.CompareTo(a.Score));        foreach (var s in students) Console.WriteLine(s.Describe());    }}
stdin
3
Mina 82
Joon 95
Sora 78
stdout
Joon 95 A
Mina 82 B
Sora 78 C

要点

  • 类里写明对象拥有的数据(字段)和对象能做的事(方法)。
  • 构造函数是在创建对象时填入初始值的特殊函数。
  • 在方法里,对象自己是 this(Python 是 self)。
    参数同名时,用 this.name = name 区分。
  • 用访问修饰符(public、private)决定外部能用哪些成员。
    字段通常藏起来,通过方法或属性读取。
  • 对象列表按某个字段排序。
    把“比较什么”交给排序函数即可。

各语言的写法

Pythonclass Student: 里写构造函数 def __init__(self, name, score):。
字段用 self.name = name 创建,所有方法的第一个参数都是 self。
创建对象不用 new:Student("Mina", 82)。
排序:students.sort(key=lambda s: s.score, reverse=True)
JavaScriptclass Student { constructor(name, score) { this.name = name; } }。
用 new Student("Mina", 82) 创建对象,漏掉 new 会报 TypeError: Class constructor Student cannot be invoked without 'new'。
排序:students.sort((a, b) => b.score - a.score)
TypeScript和 JavaScript 同样的写法,再用 score: number; 先声明字段。
private、readonly 只在编译时检查,运行时就消失了。
真要隐藏,用 JavaScript 的私有字段 #score
Java构造函数和类同名,不写返回类型。
字段用 private 隐藏,通过 getScore() 这样的 getter 读取。
不写修饰符时只在同一个包内可见。
排序:students.sort(Comparator.comparingInt(Student::getScore).reversed())
C#用属性 public int Score { get; },外部可以像字段一样读 s.Score。
不写修饰符的成员是 private。
排序:students.Sort((a, b) => b.Score.CompareTo(a.Score))
C++class 默认 private,struct 默认 public,区别仅此而已,所以示例自己写了 public: 和 private:。
构造函数用初始化列表 : name_(name), score_(score) 填字段,方法后面的 const 表示“不修改对象”。
对象是值,不用 new:Student s("Mina", 82);。
排序:std::sort 加 lambda [](const Student& a, const Student& b) { return a.score() > b.score(); }

常见错误

用 == 比较对象的内容Java · C#

new Student("Mina", 82) == new Student("Mina", 82)

→ false —— == 看的是是不是同一个对象。在重新定义 equals 之前,它和 == 完全一样

内容用 equals(C# 是 Equals)比较,并自己重新定义这个方法,或者使用 record。

方法漏写了 selfPython

class Student:
    def grade():
        return "A"

Student().grade()

→ TypeError: Student.grade() takes 0 positional arguments but 1 was given。在方法里不加 self. 直接写 score 会报 NameError

第一个参数写 self,字段总是用 self.score 读取。

默认值用了列表 []Python

def __init__(self, name, tags=[]):
    self.tags = tags

→ 这个列表只在 def 执行时创建一次。给一个学生加的标签,其他学生也能看到

用 tags=None 接收,在构造函数里新建列表。

各语言的差异
执行 b = a 后再修改 b,在 Python、JavaScript、TypeScript、Java、C# 中 a 也会变(两个名字指向同一个对象)。
C++ 的对象是值,Student b = a; 和 for (Student s : students) 都会复制,想操作原对象就用 Student&(只读时用 const Student&)。

练习题