{ }codelper

学习 › Python

各语言特有 · 37

类与对象

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

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

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

class Student:    def __init__(self, name, score):        self.name = name        self.score = score     def grade(self):        if self.score >= 90:            return "A"        if self.score >= 80:            return "B"        return "C"     def describe(self):        return f"{self.name} {self.score} {self.grade()}"  n = int(input())students = []for _ in range(n):    name, score = input().split()    students.append(Student(name, int(score))) students.sort(key=lambda s: s.score, reverse=True)for s in students:    print(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(); }

常见错误

方法漏写了 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 接收,在构造函数里新建列表。

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

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

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

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

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

练习题