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;
Lengthve 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
- Ş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. - Ortalamanın üstünde kalan notları ayrı satırlarda, sıra numarasıyla yazdır — indeks gerektiği için bu kez for kullan.
- Diziyi
Array.Reversekullanmadan 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). Sonucustring.Joinile yazdır,Array.Reverseile karşılaştırıp doğrula. - Referans deneyi:
var yedek = notlar;yap,notlar[0] = 0;yaz,yedek[0]'ı yazdır. Gördüğünü bir cümleyle açıkla. Sonrayedek = notlar.ToArray();ile gerçek kopya alıp deneyi tekrarla. - Kırıp gözle: döngü koşulunu
i <= notlar.Lengthyap,IndexOutOfRangeException'ı üret, mesajdaki bilgiyi oku, düzelt. Boş birstring[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.
