Project1

标题: 【用代码说话】#1.实例对象 [打印本页]

作者: 喵呜喵5    时间: 2016-7-17 01:17
标题: 【用代码说话】#1.实例对象
本帖最后由 喵呜喵5 于 2016-7-17 21:15 编辑

【前言】

现在的论坛已经基本上没有什么技术输出或者干货分享了,所以最近一段时间一直想正正经经写一个 RM 脚本教程来误人子弟,思考了很多形式很多主题,比如【RM 永远不会告诉你的那些 Ruby 知识】啦【如何一步一步用脚本实现某个简单的功能】啦【Try This,Not This】啦啥的,但是仔细去推敲的话又写不动了。今天看了这个帖子(https://rpg.blue/forum.php?mod=viewthread&tid=394657)忽然意识到很多人所谓的“能理解、修改脚本代码”实际上从 Ruby 的层面上来看简直惨不忍睹,技术讨论区已经没了,黑科技区则不适合发这些东西,思考再三,决定在水区开始一个不定期更新的连载,每次给出一段构造简单包含基础知识的 Ruby 代码,用代码说话,让人能够真正理解代码背后的逻辑,而不至于说出【我能改脚本,但是变量为什么前面要加@啊】这种让人啼笑皆非的话出来
当然,阅读我这个系列需要你拥有一定的脚本基础,这个脚本基础指的是:你至少应该知道代码是一行一行执行的吧?至少要知道比较是否相等用的是 == 赋值用的是 = 吧,大概就是这种类似 Ruby 语言的常识一样的东西

鉴于我自己学术不精,这个系列又完全是由我自己发起自己执行的,没有什么人来负责内容的校对,帖子包含的内容难免存在各种错误,所以如果诸位阅读者发现了什么纰漏请不吝指正
同时,由于帖子可能包含错误,我可能会直接编辑原帖内容进行修正,因此,请不要转载这些帖子的内容以免帖子中的错误在转载后给他人带来困扰,最最不济,转载烦请注明原帖地址,谢谢合作

那么,下面是这个系列的正文

【用代码说话】#1.实例对象



  1. class A
  2.   def m1
  3.     p 'foo'
  4.   end
  5. end

  6. a = A.new
  7. aa = A.new
  8. a.m1
  9. aa.m1
  10. p (a.object_id == aa.object_id)
复制代码

请不要借助任何代码执行工具,阅读上面的代码并思考,输出的结果是什么?
如果你对 Ruby 接触不深的话,这里给你提供一个简单的补充知识,object_id 可以看作是一个座位号,在你的程序中,如果两个东西的座位号是相同的,那么他们就是【完全相同】的,如果两个东西的座位号是不同的,那么系统就会把他们当成【不同的东西】来区别对待



作为一门完全面向对象的语言,这样一段代码也许是个不错的开始。关于面向对象、类、实例对象这三个术语,这里并不想对其进行过多描述性的解释,毕竟,一堆的编程语言,一堆的脚本教程里已经变着法子讨论过一遍了,又是学生又是猫又是汽车的,以我拙劣的描述水平估计再去解释也只会让你更加混乱,所以,用代码说话,在上面这段代码中
(当然,从严格的意义上来看,上面这三条描述并不严谨)

有了以上几句话的基础,下面我们可以从头开始完整的分析一遍代码了,我们一起从上往下看一下

这段代码的输出结果是
  1. "foo"
  2. "foo"
  3. false
复制代码

为什么 a 说我要执行 m1,输出的是"foo"?
因为 a 是 A 的实例,A 规定了执行 m1 要输出 "foo"

为什么 aa 说我要执行 m1,输出的是"foo"?
因为 aa 是 A 的实例,A 规定了执行 m1 要输出 "foo"

那么,为什么我们询问系统 a 和 aa 座位号是不是相同的时候,系统告诉我们不是呢?
因为,a 做了声明:坐在 a 这个位置的是一个新来的实例!
aa 也做了声明:坐在 aa 这个位置的也是一个新来的实例!
系统一看,哟呵,来了两个新来的实例,那么给你们两个各自分配一个座位号你们待着吧
这就是实例的特点了,即使 a 和 aa 都是 A 的实例,他们所做的事情是完全一样的,但是,他们却在不同的座位上坐着,在系统的层面,他们是不一样的两个东西

而像是这种用类定义行为,用实例执行行为的编程方式正是面向对象的编程方式

作为实用主义者,你大概会问了,道理我都懂,但是面向对象有什么用呢?
想想看吧,假设主角进入了一个史莱姆的巢穴,正与 1 只史莱姆战斗,史莱姆的动作非常单调,只会随机执行下列行动中的其中一种
请问,你打算如何写代码来实现这些这只史莱姆的逻辑呢?
——哦,不好,你发现敌人不止 1只史莱姆,而是 100 只史莱姆!
——哦,不好,你在和史莱姆战斗的过程中发现史莱姆的动作居然不是【防御】和【攻击】而是【防御】、【攻击】和【逃跑】
面向对象正是为了解决这样的问题,你不需要为这 100 只史莱姆都各自定义相应的行为,你只需定义一个史莱姆的类,然后让史莱姆实例执行史莱姆类定义的行为就好了。正因为所有的史莱姆实例都会忠实的执行史莱姆类所定义的行为,所以,只要改变史莱姆类的定义,所有史莱姆实例的行为都能立刻发生改变,这听起来可比重新给把100只史莱姆的逻辑都重写一遍好多了不是吗?

那么,最后,附上一个会让你对这次学到的内容感到有点混乱的恶作剧
  1. class A
  2.   def m1
  3.     p 'foo'
  4.   end
  5. end

  6. a = A.new
  7. aa = a
  8. a.m1
  9. aa.m1
  10. p (a.object_id == aa.object_id)
复制代码

如果无法理解上面这段代码的输出结果,那么,比较一下这段代码和最开始那段代码的不同,然后再考虑一遍吧
也许,你能在后续的帖子中学到更多关于这个恶作剧的细节?[groupid=572]喵呜喵5的坑[/groupid]
作者: taroxd    时间: 2016-7-17 07:08
如果每个人心里都能理解这些东西的话,感觉世界会清净很多(x

实例对象的英语到底是 instance 还是 object
作者: 正太君    时间: 2016-7-17 07:46
史莱姆明明只会攻击和逃跑,不会防御...
作者: DTSY    时间: 2016-7-17 08:33
taroxd 发表于 2016-7-17 07:08
如果每个人心里都能理解这些东西的话,感觉世界会清净很多(x

实例对象的英语到底是 instance 还是 obj ...

实例变量是instance,对象是object
作者: 邪正人鬼    时间: 2016-7-17 10:13
啊多谢大大的教程!

在下对于『类』还是不太掌握,有了大大的教程就比较容易理解了。
虽然在下还在学习编程语言……


最后那个的输出是不是
  1. "foo"
  2. "foo"
  3. true
复制代码

?

作者: RyanBern    时间: 2016-7-17 10:14
本帖最后由 RyanBern 于 2016-7-17 18:11 编辑

喵呜头像换得真勤快

这种东西不适合发水区了,养肥了之后应该送交技术发布区(

感觉喵呜需要再三强调:(某次)结果符合预期的代码 != 正确的代码,这点许多 Scripters 并不能理解。

还需要强调的是:千万不要拿别的语言来类比 Ruby。
作者: kuerlulu    时间: 2016-7-17 10:39
诈尸

TL; NR
不是很懂你们代码触

感觉人人心里都理解了面向对象的话, 或许找对象也不会因此变得容易呢【x

作者: 余烬之中    时间: 2016-7-17 11:05

噢噢噢噢 捕获喵污!


TL; NR
为何标签是胡扯

作者: 英顺的马甲    时间: 2016-7-17 11:36
本帖最后由 英顺的马甲 于 2016-7-17 11:38 编辑

那我也来
  1. class A
  2.   def m1
  3.     p 'foo'
  4.   end
  5.   def p(s)
  6.     printf("not %s\n", s)
  7.   end
  8. end

  9. a = A.new
  10. aa = a.dup
  11. a.m1
  12. aa.m1
  13. p (a = aa)
复制代码

作者: garfeng    时间: 2016-7-17 13:37
本帖最后由 garfeng 于 2016-7-17 13:46 编辑

请教一下:
1.
  1. a = A.New
  2. b = A.New
复制代码

这是创建两个类相同的对象对吧,接下来的:
  1. p a == b
复制代码

在作比较的时候,这个==运算符比较的到底是什么呢?
我知道在某些语言里,==在比较对象时,时逐个比较成员变量和成员函数,全部相同则返回true,否则false。
ruby在比较时,比较的时什么呢?
【突然觉得,比较的是a和b指向的地址是否一样吗?,因为a和b都(大概)是指针(长整型),存放用于存放对象而开辟的内存起始地址(是个很长的整数)。】
从输出结果看,的确是比较两个指针里存放的地址的具体数值。
那么ruby里有没有类似于*p这种获取指向内容的运算符呢?

如果有的话,那么:


  1. (*a) == (*b)
复制代码

是不是应该输出true呢?

2.
  1. a = A.new
  2. b = a
复制代码

这里的b被赋值的是a的地址,还是另申请一个类A的内存,把a copy一个副本存放进去,再把b指向它呢?

当然从输出结果看,是前者。
作者: 英顺的马甲    时间: 2016-7-17 14:07
本帖最后由 英顺的马甲 于 2016-7-17 14:34 编辑

再来一段,这一段当初朋友都疑惑了好久
  1. def xor(a, b)
  2.   return ((a ? true : false)!=(b ? true : false)) # 这里刚刚不小心写成了 ==
  3. end
  4. ary1 = [1,2,3,4,5]
  5. ary2 = [2,4,6,8,10]
  6. p xor(ary1.include?(1), ary1.include?(2))
  7. p xor(ary2.include?(1), ary2.include?(2))
  8. p xor(ary1.include?(0), ary2.include?(1))
  9. p xor(ary1, ary2)
  10. p xor(nil, ary2)
复制代码

作者: 有丘直方    时间: 2016-7-17 14:10
至今没搞懂为啥同一个类的实例在执行了同一个方法之后会变成不同的东西。
作者: 英顺的马甲    时间: 2016-7-17 14:31
本帖最后由 英顺的马甲 于 2016-7-17 14:35 编辑
有丘直方 发表于 2016-7-17 14:10
至今没搞懂为啥同一个类的实例在执行了同一个方法之后会变成不同的东西。 ...

  1. class Human
  2.   attr_reader :DNA
  3.   attr_reader :characteristic
  4.   def initialize
  5.     @DNA = rand
  6.     @characteristic = rand
  7.     [url=home.php?mod=space&uid=89503]@deAd[/url] = false
  8.   end
  9.   def same_as?(other)
  10.     return (other.is_a?(Human) and @DNA == other.DNA and @characteristic == other.characteristic)
  11.   end
  12.   def kill
  13.     print "dead"
  14.     @dead = true
  15.   end
  16.   def dead?
  17.     return @dead
  18.   end
  19. end
  20. 有丘直方 = Human.new
  21. p 有丘直方.is_a?(Human)
  22. temp = 有丘直方
  23. p temp == 有丘直方
  24. 复制人 = 有丘直方.clone
  25. p 复制人.is_a?(Human)
  26. 复制人.same_as?(有丘直方)
  27. p 复制人 == 有丘直方
  28. 复制人.kill
  29. p 复制人.dead?, 有丘直方.dead?
  30. p 复制人.same_as?(有丘直方)
  31. 有丘直方.kill
  32. p 有丘直方.dead?
  33. p 复制人.same_as?(有丘直方)
复制代码

作者: kuerlulu    时间: 2016-7-17 15:10
看英叔这么开心突然也想来一个好玩的【
  1. def a
  2.   def a
  3.     2
  4.   end
  5.   1
  6. end
  7. p [a, a]
复制代码

作者: 有丘直方    时间: 2016-7-17 15:46
英顺的马甲 发表于 2016-7-17 14:31
  1. class Human
  2.   attr_reader :DNA
  3.   attr_reader :characteristic
  4.   def initialize
  5.     @DNA = rand
  6.     @characteristic = rand
  7.     [url=home.php?mod=space&uid=89503]@deAd[/url] = false
  8.   end
  9.   def same_as?(other)
  10.     return (other.is_a?(Human) and @DNA == other.DNA and @characteristic == other.characteristic)
  11.   end
  12.   def kill
  13.     print "#{self}died!\n"
  14.     @dead = true
  15.   end
  16.   def dead?
  17.     return @dead
  18.   end
  19. end
  20. 有丘直方 = Human.new
  21. p 有丘直方.is_a?(Human)
  22. temp = 有丘直方
  23. p temp == 有丘直方
  24. 复制人 = 有丘直方.clone
  25. p 复制人.is_a?(Human)
  26. 复制人.same_as?(有丘直方)
  27. p 复制人 == 有丘直方
  28. 复制人.kill
  29. p 复制人.dead?, 有丘直方.dead?
  30. p 复制人.same_as?(有丘直方)
  31. 有丘直方.kill
  32. p 有丘直方.dead?
  33. p 复制人.same_as?(有丘直方)
复制代码
在ruby的运行结果是
  1. true
  2. true
  3. true
  4. false
  5. #<Human:0x872fd0>died!
  6. true
  7. nil
  8. true
  9. #<Human:0x873048>died!
  10. true
  11. true
复制代码
我知道你的论点是正确的,但是我想要知道为什么会这样。因为本身我没有指出你的论点错误,我也不想辩论,所以我只是想知道原因而已,你就需要告诉我一个答案。
作者: 有丘直方    时间: 2016-7-17 15:47
英顺的马甲 发表于 2016-7-17 14:31
  1. class Human
  2.   attr_reader :DNA
  3.   attr_reader :characteristic
  4.   def initialize
  5.     @DNA = rand
  6.     @characteristic = rand
  7.     [url=home.php?mod=space&uid=89503]@deAd[/url] = false
  8.   end
  9.   def same_as?(other)
  10.     return (other.is_a?(Human) and @DNA == other.DNA and @characteristic == other.characteristic)
  11.   end
  12.   def kill
  13.     print "#{self}died!\n"
  14.     @dead = true
  15.   end
  16.   def dead?
  17.     return @dead
  18.   end
  19. end
  20. 有丘直方 = Human.new
  21. p 有丘直方.is_a?(Human)
  22. temp = 有丘直方
  23. p temp == 有丘直方
  24. 复制人 = 有丘直方.clone
  25. p 复制人.is_a?(Human)
  26. 复制人.same_as?(有丘直方)
  27. p 复制人 == 有丘直方
  28. 复制人.kill
  29. p 复制人.dead?, 有丘直方.dead?
  30. p 复制人.same_as?(有丘直方)
  31. 有丘直方.kill
  32. p 有丘直方.dead?
  33. p 复制人.same_as?(有丘直方)
复制代码
在ruby的运行结果是
  1. true
  2. true
  3. true
  4. false
  5. #<Human:0x872fd0>died!
  6. true
  7. nil
  8. true
  9. #<Human:0x873048>died!
  10. true
  11. true
复制代码
我知道你的论点是正确的,但是我想要知道为什么会这样。因为本身我没有指出你的论点错误,我也不想辩论,所以我只是想知道原因而已,你就需要告诉我一个答案。
作者: 英顺的马甲    时间: 2016-7-17 16:05
关于实例的问题以前倒是有这么个脑残贴
链接:残脑帖,来说说rmxp储存后的阿尔西斯还是原来的阿尔西斯吗
  1. class A
  2. end
  3. a = A.new
  4. p a.object_id
  5. b = a
  6. p b.object_id
  7. p b == a
  8. b = Marshal.load(Marshal.dump(b))
  9. p b.object_id
  10. p b == a
  11. b = ObjectSpace._id2ref(a.object_id)
  12. p b.object_id
  13. p b == a
复制代码

作者: myownroc    时间: 2016-7-17 16:49
满脑子面向过程的弱鸡瑟瑟发抖
作者: garfeng    时间: 2016-7-17 17:10
本帖最后由 garfeng 于 2016-7-18 15:16 编辑
有丘直方 发表于 2016-7-17 14:10
至今没搞懂为啥同一个类的实例在执行了同一个方法之后会变成不同的东西。 ...

我猜你想问10楼我问过的东西。
因为开辟了不一样的内存地址来存储实例,而在比较时,默认==符号比较的是地址的值,而非实例里的成员的具体值。
内存地址不一样了,所以会不是同一个东西。

个人理解所写,欢迎拍砖:

RUBY 代码复制
  1. a = A.new
  2. b = a
  3.              Addr  Data    这是一个内存条
  4.              +---+-------+
  5. a ---------> | 1 |   3   |-------+
  6.              +---+-------+       |
  7. b ---------> | 2 |   3   |----+  |
  8.              +---+-------+    |  |
  9.              | 3 |       |<---+--+
  10.              +---| Obj1  |
  11.              | 4 |       |
  12.              +---+-------+
  13.              |...| ..... |
  14.              +---+-------+
  15.  
  16. a = A.new
  17. 申请3~4的内存,吧Obj1存进去,将a赋值3
  18. a --> Addr:1 --> Data:3 --> Addr:3 --> Obj1
  19. b = a
  20. b --> Addr:2 --> Data:3 --> Addr:3 --> Obj1
  21.  
  22. a和b都是3,所以
  23. a==b # =>>true
  24. a equal? b # =>> true
  25. a eql? b   # =>> true
  26.  
  27. ==========================================================================
  28.  
  29. a = A.new
  30. b = A.new
  31.  
  32.  
  33.               Addr  Data        这是一个内存条
  34.               +---+-------+
  35. a ----------> | 1 |   3   |----+  a存储的内容是3,即obj1的起始地址位
  36.               +---+-------+    |  ---------------------------------------
  37. b ----------> | 2 |   5   |----+--+ b存储的内容是5,即obj2的起始地址位
  38.               +---+-------+    |  | -------------------------------------
  39.               | 3 |       |<---+  |
  40.               +---+  Obj1 |       | 3,4号地址位里存储的内容obj1,和
  41.               | 4 |       |       | 5,6号里存储的obj2一模一样。
  42.               +---+-------+       | 假设hash也一样?
  43.               | 5 |       |<------+
  44.               +---+  Obj2 |
  45.               | 6 |       |
  46.               +---+-------+
  47.  
  48.  
  49. 当使用a所代表的object时,访问顺序:
  50.  
  51. a --> Addr:1 --> Data:3 --> Addr:3 --> Data:Obj1
  52. b --> Addr:2 --> Data:5 --> Addr:5 --> Data:Obj2
  53.  
  54. a是3,而b是5,所以
  55. a==b #=>>false
  56. a equal? b # =>> false
  57. a eql? b # =>> true
  58.  
  59. =====================================================================
  60.  
  61. 涉及到哈希值的比较?
  62.  
  63. a = A.new
  64. b = a.dup
  65.              +---+--------+
  66. a ---------> | 1 |   3    |----+
  67.              +---+--------+    |
  68. b ---------> | 2 |   5    |----+---+
  69.              +---+--------+    |   |
  70.              | 3 | Obj1   |<---+   |
  71.              +---+ hash1  |        | hash1 和hash2 不一样,
  72.              | 4 |        |        | 除此之外,Obj1和Obj2的
  73.              +---+--------+        | 其他内容都一样?
  74.              | 5 | Obj2   |<-------+
  75.              +---+ hash2  |
  76.              | 6 |        |
  77.              +---+--------+
  78.  
  79. a==b #=>>false
  80. a equal? b =>> false
  81. a eql? b =>> false



慎点……



作者: RyanBern    时间: 2016-7-17 18:06
本帖最后由 RyanBern 于 2016-7-17 18:09 编辑
garfeng 发表于 2016-7-17 17:10
我猜你想问10楼我问过的东西。
因为开辟了不一样的内存地址来存储实例,而在比较时,默认==符号比较的是地 ...


永远不要用其他语言来类比Ruby,尤其是C语言。这也是我初学的时候犯的错误之一。

首先吐槽为什么输出指针不用%p而是%d

下面是从 Ruby 官方文档上截取下来的。
链接:http://ruby-doc.org/core-2.3.1/Object.html#method-i-eql-3F

请自己体会一下 Ruby 的这个机制(约定)。顺便修改一下回复吧。
作者: garfeng    时间: 2016-7-17 19:08
本帖最后由 garfeng 于 2016-7-17 22:34 编辑
RyanBern 发表于 2016-7-17 18:06
永远不要用其他语言来类比Ruby,尤其是C语言。这也是我初学的时候犯的错误之一。

首先吐槽为什么输出 ...


谢谢指出,在你提到%p之前,我从未意识到用%p来打印地址。%p是定长的16进制吧。

我没有想用C来类比ruby,

我只是想用一个直观的办法,来解释我理解范围内的:
为什么一个类的两个实例不是同一个东西
这个问题,在JavaScript,C++,Go……等其他语言里的原因都是地址不一样。
所以我妄断在ruby里,也是这个原因,
谢谢指出问题,因为ruby的某个比较的方法,还会比较哈希值吗?
RUBY 代码复制
  1. a=A.new
  2. b=a.dup

这种情况,比较的时候,不是比较值本身而是地址吧。
ruby的比较机制确实跟其他语言大不相同呢。
谢谢指教。
你叫我把%d修改成%p吗?【已经修改,谢谢提醒】
还是修改不应该用c类比ruby这一段?【不是类比语言,而是类比数据在内存里的存储方式,所以先不改了……】
作者: RyanBern    时间: 2016-7-17 20:24
本帖最后由 RyanBern 于 2016-7-17 22:06 编辑
garfeng 发表于 2016-7-17 19:08
谢谢指出,在你提到%p之前,我从未意识到用%p来打印地址。%p是定长的16进制吧。

我没有想用C来类比ruby ...


其实喵呜在写这一段的时候不应该引入'=='这个符号的,因为这个给初学者造成了非常大的困扰。真正判断是否相等的方法应该是Object#equal?,这个方法才属于真正的所谓“判断地址”。这是因为'=='在某些内置类里面已经被覆盖,因此执行'=='判断的时候只是判断内容相等与否。
RUBY 代码复制
  1. arr_a = [0, 1]
  2. arr_b = arr_a.dup
  3. p (arr_a == arr_b) #=> true
  4. str_a = "Hello World"
  5. str_b = str_a.dup
  6. p (str_a == str_b) #=> true

喵呜举例的时候,特地用了自己定义的类A,在默认情况下,'=='的含义等同于'equal?',因此这样才不会出问题。因此我建议,不论是你还是楼主喵呜,都应该先引入'equal?'来解释这个问题为好。

嗯,不去类比或者类推是最好的,因为如果这样理解Ruby会经常出错。

另外%d表示32位整数,但是指针的话有两种,有32位指针和64位指针的区别(视系统而定),因此使用%d打印是不合理的,而%p才是专门用来打印指针的格式转换符。
作者: summer92    时间: 2016-7-17 21:07
a 当然等于 a的副本了 :arr_b = arr_a.dup
a = a.dup
p (arr_a == arr_b) #=> true 还有 P 出来,我也是醉了
作者: 有丘直方    时间: 2016-7-18 14:31
  1. class A; end
  2. p(A.new)
  3. a = A.new; p(a)
  4. b = A.new; p(b)
  5. p(a == A.new)
  6. p(b == A.new)
  7. p(a == b)
  8. p(A.new == A.new)
  9. # =>
  10. #    #<A:0x8c****>
  11. #    #<A:0x8c****>
  12. #    #<A:0x8c****>
  13. #    false
  14. #    false
  15. #    false
  16. #    false
复制代码
没错,这非常让人疑惑。@英顺的马甲 @garfeng
作者: 喵呜喵5    时间: 2016-7-18 15:44
有丘直方 发表于 2016-7-18 14:31
没错,这非常让人疑惑。@英顺的马甲 @garfeng

并没有什么好疑惑的,很简单,因为这里的 == 等于 true 代表的是:两个对象【完全一样】

什么是完全一样呢
假设,你进入一场战斗,敌人是两个史莱姆,你打了左边那个史莱姆,右边那个史莱姆一脸没事的样子看着你,那这两个史莱姆就是不一样的
你打了左边那个史莱姆,右边那个史莱姆的血跟着左边史莱姆一起扣,那这两个史莱姆是完全一样的

面向对象想要实现的就是,不管出来一只史莱姆还是一百只史莱姆,只要他们是不一样的对象,那么打了左边史莱姆,右边的史莱姆就不应该跟着一起扣血

作者: kuerlulu    时间: 2016-7-18 21:32
其实

ruby有很多应该小心的地方没错

但是一开始就讲这些对于新人是否太深了

喵喵喵觉得应该先快速地体现一下ruby"容易写"的特点(即, 书写时比较符合直觉, 而且直接读起来应该是很流畅的)
# 这也是ruby被各种语法糖包裹的一个目的

并且用ruby来解释面向对象将会十分容易(比如, 类的基本形式就是用class包住一些methods和instance_variables【雾)

快速地介绍完变量 方法 类 模块的简单写法和笑果之后, 可以紧接着画个作用域的盒模型【大雾

到这里就可以说入门结束了吧, 不用考虑一些枝节(这些东西应该直接丢doc, 而且理解了上一行的内容之后大都是可以自己看懂的)。

因为喵喵喵差不多也就是个入门的层次所以话就说到这了【逃
作者: 御曹司    时间: 2016-7-31 13:11
本帖最后由 御曹司 于 2016-7-31 13:13 编辑

好棒的帖子~~
居然今天才发现的说




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1