Go for Ruby developers(譯)

原文

如果你是一個(gè) Ruby 開(kāi)發(fā)者, 并且想要開(kāi)始使用 Go, 這篇文章可以幫助你入門(mén). 在這篇文章里, 我們會(huì)以 Ruby 的視角一探 Go 的那些很棒的特性.

Ruby is a dynamic, interpreted, reflective, object-oriented, general-purpose programming language. It was designed and developed in the mid-1990s by Yukihiro “Matz” Matsumoto in Japan.
According to the creator, Ruby was influenced by Perl, Smalltalk, Eiffel, Ada, and Lisp. It supports multiple programming paradigms, including functional, object-oriented, and imperative. It also has a dynamic type system and automatic memory management.

Go (often referred to as Golang) is a programming language designed by Google engineers Robert Griesemer, Rob Pike, and Ken Thompson. Go is a statically typed, compiled language in the tradition of C, with the added benefits of memory safety, garbage collection, structural typing, and CSP-style concurrency. The compiler, tools, and source code are all free and open source.
— Wikipedia

我們先從國(guó)際慣例 hello world 開(kāi)始:

Ruby

puts 'hello world'

Go

package main

// similar to require in ruby
import "fmt"

func main(){
  fmt.Println("Hello World!!!")
}

Ruby 中我們只用了一行,而 Go 里寫(xiě)了一大段. 先別對(duì) Go 失去信心, 我們一起看看這段 Go 代碼都做了什么.

在 Go 語(yǔ)言里面, 關(guān)鍵字 package 定義了代碼與引入庫(kù)的作用域. 在上面這個(gè)例子里, 我們用 package main 告訴 Go 編譯器這段代碼是可執(zhí)行的. 所有可執(zhí)行的 Go 程序都必須要有 package main .

關(guān)鍵字 import 與 Ruby 里的 require 有些像, 用來(lái)引入外部庫(kù). 這里我們引入了 fmt 庫(kù), 用來(lái)對(duì)輸入輸出做格式化.

main 函數(shù)是程序的執(zhí)行入口. 所有在 Go 里面執(zhí)行的代碼, 都會(huì)進(jìn)入 main 函數(shù)中. 在 Go 中, 可執(zhí)行的代碼被包在一對(duì)大括號(hào)之間. 并且, 左大括號(hào)必須與塊條件放在一行, 比如函數(shù), 分支或循環(huán)條件等.

命令行中執(zhí)行代碼

Ruby

把如下這段 Ruby 代碼復(fù)制到 hello_world.rb 文件中. ".rb" 是 Ruby 源碼文件的擴(kuò)展名. 我們可以用 ruby <filename> 運(yùn)行

> ruby hello_world.rb
Hello World!!!

Go

把如下這段 Go 代碼復(fù)制到 hello_world.go 文件中. ".go" 是 Go 源碼文件的擴(kuò)展名. 我們有兩種方式運(yùn)行下面這段代碼.

> go build hello-world.go
> ./hello-world
Hello World!!!

或者

> go run hello-world.go
Hello World!!!

第一種方式, 先使用 build 命令編譯了源碼文件同時(shí)生成了一個(gè)可執(zhí)行的二進(jìn)制文件然后運(yùn)行. 第二種方式, 我們直接使用 go run 命令執(zhí)行源碼. 這個(gè)命令實(shí)際上是在后臺(tái)編譯出了可執(zhí)行文件并運(yùn)行.

代碼注釋

Ruby 里使用 # 注釋代碼. Go 中, 單行代碼注釋使用 // , 多行注釋使用 /*..... */ .

Ruby

puts "Commenting Code in Ruby"
# this is single line comment in ruby
=begin
This is multiline 
Comment in ruby
=end

Go

package main

import "fmt"

func main(){
  fmt.Println("Commenting in Go")
  // this is single line comment in Go
  /* this is multiline
  Comment in Go */
}

變量

由于 Ruby 是動(dòng)態(tài)類型語(yǔ)音, 所以并不需要定義變量類型. 但是 Go 作為一個(gè)靜態(tài)類型語(yǔ)言, 就必須在聲明變量的同時(shí)定義類型.

Ruby

#Ruby
a = 1

Go

var a int = 1
// OR
// this dynamically declares type for variable a as int.
var a = 1
// this dynamically defines variable a and declares its type as int.
a := 1

在這個(gè)例子中可以看到, 盡管 Go 是個(gè)靜態(tài)類型語(yǔ)言, 但由于類型推導(dǎo)自動(dòng)定義了類型, 寫(xiě)起來(lái)可以像動(dòng)態(tài)語(yǔ)言一樣爽.

數(shù)據(jù)類型

這是 Go 的一些類型

var a bool = true
var b int = 1
// In go the string should be declared using double quote.
// Using single quote unlike in Ruby can be used only for single character for its byte representation
var c string = "hello world" 
var d float32 = 1.222
var x complex128 = cmplx.Sqrt(-5 + 12i)

在 Ruby 里面, 我們可以給一個(gè)變量賦值兩個(gè)不同類型的值, 但在 Go 里就不行.

a = 1
a = "Hello"
a := 1
a = "Hello"
// Gives error: cannot use "hello"(type string) as type int is assignment

Hash/Map(映射)

與 Ruby 一樣, Go 中我們也可以定義 hash 對(duì)象, 但 Go 里面叫 map. map 的語(yǔ)法是 map[string] int, 中括號(hào)內(nèi)用于指定 key 的類型, 而右邊這個(gè)用于指定 value 的類型.

#Ruby:
hash = {name:  'Nikita', lastname: 'Acharya'}
# Access data assigned to name key
name = hash[:name]

//Go
hash := map[string]string{"name":  "Nikita", "lastname": "Acharya"}
// Access data assigned to name key
name := hash["name"]

檢查一個(gè) key 是否在 map 中

我們可以通過(guò) 多重賦值的方式檢查一個(gè) key 是否存在. 在下面這個(gè)例子里, 如果 name 對(duì)象確實(shí)有 last_name 這個(gè) key , 那么 ok 變量將是 true 且 lastname 變量會(huì)賦值為 last_name 的值, 反之 ok 會(huì)是 false.


//GO
name := map[string]string{name: "Nikita"}
lastname, ok := name["lastName"]
if ok{
  fmt.Printf("Last Name: %v", lastname)
}else{
  fmt.Println("Last Name is missing")
}

Array(數(shù)組)

跟 Ruby 一樣, 我們也有數(shù)組. 但在 Go 里面, 我們需要在聲明的時(shí)候定義數(shù)組長(zhǎng)度.

#Ruby 
array = [1,2,3]
array := [3]int{1,2,3}
names := [2]string{"Nikita", "Aneeta"}

Slice(切片)

數(shù)組有個(gè)限制是不能在運(yùn)行時(shí)被修改. 也沒(méi)有提供嵌套數(shù)組的能力. 對(duì)于這個(gè)問(wèn)題, Go 有個(gè)數(shù)據(jù)類型叫 slices (切片). 切片有序的保存元素, 并且可以隨時(shí)被修改. 切片的定義與數(shù)組類似, 但不用聲明長(zhǎng)度.

var b []int

好, 接下來(lái)我們通過(guò)一些酷炫的玩法來(lái)對(duì)比一下 Ruby

給數(shù)組添加新元素

在 Ruby 中, 我們使用 + 給數(shù)組新增新元素. 在 Go 中 , 我們使用 append 函數(shù).

#Ruby
numbers = [1, 2]
a  = numbers + [3, 4]
#-> [1, 2, 3, 4]
// Go

numbers := []int{1,2}
numbers = append(numbers, 3, 4)
//->[1 2 3 4]

截取子數(shù)組

#Ruby 
number2 = [1, 2, 3, 4]
slice1 = number2[2..-1] # -> [3, 4]
slice2 = number2[0..2] # -> [1, 2, 3]
slice3 = number2[1..3] # -> [2, 3, 4]
//Go
// initialize a slice with 4 len, and values
number2 = []int{1,2,3,4}
fmt.Println(numbers) // -> [1 2 3 4]
// create sub slices
slice1 := number2[2:]
fmt.Println(slice1) // -> [3 4]
slice2 := number2[:3]
fmt.Println(slice2) // -> [1 2 3]
slice3 := number2[1:4]
fmt.Println(slice3) // -> [2 3 4]

復(fù)制數(shù)組(切片)

在 Ruby 中, 我們可以通過(guò)把數(shù)組賦值給另一個(gè)變量的方式直接復(fù)制數(shù)組. 在 Go 中, 我們不能直接賦值. 首先, 初始化一個(gè)與目標(biāo)數(shù)組長(zhǎng)度一致的數(shù)組, 然后使用 copy 方法.

#Ruby
array1 = [1, 2, 3, 4]
array2 = array2 # -> [1, 2, 3, 4]
//Go
// Make a copy of array1
array1 := []int{1,2,3,4} 
array2 := make(int[],4)
copy(array2, array1)

注意: 復(fù)制數(shù)組的元素?cái)?shù)量取決于目標(biāo)數(shù)組定義的長(zhǎng)度:

a := []int{1,2,3,4}
b := make([]int, 2)
copy(b, a) // copy a to b
fmt.Println(b)
//=> [1 2]

這里只有兩個(gè)元素被復(fù)制了, 因?yàn)?b 數(shù)組變量的長(zhǎng)度只有 2.

條件判斷

if...else

#Ruby
num = 9
if num < 0
 puts "#{num} is negative"
elsif num > 100 && num < 200
 puts "#{num} has 1 digit"
else
 puts "#{num} has multiple digits"
end
// go
num := 9
if num < 0 {
  fmt.Printf("%d is negative", num)
} else if num < 10 {
  fmt.Printf("%d has 1 digit", num)
} else {
  fmt.Printf("%d has multiple digits", num)
}

如果一個(gè)變量的作用域只在 if 塊之內(nèi), 那我們可以在 if 條件中給變量賦值, 如下:

if name, ok := address["city"]; ok {
 // do something
}

Switch Case

Go 中 switch case 的執(zhí)行方式與 Ruby 很類似 (只執(zhí)行符合條件的 case 子句, 而不會(huì)包括之后所有的 case). Go 中我們使用 switch...case 語(yǔ)法, 在 Ruby 中是 case...when.

#Ruby
def printNumberString(i)
  case i
  when 1
     puts "one"
  when 2
     puts "two"
  else 
     puts "none"
  end
end

printNumberString(1)
printNumberString(2)
//Go
func printNumberString(i int) {
  switch i {
  case 1:
    fmt.Println("one")
  case 2:
    fmt.Println("two")
  default:
    fmt.Println("none")
  }
}

func main(){
  printNumberString(1)
  //=> one
  printNumberString(2)
  //=> two
}

Type switches

用于匹配變量類型的 switch case 語(yǔ)法.

switch v := i.(type) {
    case int:
        fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
        fmt.Printf("%q is %v bytes long\n", v, len(v))
    default:
        fmt.Printf("I don't know about type %T!\n", v)
    }

循環(huán)

在 Go 中, 我們只有 loop. 但是, 我們可以用 loop 實(shí)現(xiàn) Ruby 中支持的各種不同的循環(huán). 我們來(lái)看一下:

基本循環(huán)

//Go
sum := 0
for i := 0; i < 10; i++ {
  sum += i
}
fmt.Println(sum)

While 循環(huán)

Go 中沒(méi)有 while 關(guān)鍵字. 但可以用下面這種方式實(shí)現(xiàn) while.

#Ruby
sum = 1
while sum < 1000
   sum += sum
end
puts sum
//Go
sum := 1
for sum < 1000 {
  sum += sum
}
fmt.Println(sum)

無(wú)限循環(huán)

#Ruby 
while true
  #do something
end
//Go
for {
  // do something
}

Each 循環(huán)

Go 中, 我們可以使用 loop with range 的方式遍歷映射或數(shù)組. 類似 Ruby 中的 each.

#Ruby
{name: 'Nikita', lastname: 'Acharya'}.each do |k, v|
  puts k, v
end
// Go
kvs := map[string]string{"name": "Nikita", "lastname": "Acharya"}
for k, v := range kvs {
   fmt.Printf("%s -> %s\n", k, v)
 }

Each with index

#Ruby
["a", "b", "c", "d", "e"].each_with_index do |value, index|
  puts "#{index} #{value}"
end
// Go
arr := []string{"a", "b", "c", "d", "e"}
for index, value := range arr {
  fmt.Println(index, value)
}

異常處理

Ruby 中, 我們使用 begin rescue 來(lái)處理異常.

#Ruby
begin
   #code 
rescue => e
   #rescue exception
end

在 Go 中, 我們有 Panic Defer 和 Recover 這幾種概念來(lái)捕獲與處理異常. 然而, 我們代碼中是極少需要手動(dòng)處理 panic 的, 通常由框架或庫(kù)來(lái)處理.

我們可以看看下面這個(gè)例子, 一個(gè) error 是怎么被 Panic 拋出, 然后通過(guò) defer 語(yǔ)句中 recover 關(guān)鍵字捕獲的. defer 語(yǔ)句會(huì)延遲到所在的函數(shù)結(jié)束之前執(zhí)行. 所以, 在下面這個(gè)例子里, 即使 panic 導(dǎo)致程序流程中斷, defer 語(yǔ)句仍然會(huì)執(zhí)行. 因此, 將 recover 語(yǔ)句放到 defer 當(dāng)中可以做出與 Ruby 中的 rescue 類似的效果.

//Go
package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

值得一提的是, 我們?nèi)粘?xiě)代碼通常不會(huì)直接使用 panic. 我們會(huì)使用 error 接口去檢查錯(cuò)誤, 并且做相應(yīng)的處理. 如下例子:

// Go
func Increment(n int) (int, error) {
    if n < 0 {
        // return error object
        return nil, errors.New("math: cannot process negative number")
    }
    return (n + 1), nil
}

func main() {
  num := 5

  if inc, err := Increment(num); err != nil {
    fmt.Printf("Failed Number: %v, error message: %v", num, err)
  }else {
    fmt.Printf("Incremented Number: %v", inc)
  }
}

Defer

比較像 Ruby 里的 ensure.

Defer 通常是用來(lái)做確保程序結(jié)束前會(huì)執(zhí)行的部分, 比如清理工作. 像關(guān)閉文件, 關(guān)閉 HTTP 連接.

Functions

Ruby 中, 與方法的參數(shù)類型一樣, 我們也不用指定返回類型. 但在 Go 中, 對(duì)函數(shù)的參數(shù)與返回都必須定義類型. 另外要注意 return 關(guān)鍵字在 Go 中是必須要寫(xiě)的, 不像 Ruby 里面可有可無(wú). 因?yàn)?Ruby 自動(dòng) return 方法的最后一行.

# Ruby
def multi_return(n)
  [n/10, n%10]
end
divisor, remainder = multi_return(5)
// Go
func MultiReturn(n int) (int, int) {
   return (n/10), (n % 10)
}
divisor, remainder := MultiReturn(5)

Pointer(指針)

在 Ruby 中, 我們沒(méi)有指針的概念. 在 Go 中, 我們用指針來(lái)持有某個(gè)值所在的內(nèi)存地址. 代碼中體現(xiàn)為 * 操作符. 另一個(gè)操作符是 &, 用來(lái)獲取某個(gè)值的內(nèi)存地址, 也就是生成一個(gè)指針. 下面這個(gè)例子會(huì)展示指針的引用與反引用.

// Go
//pointer initialization | 指針初始化
var a *int
b := 12
// point to the address of b | 將 a 指向 b 的內(nèi)存地址
a = &b
// read the value of b through pointer | 通過(guò)指針讀取相應(yīng)內(nèi)存地址的值
fmt.Println(*a) // => 12
// set value of b through pointer | 通過(guò)指針設(shè)置相應(yīng)內(nèi)存地址的值
*a = 5
fmt.Println(b) // => 5

面向?qū)ο缶幊?/h3>

Go 是一種輕量級(jí)的面向?qū)ο笳Z(yǔ)言. 可以使用 struct 提供封裝與類型成員函數(shù), 但是沒(méi)有面向?qū)ο笳Z(yǔ)常見(jiàn)的集成. 我們可以把一個(gè)函數(shù)綁定到 struct 上. 然后當(dāng)我創(chuàng)建 struct 實(shí)例后, 就可以擁有訪問(wèn)實(shí)例變量與綁定在其上的方法的能力

# Ruby
class Festival
  def initialize(name, description)
    @name = name
    @description = description
  end

  def is_dashain?
    if @name == "Dashain"
      @description = "Let's fly kites."
      true
    else
      false
    end
  end

  def to_s
    "#{@name}: #{@description}"
  end
package main

import "fmt"

type Festival struct {
  Name string
  Description string
}

// Festival type method
func (f *Festival) IsDashain() bool {
  if f.Name == "Dashain" {
    f.Description = "Let's fly kites."
    return true
  } else {
    return false
  }
}

// Festival type method
func (f *Festival) ToString() string {
  return fmt.Sprintf("%v: %v", f.Name, f.Description)
}

func main(){
  festival := Festival{"Tihar", "Tihar is the festival of lights."}  
  if festival.IsDashain() {
    fmt.Println("Festival:", festival.ToString())
  } else {
    fmt.Println("Let's celebrate", festival.ToString())
  }
}

在上面這個(gè)例子中, 我們用 struct 定義了一個(gè)新類型 Festival. 可以看到, 對(duì)于 Festival 我們也定義了兩種函數(shù) IsDashain ToString. 這些函數(shù)對(duì)于 Festival 實(shí)例都是可訪問(wèn)的. Go, 這類函數(shù)稱之為 方法. 分別方法與函數(shù), 可以看它是否依附于某個(gè)對(duì)象, 在其他語(yǔ)言中大致也是一樣的約定. 另外我們可以看到對(duì)于方法定義, 我們是針對(duì) Festival 指針創(chuàng)建的方法. 對(duì)于指針接受者, 有兩個(gè)用法值得注意.

  1. 當(dāng)需要修改方法接受者, 接受者就必須是指針類型. 在上面這個(gè)例子中, 當(dāng) festival 的 Name 屬性為 Dashain, 這個(gè)方法就會(huì)修改 Description 的值, 并且也可以被 main 函數(shù)訪問(wèn).

  2. 如果參數(shù)太大, 可以考慮改為傳入指針的方式以避免可能的性能問(wèn)題.

注意: Go 的 interface(接口) 可以提供類似 Ruby 中繼承/混合的感覺(jué)以及更多 OOP 的靈活性.

公有/私有 方法與變量

Ruby 中, 我們用 private 關(guān)鍵字來(lái)定義私有方法. 在 Go, 定義私有方法或私有變量, 只需要將名稱首字母小寫(xiě)就可以了.

#Ruby
class User
  attr_accessor :user_name

  def initialize(user_name, password)
    @user_name = user_name
    @password = password
  end

  def get_user_password
    secret_password
  end

  private

  def secret_password
    @password
  end
end

u = User.new("hello", "password")
puts "username: #{u.user_name}"
puts "password: #{u.get_user_password}"
// Go
type User struct {
  Username string
  password string
}

func (u User) GetPassword() string{
  return u.secretPassword()
}

func (u User) secretPassword() string {
  return u.password
}

func main(){
  user := User{"username", "password"}  
  fmt.Println("username: " + user.Username)
  fmt.Println("password: " + user.GetPassword())
}

JSON 序列化/反序列化

Go 中, 我們用 json 的 Marshal 與 Unmarshal 來(lái)做 序列號(hào)與反序列化.

JSON 序列化

#Ruby
hash = {apple: 5, lettuce: 7}
#encoding hash to json
json_data = hash.to_json
puts json_data
// Go
package main

import (
    "encoding/json"
    "fmt"
)

func main() {

    // Go
    mapA := map[string]int{"apple": 5, "lettuce": 7}
    // encoding map type to json strings
    mapB, _ := json.Marshal(mapA)
    fmt.Println(string(mapB))
}

JSON 反序列化

#ruby 
str = `{"page": 1, "fruits": ["apple", "peach"]}`
JSON.parse(str)
// Go
package main

import (
    "encoding/json"
    "fmt"
)

// Go
type response struct {
    Page   int      `json:"page"`
    Fruits []string `json:"fruits"`
}

func main() {

    str := `{"page": 1, "fruits": ["apple", "peach"]}`
    res := response{}
    json.Unmarshal([]byte(str), &res)
    fmt.Println(res.Page)
    //=> 1
}

注意: 上面這個(gè)例子里, json:"page" 命令用于將 json 中的 page 對(duì)應(yīng)到 struct 的 Page 屬性.

Package(庫(kù))

Go 的 Package 與 Ruby 中的 Gem 類似. Ruby 中, 我們用 gem install <gem name> 安裝一個(gè) gem . Go 中, 是go get <package path>.

我們也有一些內(nèi)置的基礎(chǔ)庫(kù). 常見(jiàn)的有:

fmt

顧名思義, 這個(gè)庫(kù)用于做代碼格式化. 這是一個(gè)很好用的庫(kù). 在 Ruby 中, 我們遵循 rubocop 的代碼規(guī)范做代碼格式化. 但在 Go 中, 我們不用太操心這些事. fmt 庫(kù)會(huì)幫我們處理. 只需要在寫(xiě)完代碼以后, 執(zhí)行一下 gofmtgo fmt就好了.

gofmt -w yourcode.go
//OR
go fmt path/to/your/package

代碼會(huì)自動(dòng)格式化.

log

這個(gè)庫(kù)與 Ruby 的 logger 很像. 它定義了類型 Logger, 包含格式化輸出相關(guān)的各種方法.

net/http

用于創(chuàng)建 HTTP 請(qǐng)求. 與 Ruby 的 net http 很像.

Go 的并發(fā)

不像 Ruby, 我們?nèi)绻霾l(fā)就需要引入一些額外的并發(fā)庫(kù)比如 celluloid . Go 自帶了并發(fā)能力. 我們只需要在需要并發(fā)操作的地方在前面加上 go 關(guān)鍵字就好了. 下面這個(gè)例子里, 我們?cè)?main 函數(shù)中用 go 關(guān)鍵字并發(fā)執(zhí)行了 f("goroutine").

package main

import(
 "fmt"
 "time"
)

func f(from string) {
    for i := 0; i < 3; i++ {
    time.Sleep(1 * time.Millisecond)
        fmt.Println(from, ":", i)
    }
}

func main() {
    // executing the function in another goroutine concurrently with the main.
    go f("goroutine")

    // calling synchronously
    // processing in current main thread
    f("main")
    fmt.Println("done")
}

[Running] go run "/home/lux/gogo/goroutine_example.go"

main : 0

goroutine : 0

goroutine : 1

main : 1

main : 2

done

[Done] exited with code=0 in 0.392 seconds

[Running] go run "/home/lux/gogo/goroutine_example.go"

goroutine : 0

main : 0

goroutine : 1

main : 1

goroutine : 2

main : 2

done

[Done] exited with code=0 in 0.181 seconds

還有一個(gè)概念稱之為 Channels (管道), 用于兩個(gè) goroutine 之間的通信.

關(guān)于 Go 的更多內(nèi)容

  1. https://golang.org/doc/effective_go.html

  2. https://play.golang.org/

  3. https://tour.golang.org

  4. https://gobyexample.com/

  5. https://www.safaribooksonline.com/videos/mastering-go-programming/9781786468239

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.安裝 https://studygolang.com/dl 2.使用vscode編輯器安裝go插件 3.go語(yǔ)...
    go含羞草閱讀 1,693評(píng)論 0 6
  • 環(huán)境搭建 Golang在Mac OS上的環(huán)境配置 使用Visual Studio Code輔助Go源碼編寫(xiě) VS ...
    隕石墜滅閱讀 5,860評(píng)論 0 5
  • 概要 64學(xué)時(shí) 3.5學(xué)分 章節(jié)安排 電子商務(wù)網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,853評(píng)論 0 3
  • 第一次你握著我的手過(guò)生日 謝謝你的陪伴, 不要求禮物,不要求大餐 你的擁抱和陪伴,我就很知足 以后的每一個(gè),都要與...
    簡(jiǎn)見(jiàn)揀閱讀 92評(píng)論 0 0
  • 情緒:感恩 情緒來(lái)源:今天早上八點(diǎn)開(kāi)車從廣西回廣州上班,結(jié)果現(xiàn)在準(zhǔn)備0點(diǎn)多才到,路上把雷銘老師的《九型人格》又聽(tīng)了...
    頑伴高振裕閱讀 172評(píng)論 0 1

友情鏈接更多精彩內(nèi)容