R 面向对象编程(四)—— R6

R6

4.1 介绍

R6R 的封装式面向对象编程的实现,比内置的 RC 类更简单,更快,更轻量级。

与内置的 R3R4RC 不同,R6 是一个单独的 R 包,因此不需要依赖 methods 包。

R6 类支持:

  • 属性和方法的公有化和私有化
  • 主动绑定
  • 跨包之间的继承

为什么这个包叫 R6 呢?

哈哈,当然是为了保持队形了啊

S3、S4、S5、S6,虽然 RC 的官方名称并不是 S5,但不妨碍大家这么称呼。

学过其他语言的面向对象编程系统的应该知道,我们前面几节讲的 R 中几种系统设计的并不够好,所以,需要 R6 这样的包。

4.2 创建 R6 对象

R6 是第三方包,所以记得先安装一下

install.packages("R6")
library(R6)

R6 是通过 R6Class() 函数创建类

R6Class(classname = NULL, public = list(), private = NULL,
  active = NULL, inherit = NULL, lock_objects = TRUE, class = TRUE,
  portable = TRUE, lock_class = FALSE, cloneable = TRUE,
  parent_env = parent.frame(), lock)

参数列表

在这里插入图片描述

创建一个简单的 Person

Person <- R6Class(
  "Person",
  public = list(
    name = NA,
    initialize = function(name) {
      self$name <- name
    },
    say = function() {
      cat("my name is ", self$name)
    }
  )
)

创建实例,同样使用 $new 方法来实例化

> tom <- Person$new(name = "tom")
> tom
<Person>
  Public:
    clone: function (deep = FALSE) 
    initialize: function (name) 
    name: tom
    say: function () 

查看类与实例的类型

> class(Person)
[1] "R6ClassGenerator"
> class(tom)
[1] "Person" "R6"    
> otype(tom)
[1] "S3"
> otype(Person)
[1] "S3"

我们可以看到,其实 R6 系统是基于 S3 构建的,这也是它不同于 RC 的原因

4.3 公有成员与私有成员

R6 系统的类定义中,可以设置公有成员和私有成员。这一特征与 JavaC++ 的类很像,使用私有成员来隐藏一些数据属性和方法。

R6 中公有成员的访问使用的是 self 对象来引用,而私有需要用 private 对象来引用。

在前面的例子中,我们使用的是 self$name 来获取公有属性 name,现在让我们来添加私有成员

Person <- R6Class(
  "Person",
  public = list(
    name = NA,
    initialize = function(name, money) {
      self$name <- name
      private$money <- money
    },
    say = function() {
      cat("my name is ", self$name)
    },
    incSalary = function(percent) {
      private$setMoney(private$money * (1 + percent))
      invisible(self)
    }
  ),
  private = list(
    money = NA,
    setMoney = function(m) {
      cat(paste0("change ", self$name, "'s salary!\n"))
      private$money <- m
    }
  )
)

我们添加了私有属性 money 和私有函数 setMoney

我们先创建一个实例化对象

> tom <- Person$new(name = "tom", 1000)
> tom
<Person>
  Public:
    clone: function (deep = FALSE) 
    incSalary: function (percent) 
    initialize: function (name, money) 
    name: tom
    say: function () 
  Private:
    money: 1000
    setMoney: function (m) 

然后调用对应的方法

> tom$name
[1] "tom"
> tom$money
NULL
> tom$incSalary(0.1)
change tom's salary!
> tom$setMoney
NULL
> tom$setMoney()
错误: 不适用于非函数

我们可以使用 $ 符号正常访问公有成员,但是无法访问私有成员

注意:我们在 incSalary 函数中添加了一行 invisible(self),这样我们就可以对这个方法进行链式调用了,例如

> tom$incSalary(0.1)$incSalary(0.2)$incSalary(0.3)
change tom's salary!
change tom's salary!
change tom's salary!
> tom
<Person>
  Public:
    clone: function (deep = FALSE) 
    incSalary: function (percent) 
    initialize: function (name, money) 
    name: tom
    say: function () 
    test: function () 
  Private:
    money: 1887.6
    setMoney: function (m) 

注意:我们在访问成员时都是使用了 selfprivate 对象,而不管是在 public 参数里面还是 private 参数里面

我们可以测试一下 selfprivate 到底是什么,我们在上面的例子中,添加一个 test 公有函数

Person <- R6Class(
  "Person",
  public = list(
    name = NA,
    initialize = function(name, money) {
      self$name <- name
      private$money <- money
    },
    say = function() {
      cat("my name is ", self$name)
    },
    incSalary = function(percent) {
      private$setMoney(private$money * (1 + percent))
    },
    test = function() {
      print(self)
      print(strrep("=", 20))
      print(private)
      print(strrep("=", 20))
      print(ls(envir = private))
    }
  ),
  private = list(
    money = NA,
    setMoney = function(m) {
      cat(paste0("change ", self$name, "'s salary!"))
      private$money <- m
    }
  )
)

测试一下

> tom <- Person$new(name = "tom", 1000)
> tom$test()
<Person>
  Public:
    clone: function (deep = FALSE) 
    incSalary: function (percent) 
    initialize: function (name, money) 
    name: tom
    say: function () 
    test: function () 
  Private:
    money: 1000
    setMoney: function (m) 
[1] "===================="
<environment: 0x7fe1d2135cf0>
[1] "===================="
[1] "money"    "setMoney"

从上面的输出结果可以看出,self 对象更像是实例化的对象本身,而 private 则是一个环境空间。这个环境空间就像是变量的作用域,因此,private 只在类中被调用,而对于类外部是不可见的。

4.4 主动绑定

主动绑定可以让对函数调用看起来像是在访问属性,主动绑定总是公开成员,外不可见的。

这与 Python 中的 @property 装饰器是一样的,有些时候,我们并不想直接把数据属性暴露在外面,被随意修改。

例如,我们有一个 Student 类,包含一个 score 属性,但是不想将其暴露在外面被随意修改,所以我们将其设置为私有属性,同时定义 get/set 方法,并在 set 方法中控制有效范围

Student <- R6Class(
  "Student",
  public = list(
    name = NA,
    initialize = function(name) {
      self$name <- name
    },
    getScore = function() {
      return(private$score)
    },
    setScore = function(score) {
      if (score < 0 || score > 100) 
        stop("Score incorrect!")
      private$score <- score
    }
  ),
  private = list(
    score = NA
  )
)

使用

> sam <- Student$new("sam")
> sam$setScore(99)
> sam$getScore()
[1] 99
> sam$setScore(101)
Error in sam$setScore(101) : Score incorrect!

这样是可以达到我们的目的,但还是不能像属性那样用起来方便

所以 R6 为我们提供了 active 参数,重新改写上面的例子

Student <- R6Class(
  "Student",
  public = list(
    name = NA,
    initialize = function(name) {
      self$name <- name
    }
  ),
  private = list(
    .score = NA
  ),
  active = list(
    score = function(s) {
      if (missing(s))
        return(private$.score)
      if (s < 0 || s > 100)
        stop("Score incorrect!")
      private$.score <- s
    }
  )
)

注意public, privateactive 参数内的属性名必须唯一,所以我们将私有属性改为了 .score

> sam <- Student$new("sam")
> sam
<Student>
  Public:
    clone: function (deep = FALSE) 
    initialize: function (name) 
    name: sam
    score: active binding
  Private:
    .score: NA
> sam$score
[1] NA
> sam$score <- 100
> sam$score
[1] 100

4.5 继承

R6 通过 inherit 参数指定父类,例如,我们定义一个 worker 类,它继承自上面的 Person

Worker <- R6Class(
  "Worker",
  inherit = Person,
  public = list(
    company = "Gene",
    info = function() {
      print("NGS analysis!")
    }
  )
)

创建对象并使用父类的方法

> siri <- Worker$new("Siri", 100)
> siri$incSalary(0.1)
change Siri's salary!
> siri
<Worker>
  Inherits from: <Person>
  Public:
    clone: function (deep = FALSE) 
    company: Gene
    incSalary: function (percent) 
    info: function () 
    initialize: function (name, money) 
    name: Siri
    say: function () 
    test: function () 
  Private:
    money: 110
    setMoney: function (m) 

我们可以使用 super 对象来调用父类的方法,让我们来重写 incSalary 方法

Worker <- R6Class(
  "Worker",
  inherit = Person,
  public = list(
    company = "Gene",
    info = function() {
      print("NGS analysis!")
    },
    incSalary = function(percent) {
      super$incSalary(percent + 0.1)
    }
  )
)

运行与上面相同的代码

> siri <- Worker$new("Siri", 100)
> siri$incSalary(0.1)
change Siri's salary!
> siri
<Worker>
  Inherits from: <Person>
  Public:
    clone: function (deep = FALSE) 
    company: Gene
    incSalary: function (percent) 
    info: function () 
    initialize: function (name, money) 
    name: Siri
    say: function () 
    test: function () 
  Private:
    money: 120
    setMoney: function (m) 

可以看到,工资的增长增加了 0.1

4.6 引用对象字段

如果您的 R6 类的属性中包含其他类的实例化对象时,该对象将在 R6 对象的所有实例中共享。例如

ShareClass <- R6Class(
  "ShareClass",
  public = list(
    num = NULL
  )
)

Common <- R6Class(
  "Common",
   public = list(
     share = ShareClass$new()
  )
)
> c1 <- Common$new()
> c1$share$num <- 1

> c2 <- Common$new()
> c2$share$num <- 2

> c1$share$num
[1] 2

注意:不能把实例化对象放在 initialize 方法中

UnCommon <- R6Class(
  "Common",
  public = list(
    share = NULL,
    initialize = function() {
      share <<- ShareClass$new()
    }
  )
)
n1 <- UnCommon$new()
n1$share$num <- 1

n2 <- UnCommon$new()
n2$share$num <- 2

n1$share$num

可以看到,share 属性并没有改变

4.7 可移植和不可移植类

portable 参数可以设置 R6 类是否为可移植类型还是不可移植类型,主要区别在于:

  • 可移植类支持跨包继承,但是不可移植类型的兼容性不好
  • 可移植类使用 selfprivate 来访问成员。不可移植类直接使用属性名称来访问,如 share,并使用 <<- 操作符对这些成员进行赋值

4.8 为现有类添加成员

有时候,我们需要对已经创建的类添加新的成员,可以使用 $set() 方法来完成。

例如,我们为 Student 类添加一个属性

> Student$set("public", "age", 21)
> sam <- Student$new("sam")
> sam
<Student>
  Public:
    age: 21
    clone: function (deep = FALSE) 
    initialize: function (name) 
    name: sam
    score: active binding
  Private:
    .score: NA

当然也可以使用这种方式修改属性值

> Student$set("public", "age", 18, overwrite = TRUE)
> sam <- Student$new("sam")
> sam
<Student>
  Public:
    age: 18
    clone: function (deep = FALSE) 
    initialize: function (name) 
    name: sam
    score: active binding
  Private:
    .score: NA

注意:我们设置了 overwrite=TRUE

添加一个方法

> Student$set("public", "getName", function() self$name)
> sam <- Student$new("sam")
> sam$getName()
[1] "sam"

4.9 打印对象

R6 对象有一个默认的 print 方法,列出对象的所有成员。我们可以为类自定义一个 print 方法,那么它将覆盖默认的方法

Student <- R6Class(
  "Student",
  public = list(
    name = NA,
    initialize = function(name) {
      self$name <- name
    },
    print = function(...) {
      cat("class", class(self), "\n")
      cat(ls(self), sep = ',')
    }
  ),
  private = list(
    .score = NA
  ),
  active = list(
    score = function(s) {
      if (missing(s))
        return(private$.score)
      if (s < 0 || s > 100)
        stop("Score incorrect!")
      private$.score <- s
    }
  )
)
> sam <- Student$new("sam")
> print(sam)
class Student R6 
clone,initialize,name,print,score
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要写出使用STM32F103R6控制数码管从0到9显示的编程代码,需要做如下步骤: 1. 首先,需要了解自己使用的数码管的显示方式以及编号规则。常见的数码管一般是7段数码管,每个数字的显示都需要点亮一部分数码管的LED灯,不同的数码管可能会将LED灯编号方式不同。因此,我们需要先确认好自己使用的数码管的规则。 2. 确认好数码管的规则后,我们需要对STM32F103R6进行引脚的配置。可以通过手册查询到不同引脚的功能以及编号,需要对不同引脚进行配置,将其连接到数码管对应的LED灯上。 3. 接下来,我们可以开始写代码了。代码需要定义数码管对应的引脚以及不同数字所对应LED灯的编号,然后通过控制引脚的高低电平以及对应LED灯的亮灭来显示不同的数字。 下面是一个简单实例,使用STM32F103R6控制数码管从0到9连续显示。 #include "stm32f10x.h" int main(void) { GPIO_InitTypeDef GPIO_InitStructure; // 清空GPIO结构体 GPIO_StructInit(&GPIO_InitStructure); // 打开GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 配置GPIOD的PB1 ~ PB8引脚为输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // 定义数组存储不同数字对应的LED灯编号 uint8_t digital[10] = {0x7e, 0x30, 0x6d, 0x79, 0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b}; // 循环显示数字0~9 for(int i = 0; i < 10; i++) { // 将对应引脚拉高,点亮对应LED灯 GPIO_SetBits(GPIOB, GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8); // 延时一段时间 for(int j = 0; j < 20000; j++); // 将对应引脚设为低电平,熄灭对应LED灯 GPIO_ResetBits(GPIOB, GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8); // 将对应数字的LED灯点亮 GPIO_SetBits(GPIOB, digital[i]); // 延时一段时间 for(int j = 0; j < 20000; j++); } return 0; } 以上代码仅为示例,实际编写时需要根据自己所使用的具体数码管规则进行相应更改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名本无名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值