Skip to content

Bölüm 6 / 15

Diziler

Tanım, indeks, çok boyutlu diziler.

9 dakikalık okuma

Şimdiye kadar her değer tek değişkende durdu; 100 öğrencinin notu için 100 değişken tanımlayamazsın. Dizi, aynı tipten değerleri tek ad altında, sıralı tutan ilk veri yapındır. Bu derste tanımlama, indeksleme ve çok boyutlu dizilerin yanında iki şeyi mutlaka götürmen gerekiyor: dizinin bellekte nasıl durduğu ve referans tipi olmasının doğurduğu — mülakatların en sevdiği — davranışlar.

01Ne öğreneceksin

  • Dizi tanımlama, başlatma, varsayılan değerler; Length ve indeks kuralları (0 tabanlı, ^1, aralıklar)
  • Dizinin bellekte tek blok durduğu ve indeksle erişimin neden sabit süreli olduğu
  • Dizinin referans tipi olması: atamanın kopya değil ikinci isim ürettiği — ve bunun sonuçları
  • for/foreach seçimi, çok boyutlu ve düzensiz diziler, klasik dizi kalıpları (toplam, en büyük, ters çevirme)

02Dizi nedir, bellekte nasıl durur

Dizi (array), aynı tipten, sabit sayıda elemanın sıralı koleksiyonudur. Üç niteliğin üçü de tanımın parçasıdır: int[] dizisine string koyamazsın (tip güvenliği burada da geçerli), 5 elemanlı dizi sonradan 6 elemanlı olamaz (büyüyebilen yapı bir sonraki dersin konusu) ve her elemanın bir sıra numarası vardır.

Sabit boyutun karşılığında büyük bir avantaj gelir: dizi bellekte tek parça, yan yana durur. 5 elemanlı bir int dizisi, arka arkaya beş int gözüdür. Bu yerleşim sayesinde "3. elemana git" işlemi arama gerektirmez — başlangıç adresine 3 eleman boyu eklemek yeterlidir. Eleman sayısı ne olursa olsun indeksle erişim sabit sürelidir; bu özellik Hat 02'nin veri yapıları ve Big-O derslerinde "O(1) erişim" adıyla karşına çıkacak ve dizinin diğer yapılarla kıyasındaki ana kozu olacak.

03Tanımlama, başlatma, varsayılanlar

İki tanımlama biçimi vardır — boyut vererek boş açmak ya da elemanları sayarak açmak:

int[] notlar = new int[5];              // 5 gözlü, hepsi 0
string[] gunler = { "Pzt", "Sal", "Car" };  // elemanlarıyla; boyut 3 olarak kesinleşir
var puanlar = new[] { 85, 92, 78 };     // tip elemanlardan çıkarılır: int[]

Boş açılan dizinin gözleri boş değildir; her göz tipinin varsayılan değerini alır: sayılar 0, bool false, string ve diğer referans tipleri null — "değer yok" işareti. Şu tuzağı şimdiden gör:

string[] adlar = new string[3];
Console.WriteLine(adlar[0] == null);      // True — "" değil, null
Console.WriteLine(adlar[0].Length);       // ÇALIŞMA ZAMANI HATASI: NullReferenceException

NullReferenceException, C# dünyasının en çok görülen çalışma zamanı hatasıdır; "değer yok"un üzerinde işlem yapmaya kalkmanın sonucudur. Null ile baş etmenin sistemli yolu Hat 03'te ayrı bir ders (cs-null); bugünlük kural: boş açtığın referans dizisinin gözlerini kullanmadan önce doldur. Length özelliği eleman sayısını verir: notlar.Length → 5.

04İndeks: 0'dan başlar, Length-1'de biter

Elemanlara köşeli parantezle erişilir ve sayma 0'dan başlar — ilk eleman [0], son eleman [Length - 1]:

var puanlar = new[] { 85, 92, 78, 64, 90 };
Console.WriteLine(puanlar[0]);     // 85 — ilk
Console.WriteLine(puanlar[4]);     // 90 — son (5 eleman, son indeks 4)
puanlar[1] = 95;                   // yazma da aynı sözdizimi
Console.WriteLine(puanlar[5]);     // ÇALIŞMA ZAMANI HATASI: IndexOutOfRangeException

Sınır aşımı derlemede yakalanmaz — indeks çoğu zaman değişkendir, derleyici değerini bilemez; hata çalışma anında IndexOutOfRangeException olarak patlar. Geçen dersin bir-eksik hatası burada gerçek bedeline kavuşur: i <= puanlar.Length koşullu döngü son turda diziyi aşar. Döngü koşulunun standardı bu yüzden i < dizi.Length'tir.

Modern C# sondan saymayı ve dilimlemeyi de bilir — okunması sınavdan çok gerçek kodda gerekir:

Console.WriteLine(puanlar[^1]);    // 90 — sondan birinci
var ilkUc = puanlar[0..3];         // yeni dizi: { 85, 95, 78 } — 3 dahil değil
var sonIki = puanlar[^2..];        // { 64, 90 }

Aralık sözdiziminde a..b "a dahil, b hariç" okunur ve yeni bir dizi üretir — orijinali değiştirmez.

05Dizi üzerinde döngü: for mu foreach mu

Geçen dersin iki aracı dizide buluşur:

var puanlar = new[] { 85, 95, 78, 64, 90 };

for (var i = 0; i < puanlar.Length; i++)
{
    Console.WriteLine($"{i + 1}. öğrenci: {puanlar[i]}");   // indeks elinde
}

foreach (var puan in puanlar)
{
    Console.WriteLine(puan);                                 // yalnızca değer elinde
}

Seçim kuralı: indeks bilgisi ya da elemanı değiştirme ihtiyacı varsa for; yalnızca okuyorsan foreach. İkinci yarısı önemli çünkü foreach'in döngü değişkeni salt okunurdur:

foreach (var puan in puanlar)
{
    puan = puan + 5;        // DERLEME HATASI: foreach değişkenine atanamaz
}
for (var i = 0; i < puanlar.Length; i++)
{
    puanlar[i] += 5;        // doğru yol: indeksle yaz
}

Bu kısıt bilinçlidir: foreach "koleksiyonu gez" der, "değiştir" demez; okuma niyetini sözdizimiyle belgeler. Kod okuyan kişi foreach gördüğünde koleksiyonun değişmediğinden emin olabilir — niyeti dille ifade etmek, temiz kodun (Hat 04) temel fikirlerindendir.

06Kritik dönemeç: dizi bir referans tipidir

1. derste kural netti: atama, o anki değeri kopyalar. Dizide bu kural yine geçerli — ama kopyalanan şey dizinin kendisi değil, dizinin bellekteki adresidir (referansı). Sonucu kendi gözünle gör:

var a = new[] { 1, 2, 3 };
var b = a;                 // kopyalanan: adres. İki isim, TEK dizi.
b[0] = 99;
Console.WriteLine(a[0]);   // 99 — "a'ya dokunmadım" sanıyordun

int, double, bool gibi tipler değer tipidir: değişken değerin kendisini taşır, atama içeriği kopyalar. Dizi (ve string dışındaki çoğu karmaşık tip) referans tipidir: değişken, verinin bellekteki yerini gösteren bir adres taşır; atama adresi kopyalar, veri tek kalır. b üzerinden yapılan değişikliğin a'da "görünmesinin" sebebi ortada değişen tek bir dizinin olmasıdır.

Gerçek bir kopya istiyorsan bunu açıkça söylersin:

var kopya = a.ToArray();       // yeni dizi, elemanlar kopyalandı
kopya[0] = 5;
Console.WriteLine(a[0]);        // 99 — orijinal etkilenmedi

Aynı sebepten == iki diziyi içerik olarak karşılaştırmaz — "aynı diziyi mi gösteriyorlar?" diye adresleri karşılaştırır: new[] {1,2} == new[] {1,2} False'tur. İçerik karşılaştırması a.SequenceEqual(b) ile yapılır. Değer/referans ayrımının bellek düzeyi (stack/heap) Hat 03'ün ilk dersidir ve mülakatların demirbaşıdır; bu dersteki davranışı şimdi sindirirsen o ders bir isimlendirme dersi olacak, sürpriz değil.

07Çok boyutlu ve düzensiz diziler

Tablo biçimli veri için iki yapı vardır. Çok boyutlu dizi tek parça bir ızgaradır:

int[,] tablo = new int[3, 4];        // 3 satır, 4 sütun
tablo[1, 2] = 7;                     // 2. satır, 3. sütun
Console.WriteLine(tablo.GetLength(0));  // 3 — satır sayısı
Console.WriteLine(tablo.GetLength(1));  // 4 — sütun sayısı

for (var satir = 0; satir < tablo.GetLength(0); satir++)
{
    for (var sutun = 0; sutun < tablo.GetLength(1); sutun++)
    {
        Console.Write($"{tablo[satir, sutun]} ");
    }
    Console.WriteLine();
}

Düzensiz dizi (jagged array) ise "dizilerin dizisidir" — her satır ayrı bir dizi olduğundan uzunlukları farklı olabilir:

int[][] gruplar = new int[3][];
gruplar[0] = new[] { 1, 2 };
gruplar[1] = new[] { 3, 4, 5, 6 };
gruplar[2] = new[] { 7 };
Console.WriteLine(gruplar[1][2]);    // 5 — önce satır, sonra eleman

Seçim: boyutları eşit gerçek ızgara (oyun tahtası, matris) → [,]; satır uzunlukları değişken (gruplara bölünmüş kayıtlar) → [][]. Sınavda ikisinin sözdizimi farkı ([1, 2] / [1][2]) sorulur; pratikte her satırın referans tipli ayrı dizi olduğu için düzensiz dizinin 6. bölümdeki tüm davranışları satır düzeyinde de gösterdiğini bil.

08Hazır metotlar ve klasik kalıplar

Sık işler için hazır araçlar (Hat 0'ın yazılım dersindeki ilke: çözülmüş problemi yeniden yazma, kütüphaneden al):

var sayilar = new[] { 42, 7, 19, 88, 3 };
Array.Sort(sayilar);                          // yerinde sıralar: 3 7 19 42 88
Array.Reverse(sayilar);                       // yerinde ters çevirir
var yeri = Array.IndexOf(sayilar, 19);        // bulamazsa -1
Console.WriteLine(string.Join(", ", sayilar)); // diziyi tek satırda yazdırmanın standardı

var metin = "elma,armut,kiraz";
string[] parcalar = metin.Split(',');          // metinden dizi üretir

Array.Sort ve Array.Reverse'ün yerinde çalıştığına dikkat: yeni dizi döndürmez, verdiğin diziyi değiştirir — referans tipinin günlük sonucu. string.Join / string.Split ikilisi ise dizi ile metin arasındaki köprüdür ve alıştırmalarda sürekli kullanacaksın.

Hazır metot olsa da üç kalıbı elle yazabilmen gerekir — mülakatlarda "Sort kullanmadan" diye açıkça istenir:

var enBuyuk = sayilar[0];               // en büyüğü bulma
var toplam = 0;                          // biriktirici (5. ders)
foreach (var s in sayilar)
{
    if (s > enBuyuk) { enBuyuk = s; }
    toplam += s;
}
var ortalama = (double)toplam / sayilar.Length;   // 3. ders: tamsayı bölmesine dikkat

Son satırdaki cast'i atlarsan ortalama sessizce aşağı yuvarlanır — operatör dersinin tuzağı gerçek hesapta ilk kez burada ısırır.

09Mini alıştırma — not analizi ve referans deneyi

Yaklaşık 20 dakika · dotnet new console -o diziler

  1. Şu diziyle başla: var notlar = new[] { 70, 85, 42, 90, 55, 100, 67, 78 }; Tek geçişte (tek foreach) şunları hesaplayıp yazdır: toplam, ortalama (ondalıklı!), en yüksek, en düşük, 70 ve üzeri kaç kişi.
  2. Ortalamanın üstünde kalan notları ayrı satırlarda, sıra numarasıyla yazdır — indeks gerektiği için bu kez for kullan.
  3. Diziyi Array.Reverse kullanmadan ters çevir (ipucu: i ile Length-1-i gözlerini geçici değişkenle takas et; döngü ortaya kadar dönmeli — neden sonuna kadar dönerse dizinin eski haline geldiğini açıkla). Sonucu string.Join ile yazdır, Array.Reverse ile karşılaştırıp doğrula.
  4. Referans deneyi: var yedek = notlar; yap, notlar[0] = 0; yaz, yedek[0]'ı yazdır. Gördüğünü bir cümleyle açıkla. Sonra yedek = notlar.ToArray(); ile gerçek kopya alıp deneyi tekrarla.
  5. Kırıp gözle: döngü koşulunu i <= notlar.Length yap, IndexOutOfRangeException'ı üret, mesajdaki bilgiyi oku, düzelt. Boş bir string[3] açıp ilk elemanının .Length'ine erişmeyi dene — NullReferenceException'ı da tanımış ol.

10Kendini sına

new int[4] ve new string[4] dizilerinin gözlerinde başlangıçta ne vardır? İkincisi neden daha risklidir?

Her göz tipin varsayılan değerini alır: int dizisinde 0, string dizisinde null. İkincisi risklidir çünkü null bir göz üzerinde işlem yapmak (adlar[0].Length gibi) çalışma zamanında NullReferenceException fırlatır; 0 ise geçerli bir sayı olarak sessizce hesaba karışır. Boş açılan referans dizisi kullanılmadan önce doldurulmalıdır.

var b = a; (a bir dizi) satırından sonra b[0] = 99; yapılırsa a[0] ne olur, neden?

99 olur. Dizi referans tipidir: atama dizinin kendisini değil bellekteki adresini kopyalar; a ve b aynı diziyi gösteren iki isimdir. Bağımsız kopya için a.ToArray() (ya da Array.Copy) kullanılır. Aynı sebeple == içerik değil adres karşılaştırır; içerik için SequenceEqual.

foreach içinde döngü değişkenine atama neden derleme hatasıdır? Elemanları değiştirmek istersen ne yaparsın?

foreach'in değişkeni salt okunurdur; yapı "koleksiyonu gez" niyetini temsil eder ve değişiklik niyetini sözdiziminden dışlar — okuyan kişi foreach gördüğünde koleksiyonun değişmediğinden emin olur. Eleman değiştirmek için indeksli for kullanılır: dizi[i] = ....

IndexOutOfRangeException derlemede mi çalışma zamanında mı ortaya çıkar? 5 elemanlı dizide geçerli indeks aralığı nedir?

Çalışma zamanında — indeks çoğu zaman değişken olduğundan derleyici değerini bilemez. 5 elemanlı dizide geçerli indeksler 0..4'tür; son eleman Length - 1 (ya da [^1]). En sık sebep, döngü koşulunda <= kullanılan bir-eksik hatasıdır; standart koşul i < dizi.Length'tir.

11Sınav köşesi

Yazılıda nasıl sorulur

Üç kalıp: (1) Çıktı sorusu — indeksli erişim ve döngü karışımı; 0 tabanlı sayma ile Length - 1 sınırını şaşırtmaya kurulu. (2) Referans davranışı — b = a sonrası değişikliğin a'da görünmesi; "kodun çıktısı nedir?" diye gelir, değer/referans ayrımını bilmeyen 1-2-3 der, doğru cevap 99-2-3'tür. (3) Tanım — "Dizinin elemanına indeksle erişim hangi karmaşıklıktadır?" (sabit, O(1)); çok boyutlu/düzensiz sözdizimi farkı. Elle en büyük bulma ve ters çevirme, kodlama sorusu olarak da standarttır.

Sözlüde nasıl anlatılır — 2 dakikalık taslak

"C#'ta dizi nedir, özellikleri nelerdir?" için: (1) Aynı tipten, sabit boyutlu, sıralı koleksiyon; bellekte tek blok durur, bu yüzden indeksle erişim O(1)'dir. (2) Referans tipidir — atama adres kopyalar; bağımsız kopya ToArray ile alınır. Bu cümleyi kendiliğinden kurmak, değer/referans sorusunu gelmeden cevaplamaktır. (3) Sınır aşımı çalışma zamanında IndexOutOfRangeException'dır; foreach okumada, for değişiklikte kullanılır. (4) Kapanış: "Boyut çalışma zamanında değişmeli ya da uçtan ekleme-silme gerekiyorsa dizi değil List kullanırım" — bir sonraki dersin kapısını mülakatta kendin açmış olursun.