salsa source

[이더리움 베이직] Solidity 문법2 본문

STUDY/블록체인

[이더리움 베이직] Solidity 문법2

dayofday 2018. 3. 13. 17:39

<참고 : 이더리움 베이직>



조건문과 반복문





형 변환


암묵적 변환

데이터의 손실이 없는 자료형 변환에서만 일어난다.  정수형은 더 큰 정수형으로 암묵적 변환이 가능하다.


uint8 -> int16, uint16 으로 변환할 때 암묵적 변환이 일어난다.

하지만 int->uint 로는 변환 불가능하다.


자연수는 더 큰 크기의 바이트 형으로 암묵적 변환이 가능하다.

uint16->byte2 로 변환 가능

byte2->uint16 으로 암묵적 변환 불가능


uint16으로 암묵적 변환이 가능한 자료형들은 address로 암묵적 변환 가능



명시적 변환

반환할 자료형을 고정적으로 결정할 때 사용한다. 

또한 컴파일러가 해주지 않는 변환을 개발자가 데이터 손실을 감안해서라도 변환하고 싶을 때 사용할 수 있다.

데이터 손실이 있을 수 있으니 유의해서 사용하자.



데이터 손실이 일어나는 명시적 변환 예시 코드

--------------------------------------------------

    function convertExample() returns (uint){

        int8 x = -5;

        uint32 y = uint32(x);

        

        uint32 a = 0x12345678;

        uint16 b = uint16(a);

    }

--------------------------------------------------


x == -5 == 0xFB

y == 0xFFFFFFFB == 4294967291


a == 0x12345678 == 305419896

b == 0x5678 == 22136


형 변환에 의해 값이 달라짐



형 추론

변수 선언 시 자료형 반드시 명시할 필요 없이 var 키워드로 선언 가능하다.

이 변수는 처음 대입되는 데이터의 형태로 선언된다.

단, 함수의 매개변수나 변환자에 사용할 수 없다.



배열


자료형 [크기] 이름;  과 같이 선언


배열의 기능

(1) length : 배열의 길이 반환 시 사용한다.

(2) push : 배열의 마지막에 값을 추가하고 길이를 반환한다. 저장소 동적배열에서 사용 가능하다.



동적배열 예시 코드

--------------------------------------------------

    uint [3] arr1 = [1, 2, 3];                //정적배열 선언

    uint [] arr2 = [1, 2, 3];                 //동적배열 선언

    uint [] arr3;                               //동적배열 선언. push로 값 입력 가능.

    

    function arrayExample(){                         // 지역배열 선언

        uint [3] brr;                                     // 정적배열  길이 == brr.length = 3;

        uint [4**2] brr2;                               // 정적배열  길이 == brr2.length = 16;

  uint [] memory brr3 = new uint[](8);     // 메모리 배열  brr3 길이 == brr3.length == 8;

        uint [][10] brr3;                                // 동적배열

    }

--------------------------------------------------


지역 배열의 경우 선언과 동시에 값을 입력할 수 없다.



배열 저장 위치에 따라 메모리배열과 저장소배열로 나뉜다.

메모리에 저장된 데이터는 컨트랙트 실행이 종료되면 지워지고,

저장소에 저장된 데이터는 실행이 종료되어도 영구히 저장된다.


다만, 저장소보다 메모리에 저장하는 것이 gas 소모량이 적다.


상태변수는 무조건 저장소에 저장되며, 지역변수는 옵션에 따라 저장소와 메모리를 변경할 수 있다.

기본적으로 저장소에 저장되므로 별도로 memory 키워드를 붙이지 않으면 저장소 배열로 선언된다.





아래 코드에서 선언된 배열들은(좌변) 모두 저장소 배열

입력값이나 new 키워드로 선언된 배열들(우변)은 모두 메모리 배열이다.

--------------------------------------------------

    uint32 [3] ary1 = [1, 2, 3];

    uint8[] ary2 = new uint8[](7);

    uint8[3] ary3 = [1234, 5678, 9012]; //Error 1! 

    

    function aryEx(){

        uint8[3] bry1 = [1, 2, 3]; //Error 2!

        uint8[3] bry2 = new uint8[] (bry1.length); //Error 2!

        uint8[] bry3 = ary2;

    }

--------------------------------------------------


Error 1

더 작은 형으로 변환 되어 형 변환 오류가 일어나 컴파일 오류 발생한다.

uint8 -> uint256 으로 바꾸어주면 오류가 수정된다.


Error 2

메모리배열을 상태 배열에 대입할 때 배열이 복사되어 사본이 대입된다.

하지만 메모리 배열을 지역 배열에 대입할 수 없으므로 컴파일 오류 발생한다.





지역배열의 경우 선언과 동시에 값을 입력할 수 없으나

메모리배열은 new 키워드를 사용하거나 원소들을 대괄호를 묶어 할당 가능하다.


--------------------------------------------------

    uint32 [3] ary1 = [1, 2, 3];

    uint8[] ary2 = new uint8[](7);

    uint256[3] ary3 = [1234, 5678, 9012];

    

    function aryEx(){

        uint8[3] memory bry1 = [1, 2, 3];

       // uint8[3] memory bry2 = new uint8[] (bry1.length);  //왜 에러...ㅜ

        uint8[] bry3 = ary2;

    }

--------------------------------------------------



메모리배열은 저장소 배열과 다르게 암묵적인 형변환을 지원하지않기 때문에 

uint8[3] memory bry1 = [1, 2, 3]; 에서 uint8[3]이 아닌 다른 크기로 변경하면 오류가 발생한다.



기본적으로 동적배열 길이는 실행 중에도 결정할 수 있지만

메모리 동적 배열은 저장소 동적배열과는 달리 한 번 생성된 후에는 length에 값을 입력함으로써 배열의 길이를 변경할 수 없다.




고정 바이트 배열


byte형태로도 변수 선언이 가능하고, byte형은 논리 연산(&, |, ^, ~)도 가능하다.



예시 코드

--------------------------------------------------

    function byteEx() returns (byte and, byte or, byte xor, byte nego){

        byte v1 = 0x00;

        byte v2=  0xff;

        

        and = v1 & v2;

        or = v1 | v2;

        xor = v1 ^ v2;

        nego = ~v1;

    }

--------------------------------------------------




동적 바이트 배열


기본적인 선언의 형태

bytes memory name = new bytes(length);

name : 변수명    length : 초기화 할 길이



동적과 정적 바이트 배열의 차이 예시코드

--------------------------------------------------

    function byteExample(){

        bytes2 staticArray;   //정적 바이트 배열

        staticArray = "staticByteArray"; //Error!

        

        bytes memory dynamicByteArray = new bytes(2); //동적 바이트 배열

        dynamicByteArray = "dynamicByteArray";

    }

--------------------------------------------------


Error! Why??

정적 바이트 배열은 선언할 때부터 고정된 크기를 지녀 할당된 크기보다 큰 데이터가 입력되면 오류가 발생한다.

반면 동적 바이트 배열은 초기화된 크기보다 큰 데이터가 입력되어도 동적으로 크기를 조정하여 오류/데이터 손실 이 발생하지 않는다.


동적 바이트배열이 더 편리하나 gas를 더 많이 소모하기 때문에 고정 바이트 배열을 쓰는 것을 권장한다.






문자열 배열 예시 코드


--------------------------------------------------


    function byteStringEx(string result){

        bytes memory bArray = new bytes(5);

        bArray = "Hello";

        

        result = string(bArray);

    }


--------------------------------------------------


 result = string(bArray); 처럼 string형으로 형변환을 해 주어야 문자열이 출력된다.

그렇지 않으면 utf-8 형식의 값이 출력된다.




문자열 함수 구현하기


(1) 바이트배열과 문자열 상호 변환

바이트->문자열 : string(b);

문자열->바이트 : bytes(str);

(!단, byte[]는 string으로 변환 불가 => byte[]를 bytes 로 옮긴 후 string으로 변환)

(2) 문자열 병합

입력된 두 개의 문자열 길이를 크기로 가지는 bytes형 변수 선언 후 string형을 bytes로 변환하여 입력

(3) 부분 문자열 반환

부분 문자열 반환 함수의 기본 구조

function substring(string str, uint start, uint length) returns (string){

bytes memory fullBytes = bytes(str);


if(start + length> fullyBytes.length) return str; //방어코드


bytes memory result = new bytes(length);

return string(result);

}

필요한 길이만큼만 result에 복사하여 string으로 변환 후 리턴함

(4) 문자열을 숫자로 변환

유니코드(utf-8)형식의 숫자로 반환하는 함수.

입력받은 string을 bytes로 형변환 한 뒤 48(0의 유니코드 값)을 빼면 된다.

방어코드는   0~9사이의 숫자가 아니거나 +, - 이외의 문자가 들어가지 않도록 한다.

(5) 숫자를 문자열로 변환

각 자리의 수에 48을 더한다.

(6) 문자열 탐색 함수

문자열 두 개를 입력받아 일치하는 부분 잇다면 위치 반환하고 없으면 -1 반환.

전체 문자열과 부분 문자열을 각각 Bytes로 변환하고 이중 for문을 이용하여 부분 문자열을 모두 포함하면 해당 위치를 리턴.

(7) 문자열 수정 함수

string형을 bytes형으로 변환한 뒤 바꾸고자 하는 위치의 문자를 바꾼 후 string으로 형변환하여 리턴




맵핑


배열은 [색인, 데이터] 쌍이라면 맵핑은 [키, 데이터] 쌍이라고 할 수 있다.

키는 정수형과 주소형을 사용할 수 있다.

맵핑은 상태변수로만 사용 가능하며 지역 변수로 사용 시 참조하게 된다.


mapping (key types => data type) name

key types : 키 자료형     data type : 데이터 자료형  name : 맵핑 이름


--------------------------------------------------

    mapping (address=>uint) public balances;

    

    function setBalance(uint input){

        balances(msg.sender) = input;

    }

--------------------------------------------------

msg.sender는 컨트랙트를 실행시킨 사람의 주소를 의미.

setBalance() 함수 실행하면 msg.sender와 input으로 [키, 데이터] 쌍 만들어 맵핑 balances에 삽입한다.


맵핑의 키 -> 맵핑, 동적배열, 스마트 컨트랙트, 구조체 를 제외한 모든 자료형

맵핑의 자료 -> 맵핑을 포함한 모든 자료형 사용 가능


위 코드의 첫 행처럼 맵핑에 public을 붙이면 자동으로 값을 반환할 수 있는 함수(getter)가 생성된다.

getter 함수는 매개변수로 원래 맵핑 키의 자료형을 따르고, 반활할 때는 값으 자료형을 따른다.

반환값이 또 맵핑이면 getter는 입력 매개 변수가 2개 이상이 될 수 있고, 각 키에 해당하는 자료형을 따르게 된다.



balnaces 의 값을 getter인 balances()를 통해 접근하는 예제

--------------------------------------------------

contract BalanceSeeker{

    function accessBalance(uint input) returns (uint){

        Example4 e = new Example4(); //맵핑을 활용한 컨트랙트 생성

        e.setBalance(input); //데이터 입력

        return e.balances(this); // getter로 그 데이터를 조회하는 컨트랙트

    }

}

--------------------------------------------------


balances()는 balances(address) constant returns(uint) 구조를 가짐

getter가 반환하는 값은 accessBalance함수에 넣어준 input값과 같다.




구조체


사용자 정의 자료형

(구조체를 값으로 갖는 맵핑을 만들 수 있지만 구조체가 키인 맵핑은 만들 수 없음)


지역변수로의 구조체의 대입은 사본없이 원본 구조체를 참조하기 때문에, 구조체인 지역변수의 데이터를 수정하면 원본도 



--------------------------------------------------


    struct personalInfo{  //구조체 선언

        uint32 birth;

        bool isMale;

    }

    

    struct student{        //구조체 선언

        uint8 [2] scores;

        uint8 team;

        personalInfo pi;  //구조체 멤버변수로 구조체를 가짐

    }

    

    mapping (address=>student) students;  //구조체 student의 맵핑

    student example = student([92, 97], 3, personalInfo(20180101, true));  //구조체 student를 선언하고 초깃값 할당하는 예시 상태 변수

    

    function getWholeInfo() returns (student){  //맵핑 students에 직접 접근~msg.sender의 모든 데이터를 student 형식으로 가져와 조회

        return students[msg.sender];                //msg : 스마트 컨트랙트 호출한 사용자의 메세지   msg.sender : 주소

    }

    

    function getTotalScore() returns (uint8, uint8){    //맵핑 students에 접근하여 msg.sender에 해당하는 student의 scores 가져오는 함수

        return (students[msg.sender].scores[0], students[msg.sender].scores[1]); 

    }

    

    function joinTeam(uint8 input){           //맵핑 students에 접근, msg.sender에 해당하는 student의 team 수정하는 함수

        students[msg.sender].team = input;    

    }

    

    function setPI(personalInfo input){    //msg.sender에 해당하는 student의 personalInfo에 해당하는 pi를 수정하는 함수

        students[msg.sender].pi = input;

    }

    

    function setBirth(uint32 input){                //맵핑 students에 지역변수 local을 통해 간접적으로 접근하여 

        student local = students[msg.sender];   //msg.sender에 해당하는 pi, birth를 수정하는 함수

        local.pi.birth = input;

    }


--------------------------------------------------





Comments