Solidity'de Diziler (Arrays)

Solidity'de Diziler (Arrays)

Merhabalar. Solidity'de referans türlerine devam ediyoruz. Bu yazımızda dizileri (arrays) ele alacağız.

Dizi (Array) Nedir?

Diziler (arrays), listelere benzer veri yapılarıdır. Numaralar, yazılar, boolean değerler gibi farklı değer tiplerinde verileri barındırabilirler. Dizileri örnekleyecek olursak:

C# Dizi Örneği

string[] programlamaDilleri = {"C#","Solidity", "Python", "Javascript"};

Python Dizi Örneği

programlamaDilleri = ["C#","Solidity", "Python", "Javascript"]

Javascript Dizi Örneği

const programlamaDilleri = ["C#","Solidity", "Python", "Javascript"];

Solidity Dizi Örneği

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;


contract Diziler {  
  
    // programlamaDilleri isminde bir değişken tanımladık.
    string[] programlamaDilleri;   
    //İç içe dizi tanımladık. 
    string[][2] benzerDiller;    
      
    // Listeyi doldurmak için bir fonksiyon tanımladık.
    function liste() public returns (string[4] memory, string[] memory){  
        string[4] memory diller = ["C#","Solidity", "Python", "Javascript"];  
        programlamaDilleri = ["C#","Solidity", "Python", "Javascript"];
        
        benzerDiller = [["C#","Solidity"], ["Python", "Javascript"]];


        string memory ilkDil = programlamaDilleri[0];
        return (diller, programlamaDilleri);  
  }  

Solidity'de Diziler

Dizilerin ne olduğu hakkında kısaca bilgi sahibi olduk. Peki Solidity dilindeki dizilerin özellikleri neler?

  • Diziler, sabit ya da dinamik büyüklüklerde olabilir. string[3] ifadesi 3 elemanlı sabit bir string dizisini ifade ederken string[] ifadesi değişken sayıdaki elemanın bulunduğu bir string dizisini ifade eder.
  • Dizilerde elemanlar 0'dan başlar. İlk eleman sıfır endeksine göre çağrılır. string memory ilkDil = programlamaDilleri[0];
  • Dinamik ve sabit diziler beraber kullanılabilirler. Örneğin, uint [][2] memory x dizisinde 2 elemanlı bir dizinin içerisinde dinamik elemanlı alt diziler vardır. Yukarıdaki kod bloğunda örneğini görebilirsiniz.
  • Dizinin son sayısını aşan bir çağrı yapmak hata fırlatır. Örneğin, 5 elemanlı bir diziden 8. elemanı çağırmak hata döndürecektir. (Diziler2 sözleşme kodunu inceleyin.)
  • push() ve pop() fonksiyonlarıyla eleman ekleme ve çıkarma yapılabilir. (Diziler2 sözleşme kodunu inceleyin.)
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;


contract Diziler2 {  
  
    // programlamaDilleri isminde bir değişken tanımladık.
    string[] programlamaDilleri;   
      
    // Listeyi doldurmak için bir fonksiyon tanımladık.
    function liste() public returns (string memory){  
        // push() fonksiyonu kullanarak dizimize eleman ekledik.
        programlamaDilleri.push("C++");


        // Dizimizden son elemanı sildik.
        programlamaDilleri.pop();


        string memory ilkDil = programlamaDilleri[0];
        // Eleman sayısı 4 olduğundan son elemanı almak için toplam sayıdan bir eksiği çağırıyoruz. (Dizilerin 0 endeksli olduğunu unutmayın!)
        string memory sonDil = programlamaDilleri[3];
        // Eleman sayısı 4 olduğundan son elemanı çağırırken hata alacağız. (Dizilerin 0 endeksli olduğunu unutmayın!)
        //string memory sonDilHatali = programlamaDilleri[4];


        return (sonDil);  
  } 

Bytes ve String Dizileri

bytes ve string diziler birbiriyle aynı işleve sahiptir. Aralarındaki fark string'de metnin içindeki elemanlara ulaşmak mümkün değilken bytes dizilerinde her bir elemana ulaşılabilmekte ve müdahale edilebilmektedir.

bytes Diziler

contract bytesDiziler {
  bytes1[] public bytesDizi1;
  
  /*a*/ // bytes1[] public bytesDizi2 = ["a", "a"]; // HATALI. Dizi tanımlamasında harfler için tür tanımlaması yapmadığımız için hata aldık. 
  /*b*/ bytes1[] public bytesDizi2 = [bytes1("a"),bytes1("a")]; // DOĞRU 


  // Storage dizi işlemleri
  function elemanEkle() public {
    // bytesDizi1.push("as"); // 1 byte değerinden fazla "as" bir değer eklemeye çalıştığımız için hata aldık.
    bytesDizi1.push("a"); // Bir byte değerinde eleman ekledik.


    /*
        1. ve 2. numaralı işlemler birbiriyle aynı görevi yapmaktadır. a ve b numaları işlemlerin aksine burada bytes1 vb. tanımlamaya gerek YOKTUR!
    */
    /*1.*/ bytesDizi2.push("a"); 
    /*2*/ bytesDizi2.push(bytes1("a"));


    bytesDizi1.pop();
    bytesDizi2.pop();
  }


  // Memory dizi işlemleri
  function memoryBytesDiziler() public {
    // 
    bytes1[] memory x = new bytes1[](1);
    // x.push(bytes1("a")); // HATALI KULLANIM. push ve pop mümkün burada değil.


    x[0] = bytes1("a"); // 0. indeksi yani ilk elemanı bytes1 "a" olarak tanımladık.
    x[1] = bytes1("b"); // 1. indeksi yani ikinci elemanı bytes1 "b" olarak tanımladık.


    x[2] = "c"; // 2. indeksi yani üçüncü elemanı bytes1 "c" olarak tanımladık.


    // x[2] = "sc"; // "sc" bytes2, bytes3 gibi değerlerde tanımlanabilir. Bu yüzden bytes1 tanımlamasında hata aldık.

string Diziler

  contract stringDiziler {
  // string dizi tanımladık.
  string[] public stringDizi1; 
  string[] public stringDizi2 = ["a", "b", "c"]; 
  string[] public stringDizi3 = [string("a"),string("b")]; // DOĞRU 
  string[3] public stringDizi4 = ["a", "b", "c"]; // Sabit  büyüklükteki string dizi
  string[3] public stringDizi5 = ["a", "b"]; // Sabit  büyüklükteki string dizinin ilk iki elemanını tanımladık.
  // string[1] public stringDizi5 = ["a", "b", "c"];  // 1 elemanlı olması gereken ama fazla eleman girilen sabit dizi. HATALI! 


  // Storage dizi işlemleri
  function elemanEkleCikar() public {
    // stringDizi1'e eleman ekleyelim.
    stringDizi1.push("a"); 
    stringDizi1.push(string("asdasdas")); // Farklı bir tanımlama.
    stringDizi1.push("sdadsa"); 
    // stringDizi1.push(string("ş")); // ParserError: Invalid character in string.


    stringDizi1.pop();
    stringDizi1.pop();
  }


  // Memory dizi işlemleri
  function memoryStringDiziler() public {
    string[] memory x = new string[](1);
    // x.push(bytes1("a")); // HATALI KULLANIM. push ve pop mümkün burada değil.


    x[0] = string("a"); // 0. indeksi yani ilk elemanı string "a" olarak tanımladık.
    x[1] = string("b"); // 1. indeksi yani ikinci elemanı string "b" olarak tanımladık.
    x[2] = "c"; // 2. indeksi yani üçüncü elemanı string "c" olarak tanımladık.
    // x[3] = "ğ"; // ParserError: Invalid character in string. (KARAKTER HATASI)
    x[4] = "BUYUKHARF"; 
    x[5] = "FAZLA KARAKTER"; 
  

bytes.concat ve string.concat fonksiyonları metinleri birbirine iliştirmemizi sağlar. Örneğin, "minimal" ve "block" değerlerini concat fonksiyonlarıyla birbirine birleştirebiliriz ve "minimalblock" metnini bytes ya da string değerinde elde ederiz. Aşağıdaki örnek kodları inceleyebilirsiniz.

bytes.concat

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;


contract concatFonksiyonlari{


  bytes public isim1 = "minimal"; // bytes: 0x6d696e696d616c
  bytes public isim2 = "block"; // bytes: 0x626c6f636b


  function isimBirlestir() view public returns(bytes memory){
    bytes memory birlestirilen_isim = bytes.concat(isim1, isim2);
    return birlestirilen_isim; // bytes: 0x6d696e696d616c626c6f636b
  }

concatFonksiyonlari sözleşmesindeki işlemlerin dönütleri aşağıdaki ekran görüntüsünde de gösterilmektedir.

string.concat

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;


contract concatStringFonksiyonlari{


  string public isim1 = "minimal"; // string: minimal
  string public isim2 = "block"; // string: block


  function isimBirlestir() view public returns(string memory){
    string memory birlestirilen_isim = string.concat(string(isim1), string(isim2));
    return birlestirilen_isim; // string: minimalblock döner
  }

concatStringFonksiyonlari sözleşmesindeki işlemlerin dönütleri aşağıdaki ekran görüntüsünde de gösterilmektedir.

Şubat 2022'den itibaren Solidity v0.8.12 sürümü ve sonrasında gelen derleyicilerde artık dizeleri daha basit bir şekilde birleştirebilirsiniz! Çünkü v0.8.12 öncesindeki derleyicilerde string dizelerinde concat işlemi yapılamamaktaydı ve sadece bytes.concat() destekleniyordu.

Aşağıda karşılaşılabilecek hataları, olası durumları örneklerle açıkladım. concatStringBytesFonksiyonlari sözleşmesini inceleyebilirsiniz.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;


contract concatStringBytesFonksiyonlari{


     function a(bytes1  dize1, bytes calldata dize2) view public returns(bytes memory){
        bytes memory concat_bytes = bytes.concat(dize1, dize2[:1]);
        return concat_bytes;
    }


    function b(bytes calldata dize1, bytes12 dize2, string memory dize3) view public returns(bytes memory){
        //  arrays and array slices olmayan veri türlerindeki verilerde indeks erişimi mümkün değildir.
        // TypeError: Index range access is only possible for arrays and array slices.
        // bytes memory concat_bytes1  = bytes.concat(dize1, dize2[:1]); 


        // dize3[:1] dizi parçalaması bytes türünde ve calldata konumundaki, dize1 gibi, veriler için kullanılabilir.
        // TypeError: Index range access is only supported for dynamic calldata arrays.
        // bytes memory concat_bytes2 = bytes.concat(dize1, dize3[:1]);
        
        //  string türü direk olarak bytes değerine eklenemez. bytes(dize3) şeklinde dönüştürülmelidir.
        // TypeError: Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but string memory provided.
        // bytes memory concat_bytes3 = bytes.concat(dize1, dize3); // YANLIŞ!
        bytes memory concat_bytes4 = bytes.concat(dize1[:1], bytes(dize3)); // DOĞRU!


        //  bytes türüde direk olarak string değerine eklenemez. string(dize1[:1]) şeklinde dönüştürülmelidir.
        // TypeError: Invalid type for argument in the string.concat function call. string type is required, but t_bytes_calldata_ptr_slice provided.
        string memory concat_string1 = string.concat(string(dize1[:1]), dize3); // DOĞRU!
        // string memory concat_string2 = string.concat(dize1[:1], dize3); // HATALI!


        return concat_bytes4;
 

Bellek Dizilerini Ayırma (Allocating Memory Arrays)

new koduyla dizi oluşturmak sadece bellekte (memory) depolanan dizilerde ve sabit büyüklükteki storage dizilerde mümkündür. Değişken büyüklükteki storage dizisinde new kullanıldığında hata meydana gelecektir.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;


contract DizilerMadde1 {


  uint[] public dizi1; // Dizi değişken boyutlarda olabilir.
  uint[2] public sabitElemanliDizi1; // 2 elemanlı sabit dizi. Boyut değiştirilmesi mümkün değil.




  //uint[] public dizi2 = new uint[]; //Bu tanımlama HATALIDIR. storage'da new ile değişken dizi tanımlanamaz.
  uint[] public sabitElemanliDizi2 = new uint[](6); // Sabit büyüklükteki diziyi storage'da new ile tanımladık.


  function a(uint[] memory x) public{
    uint[] memory b = new uint[](5);
    uint[] memory d = x;
 

Dizi Üyeleri (Array Members)

length: Dizilerin eleman sayısına .length ile erişmemizi sağlar. x.length şeklinde kullanılır ve integer türünde sayı döner.

push(x): Dizilerin sonuna .push(x) ile x türünde eleman eklememizi sağlar. Fonksiyon hiçbir şey döndürmez. Dinamik storage dizilerinde ve bytes'da kullanılır.

push(): Dizinin sonuna veri eklemek için dizi.push() = x şeklinde kullanılabilir.

pop(): Dizilerin sonundan .pop() ile eleman kaldırmamızı sağlar. Fonksiyon hiçbir şey döndürmez. Dinamik storage dizilerinde ve bytes'da kullanılır.

Kritik Noktalar

memory 'de tanımlanan dizilerde pop ve push ile diziye müdahale edilmesi mümkün değildir. storage 'da tanımlanan dizilerde bu işlemler gerçekleştirilebilmektedir.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;


contract DizilerMadde2 {


  uint[] public dizi; // Değişken boyutlarda tanımlanan dizi.


  function a(uint[] memory x) public{
    uint[] memory sabitMemoryDizi = new uint[](5);
    uint8[] memory sabitMemoryDizi2 = new uint8[](1);
    uint[] memory degiskenMemoryDizi = x;


    // sayıları dizinin sonuna ekleyelim.
    dizi.push(5); // => dizi = [5]
    dizi.push(3); // => dizi = [5,3]
    dizi.push(6); // => dizi = [5,3,6]


    // Diziden elemanları pop ile sondan silelim.
    dizi.pop(); // => dizi = [5,3]
    dizi.pop(); // => dizi = [5]


    // b.pop(); // TypeError: Member "pop" is not available in uint256[] memory outside of storage.
    // b.push(5); // TypeError: Member "push" is not available in uint256[] memory outside of storage.


    sabitMemoryDizi[0] = 1;
    degiskenMemoryDizi[0] = 2;


    sabitMemoryDizi2 = new uint8[](5); // Tekrardan boyut tanımlaması yaptık.
    // sabitMemoryDizi2 = new uint[](5); // Türü farklı olduğu için hata aldık. uint => uint8


    // sabitMemoryDizi = [1,3,4,5,6]; // TypeError: Type uint8[5] memory is not implicitly convertible to expected type uint256[] memory.
    // x = [1,2,3]; // Bellekteki değişken büyüklükteki bir diziye sabit tanımlama yapmak mümkün olmuyor.
    // dizi = [1,2,3]
  1. string.concat veya bytes.concat'ı bağımsız değişkenler olmadan çağırırsanız, boş bir dizi döndürürler. [1]
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;


contract concatBosDonus{


     function a(string memory  dize1) view public returns(string memory){
        string memory concat_string = string.concat();
        return concat_string;
    }

Diziler konusunu örnekler vererek inceledik. Karşılaştığım hataları ve önemli noktaları da paylaşmaya gayret ettim. Umarım faydalı olmuştur. Bundan sonraki yazımızda Array Slices konusunu inceleyeceğiz. Kolay gelsin.

Yorumunu Bırak

Çok hızlısın. Biraz dinlendikten sonra tekrar devam edebilirsin.
Bugünlük gönderebileceğin kadar yorum gönderdin. Lütfen yarın tekrar dene.
Mesajınız bize başarılı bir şekilde ulaştırıldı. Teşekkürler.

Yorumlar

0 Yorum yok

Henüz yorum yapılmamış. İlk yorum yapan sen ol.

Blog Yazarı

Ömer Faruk Coşkun
Yazar
@ofcskn

İstanbul Üniversitesi Bilgisayar Mühendisliği Öğrencisi