S的对象

S是一种面向对象的语言。一般说来,S的对象包含了若干个元素作为其数据,这些元 素的个数叫做此对象的长度(length),这些元素的共同的类型叫做此对象的模式(mode) 。另外,对象还可以包含一些特殊数据,称为属性(attribute),如列表的每一个成员(元 素)都可以有变量名,这些变量名组成的字符型向量为此列表的names属性。

S的面向对象能力依赖于对类属性的使用。S的对象有的是简单对象,这样的对象没有类(class )属性,可以认为其类为缺省类(default);有的是属于某一类的对象,这些对象有一个类 (class)属性,同类的对象具有相同的特征,可以为同类的对象定义针对这一类的特殊操作 (如显示、绘图),在面向对象术语中叫做方法。比如,向量是简单对象,它没有类属性; 数据框也是对象,但是数据框有一个类属性class=data.frame。

固有属性:mode和length

S对象都有两个基本的属性(attribute):类型(mode)属性和长度(length)属性 。

S对象可分为单纯的(atomic)和复合的(recursive)两种,单纯对象的所有元素都是同 一种基本类型(如数值、字符串),元素不再是对象,这样的对象的类型(mode)有logical (逻辑型)、numeric(数值型)、complex(复数型)、character(字符型)等等;复合对 象的元素可以是不同类型,每一个元素是一个对象,这样的对象最常用的是列表。例如,向 量(vector)是单纯对象,它的所有元素都必须是相同类型,数值型向量的所有元素必须为 数值型,字符型向量的所有元素必须为字符型;列表(list)是复合对象,类型(mode)为 列表(list),列表的每一个元素(变量)都可以是一个S对象,比如列表元素可以为一个数 ,一个字符串,一个向量,甚至一个列表。

S对象有一种特别的null(空值型)型,只有一个特殊的NULL值为这种类型,表示没有值 (不同于NA,NA是一种特殊值,而NULL根本没有对象值)。

为了判断对象的类型,S定义了许多个类似于is.numeric()这样的函数。比如,is.numeric(x) 用来检验对象x是否数值型,返回一个逻辑型标量结果;is.character()检验对象是否字符型 ,等等。

长度属性表示S对象元素的个数,比如length(2:4)等于3。注意向量允许长度为0,数值型 向量长度为零表示为numeric()或numeric(0),字符型向量长度为零表示为character()或character(0) 。

S可以强制进行类型转换,例如

> z <- 0:9
> digits <- as.character(z)
> d <- as.numeric(digits)

第二个赋值把数值型的z转换为字符型的digits。第三个赋值把digits又转换为了数值型 的d,这时d和z是一样的了。S还有许多这样的以as.开头的类型转换函数。

S允许对超出对象长度的下标赋值,这时对象长度自动伸长以包括此下标,未赋值的元素 取缺失值(NA),例如:

> x <- numeric()
> x[3] <- 100
> x
[1]  NA  NA 100
要缩短对象的长度又怎么办呢?只要给它赋一个子集就可以了。例如:
> x <- 1:4
> x <- x[1:2]
> x
[1] 1 2

访问对象属性

对象属性是对象包含的数据中除元素以外的特殊数据,每个属性有一个属性名,有一 个属性值。S定义了两个函数attributes和attr来访问对象的属性。attributes(object)返回 对象object的各特殊属性组成的列表,其中不包括固有属性mode和length。例如:

> x <- c(apple=2.5, orange=2.1)
> attributes(x)
$names
[1] "apple"  "orange"
可以用attr(object, name)的形式存取对象object的名为name的属性。例如:
> attr(x, "names")
[1] "apple"  "orange"
也可以把attr()函数写在赋值的左边以改变属性值或定义新的属性,例如:
> attr(x, "names") <- c("apple", "grapes")
> x
 apple grapes 
   2.5    2.1
> attr(x, "type") <- "fruit"
> x
 apple grapes 
   2.5    2.1 
attr(,"type")
[1] "fruit"
> attributes(x)
$names
[1] "apple"  "grapes"
 
$type
[1] "fruit"

这种对一个函数赋值的语法是在其它语言中极为少见的,而S中则经常使用这样的写法。 实际上,attr(x, "names")在这里不应该看成是一个函数值,而应该看成是用来保存对象x的names 属性的变量名。

对象的类

S用类(class)属性来支持面向对象的编程风格。对象的类属性区分对象的类,对于 同一类的对象可以定义一组特殊操作,这一点和其它面向对象语言类似。面向对象风格的最 重要的特点就是数据抽象与封装。所谓数据抽象与封装是指对象的用户要访问或修改对象只 能通过对象提供的服务来进行,用户不能看到对象内部的实现细节。这样用户不会直接修改 对象的数据从而保护了数据的完整性,而且用户只需要知道对象提供了哪些服务,即使对象 内部的实现改变了,只要接口不变则用户程序不必改变。这样的做法可以提高程序的安全性 和可重用性。

常见的面向对象语言一般先定义一个类,这个类定义了一些数据结构,然后有一些函数叫 做“方法”可以操作这些数据。所谓对象,是由某个类生成的实例,其数据结构由所属的类 定义,而实际存储的数据则是属于对象本身的。对象拥有其所属类的所有方法,方法在调用 时操作的是属于这个对象的数据。

S也支持面向对象编程,但是做法与常见的面向对象语言有很大差别。S对象的类由其类(class )属性指定,每一个类都可以定义本类的服务,服务以函数形式定义,调用格式为“函数名 (对象,其它自变量)”。可见S的类机制是比较松散的,它不象常见的面向对象语言那样必 须先定义类的所有数据结构与方法,而是可以随时定义函数作为类对象的服务。另外,S还定 义了一系列的所谓“通用函数(general functions)”,通用函数也是对象提供的服务,但 不同类的对象都可以使用相同的通用函数名字调用,同一个通用函数可以针对不同类的对象 起到相似的作用。用户只需要记忆很少的几个通用函数的名字,就可以对几乎所有对象调用 这些函数。比如,通用print()函数用来显示对象,它可以显示向量和矩阵,但显示方法不同 ;通用函数plot()函数用来画对象的图形,对一个向量画图plot()画散点图,纵轴为各元素 值,横轴为元素下标;对一个时间序列对象画图plot()将画一条时间序列曲线,并用年月等 标记时间轴。

S的每一个通用函数实际是一组函数,有一个共同的名字,在调用时根据自变量的类(class )的不同决定调用一组中的哪一个函数。例如,对向量x调用print(x)实际调用的是print.default(x) ,对数据框x调用print(x)则实际调用的是print.data.frame(x)。如果自变量没有类属性, 或者此通用函数没有为此类自变量设计特殊的操作,通用函数总有一个缺省方法可以调用( 如print.default)。通用函数针对某一类的对象的特殊函数的命名为“通用函数名.类名() ”。

对某一种类的对象有特殊操作的通用函数可以有很多个,比如,对data.frame类的对象定 义了特殊操作的通用函数就有:

		[,		[[<-,		any,		as.matrix,
		[<-,		model,		plot,	summary,

等等。如果对data.frame类的对象d调用plot(d),实际调用的函数是plot.data.frame(d) 。要列出所有对某类有特殊操作的通用函数,可以用

> methods(class="data.frame")
 [1] "Math.data.frame"          "Ops.data.frame"          
………………………
 [27] "summary.data.frame"       "t.data.frame"            
 [29] "transform.data.frame"     "xpdrows.data.frame"     

其中t.data.frame就是调用t(d)时实际调用的函数。

也可以列出某通用函数的对各类的特殊定义,例如:

> methods(plot)
 [1] "plot.data.frame" "plot.default"    "plot.density"   
 [4] "plot.factor"     "plot.formula"    "plot.function"  
 [7] "plot.lm"         "plot.mlm"        "plot.mts"       
[10] "plot.new"        "plot.ts"         "plot.window"    
[13] "plot.xy"        

比如,plot.factor是对因子对象调用plot()函数是实际调用的函数。

为了暂时去掉一个有类的对象的class属性,可以使用unclass(object)函数。