【数据分析R语言系列】R语言基础语法、基本数据结构、控制结构

基础语法

“程序 = 算法 + 数据结构”,数据结构是信息的载体,而算法是完成任务所需要的步骤。两者的构造和使用方法形成了编程语言独特的语法。本章先介绍 R 的基本数据结构,然后介绍条件和循环控制,接着介绍函数的创建与拓展包的使用,最后通过编程实战来实践和掌握本章涉及的知识点。

基本数据结构

为了表示现实世界的信息,各类编程语言常包含 3 种基本的数据类型:数值型,包括整数和浮点数;字符型,表示文本信息;逻辑型,也常称为布尔值,表示是非判断,如对与错,是与否。在 R 中,除了这些基本数据类型的实现,为了方便计算工作,R 本身还包含了矩阵、数据框和列表等复杂的数据类型,以支持表示各类常用的数据。

向量

在 R 中,数据运算常通过向量的形式进行。向量是一组同质的信息,如 20 个数字、30 个字符串(与数学术语中的向量类似,但不等同)。单一的信息在此被称为元素标量可以看作元素数量为 1 的向量。

接下来我们通过向量元素的数据类型来实际地了解和操作它。

数值

数值应该可以说是最常用的信息表现形式,如人的身高、年龄。在 R 中使用小学学到的阿拉伯表示法即可创建数值,如圆周率 π \pi π

3.14

此处 #> 后显示 R 运行代码后的返回结果,[1] 是结果的索引,以辅助用户观测,这里表示结果的第 1 个值是 3.14。

typeof()class() 是两个对于初学者非常有用的函数,它们可以返回数据的类型信息。

typeof(3.14)
class(3.14)

在 R 中不需要像其他语言一样区分数值的精度信息,typeof() 返回结果为 double 提示该值是一个浮点数。

在 R 中,任何所见的事物皆为对象class() 返回对象的类信息,此处是 numeric(数值)。

我们再来看看如何在 R 中表示整数。借助上述两个工具函数,我们不难发现下面的代码与想象不同。

3

typeof(3)
class(3)

typeof()class() 对于 3 的返回结果与 3.14 完全相同!这是因为即便只输入 3,R 也将其作为浮点数对待。

我们可以利用 identical() 函数或 is.integer() 函数进行检查:

identical(3, 3.0)

is.integer(3)

返回的结果是后面将介绍的逻辑值,TRUE 表示对、FALSE 表示错。因此可以判断 3 并不是整数。

正确的整数表示方法需要在数字后加 L 后缀,如 3L

is.integer(3L)

identical(3L, 3)

is.integer() 函数隶属于 is.xxx() 家族,该函数家族用于辅助判断对象是否属于某一类型。读者在 RStudio 中输入 is. 后 RStudio 将智能提示有哪些函数的名字以 is. 开头。

浮点数和整数都是数值,所以下面的代码都会返回 TRUE

is.numeric(3.14)
is.numeric(3L)

现实中的数据常成组出现,例如,一组学生的身高。R 使用 c() 函数(ccombine 的缩写)对数据进行组合:

c(1.70, 1.72, 1.80, 1.66, 1.65, 1.88)

这样我们就有了一组身高数据。

利用 R 自带的 mean()sd() 还是我们可以轻易求取这组数据的均值和标准差:

# 均值
mean(c(1.70, 1.72, 1.80, 1.66, 1.65, 1.88))
# 标准差
sd(c(1.70, 1.72, 1.80, 1.66, 1.65, 1.88))

上面我们计算时我们重复输入了身高数据,如果在输入时发生了小小的意外,如计算标准差时将 1.65 写成了 1.66,那么我们分析得就不是同一组数据了!虽然说在上述的简单计算不太可能发生这种情况,但如果存在 100 甚至 1000 个数据的重复输入,依靠人眼判断几乎是必然出错的。

一个解决办法是依赖系统自带的复制粘贴机制,但如果一组数据被上百次重复使用,这种办法也不实际。

正确的解决办法是引入一个符号(Symbol),用该符号指代一组数据,然后每次需要使用该数据时,使用符号代替即可。符号在编程语言中也常被称为变量,后面我们统一使用该术语。

上述代码块改写为:

heights <- c(1.70, 1.72, 1.80, 1.66, 1.65, 1.88)
mean(heights)
sd(heights)

<- 符号在 R 中称为赋值符号,我们可以将它看作数据的流动方向,这样更方便理解,我们不难猜测到 -> 的写法也是有效的:

c(1.70, 1.72, 1.80, 1.66, 1.65, 1.88) -> heights2
heights2

但通常以 <- 的写法为主。

另外,= 符号与 <- 有基本相同的含义,但不常用。如果读者有其他编程语言经验,也可以使用它作为常用赋值符号。两者的区别见本章【常见问题与方案】一节。

R 中变量的命名有一些限制,最重要的就是不要以数字开头:

3ab = 3

变量命名有 2 点建议:

  1. 对于一些临时使用的变量,以简单为主,如 ijk 等。
  2. 与数据相关的命名,建议与其信息一致,如上面的代码我使用了 heights,不然在没有注释的情况下,代码的阅读者无法快速理解你写的程序。

另外,长变量的命名通常有 2 个推荐的规则:

  1. 骆驼法

以学生身高数据为例,可以写为 studentHeights,它遵循 aBcDeF 这样的构造方式。

  1. 蛇形

以下划线作为分隔符,写为 student_heights

两种写法在 R 中都很常用,读者选择一种即可,重点在于一个 R 脚本中应当保持变量名命名风格的一致

在了解向量和变量后,我们再来学习下向量的计算方式。

假设我们有两组数据,分别以变量 ab 存储:

a <- c(1, 2, 3)
b <- c(4, 5, 6)

我们将其堆叠到一起,如图 @ref(fig:vector-construction):

layout(matrix(1:6, byrow = TRUE, ncol = 3))
layout.show(6)

当我们将 ab 相加,结果是什么呢?

a + b

两个向量之和是向量元素一一相加组成的向量。如果向量的元素不相同,结果又是如何呢?

我们将 a4 相加看一看,此时向量堆叠如图 @ref(fig:vector-add) 所示:

layout(matrix(c(1:4, 0, 0), byrow = TRUE, ncol = 3))
layout.show(4)
a + 4

上述结果与 a + c(4, 4, 4) 相同:

a + c(4, 4, 4)

因此,如果向量不等长时,短向量会通过重复与长向量先对齐(如图 @ref(fig:vector-align)),然后再相加。

layout(matrix(c(1:4, 4, 4), byrow = TRUE, ncol = 3))
layout.show(4)

注意,此过程中,长向量会保持不变,如果出现短向量重复后无法对齐的情况,多余的部分将被扔掉,R 返回结果的同时会抛出一个警告信息。

c(1, 2, 3) + c(4, 5)
# 上面的加法等价于 c(1, 2, 3) + c(4, 5, 4)

整个过程称为向量化运算。除了加法,其他任何向量(几何)运算方式都相同。

# 想减
a - b
# 相除
a / b
# 相乘
a * b
# 整除
a %/% b
# 取余数
a %% b
# 平方
a ^ 2
# 取对数
log(a, base = 2)

向量化运算的本质是成对的向量元素运算操作。这个特性让 R 在处理数据时非常方便,无论向量元素的个数是多少,在运算时我们都可以将其作为标量对待。

例如,计算数据 heights 的均值和标准差,这里我们直接通过公式而不是 R 自带的函数进行计算:

μ = ∑ x i n \mu = \frac{\sum x_i}{n} μ=nxi

s d = ∑ ( x i − μ ) 2 n − 1 sd = \sqrt\frac{\sum(x_i - \mu)^2}{n - 1} sd=n1(xiμ)2

sd 的计算中使用的是 n-1 而不是 n 的原因是我们计算的是样本标准差。

实际操作如下:

# 先计算均值
heightsMean <- sum(heights) / length(heights)
heightsMean
# 计算标准差
heightsSD <- sqrt( sum( (heights - heightsMean)^ 2) / (length(heights) - 1) )
heightsSD

将结果与 R 函数计算结果对比:

mean(heights)
sd(heights)

注意,上述我们使用了 R 的一些其他工具函数,length() 用来获取向量的长度,而 sum() 用来获取向量之和,sqrt() 用来计算开方。

初学者可能对于计算中使用的一些计算函数感到陌生,这是非常非常非常正常的,我个人也无法记得所有 R 提供的函数,编程是一门实践课程,读者需要通过使用去熟悉,而无法通过死记硬背掌握。在想要使用自己不知道的函数时,这里有几点建议:

  1. 猜测函数名。R 的函数命名方式是有规律可循的,且大体有对应的英文含义,读者不妨先尝试猜一猜函数名,看看是否真的有。
  2. 使用 R 的文档系统。R 的文档系统非常丰富,读者可以在 R 控制台 ?numeric 来获取关于 numeric 的相关信息。而 ??numeric 可以进行更为深度的搜索。学会读和理解函数文档是掌握 R 必备的技能。
  3. 使用搜索引擎。(初学者)遇到的问题基本都会有人遇到,R 的用户众多,各个博客和论坛都记录了关于 R 的使用和问题讨论,在上述 2 点无法解决问题时,读者不妨多使用搜索引擎查找相关资料。

这一小节我们通过数值数据作为对象学习了一些重要的 R 基础概念和操作。接下来我们将这些知识拓展到其他基础数据类型中就相对容易多了。

字符串

日常数据处理任务中除了常见的数值型数据,文本数据也比较常用。例如,表示性别是“男”或“女”,教育程度是“中学”还是“大学”。

在 R 中,并不能直接通过输入非数值的字符创建字符串:

文本数据需要通过单引号 '' 或双引号 "" 引号括起来,这样就可以创建字符串了:

'男'
"女"
typeof("abcde")
class("abcde")

单双引号可以通过嵌套以实现单引号或者双引号的输出:

'"abcde"'
"'abcde"

由于 R 的底层实现只有双引号,所以 '"abcde"' 输出中的 " 带有转义符号 \(反斜杠)。读者可以利用转义符号完成对引号(或其他一些特殊符号)的输出操作。

'\'abc'    # a 前面的单引号被转义了,''abc' 则是错误的写法
"\"\'abc"  # 参照上面进行理解,试试运行 ""'abc",你会观察到什么现象?为什么?

函数 nchar() 常用于获取字符串中字符的个数:

nchar("abcde")

注意,这与获取字符串向量的元素个数是不同的:

abc <- c("abcde", "f", "g")
length(abc)
nchar(abc)

字符串常涉及集合操作,如交集、并集、差集:

# 交集
intersect(c("a", "b", "c"),
          c("a", "b", "d"))
# 并集
union(c("a", "b", "c"),
      c("a", "b", "d"))
# 差集
setdiff(c("a", "b", "c"),
        c("a", "b", "d"))

注意,集合操作同样适用于其他数据类型,读者不妨试一试。

因子

因子是另类的字符串,它引入了水平信息,更有利于保存和展示分类的文本数据,创建方式如下:

sex <- factor(c("Male", "Female", "Female", "Male", "Male"))
sex

上述结果除了打印向量本身的元素,还输出了变量 sex 的水平信息。水平信息可以通过 levels() 函数获取。

levels(sex)

重命名因子水平,可以完成对应所有元素的修改:

levels(sex) <- c("Female", "男的")
sex

水平可以在创建因子时指定,如果一些分类没有对应的水平,将被转换为 NA(Not Available 的缩写),NA 是 R 中比较特殊的一个值,表示数据未知的状态。

factor(c("Male", "Female", "Female", "Male", "Male", "M", "M"), levels = c("Male", "Female"))

除了水平,我们还可以为分类添加标签以展示某一分类对应的具体信息:

factor(c("Male", "Female", "Female", "Male", "Male", "M", "M"), 
       levels = c("Male", "Female"),
       labels = c("性别:男", "性别:女"))

初学者需要额外注意,R 代码不支持中文,中文以及特殊字符只能出现在字符串中,两者的换用是代码出错的常见原因。

逻辑值

逻辑值仅有 2 个:TRUEFALSE,对应缩写为 TF。一般并不会直接使用逻辑值存储信息,而是使用它管理程序的逻辑,这一点在本章【控制结构与循环】一节中介绍。

逻辑值的另外一个重要作用是对数据取子集,相比于整数索引,它更加的高效。

我们先看一下如何利用整数索引提取子集,如提取变量 heights 的第 2 个元素:

heights[2]

再提取第 2 到第 5 个元素,这会形成新的向量:

heights[2:5]

这里 2:5 是一个便捷操作,它生成了整数向量 c(2, 3, 4, 5)

2:5

如果使用负号,将会去掉对应的元素:

heights
heights[-2]

在实际工作中,需要提取的数据子集通常不会这么有序,因此需要借助比较运算符和 which() 函数获取子集数据的索引。

例如,找出身高大于 1.7 的数据:

# 先使用 which() 找出索引
which(heights > 1.7)

# 然后组合取子集操作提取子集数据
heights[which(heights > 1.7)]

实际上,我们完全没有必要引入 which() 函数用来返回数据的整数索引,heights > 1.7 比较的结果是一个逻辑值向量,它本身就可以作为索引用于提取子集。

heights > 1.7
heights[heights > 1.7]

TRUE 对应的元素被保留,而 FALSE 对应的元素被去除。请读者记住,逻辑索引是首选的取子集方式,它更加高效。

深入向量

向量除了保存数据,还可以保存与之相关的属性。例如,为了更好展示 heights 信息,我们可以增加名字属性。

names(heights) <- paste("Student:", 1:6)
heights

上述代码中,paste() 将两个向量粘贴到一起,默认中间存在空格。

paste("Student:", 1:6)
# 修改分隔符
paste("Student", 1:6, sep = "-")

names() 函数不仅可以设定名字属性,还可以查看:

names(heights)

R 中很多函数都与 names() 类似,不仅可以用于修改,同时还可以用于获取对应的信息。

另外,R 对象所具有的属性可以通过 attributes() 函数查看:

attributes(heights)

R 默认的类系统非常自由,我们可以任意设定属性,如增加一个班级属性:

attr(heights, "class-name") <- "A"
attr(heights, "class-name")

attributes(heights)

在创建向量时,一些函数会相当有用,如 rep(),它可以用来重复数据。

rep(1:4, 3)
rep(1:4, each = 3)

读者如果想要更新部分向量值,直接对提取的子集重新赋值即可。

heights2
heights2[heights2 > 1.8] <- 1.8
heights2

数组与矩阵

我们前面看的的向量都是一个维度的,如果我们增加维度信息,将形成数组。2 维的数组比较常用,被称为矩阵。

创建一个 2x2x3 的数组:

array(1:12, dim = c(2, 2, 3))

创建一个 4x3 的矩阵:

matrix(1:12, nrow = 4, ncol = 3, byrow = TRUE)

矩阵包含 2 个常用的属性,行名 rownames 和列名 colnames

M <- matrix(1:12, nrow = 4, ncol = 3, byrow = TRUE)
rownames(M)
colnames(M)

上述创建矩阵时我们没有设定,所以默认是 NULL(空值)。我们可以自行设定:

rownames(M) <- paste0("a", 1:4)
colnames(M) <- paste0("b", 1:3)
M

还可以获取矩阵的维度信息:

dim(M)
# 行数
nrow(M)
# 列数
ncol(M)

针对数值矩阵,一些运算非常有用:

# 矩阵和
sum(M)
# 矩阵均值
mean(M)
# 行和
rowSums(M)
# 列和
colSums(M)
# 行均值
rowMeans(M)
# 列均值
colMeans(M)

取子集操作依旧是适用的,逗号 , 用于分割不同的维度:

# 第 1 行第 1 列的元素
M[1, 1]
# 第 1 行
M[1, ]
# 第 1 列
M[, 1]
# 前 2 行
M[1:2, ]
# 前 2 列
M[, 1:2]

当取单行时,由于维度信息默认丢失,返回的是一维向量,我们可以显式指定保留矩阵形式,如:

M[, 1, drop = FALSE]

逻辑索引也可以使用:

M[rowMeans(M) > 5, ]

数据框

数据框(data.frame)是 R 中非常独特的一种数据结构,它可以非常好存储和展示常见的表格数据。从外形上看,它与矩阵非常相似,但与矩阵不同的是,数据框的列可以是不同的数据类型。

例如,创建一个数据框存储性别,年龄和身高数据。

df <- data.frame(
  sex = c("F", "M", "M", "F"),
  age = c(17, 29, 20, 33),
  heights = c(1.66, 1.84, 1.83, 1.56)
)

df

str() 非常方便用于观察复杂数据类型的结构:

str(df)

默认,数据框中字符列会被自动转换为因子类型,我们可以通过设定修改它。

df <- data.frame(
  sex = c("F", "M", "M", "F"),
  age = c(17, 29, 20, 33),
  heights = c(1.66, 1.84, 1.83, 1.56),
  stringsAsFactors = FALSE
)

df

str(df)

很多适用于矩阵的操作同样适用于数据框。

例如,获取维度信息:

dim(df)
# 行数
nrow(df)
# 列数
ncol(df)

例如,获取和设定行、列名:

rownames(df)
colnames(df)

rownames(df) <- paste0("Stu", 1:4)
# 将列名大写
colnames(df) <- toupper(colnames(df))
df

数据框支持多种取子集的操作,包括整数索引、逻辑索引、行列名。

先看整数索引:

df[1:2, 1:2]

再看逻辑索引:

df[c(TRUE, TRUE, FALSE, FALSE), c(TRUE, TRUE, FALSE)]
# 等价于
df[rownames(df) %in% c("Stu1", "Stu2"), colnames(df) %in% c("SEX", "AGE")]

这里 %in% 运算符是成员判断操作,如 'a' %in% c('a', 'b') 是判断 'a' 是否在字符串向量 c('a', 'b') 中。第二种写法看起来比较繁琐,但实际工作中比较常用。

我们还可以直接使用名字:

df[c("Stu1", "Stu2"), c("SEX", "AGE")]

单独提取某一列生成一个向量是一个常用操作,读者可以使用两种操作符,包括 [[]]$

例如提取 SEX 列:

df[[1]]
df[["SEX"]]
df$SEX

需要注意 [[]][] 的区别,后者依旧返回一个数据框:

df['SEX']

另外,取子集操作可以使用 R 提供的 subset() 函数:

# 取行
subset(df, subset = rownames(df) %in% c("Stu1", "Stu2"))
# 取列
subset(df, select = colnames(df) == "SEX")
# 同时筛选行和列
subset(df, subset = rownames(df) %in% c("Stu1", "Stu2"),
           select = colnames(df) == "SEX")

数据框如果想要修改或更新某列,像向量一样重新赋值即可:

df$AGE <- 20
df

列表

列表可以表示非常非常非常复杂的数据结构。数据框可以看作列表所有列元素长度相同的特例。

创建一个列表如下:

l <- list(
  sex = c("F", "M"),
  age = c(17, 29, 20),
  heights = c(1.66, 1.84, 1.83, 1.56)
)
l

从输出上我们就可以知道如何提取不同的信息:

l$sex
l$heights

列表只有 names 属性,没有行列名属性:

names(l)

类似于数据框,[[]] 取子集得到一个列表元素,而 [] 得到一个子列表。

l['sex']
l[['sex']]

列表是支持嵌套的,下面我们将两个列表 l 放到一起:

l2 <- l
l2$l1 <- l
l2

控制结构

在处理数据分析任务时,我们很少能够简单依赖命令的顺序执行就完成任务。为了处理程序的复杂逻辑以及减少代码量,我们需要学习条件与循环控制的使用。

条件控制

if 语句

if 语句是最常用的条件结构,它由 if 关键字、条件判断语句和代码块组成:

age <-  20
if (age > 18) {
  # 如果条件判断结果为 TRUE
  # 该代码块中的语句会执行
  message("你是个成年人啦!")
}

条件判断语句结果必须返回一个逻辑值,即 TRUEFALSE。如果返回为 TRUE,随后以 {} 包裹的代码块会被执行。如果我们要处理为 FALSE 的情况,增加一个可选的 else 语句块。

age <- 16
if (age > 18) {
  # 为 TRUE 时执行
  message("你是个成年人啦!")
} else {
  # 为 FALSE 时执行
  message("你还是个小孩子哟!")
}

代码块中可以包含任意代码,所以 if-else 语句是支持内部嵌套的,结构如下:

if () {
  if () {
    
  } else {
    
  }
} else {
  
}

如果需要处理的情况是多种,if-else 语句可以连用。例如:

age <- 17
if (age > 18) {
  message("你是个成年人啦!")
} else if (age < 17) {
  message("你还是个小孩子哟!")
} else {
  message("恭喜你,快要成年了!")
}
switch 语句

swtich 语句在 R 中存在,但读者会极少见到和使用它。结构如下:

switch(EXPR, ...)

这里 EXPR 指代表达式,而 ... 说明可以输入任意参数。

这里只举一个简单的例子:

ch <- c("b")
cat(ch,":", switch(EXPR = ch, a = 1, b = 2:3), "\n")

switch 与函数式编程结合更具不凡的威力,其他场景下我极少见到该语句被使用。因此,我建议初学者了解即可,不必掌握。当然,读者如果遇到非常适合的场景也不妨试一试它,应该是可以让代码更为精炼有效的。

提示信息

编写程序时,通过输出一些提示信息可以更好地显示程序的运行状态是否如我们所预期,这是一个初学者需要掌握的一个技巧,能有效避免错误和帮助调试错误。

R 可以通过 print()message()cat()warning()stop() 输出提示信息,只有 stop() 会让程序终止。

读者通过下面的输出比较前几者的差别:

print("Running...")
message("Running...")
cat("Running...\n")
warning("Running...")

cat()message() 看起来差别不大,但 cat() 无法被禁止输出,默认没有换行。另外 message()warning() 的信息是可以被抑制掉的,如下:

message("Running...")
suppressMessages(message("Running..."))

warning("Running...")
suppressWarnings(warning("Running..."))

我们再来了解下 stop(),它会直接让程序终止掉,这可以有效避免正确的代码跑错误的数据。

例如,计算均值需要一个数值型数据,但我们却传递了一个字符串:

heights_str <- as.character(heights)

if (!is.numeric(heights_str)) {
  stop("无法对字符串计算!")
} else {
  # 下面的代码不会被运行
  mu <- mean(heights_str)
}

一般情况下,我推荐读者按需使用 message()/print()warning()stop() 这几个函数,它们体现信息的 3 个不同级别:

  • message()/print() 提供普通的输出信息。
  • warning() 提供需要注意的警告信息。
  • stop() 提供令程序停止运行的信息。

循环控制

当我们需要重复某一个(堆)操作时,就需要用到循环的力量了。R 中的循环语句效率历来被人诟病,但实际上已经大有改进。循环语句相比后面提到的 apply 家族函数具有更高的可读性,且容易理解和调试,因此我个人推荐初学者使用。如果本小节提到的几个循环控制语句确实影响到读者程序的效率,再找其他办法也不迟。

在此强调一下,无论是程序的编写还是科研分析工作,完成永远比高效重要。

for 语句

for 语句需要配合迭代变量、in 关键字一起使用,结构如下:

for (i in obj) {
  # 这里输入任意条语句
}

这里 i 指代迭代变量,它可以是索引,也可以是子数据集。obj 指代一个可迭代对象。

针对循环打印变量 heights 的信息,可以有以下 2 种方式:

# 第一种方式
# 直接循环迭代对象本身
for (i in heights) {
  print(i)
}

# 第二种方式
# 通过索引进行迭代
for (i in 1:length(heights)) {
  print(heights[i])
}

第二种方式写法看起来更为复杂,但如果针对一些复杂的程序,它则显得更加逻辑分明。

初学者容易犯的一个错误是将 in 后面的可迭代对象写成一个标量,如下:

for (i in length(heights)) {
  print(heights[i])
}

需要注意下面两者的区别:

length(heights)

1:length(heights)

一种更好的写法是使用 seq_along(heights) 替代 1:length(heights)

for (i in seq_along(heights)) {
  print(heights[i])
}

seq_along() 会自动返回可迭代对象的索引序列:

seq_along(heights)
while 语句

for 语句已经能满足一般场景的使用,while 语句则特别适合于算法的设计中:

  • 不知道要运行多少次循环。
  • 知道要退出循环的条件。

下面举一个简单的例子:

v <- 10
while(v > 2) {
  print(v)
  v <- v - 1.1
}
repeat 语句与循环退出

repeat 语句我从来没有使用过,它类似与 C 语言中的 do-while 语句,即先运行一段程序,然后看一看是否需要退出去。

它的结构如下:

repeat EXPR

EXPR 指代一个语句块。为了退出 repeat 循环,我们需要借助 break 语句的力量。

下面是一个简单例子:

i <- 1

repeat{
  print(i)
  i <- i*2
  if (i > 100) break
}

break 语句执行后将跳出当前的循环,另有 next 语句,它可以跳过后续代码的运行进入下一次循环。

基于上面的例子我们再构造一个示例:

i <- 1

repeat{
  print(i)
  i <- i*2
  if (i > 200) break()
  if (i > 100) next()
  print("Can you see me?")
}

i > 100 后,最后一条输出语句就不再运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小刘要努力。

顺便点一个赞

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

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

打赏作者

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

抵扣说明:

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

余额充值