5. Merancang dan menyusun fungsi
Pelajari parameter, nilai kembalian, nilai default, dan meneruskan nilai.
Wave Foundation
Mulai dari kode yang berulang
Fungsi adalah alat untuk mengurangi sintaksis, namun juga merupakan alat untuk membatasi tugas. Memisahkan apa yang diperlukan sebagai masukan, apa yang dihitung, dan hasil apa yang dikembalikan memungkinkan Anda memahami program Anda dalam bagian yang lebih kecil.
Dalam bab ini, kita mulai dengan program yang menulis perhitungan diskon beberapa kali. Setiap contoh diselesaikan di main.wave dan dijalankan sebagai wavec run main.wave. Kami belum membagi filenya.
fun main() {
var first_price: i32 = 2000;
var first_discount: i32 = first_price * 10 / 100;
var first_total: i32 = first_price - first_discount;
var second_price: i32 = 5000;
var second_discount: i32 = second_price * 10 / 100;
var second_total: i32 = second_price - second_discount;
println("{} {}", first_total, second_total);
}
Hasil eksekusi:
1800 4500
Kedua perhitungan tersebut hanya berbeda pada harga dan memiliki struktur yang sama. Saat mengubah aturan diskon, Anda harus mengedit kedua tempat tersebut. Jika Anda mengubah satu sisi saja, Anda akan mendapatkan hasil berbeda untuk produk yang memerlukan kebijakan yang sama.
Tentukan masukan dan keluaran
Pindahkan perhitungan berlebihan ke fungsi. Nilai yang diubah diterima sebagai masukan price, dan harga yang dihitung dikembalikan.
fun discounted(price: i32) -> i32 {
var discount: i32 = price * 10 / 100;
return price - discount;
}
fun main() {
println("{} {}", discounted(2000), discounted(5000));
}
Hasil eksekusi:
1800 4500
Nama fungsinya adalah discounted. price: i32 dalam tanda kurung adalah deklarasi parameter dan -> i32 adalah tipe hasil. Variabel lokal discount di badan hanya digunakan dalam fungsi ini.
discounted(2000) adalah ekspresi yang memanggil suatu fungsi. Angka 2000 dalam tanda kurung adalah argumen yang sebenarnya Anda sampaikan. Karena nilai yang dikembalikan oleh fungsi menjadi hasil dari ekspresi pemanggilan ini, maka dapat langsung digunakan sebagai argumen untuk println.
| terminologi | kode | artinya |
|---|---|---|
| parameter | price | Nama masukan ditentukan saat mendeklarasikan fungsi |
| faktor | 2000 | Nilai diteruskan saat menelepon |
| tipe pengembalian | i32 | Jenis nilai yang dihasilkan oleh ekspresi panggilan |
| pernyataan pengembalian | return price - discount | Berikan hasilnya dan akhiri panggilan ini |
Ikuti perintah pemanggilan dan eksekusi
Hanya menulis deklarasi fungsi di sumbernya tidak menyebabkan isi fungsi tersebut langsung dieksekusi. Dieksekusi ketika titik yang dipanggil di main tercapai.
fun calculate(value: i32) -> i32 {
println("inside: {}", value);
return value * 2;
}
fun main() {
println("before");
var result: i32 = calculate(7);
println("after: {}", result);
}
Hasil eksekusi:
before
inside: 7
after: 14
Urutan kemajuannya adalah keluaran pertama main, teks utama calculate, dan keluaran terakhir main. Ketika return dijalankan, panggilan ke calculate berakhir dengan hasil 14, dan inisialisasi result dari main selesai.
Jika beberapa pemanggilan fungsi digabungkan dalam satu ekspresi, dan urutan efek sampingnya penting, pisahkan pemanggilan tersebut menjadi pernyataan terpisah. Contoh dalam bab ini juga menyimpan hasil panggilan yang perlu ditelusuri dalam variabel lokal.
beberapa parameter
Jika Anda juga menerima tingkat diskonto sebagai input, Anda dapat menghitung beberapa polis dengan fungsi yang sama.
fun discounted(price: i32, percent: i32) -> i32 {
return price - price * percent / 100;
}
fun main() {
var standard: i32 = discounted(2000, 10);
var special: i32 = discounted(2000, 25);
println("standard={}", standard);
println("special={}", special);
}
Hasil eksekusi:
standard=1800
special=1500
Urutan argumen harus sesuai dengan deklarasi. Jika kedua parameternya adalah i32, sulit untuk membedakan maknanya melalui pemeriksaan tipe saja, meskipun urutannya diubah. Tentukan dengan jelas nama fungsi dan nama parameter, dan tulis lokasi panggilan dengan cara yang mudah dibaca.
Fungsi ini mengasumsikan jumlah kecil dan tingkat diskonto yang valid. Tidak menangani harga negatif, rasio lebih besar dari 100, atau luapan perkalian di tengah-tengah. Saat membuat suatu fungsi, Anda harus mendeskripsikan tidak hanya isi fungsi tetapi juga kondisi inputnya. Program penyelesaian selanjutnya memisahkan langkah-langkah inspeksi.
argumen bawaan
Anda dapat memberikan nilai yang sering digunakan sebagai default.
fun discounted(price: i32, percent: i32 = 10) -> i32 {
return price - price * percent / 100;
}
fun main() {
println("{}", discounted(2000));
println("{}", discounted(2000, 25));
}
Hasil eksekusi:
1800
1500
Panggilan pertama menghilangkan argumen kedua dan menggunakan 10. Panggilan kedua menggunakan 25 yang ditentukan. Nilai default dibiarkan untuk parameter opsional di akhir. Tidak digunakan sebagai tata bahasa untuk menulis spasi kosong untuk menghilangkan argumen pertama saja.
Mengubah default akan mengubah perilaku melewatkan panggilan. Nilai default fungsi publik juga merupakan bagian dari perilaku yang Anda andalkan. Inilah sebabnya mengapa panggilan dengan argumen tertentu dan panggilan dengan argumen yang dihilangkan diuji secara terpisah.
berarti melewati nilai
Melewati nilai bilangan bulat akan memisahkan nilai yang diterima fungsi dari penyimpanan variabel pemanggil. Menghitung hasil tidak secara otomatis mengubah variabel pemanggil.
fun next(value: i32) -> i32 {
return value + 1;
}
fun main() {
var count: i32 = 4;
var later: i32 = next(count);
println("count={} later={}", count, later);
count = next(count);
println("count={}", count);
}
Hasil eksekusi:
count=4 later=5
count=5
Panggilan pertama berbunyi count dan menginisialisasi later. count masih 4. Setelah panggilan kedua, hasilnya ditetapkan ke count, sehingga menjadi 5. Desain pengembalian berdasarkan nilai memperjelas kepada pemanggil di mana data telah berubah.
Jika Anda ingin mengubah ruang penyimpanan asli dalam suatu fungsi, Anda dapat meneruskan sebuah pointer. Hal ini tercakup dalam bab penunjuk. Bahkan jika Anda meneruskan sebuah pointer, Anda harus membedakan antara nilai pointer itu sendiri dan ruang penyimpanan untuk alamatnya.
Jangan tinggalkan jalan yang tidak akan kembali
Fungsi yang mengembalikan nilai harus memberikan hasil pada semua jalur yang diperlukan. Itu tidak meninggalkan jalur terakhir seperti ini:
// 의도적으로 잘못된 함수 조각
fun positive(value: i32) -> i32 {
if (value > 0) {
return value;
}
}
Jika value kurang dari atau sama dengan 0, tidak ada nilai tetap yang dapat dikembalikan. Setelah Anda menetapkan aturan yang Anda inginkan, Anda perlu menuliskan semua rute Anda.
fun positive_or_zero(value: i32) -> i32 {
if (value > 0) {
return value;
}
return 0;
}
fun main() {
println("{} {} {}", positive_or_zero(-2), positive_or_zero(0), positive_or_zero(8));
}
Hasil eksekusi:
0 0 8
Panggilan yang pertama kali mengeksekusi return tidak dilanjutkan ke return di bawah. return terakhir dicapai hanya jika kondisinya salah. Kami memeriksa aturan untuk tiga kasus: positif, 0, dan negatif.
Berfungsi tanpa hasil
Jika Anda hanya melakukan operasi seperti pencetakan, Anda dapat menghilangkan jenis pengembalian. Fungsi tanpa hasil juga dapat dihentikan lebih awal dengan return;.
fun show_positive(value: i32) {
if (value <= 0) {
return;
}
println("positive={}", value);
}
fun main() {
show_positive(-3);
show_positive(6);
}
Hasil eksekusi:
positive=6
Panggilan pertama kembali tanpa mencetak apa pun. Cetakan panggilan kedua: “Tidak ada hasil” dan “Tidak kembali ke titik panggilan” berbeda. Fungsi yang tidak kembali, seperti fungsi yang menghentikan suatu proses, diklasifikasikan berdasarkan tipe kembaliannya !.
Menyusun program lengkap dengan banyak fungsi
Sekarang kami membagi validasi input, perhitungan, dan output ke dalam fungsi yang berbeda. Karena kami telah membatasi kisaran harga, perkalian antara dalam contoh ini berada dalam kisaran i32.
fun valid_order(price: i32, quantity: i32, percent: i32) -> bool {
return price >= 0 && price <= 100000
&& quantity >= 1 && quantity <= 100
&& percent >= 0 && percent <= 100;
}
fun discounted_unit(price: i32, percent: i32) -> i32 {
return price - price * percent / 100;
}
fun order_total(price: i32, quantity: i32, percent: i32) -> i32 {
var unit: i32 = discounted_unit(price, percent);
return unit * quantity;
}
fun show_order(price: i32, quantity: i32, percent: i32) {
if (!valid_order(price, quantity, percent)) {
println("invalid order");
return;
}
var total: i32 = order_total(price, quantity, percent);
println("total={}", total);
}
fun main() {
show_order(2000, 3, 10);
show_order(2000, 0, 10);
show_order(2000, 3, 120);
}
Hasil eksekusi:
total=5400
invalid order
invalid order
Jawab satu pertanyaan untuk setiap fungsi. valid_order menentukan apakah input diperbolehkan, discounted_unit menentukan berapa besar satu diskon, order_total menentukan berapa total harga, dan show_order menentukan apa yang akan ditampilkan.
Fungsi yang lebih kecil belum tentu lebih baik. Memberi nama pada setiap ekspresi dapat mempersulit pelacakannya. Pisahkan aturan ketika aturan tersebut bermakna untuk digunakan kembali di tempat lain atau ketika ada aturan yang perlu dijelaskan dan diverifikasi secara independen.
soal latihan
- Tulis maximum, yang mengembalikan dua bilangan bulat yang lebih besar.
- Tulis fungsi clamp yang mengembalikan nilai antara dua batas. Dalam solusi di bawah ini, low <= high ditetapkan sebagai kondisi pemanggilan.
- Buat fungsi yang menambahkan pajak ke suatu nilai dan gabungkan dengan fungsi jumlah pesanan. Tentukan rentang dan titik potong bilangan bulat terlebih dahulu.
Solusi: Fungsi dengan batas
fun maximum(left: i32, right: i32) -> i32 {
if (left > right) {
return left;
}
return right;
}
fun clamp(value: i32, low: i32, high: i32) -> i32 {
if (value < low) {
return low;
}
if (value > high) {
return high;
}
return value;
}
fun main() {
println("max={}", maximum(7, 4));
println("{} {} {}", clamp(-3, 0, 10), clamp(6, 0, 10), clamp(20, 0, 10));
}
Hasil eksekusi:
max=7
0 6 10
clamp memiliki tiga jalur: kurang dari jangkauan, dalam jangkauan, dan lebih besar dari jangkauan. Periksa dengan menambahkan secara manual nilai batas 0 dan 10 juga. Jika Anda ingin menerapkan hingga low > high, Anda harus memutuskan bagaimana menyatakan kegagalan. Bab penanganan kesalahan mengatasi masalah ini menggunakan struktur hasil.
panggilan rekursif
Suatu fungsi dapat memanggil dirinya sendiri. Rekursi memerlukan kondisi keluar di mana tidak ada lagi panggilan yang dilakukan, dan proses di mana setiap panggilan semakin mendekati kondisi tersebut.
fun factorial(value: i32) -> i32 {
if (value <= 1) {
return 1;
}
return value * factorial(value - 1);
}
fun main() {
println("{}", factorial(5));
}
Hasil eksekusi:
120
Perhitungan 5 mengarah ke 5 * factorial(4), yang mengembalikan 1 ketika tercapai. Nilai yang dikembalikan diteruskan secara berurutan ke panggilan sebelumnya, menghasilkan 120. Fungsi ini adalah contoh untuk menjelaskan bilangan bulat positif kecil. Dengan input yang besar, Anda perlu mempertimbangkan rentang hasil dan kedalaman panggilan. Anda dapat menghindari masalah peningkatan kedalaman panggilan dengan menulis operasi yang sama dalam satu lingkaran.
Kesalahan umum
| fenomena | Periksa |
|---|---|
| Kesalahan dengan argumen yang tidak mencukupi atau terlalu banyak | Jumlah parameter dan nilai default opsional |
| Jenis pengembalian tidak cocok | return Jenis ekspresi dan deklarasi fungsi |
| Bukan kembali dari jalur tertentu | Apakah ia kembali meskipun kondisinya salah? |
| Argumen tipe generik tidak ada | Setelah nama fungsi <Type> |
| Dokumen asli diubah setelah fungsi penunjuk dipanggil. | Apakah ini fungsi yang hanya membaca nilai atau fungsi yang mengubahnya? |
Fungsi yang diekspor secara eksternal, seperti export(c), menggunakan tanda tangan tertentu. Fungsi generik itu sendiri tidak dapat diekspor dengan konvensi pemanggilan eksternal. ptr<T> dan array<T, N> adalah tipe memori bawaan bahasa dan membedakannya dari deklarasi struktur generik pengguna.