이더리움/솔리디티

[솔리디티] 6. 함수, 가시성 지정자, 반환 값, 지역 변수

라이튼 2023. 7. 17. 22:38

이전글

 

[솔리디티] 5. 연산자, 전역 변수, 단위

이전글 [솔리디티] 4. 생성자, 매개변수, 값 타입과 참조 타입, immutable 이전글 [솔리디티] 3. 자료형, 배열, 상수, 상태 변수 이전글 [솔리디티] 2. 리믹스 IDE 이전글 [솔리디티] 1. 솔리디티와 EVM 이

kwjdnjs.tistory.com

 

함수, 가시성 지정자, 반환 값, 지역 변수

 이번 글에서는 솔리디티의 함수와 가시성 지정자, 반환 값, 지역 변수에 대해 알아보겠습니다.

 

1. 함수와 가시성 지정자

 솔리디티에서는 다음과 같이 function을 사용해 함수를 선언할 수 있습니다. 함수를 선언하는 과정에서는 가시성 지정자를 함께 사용해야 합니다.

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Function {
    function func() public {

    }
}

 

 이전에 상태 변수를 설명하는 과정에서 가시성 지정자인 public, internal, private에 대해 살펴봤습니다. 함수에서는 추가적으로 external 가시성 지정자를 사용할 수 있습니다. 각 가시성 지정자를 정리하면 다음과 같습니다.

  • public: 함수를 내외부 모두에서 사용할 수 있습니다.
  • internal: 현재 컨트랙트나 상속된 컨트랙트에서만 함수를 사용할 수 있습니다.
  • private: 현재 컨트랙트에서만 함수를 사용할 수 있습니다.
  • external: 함수를 현재 컨트랙트 내부에서는 사용할 수 없으나, 외부에서는 호출할 수 있습니다.

 

 external은 일반적으로 인터페이스에서 사용합니다. 인터페이스에 대해서는 이후 글에서 알아보겠습니다.

 

2. 함수의 매개변수

 함수에서는 생성자와 동일한 방식으로 매개변수를 사용할 수 있습니다. 한 가지 차이점은 함수의 매개변수의 경우 참조 타입을 콜데이터와 스토리지로 사용할 수 있다는 점입니다.

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Function {
    uint8 public n;
    string public s;
    uint8[] public u;

    function func(uint8 _n, string calldata _s, uint8[] memory _u) public {
        n = _n;
        s = _s;
        _u[0] = 1; // memory로 매개변수를 선언했으므로 값 변경 가능
        u = _u;
    }
}

 

 public으로 함수를 선언하였기 때문에 컨트랙트를 배포 후 다음과 같이 func 함수 버튼이 활성화된 것을 확인할 수 있습니다.

 

 

 가장 오른쪽 'v' 버튼을 눌러서 펼치면 생성자와 동일하게 매개변수를 좀 더 정확하게 입력할 수 있습니다.

 

 

 transact 버튼을 누르면 트랜잭션을 통해 함수를 호출하게 됩니다. 게터를 이용해 각 변수의 값을 확인해 보면 함수가 정상적으로 실행된 것을 확인할 수 있습니다.

 

3. 반환 값

 솔리디티에서는 returns와 return을 통해 값을 반환할 수 있습니다. 함수를 선언하는 과정에서 returns와 함께 반환될 값의 자료형을 괄호 안에 입력해 준 뒤, 함수 내부에서 return을 사용하면 됩니다. 예시 코드는 다음과 같습니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Function {
    uint8 a = 4;

    function func(uint8 n) public returns (uint8) {
        a += n;
        return a;
    }
}

 

 함수 실행 후 반환된 값은 터미널의 트랜잭션 정보에서 output을 보면 확인할 수 있습니다.

 

 

 다음과 같이 여러 개의 값을 반환하는 것도 가능합니다.

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Function {
    uint8 a = 4;
    uint8 b = 10;

    function func(uint8 n) public returns (uint8, uint8) {
        a += n;
        b += n;
        return (a, b);
    }
}

 

 

4. 지역 변수

 마지막으로 지역 변수에 대해 알아보겠습니다. 지역 변수는 상태 변수와는 다르게 함수의 내부에서 선언되는 변수를 의미합니다. 상태 변수는 스토리지에 값이 저장됐지만, 지역 변수는 메모리에 값이 저장됩니다.

 

 다음은 값 타입의 지역 변수를 선언하는 과정입니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Function {
    uint8 a = 3;

    function func() public returns (uint8) {
        uint8 b = 4;
        a += b;

        return a;
    }
}

 

 참조 타입의 지역 변수를 선언하는 과정은 값 타입에 비해 조금 더 복잡합니다. 다음은 예시코드입니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Function {
    uint8[] public arr = [5, 6, 7, 8];

    function func(uint8[] memory n, uint8[] calldata m) public returns (uint8[] memory, uint8[] calldata, uint8[] memory) {
        uint8[] memory a = n;
        uint8[] calldata b = m;
        uint8[] storage c = arr;

        n[1] = 0;
        c.push(9);

        return (a, b, c);
    }
}

 

 참조 타입의 경우 지역 변수를 선언하는 과정에서 memory, calldata, storage 등의 키워드가 필요함을 알 수 있습니다. 이렇게 추가적인 키워드가 필요한 이유는 n[1] = 0;과 c.push(9)를 실행한 결과를 보면 알 수 있습니다.

 

 코드에서는 분명 n의 값과 c의 값을 변경했지만 실행 결과 n의 값을 할당한 a와 c에게 값을 할당해 주었던 arr의 값이 바뀐 것을 확인할 수 있습니다. a[1]의 값은 0으로 변경되었고, arr에는 전에 없던 9가 추가되었습니다.

 

 이러한 현상이 발생한 이유는 간단합니다. 바로 배열이 '참조 타입'이기 때문입니다.

 

 다른 프로그래밍언어와 마찬가지로 배열은 배열의 값을 다른 배열에게 할당할 경우 값이 복사되는 것이 아니라 배열의 주소가 복사됩니다. 따라서 a와 n이, b와 m이, c와 arr이 같은 배열을 가리키게 되는 것입니다.

 

 이것이 바로 지역 변수 배열의 선언 과정에서 memory, calldata, storage와 같은 키워드가 필요한 이유입니다. 할당하려는 배열과 할당받으려는 지역 변수 배열의 타입이 일치해야 같은 값을 가리킬 수 있기 때문입니다. n의 경우 메모리 타입의 매개변수이기 때문에 a를 memory로 선언하였으며, m은 콜데이터 타입이기 때문에 b를 calldata로 선언하였습니다. c의 경우 storage라는 새로운 키워드로 선언하였습니다. 그 이유는 이전에 알아봤던 것처럼 상태 변수 nums가 스토리지 타입이기 때문입니다.

 

 참고로 memory 지역 변수 배열의 경우 sotrage와 calldata 배열을 할당받을 수 있습니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Function {
    uint8[] public arr = [5, 6, 7, 8];

    function func(uint8[] calldata n) public returns(uint8[] memory, uint8[] memory, uint8[] calldata) {
        uint8[] memory a = arr;
        uint8[] memory b = n;

        arr[0] = 0;
        b[0] = 0;
        return (a, b, n);
    }
}

 위와 같이 memory가 storage나 calldata의 값을 할당받을 경우 배열의 주소를 복사하는 것이 아니라 배열의 값을 직접 복사합니다. 따라서 arr의 값을 변경해도 a의 값은 변경되지 않으며, b의 값을 변경해도 n의 값은 변경되지 않습니다.

 

 상태 변수 배열을 다른 상태 변수 배열에 할당하는 경우 역시 배열의 주소를 복사하는 것이 아니라 배열의 값을 직접 복사합니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Function {
    uint8[] public a = [5, 6, 7, 8];
    uint8[] public b = a; 

    function func() public {
        a[0] = 0;
    }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Function {
    uint8[] public a = [5, 6, 7, 8];
    uint8[] public b; 

    function func() public {
        b = a;
        a[0] = 0;
    }
}

 

 위 두 경우 모두 a의 값을 변경해도 b의 값이 변경되지 않음을 확인할 수 있습니다.

 

 지금까지 함수, 가시성 지정자, 반환 값, 지역 변수에 대해 알아봤습니다. 감사합니다.

 

 

다음글

 

[솔리디티] 7. view 함수, pure 함수, 모디파이어, 오버로딩

이전글 [솔리디티] 6. 함수, 가시성 지정자, 반환 값, 지역 변수 이전글 [솔리디티] 5. 연산자, 전역 변수, 단위 이전글 [솔리디티] 4. 생성자, 매개변수, 값 타입과 참조 타입, immutable 이전글 [솔리디

kwjdnjs.tistory.com