SturdyCobble's Study Note

[프로그래밍] 6.2 기억 부류 지정자 (2) 본문

휴지통/['19.06-'20.07]프로그래밍101

[프로그래밍] 6.2 기억 부류 지정자 (2)

StudyingCobble 2020. 1. 25. 18:12

NOTICE : 독학인 만큼, 잘못된 내용이 있을 수 있습니다. 내용 지적은 언제나 환영합니다.

더 이상 이 블로그에는 글을 올리지는 않을 예정입니다. 그렇지만 댓글을 달아주시면 최대한 답변드리고자 노력하겠습니다.


※이 글은 프로그래밍 언어에 대한 기초적인 이해를 가정하고 있습니다. 최소 프로그래밍 언어 하나 정도를 약간이라도 접해보시는 것을 추천합니다. 또한, 이 글은 심화 내용은 되도록 피하여 서술했습니다.

 정적(static) 변수는 말 그대로 정적인 공간에 기억되는 변수입니다. 일반적인 지역 변수와는 다르게 프로그램 실행 내내 그 값이 유지될 수 있는 장점이 있습니다. 전역 변수와 비슷해보이지만, 전역변수와는 다르게 정적 변수는 지역 변수와 같이 통용되는 범위가 제한됩니다. 선언되는 지역도 지역변수와 동일합니다. 두 기억 부류의 특징을 둘 다 가졌기에, 둘의 장점을 결합하여 다음과 같은 예시를 가능하게 합니다.

# include <iostream>

using namespace std;

void counter();

int main(){
  for(int i = 0 ; i < 10 ; i++){
    counter();
  }
}

void counter(){
  static int cnt = 0;
  cnt++;
  cout << "Number : " << cnt << endl;
}

---------------RESULT------------------
Number : 1
Number : 2
Number : 3
Number : 4
Number : 5
Number : 6
Number : 7
Number : 8
Number : 9
Number : 10

static을 빼고 int cnt = 0;이라고 했다면, 다음의 결과가 나옵니다.

Number : 1
Number : 1
Number : 1
Number : 1
Number : 1
Number : 1
Number : 1
Number : 1
Number : 1
Number : 1

 

계속 기억 공간에 저장되어 있기 때문에 가능한 일입니다. 위의 예시에서 보았듯이 static변수는 C/C++, Java에서 static int cnt;와 같이 static 키워드를 붙임으로서 선언 가능합니다. (int static cnt;도 가능합니다.)

 

Java에서 위의 코드를 그대로 가져올려고 하면, 일단 C/C++처럼 함수가 독립적으로 존재하지 못하므로 내용을 각색해야할 필요가 있습니다. 아래 코드는 나름대로 위와 비슷하게 각색해본 결과입니다.

public class HelloWorld {
    static int cnt = 0;
    public static void main(String[] args) {
      for(int i = 0 ; i <10 ; i++){
        counter();
      }
    }
    private static void counter(){
      cnt++;
      System.out.println(cnt);
    }
}
------------------RESULT----------------------
1
2
3
4
5
6
7
8
9
10

큰 차이점이 있다면, 메소드 내부에서 static 변수를 선언할 수 없어서 클래스 내부 선두에 따로 선언해주고, counter 메소드 선언할 때 정적 공간에 선언하기 위해 static 키워드를 붙였습니다.( 관련 내용은 이전에 다루었기에 생략하겠습니다. ) 

 

특별히 한 가지를 더 언급해보자면, Java에는 C/C++과 정확히 같은 개념의 전역 변수가 없지만, 정적 변수가 비슷한 느낌을 가지고 있기에, Java의 정적 변수를 전역 변수로 설명한 글들이 인터넷 상에 많이 있었습니다. 이 글에서는 확실히 그 둘을 분리하여 설명하겠습니다.

 

 Python은 아예 static 키워드가 없습니다. 그러나 비슷한 느낌을 내는 것은 가능합니다. 먼저 전역 변수를 사용하는 방법도 있겠지만, 전역 변수의 단점이 많기에, 위의 C++코드와 비슷한 느낌을 내려면 다음과 같이 써볼 수 있습니다.

def counter():
    counter.cnt += 1
    print(counter.cnt)
counter.cnt = 0
for i in range(0,10):
    counter()

 

약간 뜬금없이 counter를 객체처럼 사용하는 걸 보실 수 있습니다. 이는 함수가 python(을 비롯한 C 등을 제외한 모든 언어에서) 객체이기 때문입니다. 특별히 Python은 특별한 선언이 따로 필요하지 않고, 대입식을 선언문으로 활용할 수 있기에 위와 같은 깔끔한 식이 가능해집니다. (당연하겠지만, 함수를 인수로 받거나, 함수를 반환하는 함수도 가능합니다.)

 

참고로 C/C++에서 정적 함수를 선언하면(static method가 아니라 static function) 일반적인 함수와 (전역으로 생성되는) 비슷하지만, 외부에 알려지지 않습니다. (extern의 반대 개념 정도로 사용된다고 보면 됩니다.) 즉, 외부 모듈에서 참조할 수 없습니다.

 

typedef 키워드는 C/C++에 존재하는 기억 부류 지정자입니다. 하지만, 사실 기억 부류를 지정하는 것과는 거리가 있습니다. 그냥 C/C++의 기억 부류 지정자가 오는 자리에 오는 특별한 친구정도로 기억하시면 될 것 같습니다. 정확한 의미는 기억부류 지정자의 위치에 사용되어, 기존에 존재하는 자료형에 새로운 이름을 만들어줍니다. 아래 예시를 참고해봅시다.

  typedef short FILE_SIZE;
  FILE_SIZE f1_size = 10;

(통용 범위는 일반적인 변수와 같이 블록 내로 제한됩니다.(블록 내에서 선언되었다면))

 

(나중에 해당 이름을 다른 용도로 사용한다면 반드시 자료형을 제공해서 const flag가 아닌 const int flag와 같이 사용해야 합니다.(flag가 typedef를 통해 다른 자료형의 또다른 이름으로 사용된 상황에서) const int;와 같은 식이 불가능한 이유와 같습니다.)

 

굳이 이를 사용하는 이유는 여러가지가 있겠지만, 구조체나 클래스 형식으로 변수를 생성할 때 가독성을 늘리기 위함도 있고, 위와 같이 특정 용도로 사용하는 변수의 자료형이 바뀔 필요가 있다면, 한번의 변경으로 바꾸기 위함도 있습니다.

 

또한, 다음과 같은 예시도 가능합니다.

typedef int tfi( short ), (*ptfi)( short ); 
// short를 인수로 받고 int형을 반환하는 함수형 tfi
// short를 인수로 받고 int*형을 반환하는 함수형 포인터 ptfi

int ( *signal( short, int (*) (short)) ) ( short );
//signal은 포인터로서, 'short를 받고 int를 반환하는 함수(#1)'를 반환하는 함수(#2)를 가르킴.
//그리고 그 함수 #2는 [short형]과 ['short를 받고 int를 반환하는 함수(#3)'의 포인터(#4)]를 받음.
tfi *signal( int, tfi * ); // 위와 같은 의미
ptfi signal( int, ptfi ); // 위와 같은 의미

(https://docs.microsoft.com/ko-kr/cpp/c-language/typedef-declarations?view=vs-2019의 예시를 각색했습니다)

 

(포인터와 배열 형식도 위와 같이 typedef로 선언할 수 있습니다.) 위 코드의 자세한 의미는 포인터에 관한 글(추후 추가 예정입니다)를 보고 오면 이해가 될 겁니다. 그렇지만, typedef를 통해 코드의 가독성이 높아짐을 확인할 수 있습니다.

Comments