构造函数
本课属于 Coddy R 学习旅程的“面向对象编程”单元。第 3 课,共 57 课。
因为 R 从不检查 S3 对象的字段,所以同一类的每个对象都应该由一个函数构建,这个函数叫作构造函数。按照惯例,底层构造函数的名称由 new_ 加类名组成。它将每个字段作为参数,使用 stopifnot() 检查类型,并返回对象:
new_money <- function(amount = double(), currency = character()) {
stopifnot(is.double(amount), is.character(currency))
structure(list(amount = amount, currency = currency), class = "money")
}
m <- new_money(9.5, "EUR")
cat(m$amount, m$currency)输出:
9.5 EURstopifnot() 会在某个条件不为 TRUE 时停止程序,并在消息中指出未通过的检查。类型错误会在创建对象时立即被捕获,而不是等到其他函数使用它时才发现:
new_money <- function(amount = double(), currency = character()) {
stopifnot(is.double(amount), is.character(currency))
structure(list(amount = amount, currency = currency), class = "money")
}
m <- new_money("9.5", "EUR")输出:
Error in new_money("9.5", "EUR") : is.double(amount) is not TRUE
Calls: new_money -> stopifnot
Execution halted构造函数是类中唯一调用 structure() 的地方,因此每个对象都有相同的字段,且顺序相同。需要创建许多对象的代码会在循环中调用它,或使用 lapply() 调用它:
new_point <- function(x = 0, y = 0) {
stopifnot(is.numeric(x), is.numeric(y))
structure(list(x = x, y = y), class = "point")
}
pts <- lapply(1:3, function(i) new_point(i, i * i))
for (p in pts) cat(p$x, p$y, "\n")输出:
1 1
2 4
3 9 S3 对象不一定是列表。构造函数可以为普通向量添加标签,并将额外信息存储为属性,这正是 R 自带的因子和日期的工作方式。attr(x, "unit") 用于读取属性:
new_distance <- function(value = double(), unit = "m") {
stopifnot(is.double(value), is.character(unit))
structure(value, unit = unit, class = "distance")
}
d <- new_distance(c(1.5, 2), "km")
print(attr(d, "unit"))
print(unclass(d)[2] * 1000)输出:
[1] "km"
[1] 2000挑战
简单完成构造函数 new_book(title, author, pages),并将代码放在 Book.R 中。它使用 stopifnot() 检查 title 和 author 是否为字符类型,以及 pages 是否为数值类型,并返回一个包含这三个字段且类为 book 的列表。然后完成 book_label(b),让它返回 Dune by Frank Herbert, 412 pages。
提供的代码会读取格式为 title;author;pages 的行。如果 pages 由数字组成,就将其作为数字传入,否则传入原始文本。对于每个对象,代码会输出 book: 和标签;如果构造函数停止运行,则输出 rejected: 和标题。
你的代码应写在 Book.R 中。main.R 包含提供的输入/输出代码,不能编辑。
亲手试试
source("Book.R")
# 提供的输入和输出代码:保持原样。
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
p <- strsplit(line, ";")[[1]]
pages <- if (grepl("^[0-9]+$", p[3])) as.numeric(p[3]) else p[3]
b <- tryCatch(new_book(p[1], p[2], pages), error = function(e) NULL)
if (is.null(b)) {
cat("rejected: ", p[1], "\n", sep = "")
} else {
cat(class(b), ": ", book_label(b), "\n", sep = "")
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: R 在线编译器