← Course Hub← 课程主页 ← All Units← 返回单元列表
H I G H  S C H O O L  C O M P U T E R  S C I E N C E
Solutions答案与解析

Object-Oriented Programming (Intro)面向对象编程(入门)

Full Worked Solutions · AP CSA-Feeder · US / ON / BC / AB Styles完整解析 · AP CSA 衔接 · 美 / 安 / 卑 / 阿省风格



PART I  ·  SHORT RESPONSE第一部分  ·  短答题AP CSA-style MCQ + ON/BC short answer · 25 marksAP CSA 风格选择题 + 安/卑省考短答 · 共 25 分

Section A · Short ResponseA 部分 · 短答题

Q1 EASY 🇺🇸 US §1 Class vs Object类与对象 [3 marks][3 分]
Answer:答案: M1 · A1 · A1

(B) fido is an instance of the Dog class (blueprint).(B) fidoDog 类(蓝图)的一个实例。

Reasoning: class Dog: defines the blueprint. fido = Dog("Fido", 3) creates one concrete object (instance) from that blueprint. Dog is not an instance of anything here; it is the class definition itself.理由:class Dog: 定义蓝图。fido = Dog("Fido", 3) 从该蓝图创建一个具体对象(实例)。Dog 不是任何东西的实例,它本身就是类定义。

The class/instance distinction is the single most-tested OOP vocabulary point. Practice the template sentence: "X is an instance of the Y class." Never say "X is a Y class" when X is an object.类/实例的区分是 OOP 词汇中最常考的点。练习模板句:"X 是 Y 类的一个实例。"当 X 是对象时,永远不要说"X 是一个 Y 类"。
Q2 EASY 🇺🇸 US §2 Class Syntax / self类语法 / self [3 marks][3 分]
Answer:答案: M1 · A1 · A1

(C) The specific object on which the method was called.(C) 调用该方法的具体对象。

Reasoning: when you write s1.is_passing(), Python automatically passes s1 as the self argument. Inside the method, self refers to s1. For s2.is_passing(), self would be s2. You never pass self explicitly when calling the method.理由:写 s1.is_passing() 时,Python 自动将 s1 作为 self 参数传入。方法内部,self 指代 s1。调用 s2.is_passing() 时,self 则是 s2。调用方法时永远不要手动传入 self

Option (D) is the classic confusion: the caller does NOT pass self explicitly. Python inserts it. The first argument the caller supplies goes to the second parameter in the method signature (after self).选项 (D) 是经典混淆:调用者不显式传入 self,Python 自动插入。调用者提供的第一个实参对应方法签名中的第二个参数(在 self 之后)。
Q3 EASY 🇨🇦 ON §3 Attributes (Instance Variables)属性(实例变量) [4 marks][4 分]
(a) A1

a.name = "Whiskers". The line a.age = 5 only modified the age attribute; name was not touched.a.name = "Whiskers"a.age = 5 只修改了 age 属性;name 未被改动。

(b) A1

b.age = 2. Object b was created with age=2 and was never modified.b.age = 2。对象 b 创建时 age=2,且从未被修改。

(c) M1 · A1

Each object has its own independent copy of every instance attribute, so changing a.age only modifies the attribute stored inside object a, leaving b's copy untouched.每个对象都有每个实例属性的独立副本,因此修改 a.age 只改变对象 a 内部存储的属性,不影响 b 的副本。

This per-instance independence is the core advantage of OOP over procedural global variables. Two global variables cat1_age and cat2_age are always at risk of being overwritten by any code in the program; instance attributes live inside objects and are protected by scope.实例属性的独立性是 OOP 相较过程式全局变量的核心优势。两个全局变量 cat1_agecat2_age 随时可能被程序中任何代码覆盖;实例属性存于对象内部,受作用域保护。
Q4 MEDIUM 🇨🇦 AB §4 Methods: mutator and accessor方法:修改器与访问器 [7 marks][7 分]
(a) M1 · A2

Step-by-step trace of c.count:逐步追踪 c.count

  • c = Counter(10): count = 10 (via default start)c = Counter(10)count = 10(通过 start 参数)
  • c.increment(): count = 10 + 1 = 11 (default amount=1)c.increment()count = 10 + 1 = 11(默认 amount=1)
  • c.increment(3): count = 11 + 3 = 14c.increment(3)count = 11 + 3 = 14
  • c.reset(): count = 0c.reset()count = 0
(b) A2

First print: 14 (after increment then increment(3)).
Second print: 0 (after reset).
第一次打印:14(两次 increment 后)。
第二次打印:0(reset 后)。

(c) M1 · A1

increment: mutator - it changes (modifies) self.count.
reset: mutator - it changes self.count to 0.
get_count: accessor - it only reads and returns self.count without modifying it.
increment修改器 - 修改 self.count
reset修改器 - 将 self.count 改为 0。
get_count访问器 - 只读取并返回 self.count,不修改它。

A method with a default parameter (like increment(self, amount=1)) can be called with or without that argument. c.increment() uses the default 1; c.increment(3) overrides it with 3. This is a clean pattern for "increment by 1 usually, but by N when needed."带默认参数的方法(如 increment(self, amount=1))可以有参或无参调用。c.increment() 使用默认值 1;c.increment(3) 用 3 覆盖。这是"通常加 1,需要时加 N"的简洁模式。
Q5 MEDIUM 🇨🇦 BC §5 Constructor (__init__)构造函数(__init__) [8 marks][8 分]
(a) A2

For r1 = Rectangle(6, 4): r1.width = 6, r1.height = 4.对于 r1 = Rectangle(6, 4)r1.width = 6r1.height = 4

(b) A2

r1.area() returns 24 (6 x 4 = 24).
r2.area() returns 25 (5 x 5 = 25).
r1.area() 返回 24(6 x 4 = 24)。
r2.area() 返回 25(5 x 5 = 25)。

(c) M1 · A1

r1.is_square() returns False: because r1.width = 6 and r1.height = 4, and 6 != 4.
r2.is_square() returns True: because r2.width = 5 and r2.height = 5, and 5 == 5.
r1.is_square() 返回 False:因为 r1.width = 6r1.height = 4,6 != 4。
r2.is_square() 返回 True:因为 r2.width = 5r2.height = 5,5 == 5。

(d) M1 · A1

Python automatically calls Rectangle.__init__(r1, 6, 4), which sets r1.width = 6 and r1.height = 4, fully initialising the new object before the assignment to r1 completes.Python 自动调用 Rectangle.__init__(r1, 6, 4),将 r1.width 设为 6,r1.height 设为 4,在赋值给 r1 完成之前完全初始化新对象。

__init__ is called the "constructor" because it constructs (builds) the object's initial state. You never call it directly: writing r1.__init__(6, 4) would re-initialise an already-created object, which is almost always a mistake.__init__ 被称为"构造函数",因为它构建对象的初始状态。永远不要直接调用它:写 r1.__init__(6, 4) 会重新初始化一个已创建的对象,这几乎总是错误的。
PART II  ·  EXTENDED RESPONSE第二部分  ·  简答题AP CSA-feeder FRQ + Honors · 31 marksAP CSA 衔接简答题 + 荣誉级 · 共 31 分

Section B · Extended ResponseB 部分 · 简答题

Q6 MEDIUM 🇺🇸 US §2 + §3 Define class + trace attributes定义类 + 追踪属性 [8 marks][8 分]
(a) A2

After all four client lines: s1.grade = 85 (updated from 78 by s1.update_grade(85)), s2.grade = 55 (updated from 42 by s2.update_grade(55)).四行客户代码执行后:s1.grade = 85(由 s1.update_grade(85) 从 78 更新),s2.grade = 55(由 s2.update_grade(55) 从 42 更新)。

(b) M1 · A1

s1.is_passing() returns True: 85 ≥ 50.
s2.is_passing() returns True: 55 ≥ 50.
s1.is_passing() 返回 True:85 ≥ 50。
s2.is_passing() 返回 True:55 ≥ 50。

(c) M2 · A1
    def letter_grade(self):
        if self.grade >= 80:
            return "A"
        elif self.grade >= 70:
            return "B"
        elif self.grade >= 60:
            return "C"
        elif self.grade >= 50:
            return "D"
        else:
            return "F"
(d) A1

update_grade is a mutator because it changes (mutates) the self.grade attribute; is_passing is an accessor because it only reads self.grade and returns a value without modifying any attribute.update_grade 是修改器,因为它改变(修改)了 self.grade 属性;is_passing 是访问器,因为它只读取 self.grade 并返回一个值,不修改任何属性。

The letter_grade method uses cascaded elif (not nested if) because once a condition is met the function returns immediately. Cascaded elif is cleaner and more efficient than checking all ranges independently.letter_grade 方法使用级联 elif(而非嵌套 if),因为一旦条件满足函数立即返回。级联 elif 比独立检查所有范围更简洁高效。
Q7 MEDIUM 🇨🇦 ON §4 + §5 Methods + constructor design方法 + 构造函数设计 [8 marks][8 分]
(a) M2 · A2

Trace of acc (started with owner="Carol", balance=500):追踪 acc(初始 owner="Carol"balance=500):

  • acc.deposit(200): balance = 500 + 200 = 700. Nothing printed.balance = 500 + 200 = 700。无打印。
  • acc.withdraw(100): 100 ≤ 700, so balance = 700 - 100 = 600. Nothing printed.100 ≤ 700,所以 balance = 700 - 100 = 600。无打印。
  • acc.withdraw(700): 700 > 600, so prints Insufficient funds. Balance stays 600.700 > 600,故打印 Insufficient funds。余额保持 600。

Text printed during calls: Insufficient funds (once, on the third withdraw).调用期间打印内容:Insufficient funds(一次,第三次取款时)。

(b) A1

print(acc.get_balance()) outputs 600.print(acc.get_balance()) 输出 600

(c) A1

david = BankAccount("David")david = BankAccount("David")

(d) M1 · A1

The withdraw method checks amount ≤ self.balance before subtracting, so external code cannot directly set a negative balance; the guard inside the method enforces the constraint that the balance can never go below zero, which is encapsulation in action.withdraw 方法在扣款前检查 amount ≤ self.balance,因此外部代码无法直接设置负余额;方法内的保护逻辑强制执行余额不低于零的约束,这正是封装的体现。

Default parameters allow flexible object creation. BankAccount("David") and BankAccount("David", 0) produce identical objects because balance=0 is the default. This reduces code duplication for the common case.默认参数允许灵活创建对象。BankAccount("David")BankAccount("David", 0) 产生相同的对象,因为 balance=0 是默认值。这减少了常见情况下的代码重复。
Q8 HARD 🇨🇦 BC 🇺🇸 US §5 + §6 Constructor + list of objects构造函数 + 对象列表 [8 marks][8 分]
(a) M1 · A2

total = 88 + 45 + 72 + 50 = 255.
avg = 255 / 4 = 63.75.
total = 88 + 45 + 72 + 50 = 255
avg = 255 / 4 = 63.75

(b) M1 · A1

failing = ["Bob"].
Bob's grade (45) is the only one below 50; Alice (88), Carol (72), David (50) all pass.
failing = ["Bob"]
Bob 的成绩(45)是唯一低于 50 的;Alice(88)、Carol(72)、David(50)均通过。

(c) A2

top = max(roster, key=lambda s: s.grade)

(This returns the Student object for Alice, whose grade 88 is the highest.)(这返回 Alice 的 Student 对象,她的成绩 88 最高。)

(d) A1

Using objects bundles each student's name and score together so methods like is_passing() can be called directly on each object; with two parallel lists, you must always keep the same index in sync between lists, which is error-prone.使用对象将每位学生的姓名和成绩捆绑在一起,可以直接对每个对象调用 is_passing() 等方法;使用两个平行列表时必须始终保持两个列表的索引同步,容易出错。

The generator expression sum(s.grade for s in roster) is a concise way to iterate a list of objects and extract an attribute. Think of it as a for loop that returns a value: "for each student in the roster, give me their grade, then sum all those grades."生成器表达式 sum(s.grade for s in roster) 是遍历对象列表并提取属性的简洁方式。可以将其理解为一个返回值的 for 循环:"对名单中每个学生,给我他们的成绩,然后求和。"
Q9 HARD Honors荣誉级 🇺🇸 US §7 Encapsulation + Inheritance封装 + 继承 [7 marks][7 分]
(a) M1 · A2

Trace of savings._balance:savings._balance 追踪:

  • SavingsAccount("Emma", 1000, 0.05): _balance = 1000, rate = 0.05.SavingsAccount("Emma", 1000, 0.05)_balance = 1000rate = 0.05
  • savings.deposit(200): _balance = 1000 + 200 = 1200 (inherited method from BankAccount).savings.deposit(200)_balance = 1000 + 200 = 1200(继承自 BankAccount 的方法)。
  • savings.add_interest(): _balance = 1200 * (1 + 0.05) = 1200 * 1.05 = 1260.0.savings.add_interest()_balance = 1200 * (1 + 0.05) = 1200 * 1.05 = 1260.0
(b) M1 · A1

super().__init__(owner, balance) calls the parent class (BankAccount) constructor from inside the child (SavingsAccount) constructor, which sets up self.owner and self._balance before SavingsAccount.__init__ adds self.rate.super().__init__(owner, balance) 在子类(SavingsAccount)构造函数内调用父类(BankAccount)构造函数,从而在 SavingsAccount.__init__ 添加 self.rate 之前,先设置 self.ownerself._balance

(c) M1 · A1

The leading underscore in _balance is Python's convention for "treat as private": it signals to other programmers that _balance should not be accessed directly from outside the class, but instead through the deposit(), withdraw(), and get_balance() methods; this is encapsulation because the class controls how its internal data is read and modified._balance 的前置下划线是 Python"视为私有"的惯例:它向其他程序员表明 _balance 不应从类外部直接访问,而应通过 deposit()withdraw()get_balance() 方法访问;这就是封装,因为类控制着内部数据的读取和修改方式。

Notice that savings.deposit(200) works even though deposit is not defined in SavingsAccount. Python looks up the method in the parent class (BankAccount) automatically. This is "inherited behaviour": subclasses get parent methods for free and only need to define what is new or different.注意 savings.deposit(200) 可以工作,尽管 deposit 未在 SavingsAccount 中定义。Python 会自动在父类(BankAccount)中查找方法。这就是"继承行为":子类免费获得父类方法,只需定义新增或不同的内容。
PART III  ·  MODELING / APPLIED第三部分  ·  建模与应用Universal / multi-region applied · 25 marks通用/多地区应用题 · 共 25 分

Section C · Modeling and ApplicationsC 部分 · 建模与应用

Q10 MEDIUM 🇺🇸 US 🇨🇦 ON §6 Object list + iteration对象列表 + 迭代 [8 marks][8 分]
(a) M1 · A2
for s in students:
    if not s.passed():
        print(s.name)

Names printed: Ben (score 55, 55 < 60), Eve (score 48, 48 < 60).
Amy (75), Cleo (90), Dan (60) all satisfy score ≥ 60 and are not printed.
打印的姓名:Ben(分数 55,55 < 60),Eve(分数 48,48 < 60)。
Amy(75)、Cleo(90)、Dan(60)均满足 score ≥ 60,不打印。

(b) M1 · A2
total = sum(s.score for s in students)
avg   = total / len(students)
print(avg)

Arithmetic: 75 + 55 + 90 + 60 + 48 = 328; 328 / 5 = 65.6. Output: 65.6.计算过程:75 + 55 + 90 + 60 + 48 = 328328 / 5 = 65.6。输出:65.6

(c) M1 · A1

With a list of objects, each student's name and score are bundled together so you can add a new student as a single Student(...) call without touching two separate lists; with parallel lists, you must append to both lists in the correct order, and any mismatch corrupts all future lookups.使用对象列表时,每位学生的姓名和分数捆绑在一起,只需一次 Student(...) 调用即可添加新学生,无需同时操作两个独立列表;使用平行列表时,必须以正确顺序向两个列表各自追加,任何错位都会损坏所有后续查询。

Dan's score of 60 passes (60 ≥ 60 is True) because passed() uses >=, not >. Boundary conditions like this are classic exam trick questions: always check whether the boundary is inclusive (≥) or exclusive (>).Dan 的分数 60 通过了(60 ≥ 60 为 True),因为 passed() 使用 >= 而非 >。这类边界条件是经典考试陷阱:始终检查边界是包含(≥)还是不包含(>)。
Q11 MEDIUM 🇨🇦 ON 🇨🇦 BC §3 + §4 + §5 Design a class from scratch从零设计一个类 [9 marks][9 分]
(a) M2 · A3
class Product:
    def __init__(self, name, price, quantity):
        self.name     = name
        self.price    = price
        self.quantity = quantity

    def total_value(self):
        return self.price * self.quantity

    def restock(self, amount):
        self.quantity += amount

    def sell(self, amount):
        if amount <= self.quantity:
            self.quantity -= amount
        else:
            print("Not enough stock")
(b) M1 · A1

Create and sell:创建并销售:

widget = Product("Widget", 2.50, 10)
widget.sell(15)

sell(15): checks 15 ≤ 10 which is False, so prints Not enough stock. quantity remains 10.sell(15):检查 15 ≤ 10 为 False,故打印 Not enough stockquantity 保持 10

(c) A2

Mutators: restock and sell (both modify self.quantity).
Accessor: total_value (reads self.price and self.quantity and returns a computed value without changing any attribute).
修改器:restocksell(均修改 self.quantity)。
访问器:total_value(读取 self.priceself.quantity 并返回计算值,不修改任何属性)。

The guard in sell (if amount ≤ self.quantity) mirrors the guard in BankAccount.withdraw from Q7. This pattern - "check validity before modifying" - is a standard encapsulation technique that protects the object from reaching an invalid state (negative quantity).sell 中的保护(if amount ≤ self.quantity)与 Q7 中 BankAccount.withdraw 的保护相同。这种模式 - "修改前检查有效性" - 是标准的封装技术,保护对象不进入无效状态(负数库存)。
Q12 HARD 🇺🇸 US 🇨🇦 ON 🇨🇦 BC §1 + §7 OOP vs procedural + inheritance designOOP vs 过程式 + 继承设计 [8 marks][8 分]
(a) A2

Two problems with the procedural approach at 100 vehicles:
1. You need 300 separate variables (car1_make through car100_km), making the code extremely long and hard to read.
2. There is no way to pass all data about one vehicle as a single unit to a function; you would need to pass three separate arguments for each vehicle, and it is easy to mix up which variables belong to which car.
过程式方式在 100 辆车时的两个问题:
1. 需要 300 个独立变量(car1_makecar100_km),代码极长且难以阅读。
2. 无法将一辆车的所有数据作为单一单元传给函数;每辆车需要传入三个独立参数,容易混淆哪些变量属于哪辆车。

(b) M1 · A2
class Vehicle:
    def __init__(self, make, year, km):
        self.make = make
        self.year = year
        self.km   = km

    def add_km(self, distance):
        self.km += distance
(c) M1 · A2
class ElectricVehicle(Vehicle):
    def __init__(self, make, year, km, battery_kwh):
        super().__init__(make, year, km)
        self.battery_kwh = battery_kwh

    def charge(self, kwh):
        self.battery_kwh += kwh
The is-a rule: an ElectricVehicle IS-A Vehicle, so inheritance is the right design. It would be wrong to design it as a has-a (composition), because that would mean a vehicle "contains" another vehicle, which makes no sense. Always ask "is-a or has-a?" before choosing between inheritance and composition.is-a 规则:ElectricVehicle 是(IS-A)Vehicle,因此继承是正确设计。将其设计为 has-a(组合)是错误的,因为那意味着一辆车"包含"另一辆车,毫无意义。选择继承还是组合前,始终问自己"是 is-a 还是 has-a?"