라인 입출력 함수

언어로그/C/C++ 2011. 3. 27. 21:07



라인단위로 입출력을 다루는 함수들에 대해서 알아보자.  널문자를 삽입해주는지 여부와 버퍼에서 개행문자를
제거하는지 여부에 주목하여 보자. 


scanf(const char *TEMPLATE, ...)
scanf 함수를 사용하여, 한 라인의 문자열을 읽을 수 있지만, 개행문자를 표준입력버퍼에 남겨놓는다.  
연이은 읽기 동작에 오동작을 초래할 수 있기때문에, 입력버퍼에서 개행문자를 제거하는 것을 잊지 말자!



char* fgets(char *S, int COUNT, FILE *STREAM)
지정한 입력버퍼에서 최대 COUNT-1 개의 문자를 읽되, 엔터를 칠때까지  배열  S에 저장하는 함수이다. 
배열의 공간이 충분하다면, 개행문자와 함께 널문자를 삽입한다. (엔터에 의해 종료) 
하지만 공간이 충분하지 않다면,  COUNT-1개의 문자와 널문자를 삽입하고 종료한다. (사이즈 제약의 의해 종료)



int fputs(const char *S, FILE *STREAM)
버퍼 S의 내용을 지정한 출력버퍼에 출력하는 함수로, 널문자를 개행문자로 변환하지 않는다.  




fgets와 fputs 보다는 덜 사용되지만 라인단위로 입출력을 할 수 있는 다음과 같은 함수가 있다. 
char* gets(char *S)
표준 입력버퍼에서 개행문자를 만날 때까지 읽는 함수로,  개행문자는 S에 저장하지 않지만, 입력퍼버에서는 제거한다




int puts(const char *S)
S 문자열을 표준출력버퍼에 출력하는 함수로 널문자를 만나면 개행문자로 변환하여 출력한다. 

'언어로그 > C/C++' 카테고리의 다른 글

음수의 표현  (0) 2011.06.13
문자열 조작함수 직접 구현하기  (2) 2011.04.29
라인 입출력 함수  (0) 2011.03.27
배열의 이해  (0) 2011.03.27
기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27

배열의 이해

언어로그/C/C++ 2011. 3. 27. 20:53



1. 배열이란?
프로그램이 필요한 이유 중의 하나는 많은 양의 데이터를 처리하기 위해서이다.  
그중에서도 동일한 형태를 갖는 다수의 데이터를 다루기 위해 배열을 사용할 수 있다.  배열은 연속적인 메모리 공간에
할당된 동일한 데이터 타입을 갖는 변수들의 묶음이다.  

int array[3];

위와 같은 선언은 4바이트의 메모리가 연속적으로 3개가 할당되어 총 12바이트의 메모리가 생성된다. 
이 때 array 라는 배열의 식별자에는 할당된 메모리의 시작주소가 할당된다.  즉 포인터 상수이다. 
이렇게 할당된 메모리 공간에 배열의 식별자를 사용하여 접근할 수 있다. 


배열의 첨자를 사용하여 접근하는 방법
scanf("%d %d %d \n", &array[0], &array[1], &array[2] );


배열 식별자(포인터 상수)를 사용하여 접근하는 방법
scanf("%d %d %d \n", array,  array + 1, array + 2 );



2. 배열의 초기화
배열 또한 일반적인 변수와 동일한 초기화 규칙을 따른다. 지역변수로 선언된 배열은 기본으로 쓰레기값으로 초기화되며, 
전역변수로 선언된 경우는 0으로 초기화 된다.  배열의 선언과 초기화를 다음과 같이 할 수 있다. 

int array[3] = { 10 , 20, 30 };   // 명시적으로 요소의 개수와 각 초기값을 설정   
int array[] = { 10, 20 , 30 };     // 초기값의 수에 따라 요소의 수가 결정됨      
int array[3] = { 10 };           // 첫요소만 10으로, 나머지는 0 으로 초기화됨       
int array[3] = { 0 };           // 모든 요소를 0으로 초기화    

특히 마지막 문장은 다수의 요소를 갖는 배열을 초기화 할 때 유용한 구문이다. 
int array[100] = { 0 };

'언어로그 > C/C++' 카테고리의 다른 글

문자열 조작함수 직접 구현하기  (2) 2011.04.29
라인 입출력 함수  (0) 2011.03.27
배열의 이해  (0) 2011.03.27
기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27
함수의 이해  (0) 2011.03.27

기억부류(Storage Class) / 변수

언어로그/C/C++ 2011. 3. 27. 20:40





데이터, 스택, 힙 영역
추상적인 분류이지만 프로그램을 위한 메모리 공간은 크게 데이터영역,  스택(Stack)영역,  힙(Heap)영역으로 나뉜다.
데이터 영역에는 전역(Global)변수와 정적(Static) 변수에게 할당되는 메모리 공간으로 프로그램 시작부터 끝나는 시점까지
존재하는 메모리 영역이다. 스택영역은 블럭 또는 함수 Scope 에 할당되는 메모리 공간이며,  제어흐름이 활성화 되었을 때
생성된다. 힙 영역은 프로그램이 유동적으로 사용할 수 있는 메모리 공간으로, 프로그래머가 해당 메모리를 필요한 만큼
사용하고 난 뒤 메모리를 해제해야 한다. 



레지스터 변수,  정적변수
변수의 타입 앞에 register 라는 키워드를 사용하여, 레지스터 변수를 선언할 수 있다.  레지스터 변수는 CPU의 메모리가 아닌
레지스터에 데이터를 저장하여,  데이터 접근 속도를 향상시킨다.  하지만  register 키워드를 사용했다고 무조건 레지스터에
데이터를 저장하는 것은 아닌다. 일반적으로 CPU에 존재하는 30여 가지의 레지스터 중 1~2 개의 레지스터가 남는데, 이 
레지스터들이 사용되고 있지 않을 경우에 사용되며, 그렇지 않을  경우 메모리에 할당된다. 

register int  i,  j;   // 레지스터에  i, j 생성을 요청.     

 
정적(Static) 변수는 전역변수와 같이 메모리의 데이터 영역에  생성이 된다.  지역 정적변수는 함수가 종료된 후에도 
그 값을 사용하고 싶을 경우에 쓰인다.  함수 내부에 선언된 지역정적 변수는 함수 호출시점에 최초로 생성이 되며, 
이후 메모리 데이터 영역에 프로그램 종료시점까지 유지가 된다.

void func()
{
    static int i = 0;    // 연이은 호출에도 처음 생성된 i가 사용됨.
    i++;
}
   

'언어로그 > C/C++' 카테고리의 다른 글

라인 입출력 함수  (0) 2011.03.27
배열의 이해  (0) 2011.03.27
기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27
함수의 이해  (0) 2011.03.27
포인터의 이해  (0) 2011.03.11

전역변수와 지역변수

언어로그/C/C++ 2011. 3. 27. 20:15




1. 지역변수(local variable)
지역변수는 변수가 선언된 블럭(Scope) 내부에서만 유효한 변수이다. 지역변수는 제어흐름이 해당 블럭에
 진입되는 시점에 생성되고 , 블럭을 빠져나가는 순간 소멸되는데 흔히 자동변수라 하여 auto라는 키워드가 붙지만, 
디폴트로 생략할 수 있다. 또한 동일한 범위(Scope)  내 있는 코드에서만 접근할 수 있으며 생성 시, 의미없는 값이
들어가 있기 때문에 사용전에 반드시 초기화를 해야한다. 




2. 전역변수(global variable)
전역변수는 프로그램 실행타입 내내 유효한 변수이다. main함수가 호출되기 전에 생성되어, 프로그램 종료시 메모리가
회수된다.  프로그램 어떤 영역에서도 접근할 수 있는 특징이 있으며,  생성시 자동으로 0으로 초기화가 이루어진다.




3. 지역변수와 전역변수는 어떻게 사용해야하나?
전역변수는 어떤영역에서도 데이터에 접근할 수 있기 때문에 데이터 공유에 유용하다.  하지만 프로그램 실행타임
동안 계속 사용되기 때문에 과하게 사용하면 메모리에 낭비를 가져온다. 또한 전역변수에 대한 변경은 그 변수를
사용하는 모든 함수에 파급효과를 미쳐서 강한 결합(tight coupling)의 겨로가를 가져온다. 그렇기 때문에  프로그램을
용이하게 하는 범위에서 최소한으로 사용하고, 그 외의 경우는 지역변수를 사용하는 것이 좋다. 

 

'언어로그 > C/C++' 카테고리의 다른 글

배열의 이해  (0) 2011.03.27
기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27
함수의 이해  (0) 2011.03.27
포인터의 이해  (0) 2011.03.11
매크로, 연산자  (3) 2011.03.10

함수의 이해

언어로그/C/C++ 2011. 3. 27. 20:10




함수가 필요한 이유?
중복된 작업을 함수로 분리하면,  한번 작성으로 여러번 사용할 수 있어서 효율적인 프로그램이 된다.
또한 큰 문제를 작은 문제들로 나누어 이해하기 쉬워지며, 수정사항이 발생하면 함수 부분만 수정하면
되기 때문에 유지보수도 용이해진다. 


 
함수의 원형(프로토타입)과 정의
C언어에서 함수는 원형(프로토타입)과 정의로  분리된다.  함수의 원형(프로토타입)은 보통 헤더파일이나
메인함수 이전에 선언이 되며,  컴파일러에게 이러 이러한 함수를 아래에 정의했으니까 메인함수 또는 특정함수에서 
이 함수를 사용해도 됨을 알려주는 것이다. 
함수의 정의는 실제적으로 함수가 하는 일을 기술하는 부분이다.  C언어에서는 함수의 시그니처로 함수의 이름만을
사용하기 때문에, 파라미터의 타입과 반환값의 타입이 달라도 동일한 이름의 함수를 사용 할 수  없다.


#include <stdio.h>
 
void swap(int , int);// 함수원형(프로토타입)   

int main() {
...
return 0;
}

void swap(int a, int b) {   // 함수의 정의         
...
}





Call By Value vs Call By Reference
함수 호출시 파라미터를 넘기는 방식에 따라  Call By Value 또는 Call By Reference 라고 구분한다.
함수 호출시 변수들의 데이터(값)가 복사되는데, 함수 내에서 이 복사된 변수에  대한 조작은 원본 변수에
에  아무런 영향을 미치지 못하는데 이러한 호출방식을 Call By Value라고 부른다.   
파라미터로 변수의 주소를 넘기면, 피호출자 쪽에서는 이 주소를 조작하여 원본 변수의 데이터를 변경 할
수 있다. 이러한 호출방식을 Call By Pointer 또는 Call By Reference 라고 부른다.

 
Call By Value
#include <stdio.h>

main() {
    int a = 1, b = 5;
    swap(a, b);// 변수의 값을 전달함 
}

void swap(int a, int b)  {// main함수의 a, b는 변경되지 않음    
    int temp = a;
    a = b;
    b = temp      
}


Call By Reference 
#include <stdio.h>

main()  {
    int a = 1, b = 5;
    swap(&a, &b);// 변수의 주소를 전달함    
}
 
void swap(int *a, int *b)  { // main함수의 a, b가 변경됨    
    int temp = *a;
    *a = *b;
    *b = temp;
}

'언어로그 > C/C++' 카테고리의 다른 글

기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27
함수의 이해  (0) 2011.03.27
포인터의 이해  (0) 2011.03.11
매크로, 연산자  (3) 2011.03.10
표준입출력함수 getchar() / putchar()  (0) 2011.02.22

예외처리 (Exception Handling)

언어로그/Java 2011. 3. 24. 00:37



예외처리는 객체지향 프로그래밍을 지원하는 언어에서 에러를 처리하기 위한 메커니즘이다. 전통적인 절차지향
언어에서는 함수의 반환값을 통해 에러 유무를 판단하였다. 매번 반환값을 검사하는 작업은 상당히 번거롭고, 
그 반환값의 의미 또한 프로그래머가 인지하기 어려운 형태여서 에러처리가 불편하였다. 반면 예외처리 방식은
예외가 발생하면 그에 대한 정보를  얻을 수 있고, 제어 흐름 또한 쉽게 이동시킬 수 있는 방식이다.  
하지만 아무리 언어차원에서 좋은 메커니즘을 제공하더라도 언제, 어떻게 사용해야하는지 모른다면 무용지물이다.
C++와 Java 언어를 처음 접했을 때에도 언제, 어디서 예외를 던지고 잡아야 하는지가 난제였다.  예외처리에 대해서
좀더 자세히 알아보자. 


자바의 철학은 "잘못 만든 코드는 실행되지 않아야 한다" 는 것이다. 그래서 에러를 잡는 가장 이상적인 시기는 컴파일
시기이다. 하지만 모든 에러를 컴파일 시에 검출할 수 는 없으며,  실제론 실행시점에 어떠한 절차를 거치면서 심각한 문제들이
더 발생하게 된다.  발생한 에러는,  프로그램이 수행하는 절차와 연관되어 그 에러를 올바르게 처리하는 방법을 아는 수신자가
처리해야한다.  그렇다. 처리하는 방법을 아는 수신자가 처리하게 해야하는 것이 정답이다. 이 개념을 설명하기에 앞서 
자바에서 지원하는 예외(Exception) 클래스에 대해서 알아보자. 
 
 
1. 예외란 무엇인가?
예외란 프로그램 절차상에서 정상 흐름을 벗어난 이벤트이다. 
 


2.  자바의 예외처리 장점
비지니스 로직과 예외처리 코드를 분리해주는 장점이 있다.  try {} 블럭 안에서 비지니스 로직을 처리하며, 
그에 대한 예외처리는 catch {} 라는  별도의 블럭에서 수행도히기 때문에 로직과 예외처리 영역이 명확히
분리가 된다.  또한 예외처리 영역에서는 예외클래스의 이름을 바탕으로 예외의 종류, 발생위치와 관련된 정보를
확인할 수 있다.  (예외이름이 발생한 문제를 나타내도록 알아보기 쉽게 지어야 하는 이유이다.)                                    
 

3. 예외관련 클래스 상속도 
다음은 예외 관련 클래스 상속도이다.  모든 예외클랫들은 Throwable 인터페이스를 구현한다.  발생시 로직에 따라 적절히
처리 될 수 있는 문제들을 Exception 이라 하며,  심각한 문제들로 시스템을 중단해야할 문제들을 Error 라고 한다. 
일반적으로 Checked  Exception을 상속하는 커스텀 예외 클래스를 생성하여 사용한다. 


 
 

 
4. 예외처리를 위한 전략
발생한 예외는 상위계층으로 throw 하거나 또는  try~catch 구문을 사용하여 직접 처리할 수 있다.  throw 할지, 직접처리 할지에 
대한 결정은 처리 절차에 의존한다.  
 
 throw 할 예외와 직접 try~catch 여부를 어떻게 결정할 것인가?
  
- 예외로 무엇을 할지 알 때까지는 그것을 catch 하지 말아라
   - 예외 발생시 어떻게 해야하는지 알고있는 절차를 다루는 클래스에게 까지 예외를 throw 하자.

다음 일반적인 프로그래밍 패턴 중에 언제  throw 와 try~catch 할지에 대한 사항이다. 
 데이터 추출     -  throw 한다. 
 유효성 검사     -  throw 한다. 
 처 리                 -  try ~ catch 
 결과출력         -  try ~ catch
 
 1, 2 번 같은 경우 데이터를 제공하는 곳에서 예외를 처리 할 책임이 있다. 상위로 예외를 던진다(throw)
3. 4  번의 경우, 상위에서 제공하는 데이터에서 이상이 없고, 내가 처리하는 동안 발생한 예외므로 내가 처리할 책임이 있다. 예외를 try ~ catch 한다
 
 
예외를 위한 계획적인 전략 사용하기!
코딩하다 즉흥적으로 삽인하는 예외처리 코드가 아닌,  처음 절차에서부터 계획된 예외처리 전략을 사용해야 한다
예외처리 전략을 위한 과정은 다음과 같다
예외클래스를 정의하고,  언제, 어디서  throw 되고, try~catch 되야하는지 결정한다
비지니스 로직에 전념하고, 예외처리가 필요한 곳에는 주석으로 예외처리 표시를 한다.
예외처리가 필요한(주석을 단 부분)에 실제 처리코드를 삽입한다.
       
 
 
5. checked Exception 과 unchecked Exception

5.1 checked Exception 
컴파일 타임 예외로, 비지니스 데이터에 위해한 예외로 시스템 서비스에 부자연스러움을 가져오는 예외이다. 
소스에 예외처리를 하지  않으면, 컴파일러 에러가 발생한다. 
ex) IOException,  NumberFormatException
 
5.2 unchecked Exception
런타임 예외로, 비지니스 데이터에 위해하지 않지만, 시스템에 영향을 미칠 수 있는 예외이다. 
unchecked Exception 은 try ~ catch 블럭이 없어도, 컴파일 에러가 발생하지 않는다.  주로  비지니스 로직상이 아닌 
프로로램 자체상의 에러로,  프로그램 작성을 잘 못했을 때 발생한다. 
 ex) ClassCastException, IndexOutOfBoundsException, IllegalStateException
 

다음은  checked 예외인 Exception 을 상속하여, 다양한 생성자를 갖도록 오버로딩하여 사용하는 예이다. 
 
 
 
  다음은 unchecked 예외인 RuntimeExcepting 을 상속하여,  다양한 생성자 오버로딩를 하여 사용하는 예이다. 




'언어로그 > Java' 카테고리의 다른 글

(2) 리플렉션(Reflection) 사용하기  (0) 2011.04.07
(1) 리플렉션(Reflection)  (3) 2011.04.07
예외처리 (Exception Handling)  (0) 2011.03.24
[Java] 어노테이션 사용하기  (1) 2011.03.19
[Java] 어노테이션(Annotation)  (2) 2011.03.15
[자바] (1) 자바소개  (0) 2011.03.10

원 위에 나열된 수 제거하기

알고리즘.데이터구조 2011. 3. 23. 16:08





1부터 n까지의 수를 원 위에 차례로 나열하고 k 번째 수를 지워간다.  k보다 적을 때까지 반복한다

입력: n, k (n > k > 0)

출력: 최종 남은 숫자들(지워진 순서로 출력, 역순으로 출력) / 지워진 숫자들

조건: 함수는 재귀적인 기법 이용  / n보다 적은 배열 하나만 이용



1. 반복루프를 사용한 방법
(1) n크기의 배열을 사용. 어떤 수 n은 (n-1)배열요소에 위치한다
(2) K번째 배열요소에는 지워진 순서가 기록되고, 남아있는 수의 배열요소에는 0값을 저장하고 있다.
(3) 함수 종료 후, 기록된 순서를 통해 지워진 순서, 역순 출력을 할수 있고,
      배열요소가 0인 것을 찾아 남아있는 수들을 출력 할 수 있다.







2. 재귀함수를 사용한 방법
(1) 단순히 위의 반복버전을 재귀함수로 변경한 버전







3. 최고 n - (n/k) 크기의 배열을 사용하는 재귀함수
(1) 첫번째, 두번째 재귀호출은 배열을 한번 순회하기 위해 루프문을 구현한 것이고,
(2) 세번째 재귀호출은 한번의 순회의 남아있는 수들로 재구성한 새 배열을 가지고, (1)과정을 재적용한다.
(3) 알고리즘이 종료하면 전역변소 $e에는 남아있는 수의 개수를 저장하고 있으면 array의 0~($e-1) 요소에 그 값이 위치한다




3번에 대한 그림 설명




'알고리즘.데이터구조' 카테고리의 다른 글

[알고리즘] 알고리즘 글 목록  (0) 2013.07.08
프로그래밍 문제 접근법  (0) 2011.04.26
원 위에 나열된 수 제거하기  (0) 2011.03.23
합병정렬(Merge Sort)  (0) 2011.03.23
HMAC-SHA1  (0) 2011.03.11
문제5 그래픽편집기  (0) 2011.02.18

합병정렬(Merge Sort)

알고리즘.데이터구조 2011. 3. 23. 15:50



 n개의 아이템으로 구성된 리스트를 크기가 거의 n/3 인 3개의 부분리스트로 분할하고
각 부분리스트를 재귀적으로 정렬한 후, 그 3개의 정렬된 부분리스트를 합병하는 정렬 알고리즘을 작성하라 (n>=100)

 => 합병정렬(merge sort) 알고리즘을 작성하되, 3분할 방식을 사용하라~~는 말


먼저 2분할을 사용하는 합병정렬 소스는 다음과 같다








3분할을 사용하는 MergeSort (Ruby 버전)
# 배열리스트를 n/3으로 분할 후 정렬 / 병합
def merge_sort(low, high)
	if (high-low >= 2)
		mid1 = (high-low) /3 + low
		mid2 = ((high-low)*2)/3 + low
		
		merge_sort(low, mid1)
		merge_sort(mid1+1, mid2)
		merge_sort(mid2+1, high)
		merge(low, mid1, mid2, high)
	else
		if (high-low==1 && $a[low] > $a[high])
			tmp = $a[low]
			$a[low] = $a[high]
			$a[high] = tmp
		end
	end
end

def merge(low, mid1, mid2, high)
	i, h, j, m, k = low, low, mid1+1, mid2+1, 0	

	b = []
	pass = false;
	while ((h<=mid1) and (j<=mid2) and (m<=high))
		if ($a[h] <= $a[j] && $a[h] <= $a[m])
			b[i] = $a[h]
			h = h + 1
			i = i + 1
			next	
		end	
		if ($a[j] <= $a[h] && $a[j] <= $a[m])
			b[i] = $a[j]
			j = j + 1
			i = i + 1
			next	
		end
		if ($a[m] <= $a[h] && $a[m] <= $a[j])
			b[i] = $a[m]
			m = m + 1
			i = i + 1
			next	
		end
	end
	# 루프가 종료하고 남은 요소들을 병합
	if (h > mid1 && !pass)
		pass = true
		while ((j<=mid2) and (m<=high))
			if ($a[j] <= $a[m])
				b[i] = $a[j]
				j = j + 1
			else
				b[i] = $a[m]
				m = m + 1
			end
			i = i + 1
		end
		if (j > mid2)
			for k in m..high
				b[i] = $a[k]
				i = i + 1
			end
		else
			for k in j..mid2
				b[i] = $a[k]
				i = i + 1
			end
		end
	end
	if (j > mid2 && !pass)
		pass = true
		while ((h<=mid1) and (m<=high))
			if ($a[h] <= $a[m])
				b[i] = $a[h]
				h = h + 1
			else
				b[i] = $a[m]
				m = m + 1
			end
			i = i + 1
		end
		if h > mid1
			for k in m..high
				b[i] = $a[k]
				i = i + 1
			end
		else
			for k in h..mid1
				b[i] = $a[k]
				i = i + 1
			end
		end
	end
	if (m > high && !pass)
		while ((h<=mid1) and (j<=mid2))
			if ($a[h] <= $a[j])
				b[i] = $a[h]
				h = h + 1
			else
				b[i] = $a[j]
				j = j + 1
			end
			i = i + 1
		end
		if (h > mid1)
			for k in j..mid2
				b[i] = $a[k]
				i = i + 1
			end
		else
			for k in h..mid1
				b[i] = $a[k]
				i = i + 1
			end
		end
	end
    # 원배열에 정련된 임시배열값을 복사	
	for k in low..high
		$a[k] = b[k]
	end
end

#$a = [1,5,4,3,2,6,9,7,8]
$a = Array.new(100)
$a.each_index { |i| $a[i] = rand(100)+1 }

$a.each { |x| print x, " " }
merge_sort(0,99) 

print "결과\n"
$a.each { |x| print x, " " }









3분할을 사용하는 MergeSort (C++ 버전)
#include <iostream>

using namespace std;

void mergeSort(int * a, int low, int high);
void merge(int * a, int low, int mid1, int mid2, int high);
void print(int * arr, int size);
int * minThree(int * a, int *i, int *j, int *k);
int * minTwo(int * a, int *i, int *j);


int main()
{
	int size = 100;
	int * arr = new int[size];

	srand(time(NULL));
	// 초기화- 0~99까지 수를 랜덤으로 복사
	for (int i=0; i < size; i++)
		arr[i] = rand() % size;

	// 정렬 전 
	print(arr, size);

	mergeSort(arr, 0, size-1);
	// 정렬 후 
	print(arr,size);

	return 0;
}


void mergeSort(int * a, int low, int high)
{
	int mid1, mid2;

	if (high-low >= 2)
	{
		mid1 = (high-low)/3 + low;
		mid2 = ((high-low)*2)/3 + low;

		mergeSort(a, low, mid1);
		mergeSort(a, mid1+1, mid2);
		mergeSort(a, mid2+1, high);
		merge(a, low, mid1, mid2, high);
	}
	else
	{	
		if (high-low==1 && a[low] > a[high])
		{
			int tmp = a[low];
			a[low] = a[high];
			a[high] = tmp;
		}
	}
}


void merge(int * a, int low, int mid1, int mid2, int high)
{
	int * b = new int[high-low+1];
	bool pass = false;  // 조건을 만족할 경우 나머지 if문을 통과하기 위한 변수
	int i, h, j, m;
	
	i = 0;
	h = low;
	j = mid1 + 1; 
	m = mid2 + 1;

	// 세분할 배열중 하나를 다 복사할때까지 루프를 돈다
	for (; h<=mid1 && j<=mid2 && m<=high; i++)
	{
		int * p = minThree(a, &h, &j, &m);
		b[i] = a[*p];
		*p = *p + 1;
	}

	if (h > mid1)
	{
		pass = true;
		// 나머지 두 분할 배열중 하나를 다 복사할때까지 루프를 돈다
		for (; j<=mid2 && m<=high; i++)
		{
			int * p = minTwo(a, &j, &m);
			b[i] = a[*p];
			*p = *p + 1;
		}
		// 나머지 하나의 분할배열을 복사한다
		if (j > mid2)
			for (; m<=high; i++, m++)
				b[i] = a[m];
		else
			for (; j<=mid2; i++, j++)
				b[i] = a[j];
	}

	if (j > mid2 && !pass)
	{
		pass = true;
		for (; h<=mid1 && m<=high; i++)
		{
			int * p = minTwo(a, &h, &m);
			b[i] = a[*p];
			*p = *p + 1;
		}

		if (h > mid1)
			for (; m<=high; i++, m++)
				b[i] = a[m];
		else
			for (; h<=mid1; i++, h++)
				b[i] = a[h];
	}

	if (m > high && !pass)
	{
		for (; h<=mid1 && j<=mid2; i++)
		{
			int * p = minTwo(a, &h, &j);
			b[i] = a[*p];
			*p = *p + 1;
		}

		if (j > mid2)
			for (; h<=mid1; i++, h++)
				b[i] = a[h];
		else
			for (; j<=mid2; i++, j++)
				b[i] = a[j];
	}

	// 정렬된 임시배열을 원본배열에 복사한다
	for (int k=low; k <= high; k++)
		a[k] = b[k-low];
	
}

// 배열의 내용 출력
void print(int * a, int size)
{
	for (int i=0; i < size; i++)
		cout << a[i] << " " ;	
	cout << endl;

}


// 세 분할배열에 숫자 중 가장 작은수의 인덱스를 반환
int * minThree(int * a, int *i, int *j, int *k)
{
	if (a[*i]<=a[*j] && a[*i]<=a[*k])
		return i;
	if (a[*j]<=a[*i] && a[*j]<=a[*k])
		return j;
	if (a[*k]<=a[*i] && a[*k]<=a[*j])
		return k;
}

// 두 분할배열에 숫자 중 가장 작은수의 인덱스를 반환
int * minTwo(int * a, int * i, int * j)
{
	if (a[*i]<=a[*j])
		return i;
	else
		return j;
}	






'알고리즘.데이터구조' 카테고리의 다른 글

프로그래밍 문제 접근법  (0) 2011.04.26
원 위에 나열된 수 제거하기  (0) 2011.03.23
합병정렬(Merge Sort)  (0) 2011.03.23
HMAC-SHA1  (0) 2011.03.11
문제5 그래픽편집기  (0) 2011.02.18
문제4 LCD디스플레이  (0) 2011.02.18

다중인자를 갖는 PerformSelector 메시지

Objective-C 2011. 3. 23. 15:31




이번 포스팅에서는 다중 파라미터를 갖는 Selector를 인자로 받아  별도의 쓰레드에서 처리를 해주는 비동기 메서드
구현에 대해서 알아볼 것이다.

아이폰 앱 프로젝트 진행 중 Delegate 패턴을 구현하면서  PerformSelector 류의 메서드를 사용 할 필요가 생겼다.
Cocoa Touch 프레임웍에서는 최대 2개까지의 인자를 받는 메서드(performSelector:withObject:withObject:) 만을 제공하고,
더 많은 파라미터를 전달해야 할 경우, 클래스 또는 Dictionary를 사용하여 전달해야 했다.  매번마다 파라미터를  담기위해 Dictionary를 생성하고, 데이터를 꺼내쓰는 것은 상당히 번거로운 작업이었다.
외국 블로그에서 우연히 이 문제를 깔끔하게 해결해주는 코드를 발견했다.  C언어의 가변인자 리스트와 Objective-C 의 
NSInvocation 메커니즘을 사용하는 방법이었다.  이 포스팅에서는 가변인자 리스트와 NSInvocation 메커니즘에 대해서는
설명하지 않겠다. 추후 Objective-C 런타임 메커니즘을 알아보면서 NSInvocation에 대해서 자세히 알아볼 것이다.


다음은 가변인자 리스트와 NSInvocation을 사용하여 다중 파라미터를 갖는 Selector를 호출하는 예제코드이다. 

- (void)performSelector:(SEL)aSelector withContext:(void*)context,... {
	if (context) {
		NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
		NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
		[invocation retainArguments];
		
		NSUInteger argumentCount = [signature numberOfArguments] - 2;
		
		[invocation setTarget:self];			// index 0
		[invocation setSelector:aSelector];	    // index 1
		
		va_list arguments;
		
		va_start(arguments, context);
		
		// 인자의 개수는 +2가 된 상태 
		NSUInteger i, count = argumentCount;
		void *currentValue = context;
		
		for (i = 0; i < count; i++) {
			[invocation setArgument:¤tValue atIndex:(i + 2)];
			currentValue = va_arg(arguments, void*); // 다음 인자 읽기 
		}
		
		va_end(arguments);
		
		NSThread *customWorkerThread = [NSThread currentThread];
		
		[invocation performSelector:@selector(invoke) onThread:customWorkerThread withObject:nil waitUntilDone:NO];
		
	} else {
		[self performSelectorInBackground:aSelector withObject:nil];
        
	}
}


위 메서드를 사용하여 아래와 같이 세 개의 인자를 갖는  printArgument:andOther:andAnother: 메시지를 호출하면...

// 호출할 메서드 
- (void)printArgument:(NSString*)arg1 andOther:(NSString*)arg2 andAnother:(NSString*)arg3 {
	NSLog(@"%@", [NSThread currentThread]);  
	NSLog(@"arg1: %@", arg1);
	NSLog(@"arg2: %@", arg2);
	NSLog(@"arg3: %@", arg3);
}

// 요렇게 호출할 수 있다. 깔끔하지 않는가? 
[self performSelector:@selector(printArgument:andOther:andAnother:) withContext:@"t1", @"t2", @"t3", nil];

위 메소드를 카테고리로 정의해놓으면 아주 유용하게 사용할 수 있을 것이다.

[소스 출처]


'Objective-C' 카테고리의 다른 글

Objective-C 문자열 조작 메서드  (0) 2011.04.29
다중인자를 갖는 PerformSelector 메시지  (0) 2011.03.23
Protocol 과 Category  (0) 2011.03.22
Objective-C 훑어보기  (2) 2011.03.20
[Cocoa] Cocoa Design Pattern  (0) 2011.03.20
HMAC-SHA1 구현  (0) 2011.03.03

Protocol 과 Category

Objective-C 2011. 3. 22. 23:53



이번 포스팅에서는 Protocol과 Category에 대해서 알아보자.  Protocol은 자바의 인터페이스에 유사한 개념이다. 
Protocol은 공식 프로토콜과 비공식 프로토콜이 있으며, 그 중 공식 프로토콜이 자바의 인터페이스에 해당한다.
Category는 상속의 대안으로 클래스를 변경하지 않고도, 새로운 기능(메서드)을 추가할 수 있는 메커니즘이다. 
프로토콜과 카테고리에 대해서 좀 더 자세히 알아보자. 


1. Protocol 이란? 
자바언어의  Interface에 해당하는 개념으로 @protocol 지시어를 사용하여 정의한다. @protocol 을 구현하는 모듈은
프로토콜이 규약하는 모든 메소드를 반드시 구현해야하는 프로토콜을 공식 프로토콜(Formal Protocol)이라고 하며, 
구현해도 되고 구현하지 않아도 되는 프로토콜을 비공식 프로토콜 (Informal Protocol)이라고 한다.  공식 프로토콜은 
@required 지시어를,  비공식 프로토콜은 @optional 지시어를 사용한다.  
(비공식 프로토콜은 런타임시에 비공식 프로토콜을 구현하는 메소드가 존재하는 경우에만 호출된다.)
공식프로토콜의 모든 메소드들이 구현되지 않으면 컴파일 시에 에러가 발생할 것이다.  2개 이상의 프로토콜을 구현할
경우 ,(콤마)를 구분자로  구현할 프로토콜을 <> 안에 나열한다.

 Objective-C 에서는 Java 와는 다르게 컴파일 타임에 프로토콜 할당에 대해 타입검사를 수행하지 않는다.
 런타임시 메시지가 호출될 때, 해당하는 메소드가 구현되지 않았다면 selector  exception 예외가 발생한다. 

@interface VenusAttacks : Game
...
@end

@class Thing;

@protocol Living
- (float)age;
- (float)health;
(NSDictionary*)healthInfo;
@end

@protocol Communicating
- (NSArray*)recipientsInRange;
- (void)sendMessage:(NSString*)message to:(id)recipient;
@end

@interface Thing : NSObject 
...
@end

@interface Character : Thing 
...
@end




1.1 비공식 프로토콜(Informal Protocol)
비공식 프로토콜은 모듈클래스가 반드시 구현하지 않아도 되는 프로토콜이다.
비공식 프로토콜은 (반드시 구현하지 않아도 되기 때문에) 유연하고 동적이지만,  추가적인 문서화와 프로그래머들 간에
(통일된 사용을 위해) 협조가 요구된다.  비공식 프로토콜에서는  메서드가 구현되었는지 검사하는 코드가 들어가며  
그 형태는 아래 코드와 같다. 자바에서는 윈도우 종료시 이벤트를 인터셉트 하기위해서,  해당하는 이벤트에 대한 리스너를
등록하고, 이벤트가 발생하면 리스너를 통해 통보받는다.  Objetive-C 에서는  delegate 가 윈도우 종료시 Notification 을
통보받는 메소드를 비공식 프로토콜로 선언하고 있다. 
 
BOOL shouldClose = YES;
if ([delegate respondsToSelector:@selector(windowShouldClose:)])
   shouldClose = [delegate windowShouldClose:self];
if (!shouldClose)
return;





1.2 공식프로토콜과 비공식 프로토콜의 조합(Combining Formal and Informal Protocols)

프로토콜은 비공식 프로토콜과 공식 프로토콜이 혼합되어 사용되어지는 경우가 많다.
프로토콜에 선언한 메소드는 디폴트로 공식 프로토콜이며, @required 이라는 지시어를 명시적으로 사용할 수 있다. 
비공식 프로토콜은 @optional  지시어를 사용한다.

다음은 TableDataSource 프로토콜이며, 테이블 행의 개수를 반환하는 메소드와 해당하는 열과 행의 셀객체를 반환하는 
메소드는  @required 로 , 테이블 특정 행과 열의 셀객체를 변경하는 메소드는 @optional 로 선언되어 있어서, 비공식 프로토콜이
구현되지 않았을 경우,  read-only 로 동작하게 된다. (구현 유무에 따라 동작용도를 변경할 수 있다. )

@protocol TableDataSource
@required
- (int)numberOfRowsInTable:(Table*)table;
        - (id)table:(Table*)table objectForColumn:(int)col row:(int)ro
@optional
- (void)table:(Table*) setObject:(id)object forColumn:(int)col row:(int)row;
@end

 





 
2. 카테고리(Categories)
클래스 정의 일부에 이름을 붙힌 조각이라 할 수 있다..  자바에서는 단일블럭에서 하나의 클래스 정의가 모두 이루어지지만, 
Objective-C에서는 한 클래스 정의를 카테고리를 사용하여 여러 파일에 분리하여 정의할 수 있다.
카테고리를 사용함으로써, 복잡한 클래스를 관련이 되어 있는 인스턴스 변수와 메소드들의 그룹으로 분리하여
복잡성을 줄이고,  프로그래머들이 독립적으로 개발할 수 있도록 해준다. 
이외에도 카테고리는 클래스 정의(@interface) 와 독립적으로 클래스 메소드를 추가할 수 있게하며,
클래스 구현의 일부를 은닉할 수 있는 메커니즘을 제공한다. 클래스와 카테고리는 별도의 헤더파일과 모듈파일에
저장될 수 있으며, 해당 클래스를 사용하는 클라이언트 클래스는 관련있는 헤더파일만을  import 하여 사용한다. 

// 카테고리를 사용하지 않고 단일 클래스에 정의한 경우

@interface RecipeBoxController : NSObject {
       NSMutableArray*recipes;
NSMutableDictionary*  recipeIndex;   
}
- (id)init;
- (Document*)newRecipe;
- (Document*)newShoppingList;
- (Document*)newShoppingListFromRecipes:(NSIndexSet*)recipeIndexes;
@end
@implementation RecipeBoxController
...
@end

다음 카테고리는 리스트 설정하고 획득하는 역할만을 수행한다.
// Category 를 사용하여 클래스를 분리 
@interface RecipeBoxController (ShoppingLists)
- (Document*)newShoppingList;
- (Document*)newShoppingListFromRecipes:(NSIndexSet*)recipeIndexes;
@end

@implementation RecipeBoxController (ShoppingLists)
...
@end




2.1 카테고리 사용하여 조직하기(Using Categories for Organization)
거대한 클래스를 다룰수 있는 기능적인 단위로 나누는 것이 카테고리의 주요한 용도이다.  이 개념은 한 클래스를 
모듈화 할 수 있게하며, 각 모듈간에 캡슐화와 의존성을 줄여준다. 빌터패턴이 카테고리를 사용하여 얻을 수 있는 응용의 한
예이다. 자바에서 빌드에서 복잡한 객체 생성의 과정이 별도 클래스로 분리되는데, 
Objective-C에서는 별도의 객체생성과정이 메인 클래스 정으로부터 카테고리를 사용하여 분리될 수 있다. 


 

  2.2 메서드 은닉 (Hiding Methods)
카테고리의 또 다른 사용용도는 클래스의 일부를 은닉하는 것이다. 자바에서는 private, protect 키워드를 사용하여
내부 데이터를 은닉한다.  아래는 카테고리를 사용하여 메소드 일부를 은닉하는 예이다.  Private 이름으로 선언된 카테고리는
컴파일러에게 무시되며, 해당 클래스를 사용하는 클라이언트 클래스는 카테고리의 메소드들을 볼 수 없다. 
두개의 클래스 선언을 별도의 헤더 파일로 분리할 수도 있다.  이때, 클라이언트 클래스는 ToasterController.h 헤더파일만을  
import 하여 사용하면 된다. 카테고리의 헤더파일은 관례적으로  (클래스이름+카테고리이름.h) 이라는 형태를 갖는다.
아래에서는 ToasterController+Private.h 라는 이름을 갖을 것이다.
이런한 개념을 확장하여 Objectiv-C 에서는 extension 이라는 개념을 제공한다.

@interface ToasterController : NSObject {
   @private
float darkness;
}
- (void)setDarkness:(float)level;
- (void)startToasting
- (void)stopToasting
@end

@interface ToasterController (Private)
- (float)darkness
- (CarrierState)carrierPosition;
- (NSTimeInterval)cycleTime;
- (void)setCycleTime:(NSTimeInterval)cycleTime;
- (void)finishedToasting:(NSTimer*)timer;

 
2.2 외부 클래스 확장 (Augmenting Foreign Class)
카테고리를 사용하여 원 클래스에 어떠한 변경을 하지 않고도, 새로운 메소드를 정의할 수 있다. 
아래 코드는 Foundation Framework 의 NSArray 클래스에  카테고리를 사용하여, 배열의 첫번째 요소를 얻는
(id)firstObject 메소드를 추가하고 있다.  

* 만일 카테고리와 클래스가 동일한 메소드를 구현한다면, 카테고리 메소드가 클래스의 메소드를 대체하게 되어, 본 클래스의
메소드를 호출하지 못하게 된다.  동일한 메소드를 구현하는 카테고리가 2개 이상 있게되면 런타임에 무엇이 호출될지는 예측할 수 없다.
@interface NSArray (MyCollectionAdditions)
- (id)firstObject;
@end
@implementation NSArray (MyCollectionAdditions)
- (id)firstObject {
   return ([self objectAtIndex:0]);
}
@end




2.3 Extesions
Extension 은 익명 카테고리이다. 다만 Extension은 클래스의 @implementaion 구현부아 아닌  클래스의 @interface 정의부를
분리하기 위해 사용한다. extension 의 @interface 지시어는 비어있는 카테고리 이름을 사용한다.  extensions 내에 선언된 메소드들은 동일하게 @implentation  에서 모두 구현되어져야 한다. 카테고리와 extension은 컴파일 타임에만 프로그래머와 컴파일러로부터 메소드 선언을 은닉할 수 있으며, 런타임시에는 introspection(리플렉션)을 사용하면 노출될 수 있다. 

@interface CaseDocument : NSObject {
@private
   CaseNumber caseNumber;
}
- (CaseNumber)caseNumber;
@end
@interface CaseDocument()
- (void)setCaseNumber:(CaseNumber)number;
@end
@implementation CaseDocument
- (CaseNumber)caseNumber { return (caseNumber); }
- (void)setCaseNumber:(CaseNumber)number { caseNumber = number; }



 
 

'Objective-C' 카테고리의 다른 글

Objective-C 문자열 조작 메서드  (0) 2011.04.29
다중인자를 갖는 PerformSelector 메시지  (0) 2011.03.23
Protocol 과 Category  (0) 2011.03.22
Objective-C 훑어보기  (2) 2011.03.20
[Cocoa] Cocoa Design Pattern  (0) 2011.03.20
HMAC-SHA1 구현  (0) 2011.03.03