用Scala实现有理数类
文章目录
Scala语言号称是集各家所长的下一代开发语言,特别是把面向对象编程和函数式编程连个看起来无法兼容的冤家很自然的搞到了一起,大大丰富了语言的表现形式。
光说不练假把式,今天我们就用Scala语言实现一个面向对象编程中很基础的有理数类的功能,来体验一下用Scala语言开发的酸甜苦辣吧。
搭建Rational开发测试环境
如何使用sbt搭建Scala的开发测试环境,这是另一盘文档事情。
具体到本工程,主要涉及到三个类:
- 既然要实现有理数类,当然要有一个Rational类
- 在开发的过程中,我们需要随时查看Rational类的使用情况,因此要有一个RationalDemo,它会被实现为object单例,并有一个main的主方法
- 有开发就得有测试,因此当然要有一个RationalTest类
项目的基本目录结构是:
实现Rational类基础功能
《Scala编程》这本Scala编程圣经的第六章就有Rational类开发的内容,尽管它只开了个头,并没有把工作做完,但是从这里开始无疑是最简单的。
从构造函数开始
首先从构造函数开始,Rational类的构造自然少不了分子,分母两个参数:
class Rational(n : Int, d : Int) {
println("created " + n + "/" + d)
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
new Rational(1, 2)
}
}
运行Demo,输出结果:
created 1/2
不过这里直接在类里用print测试类的构造当然是不正规的,所以我们需要重载toString方法:
class Rational(n : Int, d : Int) {
override def toString: String = n + "/" + d
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 2)
println("r = " + r)
}
}
运行Demo,输出结果:
r = 1/2
这里还有一个漏洞需要尽早补上,从数学上讲有理数的分母d不允许为0,在Scala中需要通过前置条件避免这个问题:
class Rational(n : Int, d : Int) {
require(d != 0)
override def toString: String = n + "/" + d
}
如果有错误的输入:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 0)
println("r = " + r)
}
}
Scala的前置条件会直接抛出异常:
Exception in thread "main" java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:327)
at com.example.math.Rational.<init>(Rational.scala:4)
at com.example.math.RationalDemo$.main(RationalDemo.scala:5)
at com.example.math.RationalDemo.main(RationalDemo.scala)
既然说到了构造函数,构造函数可以有多个,从数学上讲,整数属于有理数,所以从一个整数构造一个有理数就是一件很自然的事情。在Scala中称之为辅助构造函数,不过和C++,Java等开发语言构造函数之间是平级关系不同的是,Scala的构造函数有主从关系,辅助构造函数最终一定要调用主构造函数(顺便说一句,this表示自引用,和C++,Java等语言类似):
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
override def toString: String = n + "/" + d
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 2)
println("r = " + r)
val n = new Rational(2)
println("n = " + n)
}
}
运行Demo,输出结果:
r = 1/2
n = 2/1
实现基本运算
首先来实现最简单的add运算,有理数的加法公式比较简单,于是我们兴冲冲的写下下面的代码:
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
override def toString: String = n + "/" + d
def add(that : Rational) : Rational = {
new Rational(n * that.d + d * that.n, d * that.d)
}
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 2)
println("r = " + r)
val n = new Rational(2)
println("n = " + n)
println("r + n = " + r.add(n))
}
}
运行Demo,输出结果:
Error:(11, 27) value d is not a member of com.example.math.Rational
new Rational(n * that.d + d * that.n, d * that.d)
居然出错了,错误的意思是说d不是Rational的成员,什么意思?编译器眼瞎吗?n和d不就是Rational中的成员吗?我们刚刚在toString中用过的。
这里是Scala中的诸多天坑中的一个,Scala的语言逻辑是,n和d属于this这没错,但是that中的n和d你不能访问,那是私有的。原来Scala的私有权限居然不是类级别的,而是对象级别的。
要解决这个问题,只能老老实实地用字段将n和d再封装一层:
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
def numerator: Int = n
def denominator: Int = d
override def toString: String = numerator + "/" + denominator
def add(that : Rational) : Rational = {
new Rational(numerator * that.denominator + denominator * that.numerator,
denominator * that.denominator)
}
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 2)
println("r = " + r)
val n = new Rational(2)
println("n = " + n)
println("r + n = " + r.add(n))
}
}
运行Demo,输出结果:
r = 1/2
n = 2/1
r + n = 5/2
结果终于正确了
同理,我们再实现减法,乘法,除法三种运算:
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
def numerator: Int = n
def denominator: Int = d
override def toString: String = numerator + "/" + denominator
def add(that : Rational) : Rational = {
new Rational(numerator * that.denominator + denominator * that.numerator,
denominator * that.denominator)
}
def subtract(that : Rational) : Rational = {
new Rational(numerator * that.denominator - that.numerator * denominator,
denominator * that.denominator)
}
def multiply(that : Rational) : Rational = {
new Rational(numerator * that.numerator,
denominator * that.denominator)
}
def divide(that : Rational) : Rational = {
new Rational(numerator * that.denominator,
that.numerator * denominator)
}
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 2)
println("r = " + r)
val n = new Rational(2)
println("n = " + n)
println("r + n = " + r.add(n))
val a = new Rational(2, 3);
val b = new Rational(5, 6);
println("a = " + a)
println("b = " + b)
println("a + b = " + a.add(b));
println("a - b = " + a.subtract(b));
println("a * b = " + a.multiply(b));
println("a / b = " + a.divide(b));
}
}
运行Demo,输出结果:
r = 1/2
n = 2/1
r + n = 5/2
a = 2/3
b = 5/6
a + b = 27/18
a - b = -3/18
a * b = 10/18
a / b = 12/15
看到最后四行的结果没?结果是正确的,但是却不好,因为没有做最简分数的转换。
实现最简分数
要实现最简分数其实很简单,只要分子分母同时除以它们的最大公约数即可:
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
def numerator: Int = n / g
def denominator: Int = d / g
override def toString: String = numerator + "/" + denominator
private val g = gcd(n.abs, d.abs)
private def gcd(a: Int, b : Int) : Int =
if (b == 0) a else gcd(b, a % b)
def add(that : Rational) : Rational = {
new Rational(numerator * that.denominator + denominator * that.numerator,
denominator * that.denominator)
}
def subtract(that : Rational) : Rational = {
new Rational(numerator * that.denominator - that.numerator * denominator,
denominator * that.denominator)
}
def multiply(that : Rational) : Rational = {
new Rational(numerator * that.numerator,
denominator * that.denominator)
}
def divide(that : Rational) : Rational = {
new Rational(numerator * that.denominator,
that.numerator * denominator)
}
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 2)
println("r = " + r)
val n = new Rational(2)
println("n = " + n)
println("r + n = " + r.add(n))
val a = new Rational(2, 3);
val b = new Rational(5, 6);
println("a = " + a)
println("b = " + b)
println("a + b = " + a.add(b));
println("a - b = " + a.subtract(b));
println("a * b = " + a.multiply(b));
println("a / b = " + a.divide(b));
}
}
运行Demo,输出结果:
r = 1/2
n = 2/1
r + n = 5/2
a = 2/3
b = 5/6
a + b = 3/2
a - b = -1/6
a * b = 5/9
a / b = 4/5
这下子就清爽很多了。
实现操作符
我们用add, subtract, multiply and divide 四个函数来表示加减乘除,毕竟不如+,-,*,/四个运算符来的清爽,因为后者更自然,减少了记忆成本和降低了拼写出错的可能性。在Scala中这是一件很简单的事情,只需要做替换就可以了:
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
def numerator: Int = n / g
def denominator: Int = d / g
override def toString: String = numerator + "/" + denominator
private val g = gcd(n.abs, d.abs)
private def gcd(a: Int, b : Int) : Int =
if (b == 0) a else gcd(b, a % b)
def +(that : Rational) : Rational = {
new Rational(numerator * that.denominator + denominator * that.numerator,
denominator * that.denominator)
}
def -(that : Rational) : Rational = {
new Rational(numerator * that.denominator - that.numerator * denominator,
denominator * that.denominator)
}
def *(that : Rational) : Rational = {
new Rational(numerator * that.numerator,
denominator * that.denominator)
}
def /(that : Rational) : Rational = {
new Rational(numerator * that.denominator,
that.numerator * denominator)
}
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 2)
println("r = " + r)
val n = new Rational(2)
println("n = " + n)
println("r + n = " + r + n)
val a = new Rational(2, 3);
val b = new Rational(5, 6);
println("a = " + a)
println("b = " + b)
println("a + b = " + (a + b));
println("a - b = " + (a - b));
println("a * b = " + a * b);
println("a / b = " + a / b);
}
}
注意上面的(a + b)和(a - b)的括号,这是由于运算符优先级的问题,不加括号的话是有问题的。
运行Demo,输出结果:
r = 1/2
n = 2/1
r + n = 5/2
a = 2/3
b = 5/6
a + b = 3/2
a - b = -1/6
a * b = 5/9
a / b = 4/5
结果是不变的。
实现Rational类进阶功能
实现逻辑运算
我们还不支持逻辑运算,在Scala中实现这个功能也是不困难的:
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
def numerator: Int = n / g
def denominator: Int = d / g
override def toString: String = numerator + "/" + denominator
private val g = gcd(n.abs, d.abs)
private def gcd(a: Int, b : Int) : Int =
if (b == 0) a else gcd(b, a % b)
def +(that : Rational) : Rational = {
new Rational(numerator * that.denominator + denominator * that.numerator,
denominator * that.denominator)
}
def -(that : Rational) : Rational = {
new Rational(numerator * that.denominator - that.numerator * denominator,
denominator * that.denominator)
}
def *(that : Rational) : Rational = {
new Rational(numerator * that.numerator,
denominator * that.denominator)
}
def /(that : Rational) : Rational = {
new Rational(numerator * that.denominator,
that.numerator * denominator)
}
def <(that : Rational) : Boolean = {
numerator * that.denominator < denominator * that.numerator;
}
def >(that : Rational) : Boolean = {
numerator * that.denominator > denominator * that.numerator;
}
def ==(that : Rational) : Boolean = {
numerator * that.denominator == denominator * that.numerator;
}
def !=(that : Rational) : Boolean = !(this == that)
def <=(that : Rational) : Boolean = !(this > that)
def >=(that : Rational) : Boolean = !(this > that)
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 2)
println("r = " + r)
val n = new Rational(2)
println("n = " + n)
println("r + n = " + r + n)
val a = new Rational(2, 3);
val b = new Rational(5, 6);
println("a = " + a)
println("b = " + b)
println("a + b = " + (a + b));
println("a - b = " + (a - b));
println("a * b = " + a * b);
println("a / b = " + a / b);
println("a == b = " + (a == b));
println("a >= b = " + (a >= b));
println("a <= b = " + (a <= b));
println("b == a = " + (b == a));
println("b >= a = " + (b >= a));
println("b <= a = " + (b <= a));
}
}
注意上面的(a + b)和(a - b)的括号,这是由于运算符优先级的问题,不加括号的话是有问题的。
运行Demo,输出结果:
r = 1/2
n = 2/1
r + n = 1/22/1
a = 2/3
b = 5/6
a + b = 3/2
a - b = -1/6
a * b = 5/9
a / b = 4/5
a == b = false
a >= b = false
a <= b = true
b == a = false
b >= a = true
b <= a = false
结果是正确的。
对整数和实数的兼容
到此为止,我们实现了有理数之间的基本运算,当时我们还不支持有理数和整数,有理数和实数之间的运算。在Scala中要实现这样的功能需要用到隐式类型转换。作为Scala中的惯用法,这些隐式类型转换一般写在单例的伴随对象中:
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
def numerator: Int = n / g
def denominator: Int = d / g
override def toString: String = numerator + "/" + denominator
private val g = gcd(n.abs, d.abs)
private def gcd(a: Int, b : Int) : Int =
if (b == 0) a else gcd(b, a % b)
def +(that : Rational) : Rational = {
new Rational(numerator * that.denominator + denominator * that.numerator,
denominator * that.denominator)
}
def -(that : Rational) : Rational = {
new Rational(numerator * that.denominator - that.numerator * denominator,
denominator * that.denominator)
}
def *(that : Rational) : Rational = {
new Rational(numerator * that.numerator,
denominator * that.denominator)
}
def /(that : Rational) : Rational = {
new Rational(numerator * that.denominator,
that.numerator * denominator)
}
def <(that : Rational) : Boolean = {
numerator * that.denominator < denominator * that.numerator;
}
def >(that : Rational) : Boolean = {
numerator * that.denominator > denominator * that.numerator;
}
def ==(that : Rational) : Boolean = {
numerator * that.denominator == denominator * that.numerator;
}
def !=(that : Rational) : Boolean = !(this == that)
def <=(that : Rational) : Boolean = !(this == that)
def >=(that : Rational) : Boolean = !(this == that)
};
object Rational {
implicit def intToRational(value : Int) : Rational = new Rational(value)
implicit def rationalToDouble(value : Rational) : Double = value.numerator * 1.0 / value.denominator
}
在Demo中查看一下:
object RationalDemo {
def main(args: Array[String]): Unit = {
val r = new Rational(1, 2)
println("r = " + r)
val n = new Rational(2)
println("n = " + n)
println("r + n = " + r + n)
val a = new Rational(2, 3);
val b = new Rational(5, 6);
println("a = " + a)
println("b = " + b)
println("a + b = " + (a + b));
println("a - b = " + (a - b));
println("a * b = " + a * b);
println("a / b = " + a / b);
println("a == b = " + (a == b));
println("a >= b = " + (a >= b));
println("a <= b = " + (a <= b));
println("b == a = " + (b == a));
println("b >= a = " + (b >= a));
println("b <= a = " + (b <= a));
println("r + 2 = " + (r + 2));
println("r + 2 = " + (2 + r));
println("r + 3.14 = " + (r + 3.14));
println("r + 3.14 = " + (3.14 + r));
}
}
运行Demo,输出结果:
r = 1/2
n = 2/1
r + n = 1/22/1
a = 2/3
b = 5/6
a + b = 3/2
a - b = -1/6
a * b = 5/9
a / b = 4/5
a == b = false
a >= b = false
a <= b = true
b == a = false
b >= a = true
b <= a = false
r + 2 = 5/2
r + 2 = 2.5
r + 3.14 = 3.64
r + 3.14 = 3.64
结果是正确的。
测试Rational类
不要指望简单的Demo就能解决所有的bug,是时候开始真正的测试了。当然,对Rational的测试用例我们可以写很多很多,由于篇幅所限,这里也只能是示例性质的。
测试算术运算
下面的测试用例是很直观的:
class RationalTest extends FlatSpec {
"rational and rational arithmetic" should "return a result of rational" in {
val a = new Rational(2, 3);
val b = new Rational(5, 6);
assert(a + b === new Rational(3, 2));
assert(a - b === new Rational(-1, 6));
assert(a * b === new Rational(5, 9));
assert(a / b === new Rational(4, 5));
}
}
这里的===是scalaTest中的一个运算符,可以简单的理解为是更严格的比较相等。
运行Test,输出结果:
3/2 did not equal 3/2
ScalaTestFailureLocation: com.example.math.RationalTest at (RationalTest.scala:9)
Expected :3/2
Actual :3/2
<Click to see difference>
很不幸,报错了。
Rational对象的相等性
原来Scale中对象的相等性是一个很大的天坑,《Scala编程》中第30章整整一章都是说这玩意的。
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
def numerator: Int = n / g
def denominator: Int = d / g
override def toString: String = numerator + "/" + denominator
private val g = gcd(n.abs, d.abs)
private def gcd(a: Int, b : Int) : Int =
if (b == 0) a else gcd(b, a % b)
def +(that : Rational) : Rational = {
new Rational(numerator * that.denominator + denominator * that.numerator,
denominator * that.denominator)
}
def -(that : Rational) : Rational = {
new Rational(numerator * that.denominator - that.numerator * denominator,
denominator * that.denominator)
}
def *(that : Rational) : Rational = {
new Rational(numerator * that.numerator,
denominator * that.denominator)
}
def /(that : Rational) : Rational = {
new Rational(numerator * that.denominator,
that.numerator * denominator)
}
override def equals(other: Any): Boolean =
other match {
case that : Rational =>
(that canEqual this) &&
numerator == that.numerator &&
denominator == that.denominator
case _ => false
}
def canEqual(other: Any): Boolean = other.isInstanceOf[Rational]
override def hashCode(): Int = (numerator, denominator).##
def <(that : Rational) : Boolean = {
numerator * that.denominator < denominator * that.numerator;
}
def >(that : Rational) : Boolean = {
numerator * that.denominator > denominator * that.numerator;
}
def !=(that : Rational) : Boolean = !(this == that)
def <=(that : Rational) : Boolean = !(this == that)
def >=(that : Rational) : Boolean = !(this == that)
};
object Rational {
implicit def intToRational(value : Int) : Rational = new Rational(value)
implicit def rationalToDouble(value : Rational) : Double = value.numerator * 1.0 / value.denominator
}
测试用例不变:
class RationalTest extends FlatSpec {
"rational and rational arithmetic" should "return a result of rational" in {
val a = new Rational(2, 3);
val b = new Rational(5, 6);
assert(a + b === new Rational(3, 2));
assert(a - b === new Rational(-1, 6));
assert(a * b === new Rational(5, 9));
assert(a / b === new Rational(4, 5));
}
}
运行Test,输出结果:
终于测试通过了。
测试关系运算
下面的测试用例是很直观的:
class RationalTest extends FlatSpec {
"rational and rational arithmetic" should "return a result of rational" in {
val a = new Rational(2, 3);
val b = new Rational(5, 6);
assert(a + b === new Rational(3, 2));
assert(a - b === new Rational(-1, 6));
assert(a * b === new Rational(5, 9));
assert(a / b === new Rational(4, 5));
}
"rational and rational relational " should "return a result of boolean" in {
val a = new Rational(60, 5);
val b = new Rational(12, 1);
val c = new Rational(-24, -2);
assert(a == b);
assert(a >= b);
assert(a <= b);
assert(b == a);
assert(b >= a);
assert(b <= a);
assert(a == c);
assert(a >= c);
assert(a <= c);
val d = new Rational(119, 10);
assert(a != d);
assert(a > d);
assert(a >= d);
assert(d != a);
assert(d < a);
assert(d <= a);
val e = new Rational(129, 10);
assert(a != e);
assert(a < e);
assert(a <= e);
assert(e != a);
assert(e > a);
assert(e >= a);
}
}
运行Test,输出结果:
12/1 did not equal -12/-1
ScalaTestFailureLocation: com.example.math.RationalTest at (RationalTest.scala:26)
Expected :-12/-1
Actual :12/1
<Click to see difference>
很不幸,报错了。
原因是我们的Rational类并没有对分子分母的符号进行处理,这导致程序会认为2/3与-2/(-3)是不同的,-2/3与2/(-3)是不同的,增加符号的处理即可,这里我们规定:对于负的有理数,符号统一放在分子上。
顺便我们也改进一下toString,让它更美观一点,能够将12/1直接输出12
class Rational(n: Int, d: Int) {
require(d != 0)
def this(n: Int) = this(n, 1)
def numerator: Int = n / g
def denominator: Int = d / g
override def toString: String =
if (denominator == 1) numerator.toString else numerator + "/" + denominator
private val g = noramlSign(n, d) * gcd(n.abs, d.abs)
private def noramlSign(a: Int, b : Int) : Int =
if (a < 0 && b < 0) {
-1
} else if (a > 0 && b < 0) {
-1
} else {
1
}
def sign() : Int = if (g > 0) 1 else -1
private def gcd(a: Int, b : Int) : Int =
if (b == 0) a else gcd(b, a % b)
def +(that : Rational) : Rational = {
new Rational(numerator * that.denominator + denominator * that.numerator,
denominator * that.denominator)
}
def -(that : Rational) : Rational = {
new Rational(numerator * that.denominator - that.numerator * denominator,
denominator * that.denominator)
}
def *(that : Rational) : Rational = {
new Rational(numerator * that.numerator,
denominator * that.denominator)
}
def /(that : Rational) : Rational = {
new Rational(numerator * that.denominator,
that.numerator * denominator)
}
override def equals(other: Any): Boolean =
other match {
case that : Rational =>
(that canEqual this) &&
numerator == that.numerator &&
denominator == that.denominator
case _ => false
}
def canEqual(other: Any): Boolean = other.isInstanceOf[Rational]
override def hashCode(): Int = (numerator, denominator).##
def <(that : Rational) : Boolean = {
numerator * that.denominator < denominator * that.numerator;
}
def >(that : Rational) : Boolean = {
numerator * that.denominator > denominator * that.numerator;
}
def !=(that : Rational) : Boolean = !(this == that)
def <=(that : Rational) : Boolean = !(this > that)
def >=(that : Rational) : Boolean = !(this < that)
};
object Rational {
implicit def intToRational(value : Int) : Rational = new Rational(value)
implicit def rationalToDouble(value : Rational) : Double = value.numerator * 1.0 / value.denominator
}
修改一下测试用例:
class RationalTest extends FlatSpec {
"rational and rational arithmetic" should "return a result of rational" in {
val a = new Rational(2, 3);
val b = new Rational(5, 6);
assert(a + b === new Rational(3, 2));
assert(a - b === new Rational(-1, 6));
assert(a * b === new Rational(5, 9));
assert(a / b === new Rational(4, 5));
}
"rational and rational relational " should "return a result of boolean" in {
val a = new Rational(60, 5);
val b = new Rational(12, 1);
val c = new Rational(-24, -2);
assert(a == b);
assert(a >= b);
assert(a <= b);
assert(b == a);
assert(b >= a);
assert(b <= a);
assert(a == c);
assert(a >= c);
assert(a <= c);
val d = new Rational(119, 10);
assert(a != d);
assert(a > d);
assert(a >= d);
assert(d != a);
assert(d < a);
assert(d <= a);
val e = new Rational(129, 10);
assert(a != e);
assert(a < e);
assert(a <= e);
assert(e != a);
assert(e > a);
assert(e >= a);
}
"rational sign " should "return noraml sign" in {
val a = new Rational(2, 3);
val b = new Rational(-2, -3);
assert(a === b);
assert(a.sign() == 1)
val c = new Rational(-2, 3);
val d = new Rational(2, -3);
assert(c === d);
assert(a.sign() == -1)
}
"rational toString " should "return String" in {
val a = new Rational(2, 3);
val b = new Rational(-2, -3);
assert(a.toString === "2/3");
assert(b.toString === "2/3");
val c = new Rational(-2, 3);
val d = new Rational(2, -3);
assert(c.toString === "-2/3");
assert(d.toString === "-2/3");
val e = new Rational(60, 5);
val f = new Rational(-12, 1);
assert(e.toString === "12");
assert(f.toString === "-12");
}
}
运行Test,测试通过。
不算结束的结束
即使到现在,我们Rational类依然称不上完美,要写出工业级的代码不是一件轻松的事情,需要长时间的积累和测试。今天我们就到这里吧,希望小伙伴们也收获满满。