İşlevler
Eğlence yeni başlıyor! :o)
İşlevler gerçek programların temel taşlarıdır. Nasıl temel türler, bütün türlerin yapı taşları iseler, işlevler de program davranışlarının yapı taşlarıdır.
İşlevlerin ustalıkla da ilgisi vardır. Ustaca yazılmış programcıları, işlevlerinin kısa ve öz oluşundan ayırt edebilirsiniz. Bunun tersi de doğrudur: kısa ve öz işlevler yazmaya çalışmak, ustalık yolunda ilerlemenin önemli adımlarındandır. İşlemleri oluşturan alt adımları görmeye çalışmak, ve o adımları küçük işlevler halinde tanımlamaya çalışmak; programcılık konusunda gelişmenize yardım edecektir.
Bundan önceki derslerde temel deyimler ve ifadeler öğrendik. Daha hepsini bitirmedik ama D'nin programlarda çok kullanılan, çok yararlı, ve çok önemli olanaklarını gördük. Yine de, hiçbirisi büyük programlar yazmak için yeterli değildir. Şimdiye kadar yazdığımız biçimdeki programlar, deneme programları gibi hiçbir karmaşıklığı olmayan çok basit programlar olabilirler. En ufak bir karmaşıklığı bulunan bir işi işlev kullanmadan yazmaya çalışmak çok zordur, ve ortaya çıkan program da hataya açık olur.
İşlevler, ifade ve deyimleri bir araya getiren olanaklardır. Bir araya getirilen ifade ve deyimlere toplu olarak yeni bir isim verilir, ve o işlemlerin hepsi birden bu isimle işletilir.
Bir araya getirerek yeni isim verme kavramını günlük hayattan tanıyoruz. Örneğin yağda yumurta yapma işini şu adımlarla tarif edebiliriz:
- tavayı çıkart
- yağı çıkart
- yumurtayı çıkart
- ateşi aç
- tavayı ateşe koy
- tava ısınınca yağı içine at
- yağ eriyince yumurtayı içine kır
- yumurtanın beyazı pişince tavayı ateşten al
- ateşi söndür
O kadar ayrıntıya girmek zamanla gereksiz ve içinden çıkılmaz bir hâl alacağı için, birbiriyle ilişkili adımların bazılarına tek bir isim verilebilir:
- malzemeleri hazırla (tavayı, yağı, yumurtayı çıkart)
- ateşi aç
- yumurtayı pişir (tavayı ateşe koy, vs.)
- ateşi söndür
Daha sonra daha da ileri gidilebilir ve bütün o adımları içeren tek bir ifade de kullanılabilir:
- yağda yumurta yap (bütün adımlar)
İşlevlerin bundan farkı yoktur: Yaptıkları işlerin hepsine birden genel bir isim verilebilen adımlar tek bir işlev olarak tanımlanırlar. Örnek olarak kullanıcıya bir menü gösteren şu satırlara bakalım:
writeln(" 0 Çıkış"); writeln(" 1 Toplama"); writeln(" 2 Çıkarma"); writeln(" 3 Çarpma"); writeln(" 4 Bölme");
Onların hepsine birden menüyüGöster gibi bir isim verilebileceği için onları bir işlev olarak şu şekilde bir araya getirebiliriz:
void menüyüGöster() { writeln(" 0 Çıkış"); writeln(" 1 Toplama"); writeln(" 2 Çıkarma"); writeln(" 3 Çarpma"); writeln(" 4 Bölme"); }
Artık o işlevi main içinden kısaca ismiyle işletebiliriz:
void main() { menüyüGöster(); // ... diğer işlemler ... }
menüyüGöster ile main'in tanımlarındaki benzerliğe bakarak main'in de bir işlev olduğunu görebilirsiniz. İsmi İngilizce'de "ana işlev" kullanımındaki "ana" anlamına gelen main, D programlarının ana işlevidir. D programlarının işleyişi bu işlevle başlar ve programcının istediği şekilde başka işlevlere dallanır.
Parametre
İşlevlerin güçlü yanlarından birisi, yaptıkları işlerin belirli ölçüde ayarlanabiliyor olmasından gelir.
Yine yumurta örneğine dönelim, ve bu sefer beş yumurta yapmak isteyelim. İzlenmesi gereken adımlar aslında bu durumda da aynıdır; tek farkları, yumurta sayısındadır. Daha önce dörde indirgediğimiz adımları beş yumurtaya uygun olarak şöyle değiştirebiliriz:
- malzemeleri beş yumurta için hazırla
- ateşi aç
- yumurtaları pişir
- ateşi söndür
Teke indirgediğimiz adım da şöyle değişir:
- yağda beş yumurta yap
Yani, temelde aynı olan yumurta pişirme işiyle ilgili bir bilgiyi ek olarak belirtmemiz gerekir: "beş yumurta çıkart" veya "beş yumurta yap" gibi.
İşlevlerin davranışları da benzer şekilde ayarlanabilir. İşlevlerin işlerini bu şekilde etkileyen bilgilere parametre denir. Parametreler, parametre listesinde virgüllerle ayrılarak bildirilirler. Parametre listesi, işlevin isminden hemen sonra yazılan parantezin içidir.
Daha önceki menüyüGöster işlevinde parametre parantezini boş olarak tanımlamıştık; çünkü o işlev her zaman için aynı menüyü göstermekteydi. Menüdeki ilk seçeneğinin hep "Çıkış" olması yerine, duruma göre değişen bir seçenek olmasını istesek, bunu bir parametreyle sağlayabiliriz. Örneğin ilk seçeneği bazı durumlarda "Geri Dön" olarak yazdırmak için bu bilgiyi bir parametre olarak tanımlayabiliriz:
void menüyüGöster(string ilkSeçenek) { writeln(" 0 ", ilkSeçenek); writeln(" 1 Toplama"); writeln(" 2 Çıkarma"); writeln(" 3 Çarpma"); writeln(" 4 Bölme"); }
ilkSeçenek parametresi bu örnekte bir dizgi olduğu için türünü de string olarak belirledik. Bu işlevi artık değişik dizgilerle işleterek menünün ilk satırının farklı olmasını sağlayabiliriz. Tek yapmamız gereken, parametre değerini parantez içinde belirtmektir:
menüyüGöster("Çıkış"); menüyüGöster("Geri Dön");
char[] türünde dizgilerle kullanılamaz. Örneğin şu kod, char[] ile string uyumlu olmadıkları için derleme hatasına neden olur:
char[] birSeçenek = "Kare Kök Al"; menüyüGöster(birSeçenek); // ← derleme HATASIÖte yandan,
menüyüGöster'in tanımında parametrenin türünü char[] olarak belirlediğinizde de işlevi "Çıkış" gibi bir string'le çağıramazsınız. immutable ile ilgili olan bu önemli konuyu bir sonraki derste açıklayacağım.
Biraz daha ileri gidelim ve seçenek numaralarının hep 0 ile değil, duruma göre değişik bir değerle başlamasını istiyor olalım. Bu durumda başlangıç numarasını da parametre olarak verebiliriz. İki parametreyi ayırmak için aralarına virgül yazarak:
void menüyüGöster(string ilkSeçenek, int ilkNumara) { writeln(' ', ilkNumara, ' ', ilkSeçenek); writeln(' ', ilkNumara + 1, " Toplama"); writeln(' ', ilkNumara + 2, " Çıkarma"); writeln(' ', ilkNumara + 3, " Çarpma"); writeln(' ', ilkNumara + 4, " Bölme"); }
O işleve hangi numarayla başlayacağını artık biz bildirebiliriz:
menüyüGöster("Geri Dön", 100);
Çağırmak
İşlevin işini yapması için başlatılmasına işlevin çağrılması denir. Örneğin şimdiye kadar hep kullandığımız writeln'i, yazdırmasını istediğimiz değerlerle şöyle çağırıyorduk:
writeln("Merhaba ", öğrenciİsmi);
İşlev çağrısının söz dizimi şöyledir:
işlevin_ismi(parametre_değerleri)
İşini yaparken kullanması için işleve verilen bilgilere parametre değeri denir. Yukarıdaki kullanımda writeln'e verilen parametre değerleri, "Merhaba" ve öğrenciİsmi dizgileridir.
İş yapmak: yan etki oluşturmak veya değer üretmek
Hem daha önceki derslerde, hem de bu derste iş yapmaktan söz ettim. Program adımlarının, ifadelerin, işlevlerin, iş yaptıklarını söyledim. Programın iş yapması, iki anlama gelebilir:
- Yan etki oluşturmak: Bazı işlemlerin yalnızca yan etkileri vardır. Örneğin çıkışa menü yazdıran
menüyüGösterişlevi çıkışı etkilemektedir; oluşturduğu bir değer yoktur. Başka bir örnek olarak, kendisine verilen birÖğrencinesnesini bir öğrenciler listesine ekleyen bir işlevin etkisi, listenin büyümesidir. Onun da ürettiği bir değer yoktur. - Değer üretmek: Bazı işlemler yalnızca değer üretirler. Örneğin toplama işleminin sonucunu veren bir işlev, toplanan değerlerin toplamını üretir. Başka bir örnek olarak; isim, adres, vs. gibi kendisine verilen bilgiyi bir araya getirerek bir
Öğrencinesnesi oluşturan bir işlevin de bir nesne ürettiği söylenir. - Hem değer üretmek, hem yan etki oluşturmak: Bazı işlemler hem değer üretirler, hem de yan etkileri vardır. Örneğin girişten okuduğu sayıların toplamını hesaplayan bir işlev, hem toplamın sonucunu üretmektedir; hem de içinden karakterler çıkarttığı için girişi etkilemektedir.
Genel olarak, programın durumunda bir değişikliğe neden olan bir işlemin yan etkisinin olduğu söylenir.
Bu tür işlemlerin ayrıca yan etkileri yoktur; programın durumunda hiçbir değişikliğe neden olmazlar; yalnızca değer üretirler.
Her işlev bu üç gruptan birisine girer: ya yalnızca değer üretir, ya yan etki veya etkileri vardır, ya da ikisi birden... Not: Ne değer üreten ne de yan etkisi olan bir işlevin programda bir "iş yaptığını" söyleyemeyiz.
Dönüş değeri
Değer üreten bir işlevin ürettiği değere, işlevin dönüş değeri denir. Bu terim, işlevin işini bitirdikten sonra bize geri dönmesi gibi bir düşünceden türemiştir. İşlevi "çağırırız", ve "döndürdüğü" değeri kullanırız.
Her değerin olduğu gibi, dönüş değerinin de türü vardır. Bu tür, işlevin isminden önce yazılır. Örneğin iki tane int değeri toplayan bir işlev, eğer sonuçta yine int türünde bir değer üretiyorsa, dönüş türü olarak int yazılır:
int topla(int birinci, int ikinci) { // ... yapılan işlemler ... }
İşlevin döndürdüğü değer, sanki o değer işlev çağrısının yerine yazılmış gibi, onun yerine geçer. Örneğin topla(5, 7) çağrısının değer olarak 12 ürettiğini düşünürsek, şu iki satır birbirinin eşdeğeridir:
writeln("Toplam: ", topla(5, 7)); writeln("Toplam: ", 12);
writeln çağrılmadan önce, topla(5, 7) işlevi çağrılır ve onun döndürdüğü değer olan 12, yazdırması için writeln'e parametre olarak verilir.
Bu sayede işlevlerin değerlerini başka işlevlere parametre olarak verebilir ve daha karmaşık ifadeler oluşturabiliriz:
writeln("Sonuç: ", topla(5, böl(100, kişiSayısı())));
O örnekte kişiSayısı'nın dönüş değeri böl'e, böl'ün dönüş değeri topla'ya, ve en sonunda da topla'nın dönüş değeri writeln'e parametre olarak verilmektedir.
return
İşlevin ürettiği değer, "döndür" anlamına gelen return anahtar sözcüğü ile bildirilir:
int topla(int birinci, int ikinci) { int toplam = birinci + ikinci; return toplam; }
İşlev, gereken işlemleri ve hesapları yaparak dönüş değerini üretir ve en son olarak return ile döndürür. İşlevin işleyişi de o noktada sona erer ve işlevden dönülmüş olur.
İşlevlerde birden fazla return anahtar sözcüğü kullanılabilir. İfade ve deyimlere bağlı olarak önce hangi return işletilirse, işlevin dönüş değeri olarak o return'ün döndürdüğü değer kullanılır:
int karmaşıkHesap(int birParametre, int başkaParametre) { if (birParametre == başkaParametre) { return 0; } return birParametre * başkaParametre; }
O işlev; iki parametresi birbirlerine eşitse 0 değerini, değilse iki parametrenin çarpımını döndürür.
void işlevler
Eğer işlev değer üretmeyen bir işlevse, dönüş türü olarak "boşluk, yokluk" anlamına gelen void yazılır. O yüzden main'in ve menüyüGöster'in dönüş türlerini void olarak yazdık; şimdiye kadar gördüğümüz kadarıyla ikisi de değer üretmeyen işlevlerdir.
Not: main aslında int de döndürebilir. Bunu daha sonraki bir derste anlatacağım.
İşlevin ismi
İşlevin ismi, programcı tarafından işlevin yaptığı işi açıklayacak şekilde seçilmelidir. Örneğin iki sayıyı toplayan işlevin ismini topla olarak seçtik; veya menüyü gösteren işleve menüyüGöster dedik.
İşlevlere isim verirken izlenen bir kural, isimleri topla'da ve menüyüGöster'de olduğu gibi ikinci tekil şahıs emir kipinde seçmektir. Yani 'da ve toplam'de olduğu gibi isim halinde değil... Böylece işlevin bir eylemde bulunduğu isminden anlaşılır.
menü
Öte yandan hiçbir yan etkileri olmayan, yani yalnızca değer üreten işlevlere içinde eylem bulunmayan isimler de seçilebilir. Örneğin şu andaki hava sıcaklığını veren bir işlev için yerine havaSıcaklığınıVerhavaSıcaklığı gibi bir ismin daha uygun olduğunu düşünebilirsiniz.
İşlev, nesne, değişken, vs. isim seçimlerinin programcılığın sanat tarafında kaldığını düşünebilirsiniz. İşe yarar, yeterince kısa, ve programdaki diğer isimlerle tutarlı olan isimler bulmak bazen yaratıcılık gerektirir.
Kod tekrarı (yapmayın)
Programcılıkta kaçınılması gereken bir eylem, kod tekrarıdır. Kod tekrarı, aynı işi yapan işlemlerin programda birden fazla yerde tekrarlanması anlamına gelir.
Bu tekrar bazen bilinçli olarak satırların bir yerden başka bir yere kopyalanması ile yapılabilir. Bazen de farkında olmadan, aynı işlemlerin aynı şekilde kodlanmaları şeklinde ortaya çıkabilir.
Kod tekrarının sakıncalarından birisi; tekrarlanan işlemlerdeki olası hataların bütün kopyalarda da bulunması, ve aynı hatanın her kopyada giderilmesinin gerekmesidir. Oysa; tekrarlanan kod tek bir işlev içinde bulunuyor olsa, hatayı yalnızca bir kere gidermek yeter.
Yukarıda işlevlerin ustalıkla ilgili olduklarına değinmiştim. Usta programcılar koddaki işlemler arasındaki benzerlikleri yakalamaya ve kod tekrarını ortadan kaldırmaya çalışırlar.
Bir örnek olarak, girişten aldığı sayıları önce girişten geldikleri sırada, sonra da sıralanmış olarak yazdıran bir programa bakalım:
import std.cstream; void main() { int[] sayılar; int adet; dout.writef("Kaç sayı gireceksiniz? "); din.readf(&adet); // Sayıları oku for (int i = 0; i != adet; ++i) { int sayı; dout.writef("Sayı ", i, "? "); din.readf(&sayı); sayılar ~= sayı; } // Diziyi çıkışa yazdır dout.writefln("Sıralamadan önce:"); foreach (i, sayı; sayılar) { dout.writefln("%3d:%5d", i, sayı); } sayılar.sort; // Diziyi çıkışa yazdır dout.writefln("Sıraladıktan sonra:"); foreach (i, sayı; sayılar) { dout.writefln("%3d:%5d", i, sayı); } }
Kod tekrarı görüyor musunuz? Diziyi yazdırmak için kullanılan son iki foreach döngüsü birbirinin aynısı... diziYazdır ismiyle bir işlev tanımlasak ve yazdırmasını istediğimiz diziyi de parametre olarak versek, bu kod tekrarını ortadan kaldırmış oluruz:
void diziYazdır(int[] dizi) { foreach (i, eleman; dizi) { dout.writefln("%3s:%5s", i, eleman); } }
Dikkat ederseniz, parametrenin ismi olarak sayılar yerine ondan daha genel olan dizi ismini seçtik. Bunun nedeni, bu işlev bağlamında dizi yazdırmak dışında bir şey bilmiyor olduğumuzdur. Dizinin elemanlarının ne olduklarından bu işlev içinde haberimiz yoktur. Dizideki int elemanların ne anlama geldiklerini ancak bu işlevi çağıran kapsam bilir: belki öğrenci kayıt numaralarıdır, belki bir şifrenin parçalarıdır, belki bir grup insanın yaşlarıdır... Ne olduklarını diziYazdır işlevi içinde bilemediğimiz için, ancak dizi ve eleman gibi genel isimler kullanabiliyoruz.
Şimdi kod biraz daha düzenli bir hale gelir:
import std.cstream; void diziYazdır(int[] dizi) { foreach (i, eleman; dizi) { dout.writefln("%3s:%5s", i, eleman); } } void main() { int[] sayılar; int adet; dout.writef("Kaç sayı gireceksiniz? "); din.readf(&adet); // Sayıları oku for (int i = 0; i != adet; ++i) { int sayı; dout.writef("Sayı ", i, "? "); din.readf(&sayı); sayılar ~= sayı; } // Diziyi çıkışa yazdır dout.writefln("Sıralamadan önce:"); diziYazdır(sayılar); sayılar.sort; // Diziyi çıkışa yazdır dout.writefln("Sıraladıktan sonra:"); diziYazdır(sayılar); }
İşimiz bitmedi: iki diziYazdır çağrısından önce birer de başlık yazdırılıyor. Yazdırılan dizgi farklı olsa da, işlem aynıdır. Eğer başlığı da diziYazdır'a parametre olarak verirsek, başlığı yazdırma tekrarından da kurtulmuş oluruz. Programın yalnızca değişen bölümlerini gösteriyorum:
void diziYazdır(string başlık, int[] dizi) { dout.writefln(başlık, ":"); foreach (i, eleman; dizi) { dout.writefln("%3s:%5s", i, eleman); } } // ... // Diziyi çıkışa yazdır diziYazdır("Sıralamadan önce", sayılar); // ... // Diziyi çıkışa yazdır diziYazdır("Sıraladıktan sonra", sayılar);
Bu işlemin diziYazdır'dan önceki açıklama satırlarını da gereksiz hale getirdiğini görebiliriz. İşlemlere diziYazdır diye açıklayıcı bir isim verdiğimiz için, ayrıca "Diziyi çıkışa yazdır" gibi bir açıklamaya da gerek kalmamış oluyor. Şimdi programın son satırları şöyle kısaltılabilir:
diziYazdır("Sıralamadan önce", sayılar); sayılar.sort; diziYazdır("Sıraladıktan sonra", sayılar); }
Bu programda bir kod tekrarı daha var: girişten adet ve sayı için aynı şekilde tamsayı okunuyor. Tek farkları, kullanıcıya verilen mesaj:
int adet; dout.writef("Kaç sayı gireceksiniz? "); din.readf(&adet); // ... int sayı; dout.writef("Sayı ", i, "? "); din.readf(&sayı);
sayıOku diye bir işlev yazarsak ve kullanıcıya gösterilecek mesajı bir parametre olarak alırsak, kod çok daha temiz bir hale gelir. Bu sefer bu işlevin girişten okuduğu değeri üretmesi gerektiğine dikkat edin:
int sayıOku(string mesaj) { int sayı; dout.writef(mesaj, "? "); din.readf(&sayı); return sayı; }
Bu işlevi çağırarak adet'i okumak kolay. adet'i işlevin dönüş değeriyle ilkleyebiliriz:
int adet = sayıOku("Kaç sayı gireceksiniz");
Fakat sayı'yı okumak o kadar kolay değil, çünkü döngü sayacı olan i de bu mesajın bir parçası... std.conv modülündeki to!string dönüşüm işlevini, ve dizgi birleştirmek için kullanılan ~ işlecini hatırlayarak:
int sayı = sayıOku("Sayı " ~ to!string(i));
sayı'nın for içinde tek bir yerde kullanıldığını görerek sayı'nın tanımını da tamamen kaldırabilir ve onun kullanıldığı tek yerde doğrudan sayıOku işlevini çağırabiliriz. Böylece döngü içindeki satırlar da azalmış olur:
for (int i = 0; i != adet; ++i) { sayılar ~= sayıOku("Sayı " ~ to!string(i)); }
Ben bu programda son bir değişiklik daha yapacağım ve sayıların okunmasıyla ilgili bütün işlemleri tek bir işleve taşıyacağım. Böylece "Sayıları oku" açıklaması da ortadan kalkacak; çünkü yeni işlevin ismi zaten ne işlem yapılmakta olduğunu açıklayacak.
sayılarıOku ismini verebileceğimiz bu işlevin hiçbir parametre alması gerekmez, ama değer olarak bütün diziyi üretirse ismi ile de uyumlu bir kullanımı olur.
Böylece bütün program son olarak şöyle yazılabilir:
import std.cstream; import std.conv; void diziYazdır(string başlık, int[] dizi) { dout.writefln(başlık, ":"); foreach (i, eleman; dizi) { dout.writefln("%3s:%5s", i, eleman); } } int sayıOku(string mesaj) { int sayı; dout.writef(mesaj, "? "); din.readf(&sayı); return sayı; } int[] sayılarıOku() { int[] sayılar; int adet = sayıOku("Kaç sayı gireceksiniz"); for (int i = 0; i != adet; ++i) { sayılar ~= sayıOku("Sayı " ~ to!string(i)); } return sayılar; } void main() { int[] sayılar = sayılarıOku(); diziYazdır("Sıralamadan önce", sayılar); sayılar.sort; diziYazdır("Sıraladıktan sonra", sayılar); }
Programın bu halini ilk haliyle karşılaştırın. Yeni programda main işlev içinde programın ana adımları açık bir şekilde anlaşılmaktadır. Oysa ilk halinde programın ne yaptığını ancak kodları ve açıklamalarını okuyarak anlamak zorunda kalıyorduk.
Programın son halinde daha fazla satır bulunuyor olması sizi yanıltmasın. İşlevler aslında kodu küçültürler, ama bunu çok kısa olan bu programda göremiyoruz. Örneğin sayıOku işlevini yazmadan önce, girişten her tamsayı okumak için 3 satır kod yazıyorduk. Şimdi ise sayıOku'yu çağırarak her noktadaki kod satırı sayısını 1'e indirmiş olduk. Hatta, for döngüsü içindeki sayı'nın tanımını da tamamen kaldırabildik.
Açıklamalar (gerekmesinler)
Eğer programdaki işlemlerin bazılarının ne yaptıklarını açıklama satırları yazarak açıklama gereği duyuyorsanız, belki de o işlemlerin bir işleve taşınmaları zamanı gelmiştir. İşlevin ismini açıklayıcı olarak seçmek, açıklama satırının gereğini de ortadan kaldırır.
Yukarıdaki programdaki üç açıklama satırından bu sayede kurtulmuş olduk.
Açıklama satırlarından kurtulmanın önemli başka bir nedeni daha vardır: açıklama satırları zaman içinde kodun ne yaptığı hakkında yanlış bilgi vermeye başlarlar. Baştan iyi niyetle ve doğru olarak yazılan açıklama satırı, kod değiştiğinde unutulur ve zamanla koddan ilgisiz hale gelebilir. Artık açıklama satırı ya yanlış bilgi veriyordur, ya da tamamen işe yaramaz durumdadır. Bu yüzden programları açıklama satırlarına gerek bırakmadan yazmaya çalışmak önemlidir.
Parçalara ayırın
Programı bir bütün halinde main içinde yazmak yerine küçük parçalara ayırmak bütün programı kolaylaştırır. Küçük işlevlerin birim olarak işlemleri de basit olacağından, teker teker yazılmaları çok daha kolay olur. Programın diğer işlemleri bu yapı taşları üzerine kurulunca bütün program daha kolay yazılır. Daha da önemlisi, programda daha sonradan gereken değişiklikler de çok daha kolay olur.
Problemler
menüyüGösterişlevini, bütün seçeneklerini bir dizi olarak alacak şekilde değiştirin. Örneğin şu şekilde çağırabilelim:- Komut satırından girilen dizgiyi mors koduna çeviren bir program yazın. Bu programı yazarken amacınız olabildiğince çok işlev kullanmak olsun. Fikirler:
- Türkçe harfleri boşverin. Hem mors kodunun günümüzde geçerliliği kalmadı, hem de zaten Türk harfi eki olduğunu sanmıyorum
- Dizgideki harflerin mors kodu karşılıklarını bir eşleme tablosu ile bulabilirsiniz:
string[char] morsAlfabesi = [ 'a' : ".-", 'b' : "-...", 'c' : "-.-.", /* ... diğerleri ... */ ];
- Hiç gerekmese de, bu programın çıktısını daha doğru yapmak isterseniz mors kodundaki boşlukları da göze alın. Mors kodunun hep kısa ve uzun işaretlerden oluştuğunu duyarız. Oysa bu işaretlerin aralarındaki boşlukların uzunlukları da önemlidir:
- uzun işaret kısanın 3 katı uzunluğundadır
- boşluk, kısa işaret uzunluğundadır
- kısa ve uzun işaretler arasında tek boşluk vardır
- harf aralarında 3 boşluk vardır
- sözcük aralarında 7 boşluk vardır
Boşluk için '.', kısa işaret için '=', ve uzun işaret için '===' kullanırsak; "ab 12" dizgisi için programın çıktısı şöyle olabilir:
=.===...===.=.=.=.......=.===.===.===.===...=.=.===.===.=== a b 1 2
- İki boyutlu bir diziyi bir resim kağıdı gibi kullanan bir program yazdım. Siz bu programı istediğiniz şekilde değiştirin:
Bundan sonraki bütün derslerde ve yazacağınız bütün programlarda hep işlevler olacak. O yüzden hiç işlev deneyimi sıkıntısı çekmeyeceksiniz. Burada ilginç problemler bulabildiğime inanmıyorum. Hiç olmazsa çözümlerini ilginç bulabilirsiniz.
string[] seçenekler =
[ "Siyah", "Kırmızı", "Yeşil", "Mavi", "Beyaz" ];
menüyüGöster(seçenekler, 1);
Çıktısı şöyle olsun:
1 Siyah 2 Kırmızı 3 Yeşil 4 Mavi 5 Beyaz
import std.cstream; /* * 20 adet char[60] dizisi... Yani, 20 satır ve 60 sütundan * oluşan iki boyutlu bir alan */ alias char[60][20] Kağıt; /* * Not: alias, "takma isim" anlamına gelir. Programın geri * kalanında hep char[60][20] yazmak yerine, daha * açıklayıcı olarak 'Kağıt' yazabilmemizi sağlıyor. */ /* * Verilen kağıdı satır satır çıkışa gönderir */ void kağıdıGöster(Kağıt kağıt) { foreach (satır; kağıt) { foreach (kare; satır) { /* * Not: Bir türün .init niteliği, o türün "ilk * değeri" anlamına gelir. Bu değer çoğu tür * için 0'dır; kesirli sayı türleri için * .nan'dır. */ if (kare == char.init) { dout.writef('.'); } else { dout.writef(kare); } } dout.writefln(); } } /* * Verilen kağıdın belirtilen yerine bir benek koyar; orayı * bir anlamda "boyar" */ void benekKoy(Kağıt kağıt, int satır, int sütun) { kağıt[satır][sütun] = '#'; } /* * Kağıdın belirtilen yerinden aşağıya doğru, belirtilen * uzunlukta çizgi çizer */ void düşeyÇizgiÇiz(Kağıt kağıt, int satır, int sütun, int uzunluk) { foreach (çizilecekSatır; satır .. satır + uzunluk) { benekKoy(kağıt, çizilecekSatır, sütun); } } void main() { Kağıt kağıt; benekKoy(kağıt, 7, 30); düşeyÇizgiÇiz(kağıt, 5, 10, 4); kağıdıGöster(kağıt); }
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları