当 R 函数存在打印等操作时(类似于 C++ 中函数的副作用),使用该函数给 R 对象赋值时只会接收函数的值。而使用这种方法赋值的对象在之后的调用中并不会执行原来的函数中的那些打印等操作。为了解决这些问题,R 实现了 S3 系统。
S3 系统
S3 是 R 自带的类系统,其控制着 R 处理具有不同类的对象的应对方法。部分函数会查询对象的 S3 类再进行下一步的操作。
例如对于同一个值,在不同的数据类型下,可能会有不同的效果:
1 | > num<-1000000000 |
R 的 S3 系统有三个组成部分:属性、泛型函数和方法。
属性
如前所述,属性包含了对象的某些额外信息,并且被赋予了属性名称,附加在对象上。属性不会影响对象的实际取值,但作为对象的某种类型的元数据,可以被 R 用于控制和管理该对象。
属性不是只读值,因此可以通过赋值的形式修改。R 中包括许多操作对象的辅助函数,基于属性的辅助函数包括但不限于:
名称 | 参数 | 说明 |
---|---|---|
names | R 对象 | 获取或设置对象的名称 |
dim | R 对象 | 检索或设置对象的维度 |
class | R 对象 | 获取或设置对象的类 |
row.names | 数据框 | 获取或设置数据框行向量的名称 |
levels | R 对象 | 获取或设置对象的水平 |
attr
函数可以给对象添加任何属性,也可以用来查询对象所包含的属性。
1 | > x<-1:12 |
attr 函数的第二参数以字符串的形式给出
如果对象的类在赋予属性后发生了变化,那么该属性的表示形式可能会发生变化。
structure
函数可以用来将提供的参数名称作为属性名称赋给该对象。例如上述的示例可以用 structure
函数来压缩定义过程:
1 | > x<-1:12 |
structure 函数只是构建一个含有属性的对象,一般情况下并不会修改参数的值,例如上面的 x 并不会因函数而具有属性。不过,可以使用同名覆盖的方式来实现这一目的:
1 | > x |
泛函
除了可以在命令行等函数的外部对函数进行操作以显示特定格式下的特定属性,也可以在函数内部完成这一步骤。而最常见的就是使用 print
函数。当希望 print
函数以特定的形式展示对象时,可以对 print
函数做出一些修改。而这些修改并不会影响 print
函数原本的用法(除非有冲突),而实现这一功能的基础便是“ print
函数时泛型函数”。
泛型函数,简称泛函,是一个函数族,其中的每个函数都有相似的功能,但是每个函数都只适用于某个特定的类。类似于 C++ 中的函数重载。例如:
1 | > num<-1000000000 |
可以看出,对于同一个值相同的对象,更换所属的类会改变 print
函数的显示值。
方法
对具有相同值的两个对象分别赋予不同的类 POSIXct
和 factor
时,分别以这两个对象为参数调用 print
函数会导致它们实际上会调用不同的函数 print.POSIXct
和 print.factor
。而这两个函数被称为 print
函数的方法。
可以使用 methods
函数来查看泛函的所有方法:
1 | > methods(print) |
由泛型函数、方法和基于类的分派方式所构成的系统就是 R 的 S3 系统。而 S3 这个名称是来源于 R 语言的前身—— S 语言,3 是指第 3 个版本。即 S3 系统来源于 S 语言的第 3 个版本。
由于基本上每个函数在命令行上运行时都会调用 print
函数来显示函数的值、属性等信息,因此,最常见的方法便是赋予函数名为 classname
的类属性,并构造泛函 print.classname
。
类
可以利用 R 的 S3 系统为对象创建一个类,R 会以一致且合理的方式处理同属一类的对象。创建类的步骤如下:
给类起一个名称
为属于该类的每个对象赋予 class 属性
为属于该类的对象编写常用泛型函数的类方法
可以使用 methods
函数来查看指定类的方法:
1 | > methods(class = "factor") |
但是由于以下两个方面的问题,使得为指定类创建方法颇具困难:
- R 在将多个对象组合成一个向量时会丢弃对象的属性(包括类属性)
- R 在对某个对象取子集时也会丢弃对象的属性
调试
对于大多数的类,其方法函数的名称应该符合 function.class
的语法结构,或者是 function.default
。可以使用 methods
函数查看与某个函数或类相关的方法。
S4 和 R5
除了 S3 之外,R 还有另外两个可以用来创建类属性行为的系统,分别是 S4 系统和 R5 系统。S5 系统也叫作引用类系统