Java轉Ruby【快速入門】

最近參加實習了,公司的技術棧中須要用到 Ruby 以及 Rails 框架,因此算是開始了踩坑之旅吧..html

Ruby 簡介

網上的簡介要搜都能搜到,具體涉及的包括歷史啦之類這裏再也不贅述,提幾個關鍵詞吧:java

  • 1993 年由日本的松本行弘建立
  • 純粹面相對象編程/ 腳本語言/ 解釋型/ 動態類型

對於準備邁入 Ruby 的 Java 程序員來講,有幾個地方須要特別的去了解一下。git

  • 純粹面相對象
    其實通過論證,Java 同 Ruby 同樣都是純粹的面相對象的語言,這也就意味着包含全部的數字等在內都是對象,注意全部的都是。
  • 腳本語言
    這意味着你寫的程序不用編譯就能運行,甚至實時生效。
  • 解釋型
    同 Java 同樣,Ruby 有本身的虛擬機,運行須要必定的環境,也就是 Ruby 解釋器,它會負責把 Ruby 翻譯成及其可以執行的代碼。
  • 動態類型
    Ruby 中的數據更像是一種符號,在使用的時候不檢查類型,而是在運行時動態的檢查。

爲何是 Ruby ?

  • 緣由很簡單:高效/ 靈活/ 優雅/ 簡單

若是你再稍微花一些心思搜索一下 Ruby on Rails 這個 Web 開發框架,而且打開一些詳細說明了體驗以後的文章或者是多年經驗開發者的分享,你可能會對它產生一些興趣,這一Part就留着以後介紹了,這也是爲之後學習 RoR 框架作準備的。程序員

總之咱們要明確咱們目的:知道最基本的語法,理解其中的一些關鍵概念便可。github

Ruby 初體驗

Mac OX 中有默認的 Ruby 環境,咱們能夠來一個最短的 "Hello World" 程序,首先在控制檯中輸入 irb 命令,而後輸入 puts "Hello World!" 命令:shell

irb
irb(main):001:0> puts "Hello World!"
Hello World!
=> nil

你就能看到緊跟着你的輸入會有一個 Hello World! 的輸出以及一個 nil (對應 Java 中的 null)的返回。express

再來一個更加複雜的例子,咱們這一次來建立一個數組而後循環輸出它:編程

irb(main):002:0> properties = ['name','age','sex']
=> ["name", "age", "sex"]
irb(main):003:0> properties
=> ["name", "age", "sex"]
irb(main):005:0> properties.each {|property| puts "This is #{property}."}
This is name.
This is age.
This is sex.
=> ["name", "age", "sex"]

不知道感受如何?至少咱們能夠直觀的感覺到:數組

  • 不用生命變量,直接 = 就好
  • 每條 Ruby 代碼都會返回某個值

從 Java 到 Ruby

Java 很是成熟,而且經過 Spring 的加持獲得了許多企業的青睞,可是不知道你們有沒有感覺到一點:它或許有些囉嗦...(我亂說的啊,我也不知道,別問我啊..)從 Java 到 Ruby 聽說能夠預見性的將代碼的規模量大大縮小,所以也能使用更少的時間來輸出產品原型。安全

類似點

Ruby 與 Java 有一些類似的地方...

  • 垃圾回收器幫你管理內存。
  • 強類型對象。
  • 有 public、 private 和 protected 方法。
  • 擁有嵌入式文檔工具(Ruby 的工具叫 rdoc)。rdoc 生成的文檔與 javadoc 很是類似。

不一樣點

Ruby 與 Java 不一樣的地方...

  • 你不須要編譯你的代碼。你只須要直接運行它。
  • 有幾個不一樣的流行的第三方GUI工具包。Ruby 用戶能夠嘗試 WxRuby、 FXRuby、 Ruby-GNOME2、 Qt 或 Ruby 內置的 Tk。
  • 定義像類這樣的東西時,可使用 end 關鍵字,而不使用花括號包裹代碼塊。
  • 使用 require 代替 import
  • 全部成員變量爲私有。在外部,使用方法獲取全部你須要的一切。
  • 方法調用的括號一般是可選的,常常被省略。
  • 一切皆對象,包括像 2 和 3.14159 這樣的數字。
  • 沒有靜態類型檢查。
  • 變量名只是標籤。它們沒有相應的類型。
  • 沒有類型聲明。按需分配變量名,及時可用(如:a = [1,2,3] 而不是 int[] a = {1,2,3};)。
  • 沒有顯式轉換。只須要調用方法。代碼運行以前,單元測試應該告訴你出現異常。
  • 使用 foo = Foo.new("hi") 建立新對象,而非 Foo foo = new Foo("hi")
  • 構造器老是命名爲「initialize」 而不是類名稱。
  • 做爲接口的替代,你將得到「混入(mixins)」。
  • 相比 XML,傾向於使用 YAML。
  • nil 替代 null
  • Ruby 對 == 和 equals() 的處理方式與 Java 不同。測試相等性使用 ==(Java 中是 equals())。測試是否爲同一對象使用 equals?()(Java 中是 ==)。

以上的相同與不一樣來自:https://www.ruby-lang.org/zh_cn/documentation/ruby-from-other-languages/to-ruby-from-java/
延伸閱讀:https://gquintana.github.io/2017/01/08/From-Java-to-Ruby.html


Ruby 基礎

在大體瞭解了 Ruby 一些基礎信息以後,咱們開始 Ruby 基礎語法的學習,雖然面對一門新的語言,語法啊特性啊之類的瞭解頗有必要,但仍是想在瞭解以前看一看 Ruby 的一些代碼規範,好讓本身能快速瞭解 Ruby 的基礎上還能養成一個良好的編碼習慣。

學習以前必備 - 代碼規範

或許有些語句還不能理解,不要緊,有一個基礎印象就好。

  • 通常來說,Ruby 中的變量名和方法名使用下劃線命名法(小寫字母 + _),類名和模塊名使用 Java 相似的駝峯命名法

  • 每一個縮進級別使用兩個 space(又名軟 tabs),不要使用硬 tabs

# bad - four spaces
def some_method
    do_something
end

# good
def some_method
  do_something
end
  • 不用使用 ; 來分割語句和表達式。以此推論 - 一行使用一個表達式
# bad
puts 'foobar'; # superfluous semicolon

puts 'foo'; puts 'bar' # two expression on the same line

# good
puts 'foobar'

puts 'foo'
puts 'bar'

puts 'foo', 'bar' # this applies to puts in particular
  • 避免單行方法。即使仍是會受到一些人的歡迎,這裏仍是會有一些古怪的語法用起來很容易犯錯. 不管如何 - 應該一行不超過一個單行方法.
# bad
def too_much; something; something_else; end

# okish - notice that the first ; is required
def no_braces_method; body end

# okish - notice that the second ; is optional
def no_braces_method; body; end

# okish - valid syntax, but no ; make it kind of hard to read
def some_method() body end

# good
def some_method
  body
end

空方法是這個規則的例外。

# good
def no_op; end
  • 當賦值一個條件表達式的結果給一個變量時,保持分支的縮排在同一層。
# bad - pretty convoluted
kind = case year
when 1850..1889 then 'Blues'
when 1890..1909 then 'Ragtime'
when 1910..1929 then 'New Orleans Jazz'
when 1930..1939 then 'Swing'
when 1940..1950 then 'Bebop'
else 'Jazz'
end

result = if some_cond
  calc_something
else
  calc_something_else
end

# good - it's apparent what's going on
kind = case year
       when 1850..1889 then 'Blues'
       when 1890..1909 then 'Ragtime'
       when 1910..1929 then 'New Orleans Jazz'
       when 1930..1939 then 'Swing'
       when 1940..1950 then 'Bebop'
       else 'Jazz'
       end

result = if some_cond
           calc_something
         else
           calc_something_else
         end

# good (and a bit more width efficient)
kind =
  case year
  when 1850..1889 then 'Blues'
  when 1890..1909 then 'Ragtime'
  when 1910..1929 then 'New Orleans Jazz'
  when 1930..1939 then 'Swing'
  when 1940..1950 then 'Bebop'
  else 'Jazz'
  end

result =
  if some_cond
    calc_something
  else
    calc_something_else
  end
  • 在方法定義之間使用空行而且一個方法根據邏輯段來隔開。
def some_method
  data = initialize(options)

  data.manipulate!

  data.result
end

def some_methods
  result
end
  • 不要使用區塊註釋。它們不能由空白引導(=begin 必須頂頭開始),而且不如普通註釋容易辨認。
# bad
== begin
comment line
another comment line
== end

# good
# comment line
# another comment line
  • 使用括號將def的參數括起來。當方法不接收任何參數的時候忽略括號。
# bad
def some_method()
  # body omitted
end

# good
def some_method
  # body omitted
end

# bad
def some_method_with_arguments arg1, arg2
  # body omitted
end

# good
def some_method_with_arguments(arg1, arg2)
  # body omitted
end
  • 歷來不要使用 for, 除非你知道使用它的準確緣由。大多數時候迭代器均可以用來替 forfor 是由一組 each 實現的 (所以你正間接添加了一級),可是有一個小道道 - for 並不包含一個新的 scope (不像 each)而且在它的塊中定義的變量在外面也是能夠訪問的。(這裏有些像 Java 中不用 for-each 語句相似,感興趣的也能夠去搜一搜)
arr = [1, 2, 3]

# bad
for elem in arr do
  puts elem
end

# note that elem is accessible outside of the for loop
elem #=> 3

# good
arr.each { |elem| puts elem }

# elem is not accessible outside each's block
elem #=> NameError: undefined local variable or method `elem'
  • 利用 if and case 是表達式這樣的事實它們返回一個結果。
# bad
if condition
  result = x
else
  result = y
end

# good
result =
  if condition
    x
  else
    y
  end
  • 布爾表達式使用&&/||, and/or用於控制流程。(經驗Rule:若是你必須使用額外的括號(表達邏輯),那麼你正在使用錯誤的的操做符。)
# boolean expression
if some_condition && some_other_condition
  do_something
end

# control flow
document.save? or document.save!
  • 永遠不要使用 unlesselse 組合。將它們改寫成確定條件。
# bad
unless success?
  puts 'failure'
else
  puts 'success'
end

# good
if success?
  puts 'success'
else
  puts 'failure'
end
  • 不用使用括號包含 if/unless/while 的條件。
# bad
if (x > 10)
  # body omitted
end

# good
if x > 10
  # body omitted
end
  • 傾向使用 module,而不是隻有類方法的 class。類別應該只在建立實例是合理的時候使用。
# bad
class SomeClass
  def self.some_method
    # body omitted
  end

  def self.some_other_method
  end
end

# good
module SomeClass
  module_function

  def some_method
    # body omitted
  end

  def some_other_method
  end
end
  • 當你但願將模塊的實例方法變成 class 方法時,偏心使用 module_function 賽過 extend self
module Utilities
  extend self

  def parse_something(string)
    # do stuff here
  end

  def other_utility_method(number, string)
    # do some more stuff
  end
end

# good
module Utilities
  module_function

  def parse_something(string)
    # do stuff here
  end

  def other_utility_method(number, string)
    # do some more stuff
  end
end
  • 老是爲你本身的類提供 to_s 方法, 用來表現這個類(實例)對象包含的對象.
class Person
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def to_s
    "#@first_name #@last_name"
  end
end
  • 考慮使用 Struct.new, 它能夠定義一些瑣碎的 accessors, constructor(構造函數) 和 comparison(比較) 操做。
# good
class Person
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
end

# better
class Person < Struct.new(:first_name, :last_name)
end
  • 考慮使用 Struct.new,它替你定義了那些瑣碎的存取器(accessors),構造器(constructor)以及比較操做符(comparison operators)。
# good
class Person
  attr_accessor :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
end

# better
Person = Struct.new(:first_name, :last_name) do
end
  • 不要去 extend 一個 Struct.new - 它已是一個新的 class。擴展它會產生一個多餘的 class 層級 而且可能會產生怪異的錯誤若是文件被加載屢次。

  • 鴨子類型(duck-typing)優於繼承。

# bad
class Animal
  # abstract method
  def speak
  end
end

# extend superclass
class Duck < Animal
  def speak
    puts 'Quack! Quack'
  end
end

# extend superclass
class Dog < Animal
  def speak
    puts 'Bau! Bau!'
  end
end

# good
class Duck
  def speak
    puts 'Quack! Quack'
  end
end

class Dog
  def speak
    puts 'Bau! Bau!'
  end
end
  • 當訪問一個數組的第一個或者最後一個元素,傾向使用 first 或 last 而不是 [0] 或 [-1]。

  • 優先使用 字符串插值 來代替 字符串串聯

# bad
email_with_name = user.name + ' <' + user.email + '>'

# good
email_with_name = "#{user.name} <#{user.email}>"

# good
email_with_name = format('%s <%s>', user.name, user.email)
  • 在對象插值的時候不要使用 Object#to_s,它將會被自動調用。

  • 操做較大的字符串時, 避免使用 String#+ 作爲替代使用 String#<<。就地級聯字符串塊老是比 String#+ 更快,它建立了多個字符串對象。

# good and also fast
html = ''
html << '<h1>Page title</h1>'

paragraphs.each do |paragraph|
  html << "<p>#{paragraph}</p>"
end
  • RuboCop
    RuboCop 是一個基於本指南的 Ruby 代碼風格檢查工具。 RuboCop 涵蓋了本指南至關大的部分,支持 MRI 1.9 和 MRI 2.0,並且與 Emacs 整合良好。

  • RubyMine
    RubyMine 的代碼檢查是 部分基於 本指南的。

基於下面連接簡化了大部分,由於有一些編碼風格能從 Java 天然的切換過來,並且咱們也可使用相應的工具來檢查咱們的代碼,更多能夠看這裏:https://ruby-china.org/wiki/coding-style

Ruby 語言基礎

其實經過上面的語法規範,咱們或多或少能感知到一些 Ruby 的語法,有一些細節的地方能夠自行去菜鳥教程or其餘網站能看到,下面咱們就着一些重要的東西快速的學習一遍吧..

數據類型

Ruby 中有如下幾種不一樣的數據類型:

  • 數字/ 字符串/ 符號/ 哈希/ 數組/ 布爾
    比較在乎的是 Ruby 並無 Java 中的枚舉類型,多是出於安全方面的考慮吧..(我也不知道...)

  • 符號就像字符串。一個符號以前是冒號(:)。例如:

:abcd

它們不包含空格。 含有多個單詞的符號用(_)寫成。 字符串和符號之間的一個區別是,若是文本是一個數據,那麼它是一個字符串,但若是它是一個代碼,它是一個符號。

符號是惟一的標識符,表示靜態值,而字符串表示更改的值。

示例:

irb(main):011:0> "string".object_id
=> 26922000
irb(main):012:0> "string".object_id
=> 29115220
irb(main):013:0> :symbol.object_id
=> 788188
irb(main):014:0> :symbol.object_id
=> 788188
  • 哈希將其值分配給其鍵。 它們能夠用鍵關聯指定值。鍵的值由 => 符號分配。 鍵/值對之間用逗號分隔,全部對都用大括號括起來。 例如:
{"key1" => "value1", "key2" => "Chemistry", "key3" => "Maths"}

示例:

data = {"key1" => "Physics", "key2" => "Chemistry", "key3" => "Maths"}   
puts data["key1"]   
puts data["key2"]   
puts data["key3"]

執行上述代碼,獲得如下結果:

Physics
Chemistry
Maths

變量

Ruby 有四種類型的變量,變量的命名方式決定了變量的種類

  • 局部變量
    以英文小寫字母或者 _ 開頭,做用域等同於 Java 局部變量。

  • 全局變量
    $ 開頭,做用域等同於 Java 全局變量。只要全局變量的名稱相同,無論變量在程序的哪一個部分使用,程序都認爲是它們是同一個變量。未初始化的全局變量的值會被初始化爲:nil。建議不要使用全局變量,由於它們使程序變得祕密和複雜。

示例:

$global_var = "GLOBAL"   
class One   
  def display   
     puts "Global variable in One is #$global_var"   
  end   
end   
class Two   
  def display   
     puts "Global variable in Two is #$global_var"   
  end   
end   

oneobj = One.new   
oneobj.display   
twoobj = Two.new   
twoobj.display

執行代碼輸出結果以下:

Total number of states written: 4
Total number of states written: 4
Total number of states written: 4
Total number of states written: 4
  • 實例變量
    @ 開頭,在同一個實例中,程序能夠超越方法定義,任意引用、修改實例變量。它屬於類的一個實例,能夠從方法中的類的任何實例訪問。 它們只能訪問一個特定的類的實例。它們不須要初始化,未初始化的實例變量的值是:nil

示例:

class States   
   def initialize(name)   
      @states_name=name   
   end   
   def display()   
      puts "States name #@states_name"   
    end   
end   

# Create Objects   
first=States.new("Hainan")   
second=States.new("GuangDong")   
third=States.new("Beijing")   
fourth=States.new("ShangDong")   

# Call Methods   
first.display()   
second.display()   
third.display()   
fourth.display()

執行代碼輸出結果以下:

States name GuangDong
States name Beijing
States name ShangDong
  • 類變量
    @@ 開頭,做用域爲該類的全部實例。須要在使用前進行初始化,由類的全部後代共享,未初始化的變量將致使錯誤。

示例:

class States   
   @@no_of_states=0   
   def initialize(name)   
      @states_name=name   
      @@no_of_states += 1   
   end   
   def display()   
     puts "State name #@state_name"   
    end   
    def total_no_of_states()   
       puts "Total number of states written: #@@no_of_states"   
    end   
end   

# Create Objects   
first=States.new("Assam")   
second=States.new("Meghalaya")   
third=States.new("Maharashtra")   
fourth=States.new("Pondicherry")   

# Call Methods   
first.total_no_of_states()   
second.total_no_of_states()   
third.total_no_of_states()   
fourth.total_no_of_states()

執行上面代碼輸出結果以下:

Total number of states written: 4
Total number of states written: 4
Total number of states written: 4
Total number of states written: 4

類和對象

Object 類是全部 Ruby 對象的默認根。 Ruby 對象繼承自 BasicObject(它是Ruby中全部類的父類)類,容許建立替代對象層次結構。

首先與 Java 很不一樣的是建立對象:

Object newObject = new Object(); // Java 中新建對象

對比 Ruby:

objectName = className.new

每一個 Ruby 類都是 Class 類的一個實例。一般對於類名,使用駝峯命名規則,類的名稱始終以大寫字母開頭。定義類是用 end 關鍵字完成的。

語法

class ClassName  
    codes...  
end

咱們使用上面學習過的語法規範來建立一個 Person 類(寫上 to_s 方法):

class Person
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def to_s
    "#@first_name #@last_name"
  end
end

注意這裏的 attr_reader 對應在 Java 中至關於爲 first_namelast_name 定義了 getter ,在 Ruby 中,從對象外部不能直接訪問實例變量或對實例變量賦值,須要經過方法來訪問對象的內部,若是像 Java 那樣一遍一遍爲每個變量寫 getter/setter 就有些冗雜了,Ruby 爲咱們提供了一些方便的存取器。

定義 意義
attr_reader :name 只讀(定義 name 方法)
attr_writer :name 只寫(定義 name= 方法)
attr_accessor :name 讀寫(定義以上兩個方法)

另一點上面有一個很是有趣的規範是使用 Struct.new 來簡化代碼,我以爲很酷也想把它應用在上述 Person 類的建立中,可是發現失敗了(不能在其中定義其餘功能性的代碼),因此可能結論是:這樣的簡化只適用於一些實體類保存數據的類吧。

方法

Ruby 方法使用 def 關鍵字開始,最後還須要使用 end 關鍵字來表示方法定義結束。

語法:

def methodName  
    code...  
end

示例:

def method_defining(a1 = "Ruby", a2 = "Python")
   puts "The programming language is #{a1}"
   puts "The programming language is #{a2}"
end
method_defining "C", "C++"
method_defining

運行上面的代碼執行結果以下:

The programming language is C
The programming language is C++
The programming language is Ruby
The programming language is Python

方法返回值:

在初探 Ruby 的時候咱們就感覺到,貌似每一條指令都會返回一個返回值,方法也是這樣,在 Ruby 中每一個方法都有一個返回值,這個返回的值將是最後一個語句的值。例如:

def my_method
   i = 100
   j = 10
   k = 1
end

上面代碼中,最後方法的返回值是 1

Ruby return 語句

Ruby 中的 return 語句用於從 Ruby 方法中返回一個或多個值

示例:

def method
   i = 100
   j = 200
   k = 300
return i, j, k
end
var = method
puts var

上面代碼結果以下:

100
200
300

可變參數:

假設聲明一個方法須要兩個參數,每當調用這個方法時,須要傳遞兩個參數。可是,Ruby容許您聲明使用可變數量參數的方法。 讓咱們來看一下這個示例:

def sample (*test)
   puts "The number of parameters is #{test.length}"
   for i in 0...test.length
      puts "The parameters are #{test[i]}"
   end
end
sample "Maxsu", "6", "F"
sample "Mac", "38", "M", "MCA"

執行上面代碼,獲得以下結果:

The number of parameters is 3
The parameters are Maxsu
The parameters are 6
The parameters are F
The number of parameters is 4
The parameters are Mac
The parameters are 38
The parameters are M
The parameters are MCA

類方法:

當方法在類定義以外定義時,默認狀況下該方法被標記爲 private。 另外一方面,默認狀況下,類定義中定義的方法被標記爲 public。模塊的默承認見性和 private 標記能夠經過模塊的 publicprivate 更改。

Ruby 給出一種不用實例化一個類就能夠訪問一個方法。下面來看看看如何聲明和訪問類方法 -

class Accounts
   def reading_charge
   end
   def Accounts.return_date
   end
end

訪問類方法 -

Accounts.return_date

模板

Ruby 模塊是方法和常量的集合。暫時你可簡單的理解爲一個不能實例化的類,這樣作的好處是一來能夠提供一個命名空間避免名字衝突,另外一個是實現了 mixin 的功能。

不知道您有沒有發現,Ruby 沒有提供多重繼承的功能,但 Ruby 的模板幾乎消除了多重繼承的須要,提供了一種名爲 mixin 的裝置。

示例:

module A
  def a1
  end
  def a2
  end
end
module B
  def b1
  end
  def b2
  end
end

class Sample
  include A
  include B
  def s1
  end
end

samp=Sample.new
samp.a1
samp.a2
samp.b1
samp.b2
samp.s1

Ruby 代碼在其餘編程語言中被稱爲閉包。它由一組代碼組成,它們始終用大括號括起來,或者在 do..end 之間書寫。大括號語法老是具備比 do..end 語法更高的優先級。也就是說大括號優先級高,do..end 優先級低。

語法:

block_name{
   statement1
   statement2
   ..........
}

yield語句:

def test
   puts "在 test 方法內"
   yield
   puts "你又回到了 test 方法內"
   yield
end
test {puts "你在塊內"}

上面代碼運行結果以下:

在 test 方法內
你在塊內
你又回到了 test 方法內
你在塊內

塊和方法:

def test
  yield
end
test{ puts "Hello world"}

本實例是實現塊的最簡單的方式。您使用 yield 語句調用 test 塊。

可是若是方法的最後一個參數前帶有 &,那麼您能夠向該方法傳遞一個塊,且這個塊可被賦給最後一個參數。若是 *& 同時出如今參數列表中,& 應放在後面。

def test(&block)
   block.call
end
test { puts "Hello World!"}

上述代碼運行結果以下:

Hello World!

這一部分建議再去看一下慕課網上的教程,看關於第三章的內容便可:Ruby語言快速入門:https://www.imooc.com/learn/765


總結

通過以上簡單的學習,咱們對 Ruby 有了一個大體的瞭解,算是簡單入了個門(有一些簡單的例如循環啊,判斷啊,運算符之類的簡單我就沒有寫了),更多的東西須要本身平時的編碼中去總結學習(確定有一些坑須要本身去填的)。

參考文章:
1.Ruby 教程 - 菜鳥驛站 - http://www.runoob.com/ruby/ruby-intro.html
2.Ruby 教程 - 易百教程 - https://www.yiibai.com/ruby
3.20分鐘體驗 Ruby - https://www.ruby-lang.org/zh_cn/documentation/quickstart/
4.笨方法學 Ruby - https://lrthw.github.io/intro/
5.Ruby 快速入門 - https://wdxtub.com/2016/03/30/ruby-first-step/


按照慣例黏一個尾巴:

歡迎轉載,轉載請註明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz 歡迎關注公衆微信號:wmyskxz 分享本身的學習 & 學習資料 & 生活 想要交流的朋友也能夠加qq羣:3382693

相關文章
相關標籤/搜索