Berikut daftar CLI atau Command Line Interface pada golang :
Command go mod init digunakan untuk inisialisasi project pada Go (menggunakan Go Modules). Untuk nama project bisa menggunakan apapun, tapi umumnya adalah disamakan dengan nama direktori.
Nama project ini penting karena nantinya berpengaruh pada import path sub packages yang ada dalam project tersebut.
mkdir
cd
go mod init
Command go run digunakan untuk eksekusi file program (file ber-ekstensi .go). Cara penggunaannya dengan menuliskan command tersebut diikuti argumen nama file.
Berikut adalah contoh penerapan go run untuk eksekusi file program main.go yang tersimpan di path project-pertama yang path tersebut sudah diinisialisasi menggunakan go mod init.
cd project-pertama
go run main.go
Command go run hanya bisa digunakan pada file yang nama package-nya adalah main.
Jika ada banyak file yang package-nya main dan file-file tersebut berada pada satu direktori level dengan file utama, maka eksekusinya adalah dengan menuliskan semua file sebagai argument command go run. Contohnya bisa dilihat pada kode berikut.
go run main.go library.go
Command ini digunakan untuk mengkompilasi file program.
Sebenarnya ketika eksekusi program menggunakan go run, terjadi proses kompilasi juga. File hasil kompilasi akan disimpan pada folder temporary untuk selanjutnya langsung dieksekusi.
Berbeda dengan go build, command ini menghasilkan file executable atau binary pada folder yang sedang aktif. Contohnya bisa dilihat pada kode berikut.
go build
Pada contoh di atas, project project-pertama di-build, menghasilkan file baru pada folder yang sama, yaitu project-pertama.exe, yang kemudian dieksekusi. Default-nya nama project akan otomatis dijadikan nama binary.
Untuk nama executable sendiri bisa diubah menggunakan flag -o. Contoh:
go build -o
go build -o program.exe
Untuk sistem operasi non-windows, tidak perlu menambahkan akhiran .exe pada nama binary.
Command go get digunakan untuk men-download package. Sebagai contoh saya ingin men-download package Kafka driver untuk Go pada project project-pertama.
cd project-pertama
go get github.com/segmentio/kafka-go
Command go get harus dijalankan dalam folder project. Jika dijalankan di-luar project maka akan diunduh ke pada GOPATH.
Command go mod tidy digunakan untuk memvalidasi dependensi. Jika ada dependensi yang belum ter-download, maka akan otomatis di-download.
Vendoring di Go merupakan kapabilitas untuk mengunduh semua dependency atau 3rd party, untuk disimpan di lokal dalam folder project, dalam folder bernama vendor.
Dengan adanya folder tersebut, maka Go tidak akan lookup 3rd party ke cache folder, melainkan langsung mempergunakan yang ada dalam folder vendor. Jadi tidak perlu download lagi dari internet.
go mod vendor
Di golang kita akan banyak menggunakan method fmt Print, Printf, dan Sprintf dengan layout format string seperti %s
, %d
, %.2f
, dan lainnya; untuk keperluan menampilkan output ke layar ataupun untuk memformat string.
Layout format string digunakan dalam konversi data ke bentuk string. Contohnya seperti %.3f
yang untuk konversi nilai double
ke string
dengan 3 digit desimal. untuk lebih jelas berikut daftar format penggunaannya.
The verbs:
General:
%v the value in a default format when printing structs, the plus flag (%+v) adds field names %#v a Go-syntax representation of the value %T a Go-syntax representation of the type of the value %% a literal percent sign; consumes no value
Boolean:
%t the word true or false
Integer:
%b base 2 %c the character represented by the corresponding Unicode code point %d base 10 %o base 8 %O base 8 with 0o prefix %q a single-quoted character literal safely escaped with Go syntax. %x base 16, with lower-case letters for a-f %X base 16, with upper-case letters for A-F %U Unicode format: U+1234; same as "U+%04X"
Floating-point and complex constituents:
%b decimalless scientific notation with exponent a power of two, in the manner of strconv.FormatFloat with the 'b' format, e.g. -123456p-78 %e scientific notation, e.g. -1.234456e+78 %E scientific notation, e.g. -1.234456E+78 %f decimal point but no exponent, e.g. 123.456 %F synonym for %f %g %e for large exponents, %f otherwise. Precision is discussed below. %G %E for large exponents, %F otherwise %x hexadecimal notation (with decimal power of two exponent), e.g. -0x1.23abcp+20 %X upper-case hexadecimal notation, e.g. -0X1.23ABCP+20
String and slice of bytes (treated equivalently with these verbs):
%s the uninterpreted bytes of the string or slice %q a double-quoted string safely escaped with Go syntax %x base 16, lower-case, two characters per byte %X base 16, upper-case, two characters per byte
Slice:
%p address of 0th element in base 16 notation, with leading 0x
Pointer:
%p base 16 notation, with leading 0x The %b, %d, %o, %x and %X verbs also work with pointers, formatting the value exactly as if it were an integer.
The default format for %v is:
bool: %t int, int8 etc.: %d uint, uint8 etc.: %d, %#x if printed with %#v float32, complex64, etc: %g string: %s chan: %p pointer: %p
For compound objects, the elements are printed using these rules, recursively, laid out like this:
struct: {field0 field1 ...} array, slice: [elem0 elem1 ...] maps: map[key1:value1 key2:value2 ...] pointer to above: &{}, &[], &map[]
Width is specified by an optional decimal number immediately preceding the verb. If absent, the width is whatever is necessary to represent the value. Precision is specified after the (optional) width by a period followed by a decimal number. If no period is present, a default precision is used. A period with no following number specifies a precision of zero. Examples:
%f default width, default precision %9f width 9, default precision %.2f default width, precision 2 %9.2f width 9, precision 2 %9.f width 9, precision 0
Komentar biasa dimanfaatkan untuk menyisipkan catatan pada kode program, menulis penjelasan/deskripsi mengenai suatu blok kode, atau bisa juga digunakan untuk me-remark kode (men-non-aktifkan kode yg tidak digunakan). Komentar akan diabaikan ketika kompilasi maupun eksekusi program.
Ada 2 jenis komentar di Go, inline & multiline. Pada pembahasan ini akan dijelaskan tentang penerapan dan perbedaan kedua jenis komentar tersebut.
Penulisan komentar jenis ini di awali dengan tanda double slash (//) lalu diikuti pesan komentarnya. Komentar inline hanya berlaku untuk satu baris pesan saja. Jika pesan komentar lebih dari satu baris, maka tanda // harus ditulis lagi di baris selanjutnya.
Komentar yang cukup panjang akan lebih rapi jika ditulis menggunakan teknik komentar multiline. Ciri dari komentar jenis ini adalah penulisannya diawali dengan tanda /* dan diakhiri */.
Go mengadopsi dua jenis penulisan variabel, yaitu yang dituliskan tipe data-nya, dan juga yang tidak. Kedua cara tersebut valid dan tujuannya sama, pembedanya hanya cara penulisannya saja.
Pada chapter ini akan dikupas tuntas tentang macam-macam cara deklarasi variabel.
Go memiliki aturan cukup ketat dalam hal penulisan variabel. Ketika deklarasi, tipe data yg digunakan harus dituliskan juga. Istilah lain dari konsep ini adalah manifest typing.
package main
import "fmt"
func main() {
var firstName string = "john"
var lastName string
lastName = "wick"
fmt.Printf("halo %s %s!\n", firstName, lastName)
}
Keyword var di atas digunakan untuk deklarasi variabel, contohnya bisa dilihat pada firstName dan lastName.
Nilai variabel firstName diisi langsung ketika deklarasi, berbeda dibanding lastName yang nilainya diisi setelah baris kode deklarasi, hal seperti ini diperbolehkan di Go.
Selain manifest typing, Go juga mengadopsi konsep type inference, yaitu metode deklarasi variabel yang tipe data-nya ditentukan oleh tipe data nilainya, cara kontradiktif jika dibandingkan dengan cara pertama. Dengan metode jenis ini, keyword var dan tipe data tidak perlu ditulis.
// menggunakan var, tanpa tipe data, menggunakan perantara "="
var firstName = "john"
// tanpa var, tanpa tipe data, menggunakan perantara ":="
lastName := "wick"
Kedua deklarasi di atas maksudnya sama. Silakan pilih yang nyaman di hati.
Tanda := hanya digunakan sekali di awal pada saat deklarasi. Untuk assignment nilai selanjutnya harus menggunakan tanda =, contoh.
Pengisian nilai juga bisa dilakukan bersamaan pada saat deklarasi. Caranya dengan menuliskan nilai masing-masing variabel berurutan sesuai variabelnya dengan pembatas koma (,).
var first, second, third string
first, second, third = "satu", "dua", "tiga"
Go memiliki aturan unik yang jarang dimiliki bahasa lain, yaitu tidak boleh ada satupun variabel yang menganggur. Artinya, semua variabel yang dideklarasikan harus digunakan. Jika ada variabel yang tidak digunakan tapi dideklarasikan, error akan muncul pada saat kompilasi dan program tidak akan bisa di-run.
Underscore (_) adalah reserved variable yang bisa dimanfaatkan untuk menampung nilai yang tidak dipakai. Bisa dibilang variabel ini merupakan keranjang sampah.
_ = "belajar Golang"
_ = "Golang itu mudah"
name, _ := "john", "wick"
Pada contoh di atas, variabel name akan berisikan text john, sedang nilai wick ditampung oleh variabel underscore, menandakan bahwa nilai tersebut tidak akan digunakan.
Variabel underscore adalah predefined, jadi tidak perlu menggunakan := untuk pengisian nilai, cukup dengan = saja. Namun khusus untuk pengisian nilai multi variabel yang dilakukan dengan metode type inference, boleh di dalamnya terdapat variabel underscore.
Biasanya variabel underscore sering dimanfaatkan untuk menampung nilai balik fungsi yang tidak digunakan.
Perlu diketahui, bahwa isi variabel underscore tidak dapat ditampilkan. Data yang sudah masuk variabel tersebut akan hilang. Ibaratkan variabel underscore seperti blackhole, objek apapun yang masuk ke dalamnya, akan terjebak selamanya di-dalam singularity dan tidak akan bisa keluar
Keyword new digunakan untuk membuat variabel pointer dengan tipe data tertentu. Nilai data default-nya akan menyesuaikan tipe datanya.
name := new(string)
fmt.Println(name) // 0x20818a220
fmt.Println(*name) // ""
Variabel name menampung data bertipe pointer string. Jika ditampilkan yang muncul bukanlah nilainya melainkan alamat memori nilai tersebut (dalam bentuk notasi heksadesimal). Untuk menampilkan nilai aslinya, variabel tersebut perlu di-dereference terlebih dahulu, menggunakan tanda asterisk (*).
Keyword ini hanya bisa digunakan untuk pembuatan beberapa jenis variabel saja, yaitu:
Dan lagi, mungkin banyak yang akan bingung. Ketika sudah masuk ke pembahasan masing-masing poin tersebut, akan terlihat apa kegunaan dari keyword make ini.
Go mengenal beberapa jenis tipe data, di antaranya adalah tipe data numerik (desimal & non-desimal), string, dan boolean.
Tipe data numerik non-desimal atau non floating point di Go ada beberapa jenis. Secara umum ada 2 tipe data kategori ini yang perlu diketahui.
Kedua tipe data di atas kemudian dibagi lagi menjadi beberapa jenis, dengan pembagian berdasarkan lebar cakupan nilainya, detailnya bisa dilihat di tabel berikut.
Tipe data | Cakupan bilangan |
---|---|
uint8 | 0 ↔ 255 |
uint16 | 0 ↔ 65535 |
uint32 | 0 ↔ 4294967295 |
uint64 | 0 ↔ 18446744073709551615 |
uint | sama dengan uint32 atau uint64 (tergantung nilai) |
byte | sama dengan uint8 |
int8 | -128 ↔ 127 |
int16 | -32768 ↔ 32767 |
uint32 | -2147483648 ↔ 2147483647 |
int64 | -9223372036854775808 ↔ 9223372036854775807 |
int | sama dengan int32 atau int64 (tergantung nilai) |
rune | sama dengan int32 |
Dianjurkan untuk tidak sembarangan dalam menentukan tipe data variabel, sebisa mungkin tipe yang dipilih harus disesuaikan dengan nilainya, karena efeknya adalah ke alokasi memori variabel. Pemilihan tipe data yang tepat akan membuat pemakaian memori lebih optimal, tidak berlebihan.
var positiveNumber uint8 = 89
var negativeNumber = -1243423644
fmt.Printf("bilangan positif: %d\n", positiveNumber)
fmt.Printf("bilangan negatif: %d\n", negativeNumber)
Variabel positiveNumber bertipe uint8 dengan nilai awal 89. Sedangkan variabel negativeNumber dideklarasikan dengan nilai awal –1243423644. Compiler secara cerdas akan menentukan tipe data variabel tersebut sebagai int32 (karena angka tersebut masuk ke cakupan tipe data int32).
Template %d pada fmt.Printf() digunakan untuk memformat data numerik non-desimal.
Tipe data numerik desimal yang perlu diketahui ada 2, float32
dan float64
. Perbedaan kedua tipe data tersebut berada di lebar cakupan nilai desimal yang bisa ditampung. Untuk lebih jelasnya bisa merujuk ke spesifikasi IEEE-754 32-bit floating-point numbers.
var decimalNumber = 2.62
fmt.Printf("bilangan desimal: %f\n", decimalNumber)
fmt.Printf("bilangan desimal: %.3f\n", decimalNumber)
Pada kode di atas, variabel decimalNumber akan memiliki tipe data float32, karena nilainya berada di cakupan tipe data tersebut.
Template %f digunakan untuk memformat data numerik desimal menjadi string. Digit desimal yang akan dihasilkan adalah 6 digit. Pada contoh di atas, hasil format variabel decimalNumber adalah 2.620000. Jumlah digit yang muncul bisa dikontrol menggunakan %.nf, tinggal ganti n dengan angka yang diinginkan. Contoh: %.3f maka akan menghasilkan 3 digit desimal, %.10f maka akan menghasilkan 10 digit desimal.
Tipe data bool berisikan hanya 2 variansi nilai, true dan false. Tipe data ini biasa dimanfaatkan dalam seleksi kondisi dan perulangan.
var exist bool = true
fmt.Printf("exist? %t \n", exist)
Gunakan %t untuk memformat data bool menggunakan fungsi fmt.Printf().
Ciri khas dari tipe data string adalah nilainya di apit oleh tanda quote atau petik dua (“). Contoh penerapannya:
var message string = "Halo"
fmt.Printf("message: %s \n", message)
Selain menggunakan tanda quote, deklarasi string juga bisa dengan tanda grave accent/backticks (`), tanda ini terletak di sebelah kiri tombol 1. Keistimewaan string yang dideklarasikan menggunakan backtics adalah membuat semua karakter di dalamnya tidak di escape, termasuk \n, tanda petik dua dan tanda petik satu, baris baru, dan lainnya. Semua akan terdeteksi sebagai string.
var message = `Nama saya "John Wick".
Salam kenal.
Mari belajar "Golang".`
fmt.Println(message)
Ketika dijalankan, output akan muncul sama persisi sesuai nilai variabel message di atas. Tanda petik dua akan muncul, baris baru juga muncul, sama persis.
nil bukan merupakan tipe data, melainkan sebuah nilai. Variabel yang isi nilainya nil berarti memiliki nilai kosong.
Semua tipe data yang sudah dibahas di atas memiliki zero value (nilai default tipe data). Artinya meskipun variabel dideklarasikan dengan tanpa nilai awal, tetap akan ada nilai default-nya.
Zero value berbeda dengan nil. Nil adalah nilai kosong, benar-benar kosong. nil tidak bisa digunakan pada tipe data yang sudah dibahas di atas. Ada beberapa tipe data yang bisa di-set nilainya dengan nil, di antaranya:
Konstanta adalah jenis variabel yang nilainya tidak bisa diubah. Inisialisasi nilai hanya dilakukan sekali di awal, setelah itu variabel tidak bisa diubah nilainya.
Data seperti pi (22/7), kecepatan cahaya (299.792.458 m/s), adalah contoh data yang tepat jika dideklarasikan sebagai konstanta daripada variabel, karena nilainya sudah pasti dan tidak berubah.
Cara penerapan konstanta sama seperti deklarasi variabel biasa, selebihnya tinggal ganti keyword var dengan const.
const firstName string = "john"
fmt.Print("halo ", firstName, "!\n")
Teknik type inference bisa diterapkan pada konstanta, caranya yaitu cukup dengan menghilangkan tipe data pada saat deklarasi.
const lastName = "wick"
fmt.Print("nice to meet you ", lastName, "!\n")
Chapter ini membahas mengenai macam operator yang bisa digunakan di Go. Secara umum terdapat 3 kategori operator: aritmatika, perbandingan, dan logika.
Operator aritmatika adalah operator yang digunakan untuk operasi yang sifatnya perhitungan. Go mendukung beberapa operator aritmatika standar, list-nya bisa dilihat di tabel berikut.
Tanda | Penjelasan |
---|---|
+ | penjumlahan |
- | pengurangan |
* | perkalian |
/ | pembagian |
% | modulus / sisa hasil pembagian |
Contoh penggunaan:
var value = (((2 + 6) % 3) * 4 - 2) / 3
Operator perbandingan digunakan untuk menentukan kebenaran suatu kondisi. Hasilnya berupa nilai boolean, true atau false.
Tabel di bawah ini berisikan operator perbandingan yang bisa digunakan di Go.
Tanda | Penjelasan |
---|---|
== | apakah nilai kiri sama dengan nilai kanan |
!= | apakah nilai kiri tidak sama dengan nilai kanan |
< | apakah nilai kiri lebih kecil daripada nilai kanan |
<= | apakah nilai kiri lebih kecil atau sama dengan nilai kanan |
> | apakah nilai kiri lebih besar dari nilai kanan |
>= | apakah nilai kiri lebih besar atau sama dengan nilai kanan |
Contoh penggunaan:
var value = (((2 + 6) % 3) * 4 - 2) / 3
var isEqual = (value == 2)
fmt.Printf("nilai %d (%t) \n", value, isEqual)
Pada kode di atas, terdapat statement operasi aritmatika yang hasilnya ditampung oleh variabel value. Selanjutnya, variabel tersebut dibandingkan dengan angka 2 untuk dicek apakah nilainya sama. Jika iya, maka hasilnya adalah true, jika tidak maka false. Nilai hasil operasi perbandingan tersebut kemudian disimpan dalam variabel isEqual.
Untuk memunculkan nilai bool menggunakan fmt.Printf(), bisa gunakan layout format %t.
Operator ini digunakan untuk mencari benar tidaknya kombinasi data bertipe bool (bisa berupa variabel bertipe bool, atau hasil dari operator perbandingan).
Beberapa operator logika standar yang bisa digunakan :
Tanda | Penjelasan |
---|---|
&& | kiri dan kanan |
|| | kiri atau kanan |
! | negasi / nilai kebalikan |
Contoh penggunaan :
var left = false
var right = true
var leftAndRight = left && right
fmt.Printf("left && right \t(%t) \n", leftAndRight)
var leftOrRight = left || right
fmt.Printf("left || right \t(%t) \n", leftOrRight)
var leftReverse = !left
fmt.Printf("!left \t\t(%t) \n", leftReverse)
Hasil dari operator logika sama dengan hasil dari operator perbandingan, yaitu berupa boolean.
Berikut penjelasan statemen operator logika pada kode di atas.
Template \t digunakan untuk menambahkan indent tabulasi. Biasa dimanfaatkan untuk merapikan tampilan output pada console.
Go memiliki 2 macam keyword untuk seleksi kondisi, yaitu if else dan switch. Pada chapter ini kita akan mempelajarinya satu-persatu.
Untuk catatan : Go tidak mendukung seleksi kondisi menggunakan ternary.
Statement seperti: var data = (isExist ? “ada” : “tidak ada”) adalah invalid dan menghasilkan error.
Cara penerapan if-else di Go sama seperti pada bahasa pemrograman lain. Yang membedakan hanya tanda kurungnya (parentheses), di Go tidak perlu ditulis. Kode berikut merupakan contoh penerapan seleksi kondisi if else, dengan jumlah kondisi 4 buah.
var point = 8
if point == 10 {
fmt.Println("lulus dengan nilai sempurna")
} else if point > 5 {
fmt.Println("lulus")
} else if point == 4 {
fmt.Println("hampir lulus")
} else {
fmt.Printf("tidak lulus. nilai anda %d\n", point)
}
Dari ke-empat kondisi di atas, yang terpenuhi adalah if point > 5, karena nilai variabel point memang lebih besar dari 5. Maka blok kode tepat di bawah kondisi tersebut akan dieksekusi (blok kode ditandai kurung kurawal buka dan tutup), hasilnya text “lulus” muncul sebagai output.
Skema if else Go sama seperti pada pemrograman umumnya. Yaitu di awal seleksi kondisi menggunakan if, dan ketika kondisinya tidak terpenuhi akan menuju ke else (jika ada). Ketika ada banyak kondisi, gunakan else if.
Catatan : Di bahasa pemrograman lain, ketika ada seleksi kondisi yang isi blok-nya hanya 1 baris saja, kurung kurawal boleh tidak dituliskan. Berbeda dengan aturan di Go, kurung kurawal harus tetap dituliskan meski isinya hanya 1 blok satement.
Variabel temporary adalah variabel yang hanya bisa digunakan pada blok seleksi kondisi di mana ia ditempatkan saja. Penggunaan variabel ini membawa beberapa manfaat, antara lain:
var point = 8840.0
if percent := point / 100; percent >= 100 {
fmt.Printf("%.1f%s perfect!\n", percent, "%")
} else if percent >= 70 {
fmt.Printf("%.1f%s good\n", percent, "%")
} else {
fmt.Printf("%.1f%s not bad\n", percent, "%")
}
Variabel percent nilainya didapat dari hasil perhitungan, dan hanya bisa digunakan di deretan blok seleksi kondisi itu saja.
Deklarasi variabel temporary hanya bisa dilakukan lewat metode type inference yang menggunakan tanda :=. Penggunaan keyword var di situ tidak diperbolehkan karena akan menyebabkan error.
Switch merupakan seleksi kondisi yang sifatnya fokus pada satu variabel, lalu kemudian di-cek nilainya. Contoh sederhananya seperti penentuan apakah nilai variabel x adalah: 1, 2, 3, atau lainnya.
var point = 6
switch point {
case 8:
fmt.Println("perfect")
case 7:
fmt.Println("awesome")
default:
fmt.Println("not bad")
}
Pada kode di atas, tidak ada kondisi atau case yang terpenuhi karena nilai variabel point tetap 6. Ketika hal seperti ini terjadi, blok kondisi default dipanggil. Bisa dibilang bahwa default merupakan else dalam sebuah switch.
Perlu diketahui, switch pada pemrograman Go memiliki perbedaan dibanding bahasa lain. Di Go, ketika sebuah case terpenuhi, tidak akan dilanjutkan ke pengecekan case selanjutnya, meskipun tidak ada keyword break di situ. Konsep ini berkebalikan dengan switch pada umumnya, yang ketika sebuah case terpenuhi, maka akan tetap dilanjut mengecek case selanjutnya kecuali ada keyword break.
Di Go keyword perulangan hanya for saja, tetapi meski demikian, kemampuannya merupakan gabungan for, foreach, dan while ibarat bahasa pemrograman lain.
Ada beberapa cara standar menggunakan for. Cara pertama dengan memasukkan variabel counter perulangan beserta kondisinya setelah keyword. Perhatikan dan praktekan kode berikut.
for i := 0; i < 5; i++ {
fmt.Println("Angka", i)
}
Perulangan di atas hanya akan berjalan ketika variabel i bernilai di bawah 5, dengan ketentuan setiap kali perulangan, nilai variabel i akan di-iterasi atau ditambahkan 1 (i++ artinya ditambah satu, sama seperti i = i + 1). Karena i pada awalnya bernilai 0, maka perulangan akan berlangsung 5 kali, yaitu ketika i bernilai 0, 1, 2, 3, dan 4.
Cara ke-2 adalah dengan menuliskan kondisi setelah keyword for (hanya kondisi). Deklarasi dan iterasi variabel counter tidak dituliskan setelah keyword, hanya kondisi perulangan saja. Konsepnya mirip seperti while milik bahasa pemrograman lain.
Kode berikut adalah contoh for dengan argumen hanya kondisi (seperti if), output yang dihasilkan sama seperti penerapan for cara pertama.
var i = 0
for i < 5 {
fmt.Println("Angka", i)
i++
}
Pointer adalah reference atau alamat memori. Variabel pointer berarti variabel yang berisi alamat memori suatu nilai. Sebagai contoh sebuah variabel bertipe integer memiliki nilai 4, maka yang dimaksud pointer adalah alamat memori di mana nilai 4 disimpan, bukan nilai 4 itu sendiri.
Variabel-variabel yang memiliki reference atau alamat pointer yang sama, saling berhubungan satu sama lain dan nilainya pasti sama. Ketika ada perubahan nilai, maka akan memberikan efek kepada variabel lain (yang referensi-nya sama) yaitu nilainya ikut berubah.
Variabel bertipe pointer ditandai dengan adanya tanda asterisk (*
) tepat sebelum penulisan tipe data ketika deklarasi.
var number *int
var name *string
Nilai default variabel pointer adalah nil
(kosong). Variabel pointer tidak bisa menampung nilai yang bukan pointer, dan sebaliknya variabel biasa tidak bisa menampung nilai pointer.
Ada dua hal penting yang perlu diketahui mengenai pointer:
&
) tepat sebelum nama variabel. Metode ini disebut dengan referencing.*
) tepat sebelum nama variabel. Metode ini disebut dengan dereferencing.
package main
import (
"fmt"
)
func main() {
var numberA int = 4
var numberB *int = &numberA
fmt.Println("numberA (value) :", numberA) // 4
fmt.Println("numberA (address) :", &numberA) // 0xc20800a220
fmt.Println("numberB (value) :", *numberB) // 4
fmt.Println("numberB (address) :", numberB) // 0xc20800a220
}
numberA (value) : 4
numberA (address) : 0xc00000e0a8
numberB (value) : 4
numberB (address) : 0xc00000e0a8
Variabel numberB
dideklarasikan bertipe pointer int
dengan nilai awal adalah referensi variabel numberA
(bisa dilihat pada kode &numberA
). Dengan ini, variabel numberA
dan numberB
menampung data dengan referensi alamat memori yang sama.
Variabel pointer jika di-print akan menghasilkan string alamat memori (dalam notasi heksadesimal), contohnya seperti numberB
yang diprint menghasilkan 0xc20800a220
.
Nilai asli sebuah variabel pointer bisa didapatkan dengan cara di-dereference terlebih dahulu (bisa dilihat pada kode *numberB
).
Ketika salah satu variabel pointer di ubah nilainya, sedang ada variabel lain yang memiliki referensi memori yang sama, maka nilai variabel lain tersebut juga akan berubah.
var numberA int = 4
var numberB *int = &numberA
fmt.Println("numberA (value) :", numberA)
fmt.Println("numberA (address) :", &numberA)
fmt.Println("numberB (value) :", *numberB)
fmt.Println("numberB (address) :", numberB)
fmt.Println("")
numberA = 5
fmt.Println("numberA (value) :", numberA)
fmt.Println("numberA (address) :", &numberA)
fmt.Println("numberB (value) :", *numberB)
fmt.Println("numberB (address) :", numberB)
Variabel numberA
dan numberB
memiliki referensi memori yang sama. Perubahan pada salah satu nilai variabel tersebut akan memberikan efek pada variabel lainnya. Pada contoh di atas, numberA
nilainya di ubah menjadi 5
. membuat nilai asli variabel numberB
ikut berubah menjadi 5
.
numberA (value) : 4
numberA (address) : 0xc000122058
numberB (value) : 4
numberB (address) : 0xc000122058
numberA (value) : 5
numberA (address) : 0xc000122058
numberB (value) : 5
numberB (address) : 0xc000122058
Parameter bisa juga dirancang sebagai pointer. Cara penerapannya kurang lebih sama, dengan cara mendeklarasikan parameter sebagai pointer.
package main
import "fmt"
func main() {
var number = 4
fmt.Println("before :", number) // 4
change(&number, 10)
fmt.Println("after :", number) // 10
}
func change(original *int, value int) {
*original = value
}
Fungsi change()
memiliki 2 parameter, yaitu original
yang tipenya adalah pointer int
, dan value
yang bertipe int
. Di dalam fungsi tersebut nilai asli parameter pointer original
diubah.
Fungsi change()
kemudian diimplementasikan di main
. Variabel number
yang nilai awalnya adalah 4
diambil referensi-nya lalu digunakan sebagai parameter pada pemanggilan fungsi change()
.
Nilai variabel number
berubah menjadi 10
karena perubahan yang terjadi di dalam fungsi change
adalah pada variabel pointer.
before : 4
after : 10
Di Go sebenarnya tidak ada istilah public modifier dan private modifier. Yang ada adalah exported yang kalau di bahasa lain ekuivalen dengan public modifier, dan unexported untuk private modifier.
Pengembangan aplikasi dalam real development pasti membutuhkan banyak sekali file program. Tidak mungkin dalam sebuah project semua file memiliki nama package main
, biasanya akan dipisah sebagai package berbeda sesuai bagiannya.
Project folder selain berisikan file-file .go
juga bisa berisikan sub-folder lainnya. Di Go, setiap folder atau sub-folder adalah satu package, file-file yang ada di dalam sebuah folder package-nya harus sama. Dan package pada file-file tersebut harus berbeda dengan package pada file-file lainnya yang berada pada folder berbeda.
Jadi mudahnya, 1 folder adalah 1 package.
Dalam sebuah package, biasanya kita menulis sangat banyak komponen, entah itu fungsi, struct, variabel, atau lainnya. Komponen tersebut bisa leluasa digunakan dalam package yang sama. Contoh sederhananya seperti program yang telah kita praktekan pada chapter sebelum-sebelumnya, dalam package main
ada banyak yang di-define: fungsi, variabel, closure, struct, dan lainnya; ke semuanya bisa langsung dimanfaatkan.
Jika dalam satu program terdapat lebih dari 1 package, atau ada package lain selain main
, maka komponen dalam package lain tersebut tidak bisa diakses secara bebas dari file yang package-nya main
, karena tiap komponen memiliki hak akses.
Ada 2 jenis hak akses di Go:
Penentuan hak akses yang tepat untuk tiap komponen sangatlah penting.
Di Go cara menentukan level akses atau modifier sangat mudah, penandanya adalah character case huruf pertama nama fungsi, struct, variabel, atau lainnya. Ketika namanya diawali dengan huruf kapital menandakan kalau exported (atau public). Dan sebaliknya, jika diawali huruf kecil, berarti unexported (atau private).
Misal dalam sebuah project terdapat beberapa library
my-app
– go.mod
– go.sum
– main.go
– libraries
– – librarysatu.go
– – librarydua.go
– partial.go
libraries/librarysatu.go
package libraries
import "fmt"
func SayHello() {
fmt.Println("hello")
}
func introduce(name string) {
fmt.Println("nama saya", name)
}
File library.go
yang telah dibuat ditentukan nama package-nya adalah library
(sesuai dengan nama folder), berisi dua buah fungsi, SayHello()
dan introduce()
.
SayHello()
, level aksesnya adalah publik, ditandai dengan nama fungsi diawali huruf besar.introduce()
dengan level akses private, ditandai oleh huruf kecil di awal nama fungsi.Selanjutnya kita lakukan tes apakah memang fungsi yang ber-modifier private dalam package library
tidak bisa diakses dari package lain.
Buka file main.go
, lalu tulis kode berikut.
package main
import "example.com/library"
func main() {
library.SayHello()
library.introduce("ethan")
}
Sebenarnya dari Visual Studio Code sudah terlihat error, dan jika dijalankan
.\main.go:13:11: undefined: examples.introduce
.\main.go:13:21: more than one character in rune literal
Level akses exported (atau public) dan unexported (atau private) juga bisa diterapkan di fungsi, struct, method, maupun property variabel. Cara penggunaannya sama seperti pada pembahasan sebelumnya, yaitu dengan menentukan character case huruf pertama nama komponen, apakah huruf besar atau kecil.
Belajar tentang level akses di Go akan lebih cepat jika langsung praktek. Oleh karena itu langsung saja. Hapus isi file library.go
, buat struct baru dengan nama student
di dalamnya. Contohnya seperti ini.
type Student struct { //student unexported
Name string //name unexported
Grade int
}
Selain nama struct-nya harus berbentuk exported, properti yang diakses juga harus exported juga.
Di Go, komponen yang berada di package lain yang di-import bisa dijadikan se-level dengan komponen package peng-import, caranya dengan menambahkan tanda titik (.
) setelah penulisan keyword import
. Maksud dari se-level di sini adalah, semua properti di package lain yg di-import bisa diakses tanpa perlu menuliskan nama package, seperti ketika mengakses sesuatu dari file yang sama.
import (
. "example.com/library"
"fmt"
)
func main() {
SayHello() // tadinya library.SayHello()
}
Fungsi yang berada di package lain bisa diakses dengan cara menuliskan nama-package diikuti nama fungsi-nya, contohnya seperti fmt.Println()
. Package yang sudah di-import tersebut bisa diubah namanya dengan cara menggunakan alias pada saat import. Contohnya bisa dilihat pada kode berikut.
import (
f "fmt"
)
func main() {
f.Println("Hello World!")
}
Pada kode di-atas, package fmt
di tentukan aliasnya adalah f
, untuk mengakses Println()
cukup dengan f.Println()
.
Jika properti yang ingin di akses masih dalam satu package tapi berbeda file, cara mengaksesnya bisa langsung dengan memanggil namanya. Hanya saja ketika eksekusi, file-file lain yang yang nama package-nya sama juga ikut dipanggil.
Langsung saja kita praktekan, buat file baru dalam my-app
dengan nama partial.go
.
package main
import "fmt"
func sayHelloPartial(name string) string {
return fmt.Sprintf("halo %s from partial", name)
}
Hapus semua isi file main.go
, lalu silakan tulis kode berikut.
package main
func main() {
sayHelloPartial("ethan")
}
Sekarang terdapat 2 file berbeda (main.go
dan partial.go
) dengan package adalah sama, main
. Pada saat go build
atau go run
, semua file dengan nama package main
harus dituliskan sebagai argumen command.
go run main.go partial.go
//atau
go run .
Fungsi sayHello
pada file partial.go
bisa dikenali meski level aksesnya adalah unexported. Hal ini karena kedua file tersebut (main.go
dan partial.go
) memiliki package yang sama.
Selain fungsi main()
, terdapat juga fungsi spesial, yaitu init()
. Fungsi ini otomatis dipanggil pertama kali ketika aplikasi di-run. Jika fungsi ini berada dalam package main, maka dipanggil lebih dulu sebelum fungsi main()
dieksekusi.
Langsung saja kita praktekkan. Buka file librarysatu.go
, hapus isinya lalu isi dengan kode berikut.
package libraries
import "fmt"
var Student = struct {
Name string
Grade int
}{}
func init() {
Student.Name = "John Wick"
Student.Grade = 2
fmt.Println("--> library/library.go imported")
}
Pada package tersebut, variabel Student
dibuat dengan isi anonymous struct. Dalam fungsi init, nilai Name
dan Grade
variabel di-set.
Selanjutnya buka file main.go
, isi dengan kode berikut.
package main
import "example.com/library"
import "fmt"
func main() {
fmt.Printf("Name : %s\n", library.Student.Name)
fmt.Printf("Grade : %d\n", library.Student.Grade)
}
Package library
di-import, dan variabel Student
dikonsumsi. Pada saat import package, fungsi init()
yang berada di dalamnya langsung dieksekusi.
Property variabel objek Student
akan diisi dan sebuah pesan ditampilkan ke console.
Dalam sebuah package diperbolehkan ada banyak fungsi init()
(urutan eksekusinya adalah sesuai file mana yg terlebih dahulu digunakan). Fungsi ini dipanggil sebelum fungsi main()
, pada saat eksekusi program.
--> libraries/library.go imported
Name : John Wick
Grade : 2
Defer digunakan untuk mengakhirkan eksekusi sebuah statement tepat sebelum blok fungsi selesai. Sedangkan Exit digunakan untuk menghentikan program secara paksa (ingat, menghentikan program, tidak seperti return
yang hanya menghentikan blok kode).
Seperti yang sudah dijelaskan secara singkat di atas, bahwa defer digunakan untuk mengakhirkan eksekusi baris kode dalam skope blok fungsi. Ketika eksekusi blok sudah hampir selesai, statement yang di-defer dijalankan.
Defer bisa ditempatkan di mana saja, awal maupun akhir blok. Tetapi tidak mempengaruhi kapan waktu dieksekusinya, akan selalu dieksekusi di akhir.
package main
import "fmt"
func main() {
defer fmt.Println("halo")
fmt.Println("selamat datang")
}
selamat datang
halo
Keyword defer
di atas akan mengakhirkan ekseusi fmt.Println("halo")
, efeknya pesan "halo"
akan muncul setelah "selamat datang"
.
Statement yang di-defer akan tetap muncul meskipun blok kode diberhentikan ditengah jalan menggunakan return
. Contohnya seperti pada kode berikut.
func main() {
orderSomeFood("pizza")
orderSomeFood("burger")
}
func orderSomeFood(menu string) {
defer fmt.Println("Terimakasih, silakan tunggu")
if menu == "pizza" {
fmt.Print("Pilihan tepat!", " ")
fmt.Print("Pizza ditempat kami paling enak!", "\n")
return
}
fmt.Println("Pesanan anda:", menu)
}
Pilihan tepat! Pizza ditempat kami paling enak!
Terimakasih, silakan tunggu
Pesanan anda: burger
Terimakasih, silakan tunggu
Info tambahan, ketika ada banyak statement yang di-defer, maka seluruhnya akan dieksekusi di akhir secara berurutan.
Eksekusi defer adalah di akhir blok fungsi, bukan blok lainnya seperti blok seleksi kondisi.
func main() {
number := 3
if number == 3 {
fmt.Println("halo 1")
defer fmt.Println("halo 3")
}
fmt.Println("halo 2")
}
halo 1
halo 2
halo 3
Pada contoh di atas halo 3
akan tetap di print setelah halo 2
meskipun statement defer dipergunakan dalam blok seleksi kondisi if
. Hal ini karena defer eksekusinya terjadi pada akhir blok fungsi (dalam contoh di atas main()
), bukan pada akhir blok if
.
Agar halo 3
bisa dimunculkan di akhir blok if
, maka harus dibungkus dengan IIFE. Contoh:
func main() {
number := 3
if number == 3 {
fmt.Println("halo 1")
func() {
defer fmt.Println("halo 3")
}()
}
fmt.Println("halo 2")
}
halo 1
halo 3
halo 2
Bisa dilihat halo 3
muncul sebelum halo 2
, karena dalam blok seleksi kondisi if
eksekusi defer terjadi dalam blok fungsi anonymous (IIFE).
Exit digunakan untuk menghentikan program secara paksa pada saat itu juga. Semua statement setelah exit tidak akan dieksekusi, termasuk juga defer.
Fungsi os.Exit()
berada dalam package os
. Fungsi ini memiliki sebuah parameter bertipe numerik yang wajib diisi. Angka yang dimasukkan akan muncul sebagai exit status ketika program berhenti.
package main
import "fmt"
import "os"
func main() {
defer fmt.Println("halo")
os.Exit(1)
fmt.Println("selamat datang")
}
Meskipun defer fmt.Println("halo")
ditempatkan sebelum os.Exit()
, statement tersebut tidak akan dieksekusi, karena di-tengah fungsi program dihentikan secara paksa.
exit status 1