Go contoh dan idiom [tertutup]

91

Tidak banyak kode Go untuk dipelajari, dan saya yakin saya bukan satu-satunya yang bereksperimen dengannya. Jadi, jika Anda menemukan sesuatu yang menarik tentang bahasa tersebut, silakan kirim contohnya di sini.

Saya juga mencari

  • cara idiomatis untuk melakukan sesuatu di Go,
  • Gaya berpikir C / C ++ "diporting" ke Go,
  • kesalahan umum tentang sintaksis,
  • sesuatu yang menarik, sungguh.
György Andrasek
sumber
Dukungan ARM seperti 8-bit atau 16-bit. Bahasa D masih belum.
1
Perpustakaan ( golang.org/pkg ) adalah sumber yang sangat baik untuk mempelajari bagaimana go digunakan. Secara pribadi, saya menemukan bahwa mempelajari bagaimana struktur data diimplementasikan sangat membantu untuk mempelajari bahasa.
tkokasih

Jawaban:

35

Tunda pernyataan

Pernyataan "defer" memanggil fungsi yang eksekusinya ditangguhkan saat fungsi di sekitarnya kembali.

DeferStmt = Ekspresi "menunda".

Ekspresi tersebut harus berupa pemanggilan fungsi atau metode. Setiap kali pernyataan "defer" dijalankan, parameter ke pemanggilan fungsi dievaluasi dan disimpan lagi tetapi fungsi tersebut tidak dipanggil. Panggilan fungsi yang ditangguhkan dijalankan dalam urutan LIFO segera sebelum fungsi sekitarnya kembali, tetapi setelah nilai yang dikembalikan, jika ada, telah dievaluasi.


lock(l);
defer unlock(l);  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
    defer fmt.Print(i);
}

Memperbarui:

defersekarang juga merupakan cara idiomatik untuk menangani panicdengan cara seperti pengecualian :

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)
}
György Andrasek
sumber
17
Sepertinya RAII lama yang bagus (dibuat eksplisit).
Konrad Rudolph
4
+1 karena saya banyak membaca tentang Go, tetapi saya masih tidak melihat ini (sampai Anda menunjukkannya kepada saya)!
u0b34a0f6ae
Pintar, meskipun akan lebih masuk akal bagi saya jika menunda pernyataan yang dieksekusi dalam urutan FIFO (dari atas ke bawah), tapi mungkin itu hanya saya ...
Mike Spross
Keren. Mengingatkan saya pada scrop guard dari D digitalmars.com/d/2.0/exception-safe.html
hasen
4
@ Mike: jika Anda membandingkan dengan blok "coba: .. akhirnya:" LIFO bersarang dengan cara yang sama. Untuk pasangan buka / tutup sumber daya dll, menumpuk seperti ini adalah satu-satunya hal yang masuk akal (Pembukaan pertama akan ditutup terakhir).
u0b34a0f6ae
25

File objek Go sebenarnya menyertakan header cleartext:

jurily@jurily ~/workspace/go/euler31 $ 6g euler31.go
jurily@jurily ~/workspace/go/euler31 $ cat euler31.6
amd64
  exports automatically generated from
  euler31.go in package "main"
    import

$$  // exports
  package main
    var main.coin [9]int
    func main.howmany (amount int, max int) (? int)
    func main.main ()
    var main.initdone· uint8
    func main.init ()

$$  // local types
  type main.dsigddd_1·1 struct { ? int }

$$

!
<binary segment>
György Andrasek
sumber
6
Itu lebih seperti fitur tersembunyi daripada contoh idiomatik
hasen
22

Saya telah melihat beberapa orang mengeluh tentang loop-for, di sepanjang baris "mengapa kita harus mengatakan i = 0; i < len; i++di zaman sekarang ini?".

Saya tidak setuju, saya suka untuk konstruksi. Anda dapat menggunakan versi panjang jika Anda mau, tetapi idiomatis Go adalah

var a = []int{1, 2, 3}
for i, v := range a {
    fmt.Println(i, v)
}

The for .. rangemembangun loop atas semua elemen dan persediaan dua nilai - indeks idan nilai v.

range juga berfungsi pada peta dan saluran.

Namun, jika Anda tidak suka fordalam bentuk apa pun, Anda dapat menentukan each, mapdll. Dalam beberapa baris:

type IntArr []int

// 'each' takes a function argument.
// The function must accept two ints, the index and value,
// and will be called on each element in turn.
func (a IntArr) each(fn func(index, value int)) {
    for i, v := range a {
        fn(i, v)
    }
}

func main() {
    var a = IntArr([]int{2, 0, 0, 9}) // create int slice and cast to IntArr
    var fnPrint = func(i, v int) {
        fmt.Println(i, ":", v)
    } // create a function

    a.each(fnPrint) // call on each element
}

cetakan

0 : 2
1 : 0
2 : 0
3 : 9

Saya mulai sangat menyukai Go :)

jg-faustus
sumber
Meskipun rangehanya bagus, jika dikompilasi ke kode yang sama dengan loop for-3.
Thomas Ahle
19

Pergi dan dapatkan reputasi stackoverflow Anda

Ini adalah terjemahan dari jawaban ini .

package main

import (
    "json"
    "fmt"
    "http"
    "os"
    "strings"
)

func die(message string) {
    fmt.Printf("%s.\n", message);
    os.Exit(1);
}

func main() {
    kinopiko_flair := "https://stackoverflow.com/users/flair/181548.json"
    response, _, err := http.Get(kinopiko_flair)
    if err != nil {
        die(fmt.Sprintf("Error getting %s", kinopiko_flair))
    }

    var nr int
    const buf_size = 0x1000
    buf := make([]byte, buf_size)

    nr, err = response.Body.Read(buf)
    if err != nil && error != os.EOF {
        die(fmt.Sprintf("Error reading response: %s", err.String()))
    }
    if nr >= buf_size { die ("Buffer overrun") }
    response.Body.Close()

    json_text := strings.Split(string(buf), "\000", 2)
    parsed, ok, errtok := json.StringToJson(json_text[0])
    if ! ok {
        die(fmt.Sprintf("Error parsing JSON %s at %s", json_text, errtok))
    }

    fmt.Printf("Your stackoverflow.com reputation is %s\n", parsed.Get ("reputation"))
}

Terima kasih kepada Scott Wales atas bantuannya dengan .Read ().

Ini masih terlihat cukup kikuk, dengan dua string dan dua buffer, jadi jika ada pakar Go yang memiliki saran, beri tahu saya.

pengguna181548
sumber
Saya tidak yakin apa yang seharusnya salah dengan pemformatan; Saya telah memulihkannya.
5
Penulis Go merekomendasikan ke gofmtkode Anda :-)
ℝaphink
Saya tidak dapat mengkompilasinya: $ ../go/src/cmd/6g/6g SO.go SO.go: 34: undefined: json.StringToJson
ℝaphink
@Raphink: bahasanya telah berubah sejak saya membuat ini.
Ya, apakah Anda tahu mungkin apa yang paling mirip dengan StringToJson? Dulu menyiapkan pembangun secara internal, sekarang seseorang harus menyediakan sendiri dengan struktur asli yang telah ditentukan?
macbirdie
19

Inilah contoh bagus iota dari pos Kinopiko :

type ByteSize float64
const (
    _ = iota;   // ignore first value by assigning to blank identifier
    KB ByteSize = 1<<(10*iota)
    MB
    GB
    TB
    PB
    YB
)

// This implicitly repeats to fill in all the values (!)
György Andrasek
sumber
5
Perhatikan bahwa titik koma tidak diperlukan.
mk12
18

Anda dapat menukar variabel dengan tugas paralel:

x, y = y, x

// or in an array
a[j], a[i] = a[i], a[j]

sederhana tetapi efektif.

u0b34a0f6ae
sumber
18

Berikut ini adalah idiom dari Efektif Go halaman

switch {
case '0' <= c && c <= '9':
    return c - '0'
case 'a' <= c && c <= 'f':
    return c - 'a' + 10
case 'A' <= c && c <= 'F':
    return c - 'A' + 10
}
return 0

Pernyataan switch menjadi true ketika tidak ada ekspresi yang diberikan. Jadi ini setara dengan

if '0' <= c && c <= '9' {
    return c - '0'
} else if 'a' <= c && c <= 'f' {
    return c - 'a' + 10
} else if 'A' <= c && c <= 'F' {
    return c - 'A' + 10
}
return 0

Saat ini, versi sakelar tampak sedikit lebih bersih bagi saya.

Rob Russell
sumber
6
Wah, benar-benar robek dari VB. ;-) ( Switch True…)
Konrad Rudolph
@Konrad, kalahkan aku! :) Saya telah menggunakan idiom itu dalam kode VB6 sebelumnya dan itu pasti dapat membantu keterbacaan dalam situasi tertentu.
Mike Spross
Apa itu '<='? Apakah itu terkait dengan '<-'?
ℝaphink
@Raphink: kurang dari atau sama.
Paul Ruane
17

Jenis sakelar :

switch i := x.(type) {
case nil:
    printString("x is nil");
case int:
    printInt(i);  // i is an int
case float:
    printFloat(i);  // i is a float
case func(int) float:
    printFunction(i);  // i is a function
case bool, string:
    printString("type is bool or string");  // i is an interface{}
default:
    printString("don't know the type");
}
György Andrasek
sumber
16

Saat mengimpor paket, Anda dapat menentukan ulang namanya menjadi apa pun yang Anda inginkan:

package main

import f "fmt"

func main() {
    f.Printf("Hello World\n")
}
Alvin Row
sumber
3
Saya sudah menggunakan ini: stackoverflow.com/questions/1726698/…
14

Parameter hasil bernama

Kembali atau hasil "parameter" dari fungsi Go dapat diberi nama dan digunakan sebagai variabel biasa, seperti parameter yang masuk. Ketika dinamai, mereka diinisialisasi ke nilai nol untuk tipenya saat fungsi dimulai; jika fungsi menjalankan pernyataan pengembalian tanpa argumen, nilai saat ini dari parameter hasil digunakan sebagai nilai yang dikembalikan.

Nama tidak wajib tetapi dapat membuat kode lebih pendek dan lebih jelas: ini adalah dokumentasi. Jika kita menamai hasil nextInt maka menjadi jelas mana yang dikembalikan int adalah yang mana.

func nextInt(b []byte, pos int) (value, nextPos int) {

Karena hasil yang diberi nama diinisialisasi dan dikaitkan dengan pengembalian tanpa hiasan, hasil tersebut dapat menyederhanakan dan juga memperjelas. Ini adalah versi io.ReadFull yang menggunakannya dengan baik:

func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
    for len(buf) > 0 && err == nil {
        var nr int;
        nr, err = r.Read(buf);
        n += nr;
        buf = buf[nr:len(buf)];
    }
    return;
}
György Andrasek
sumber
1
Saya penasaran - apakah ada bahasa lain yang memiliki ini?
u0b34a0f6ae
1
Matlab memiliki sesuatu yang serupa.
Dan Lorenc
pascal menggunakan sintaks yang serupa untuk mengembalikan satu nilai.
nes1983
1
@ nes1983 Bagi mereka yang tidak tahu, di Pascal Anda secara klasik menetapkan nilai kembali ke nama fungsi.
fuz
FORTRAN memiliki cukup banyak ini.
Hut8
14

Dari jawaban James Antill :

foo := <-ch     // This blocks.
foo, ok := <-ch // This returns immediately.

Juga, potensi jebakan: perbedaan halus antara operator penerima dan pengirim:

a <- ch // sends ch to channel a
<-ch    // reads from channel ch
György Andrasek
sumber
3
Menerima Operator itu sendiri adalah sekarang operasi pemblokiran, sebagai Go 1.0.3. Spesifikasi telah diubah: golang.org/ref/spec#Receive_operator . Silakan coba perilaku pemblokiran (deadlock) di sini: play.golang.org/p/0yurtWW4Q3
Deleplace
13
/* 
 * How many different ways can £2 be made using any number of coins?
 * Now with 100% less semicolons!
 */

package main
import "fmt"


/* This line took me over 10 minutes to figure out.
 *  "[...]" means "figure out the size yourself"
 * If you only specify "[]", it will try to create a slice, which is a reference to an existing array.
 * Also, ":=" doesn't work here.
 */
var coin = [...]int{0, 1, 2, 5, 10, 20, 50, 100, 200}

func howmany(amount int, max int) int {
    if amount == 0 { return 1 }
    if amount < 0 { return 0 }
    if max <= 0 && amount >= 1 { return 0 }

    // recursion works as expected
    return howmany(amount, max-1) + howmany(amount-coin[max], max)
}


func main() {
    fmt.Println(howmany(200, len(coin)-1))
}
György Andrasek
sumber
4
Saya sarankan menghapus nama situs pemecahan masalah serta nomor id. Mungkin ulangi pertanyaannya. Agar tidak merusak masalah kepada seseorang yang tersandung olehnya. Atau mencoba menipu dengan mencari masalah di internet.
Mizipzor
1
Sebagai catatan: ini adalah algoritme dari algoritmeist.com/index.php/Coin_Change Ini adalah hasil Google pertama untuk "perubahan koin".
György Andrasek
13

Saya suka Anda dapat mendefinisikan ulang tipe, termasuk primitif seperti int, sebanyak yang Anda suka dan melampirkan metode yang berbeda. Seperti mendefinisikan tipe RomanNumeral:

package main

import (
    "fmt"
    "strings"
)

var numText = "zero one two three four five six seven eight nine ten"
var numRoman = "- I II III IV V VI VII IX X"
var aText = strings.Split(numText, " ")
var aRoman = strings.Split(numRoman, " ")

type TextNumber int
type RomanNumber int

func (n TextNumber) String() string {
    return aText[n]
}

func (n RomanNumber) String() string {
    return aRoman[n]
}

func main() {
    var i = 5
    fmt.Println("Number: ", i, TextNumber(i), RomanNumber(i))
}

Yang mencetak

Number:  5 five V

The RomanNumber()panggilan dasarnya gips, itu mengubah jenis int sebagai jenis yang lebih spesifik dari int. Dan Println()panggilan di String()belakang layar.

jg-faustus
sumber
12

Mengembalikan saluran

Ini adalah idiom sejati yang cukup penting: bagaimana memasukkan data ke dalam saluran dan menutupnya setelahnya. Dengan ini Anda dapat membuat iterator sederhana (karena range akan menerima saluran) atau filter.

// return a channel that doubles the values in the input channel
func DoublingIterator(input chan int) chan int {
    outch := make(chan int);
    // start a goroutine to feed the channel (asynchronously)
    go func() {
        for x := range input {
            outch <- 2*x;    
        }
        // close the channel we created and control
        close(outch);
    }();
    return outch;
}
u0b34a0f6ae
sumber
+1. Selain itu, Anda juga dapat melewatkan saluran melalui saluran.
György Andrasek
5
Tapi hati-hati agar tidak keluar dari loop for x: = range chan {}, Anda akan membocorkan goroutine, dan semua memori yang direferensikannya.
Jeff Allen
3
@JeffAllen bagaimana defer close(outch);dengan pernyataan pertama dari goroutine?
1
Tunda antrian pernyataan untuk dieksekusi ketika fungsi kembali, tidak peduli titik pengembalian mana yang diambil. Tetapi jika input saluran tidak pernah ditutup, maka fungsi anonim dalam contoh ini tidak akan pernah meninggalkan loop for.
Jeff Allen
11

Batas waktu untuk pembacaan saluran:

ticker := time.NewTicker(ns);
select {
    case v := <- chan_target:
        do_something_with_v;
    case <- ticker.C:
        handle_timeout;
}

Dicuri dari Davies Liu .

György Andrasek
sumber
11
for {
    v := <-ch
    if closed(ch) {
        break
    }
    fmt.Println(v)
}

Karena range secara otomatis memeriksa saluran tertutup, kita dapat mempersingkatnya menjadi ini:

for v := range ch {
    fmt.Println(v)
}
mbarkhau
sumber
9

Ada pengaturan sistem make yang dapat Anda gunakan di $ GOROOT / src

Siapkan makefile Anda dengan

TARG=foobar           # Name of package to compile
GOFILES=foo.go bar.go # Go sources
CGOFILES=bang.cgo     # Sources to run cgo on
OFILES=a_c_file.$O    # Sources compiled with $Oc
                      # $O is the arch number (6 for x86_64)

include $(GOROOT)/src/Make.$(GOARCH)
include $(GOROOT)/src/Make.pkg

Anda kemudian dapat menggunakan alat pengujian otomatis dengan menjalankan make test, atau menambahkan paket dan objek bersama dari cgo ke $ GOROOT Anda dengan make install.

Scott Wales
sumber
7

Hal menarik lainnya di Go adalah itu godoc. Anda dapat menjalankannya sebagai server web di komputer Anda menggunakan

godoc -http=:8080

di mana 8080 adalah nomor port, dan seluruh situs web di golang.org kemudian tersedia di localhost:8080.

pengguna181548
sumber
Apakah ini program biasa atau daemon?
György Andrasek
Ini adalah program biasa.
Jeremy
7

Ini adalah implementasi dari tumpukan. Ini mengilustrasikan menambahkan metode ke sebuah tipe.

Saya ingin membuat bagian tumpukannya menjadi irisan dan menggunakan properti irisan, tetapi meskipun saya membuatnya bekerja tanpa type, saya tidak dapat melihat sintaks untuk menentukan irisan dengan a type.

package main

import "fmt"
import "os"

const stack_max = 100

type Stack2 struct {
    stack [stack_max]string
    size  int
}

func (s *Stack2) push(pushed_string string) {
    n := s.size
    if n >= stack_max-1 {
        fmt.Print("Oh noes\n")
        os.Exit(1)
    }
    s.size++
    s.stack[n] = pushed_string
}

func (s *Stack2) pop() string {
    n := s.size
    if n == 0 {
        fmt.Print("Underflow\n")
        os.Exit(1)
    }
    top := s.stack[n-1]
    s.size--
    return top
}

func (s *Stack2) print_all() {
    n := s.size
    fmt.Printf("Stack size is %d\n", n)
    for i := 0; i < n; i++ {
        fmt.Printf("%d:\t%s\n", i, s.stack[i])
    }
}

func main() {
    stack := new(Stack2)
    stack.print_all()
    stack.push("boo")
    stack.print_all()
    popped := stack.pop()
    fmt.Printf("Stack top is %s\n", popped)
    stack.print_all()
    stack.push("moo")
    stack.push("zoo")
    stack.print_all()
    popped2 := stack.pop()
    fmt.Printf("Stack top is %s\n", popped2)
    stack.print_all()
}
pengguna181548
sumber
10
Daripada menggunakan fmt.Printf(...); os.Exit();, Anda bisa menggunakan panic(...).
notnoop
1
Itu memberikan jejak tumpukan, yang tidak saya inginkan.
3
Mengapa dibatasi? Go adalah bahasa terkelola dan gc'd. Tumpukan Anda bisa sedalam yang Anda inginkan. Gunakan append () builtin yang baru, yang akan melakukan sesuatu seperti realokasi C jika diperlukan.
Jeff Allen
"Pergi tidak perlu obat generik", kata mereka.
cubuspl42
4

Memanggil kode c dari go

Anda dapat mengakses tingkat perjalanan yang lebih rendah dengan menggunakan runtime c.

Fungsi C ada dalam bentuk

void package·function(...)

(perhatikan pemisah titik adalah karakter unicode) di mana argumen dapat berupa tipe dasar go, irisan, string, dll. Untuk mengembalikan panggilan nilai

FLUSH(&ret)

(Anda dapat mengembalikan lebih dari satu nilai)

Misalnya untuk membuat sebuah fungsi

package foo
bar( a int32, b string )(c float32 ){
    c = 1.3 + float32(a - int32(len(b))
}

di C yang Anda gunakan

#include "runtime.h"
void foo·bar(int32 a, String b, float32 c){
    c = 1.3 + a - b.len;
    FLUSH(&c);
}

Perhatikan bahwa Anda masih harus mendeklarasikan fungsi dalam file go, dan Anda harus menjaga memorinya sendiri. Saya tidak yakin apakah mungkin untuk memanggil pustaka eksternal menggunakan ini, mungkin lebih baik menggunakan cgo.

Lihat $ GOROOT / src / pkg / runtime untuk contoh yang digunakan dalam runtime.

Lihat juga jawaban ini untuk menghubungkan kode c ++ dengan go.

Scott Wales
sumber
3
Benarkah itu menggunakan "titik terbang"? Saya tidak berani mengedit, tapi sepertinya agak tidak terduga dan radikal.
bersantai
Ya, Anda perlu mengkompilasi dengan 6c (atau 8c, dll). Menurut saya gcc tidak menangani pengenal unicode.
Scott Wales
1
Saya pikir AltGr + tipe periode yang sama · tetapi dengan unicode saya tidak yakin. Sangat terkejut melihat bahwa di sumber yang saya baca .. mengapa tidak menggunakan sesuatu seperti ::?
u0b34a0f6ae
Karakternya adalah MIDDLE DOT U + 00B7. Pengurai mungkin telah dipalsukan sehingga melihat ini sebagai karakter untuk membuat pengenal c yang valid, yang saya percaya akan menghalangi ::.
Scott Wales
4
'·' Hanyalah peretasan sementara, rob bahkan terkejut bahwa itu masih ada, katanya akan diganti dengan sesuatu yang tidak terlalu aneh.
uriel
3

Apakah Anda menonton pembicaraan ini ? Ini menunjukkan banyak hal keren yang dapat Anda lakukan (akhir pembicaraan)

pengguna180100
sumber
2
Ya saya lakukan. Intinya adalah "Ada lebih banyak lagi di sana, mari kita lanjutkan ke topik berikutnya."
György Andrasek
Ya, tampaknya banyak yang bisa dikatakan dengan sedikit waktu
3

Tumpukan berdasarkan jawaban lain, tetapi menggunakan penambahan irisan tidak memiliki batas ukuran.

package main

import "fmt"
import "os"

type Stack2 struct {
        // initial storage space for the stack
        stack [10]string
        cur   []string
}

func (s *Stack2) push(pushed_string string) {
        s.cur = append(s.cur, pushed_string)
}

func (s *Stack2) pop() (popped string) {
        if len(s.cur) == 0 {
                fmt.Print("Underflow\n")
                os.Exit(1)
        }
        popped = s.cur[len(s.cur)-1]
        s.cur = s.cur[0 : len(s.cur)-1]
        return
}

func (s *Stack2) print_all() {
        fmt.Printf("Stack size is %d\n", len(s.cur))
        for i, s := range s.cur {
                fmt.Printf("%d:\t%s\n", i, s)
        }
}

func NewStack() (stack *Stack2) {
        stack = new(Stack2)
        // init the slice to an empty slice of the underlying storage
        stack.cur = stack.stack[0:0]
        return
}

func main() {
        stack := NewStack()
        stack.print_all()
        stack.push("boo")
        stack.print_all()
        popped := stack.pop()
        fmt.Printf("Stack top is %s\n", popped)
        stack.print_all()
        stack.push("moo")
        stack.push("zoo")
        stack.print_all()
        popped2 := stack.pop()
        fmt.Printf("Stack top is %s\n", popped2)
        stack.print_all()
}
Jeff Allen
sumber
3
const ever = true

for ever {
    // infinite loop
}
György Andrasek
sumber
25
ahem. for { /* infinite loop */ }cukup.
u0b34a0f6ae
2
Tentu saja. Persis seperti itulah yang terjadi di sini. Saya hanya suka foreverkata kuncinya. Bahkan Qt memiliki makro untuk itu.
György Andrasek
6
tapi Go tidak membutuhkan makro atau alias lucu untuk melakukan ini.
u0b34a0f6ae
@ kaizer.se: Poin Jurily adalah bahwa for ever(setelah mendeklarasikan variabel) adalah sesuatu yang lucu yang dapat Anda lakukan di Go jika Anda mau. Sepertinya bahasa Inggris (modulo kosong).
Frank
8
itu adalah sesuatu yang lucu yang dapat Anda lakukan di C juga .. :-)#define ever (;;)
u0b34a0f6ae
2

Ada banyak program kecil di testdirektori utama. Contoh:

  • peano.go mencetak faktorial.
  • hilbert.go memiliki beberapa perkalian matriks.
  • iota.go memiliki contoh hal iota yang aneh.
pengguna181548
sumber