ProgrammingInObjectiveC:Chapter 06

From 흡혈양파의 번역工房
Jump to navigation Jump to search
6장
의사결정하기

6장 :: 의사결정하기

의사결정 능력은 모든 프로그래밍 언어의 기본적인 기능이다. 의사결정이란, 반복문의 실행 중에 종료 시점을 결정하는 것이다. 이 장에서 Objective-C 언어가 제공하는 몇 가지 의사결정 구문을다룬다.

  • if 문
  • switch 문
  • conditional 연산자


if 문

Objective-C 에서는 대개 if 문 이라는 구조를 써서 의사결정을 한다. if 문의 일반적인 형태는 다음과 같다.

if ( expression )
    program statement


"비가 오지 않으면, 수영하러 가겠다" 와 같은 문장을 Objective-C 로 번역한다고 해보자. if 문을 써서 이 문장을 나타내려면 Objective-C 에서는 다음처럼 '작성' 한다.

if( it is not raining )
    1 will go swimming


if 문은 특정 조건에 따라 프로그램 명령문(중괄호를 사용하는 경우 명령문들)의 실행 여부를 결정한다. 비가 오지 않으면, 수영하러 가는 것이다. 다음 명령문도 비슷하다.

if ( count > MAXIMUM_SONGS )
    [playlist maxExceeded];


count 값이 MAXIMUM_SONGS 보다 클 때에만 maxExceeded 메시지가 playlist 에 보내지고 그 외에는 실행이 무시된다.

실제 프로그램 예제를 보면서 좀더 이해해 보자. 키보도에서 정수 값을 입력받아 그 정수의 절대값을 표시하는 프로그램을 작성하고 싶다고 가정하자. 절대값을 얻는 가장 간단한 방법은 값이 0 보다 작다면 빼기 기호(-)를 불이는 것이다. 즉, 여기서 '값이 0 보다 작다면'이라는 말은 프로그램이 어떤 결정을 해야 함을 의미한다. 다음 프로그램처럼 if 문을 사용하여 이 결정을 내릴 수 있다.



프로그램 6.1


// 정수의 절댓값을 계산한다.

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int number;

    NSLog (@"Type in your number: ");
    scanf ("%i", &number);

    if ( number < 0 )
        number = -number;

    NSLog (@"The absolute value is %i", number);

    [pool drain];
    return 0;
}

프로그램 6.1 의 출력결과


Type in your number:
-100
The absolute value is 100

프로그램 6.1 의 출력결과(재실행)


Type in your number:
2000
The absolute value is 2000


프로그램을 두 번 실행해서 제대로 동작하는지 확인해 보았다. 물론 좀더 높은 신뢰도를 얻으려면 테스트 횟수를 늘려야 겠지만, 이 정도로도 프로그램 의사결정에 따라 다른 두가지 결과를 확인할 수 있다.

사용자에게 메시지를 보여 주고 number에 정수 값을 입력받은 후, 프로그램은 number 값이 0 보다 작은지 확인한다. 0 보다 작다면 number의 부호를 바꾸는 명령문이 실행된다. 만일 0 보다 작지 않다면,프로그램 명령문을 자동으로 건너뛴다. (만일 양수가 입력되었다면 부호를 바꿀 필요가 없다). 그 후 number의 절대값이 표시되고 프로그램 실행은 종료된다.

if 문을 실행하는 다른 프로그램을 살펴보자. Fraction 클래스에 추가하는 convertToNum 이라는 이 메서드는 실수로 표시된 분수의 값을 반환한다. 다른 말로 하면, 분자를 분모로 나눠 그값을 2배 정밀도 부동소수점 값으로 반환할 것이다. 따라서 분수 1/2가 있을 때 이 메서드를사용하면 반환 값은 0.5가 될 것이다.


이 메서드는 다음과 같이 선언한다.

-(double) convertToNum;


다음은 이 메서드의 정의 부분이다.

-(double) convertToNum
{
    return numerator / denominator;
}


음, 아주 잘 되었다고 할 수는 없겠다. 앞부분의 정의대로라면 이 메서드는 두 가지 문제가 있다. 문제점이 보이는가? 먼저, 산술 변환이다. numerator와 denominator가 모두 정수 인스턴스 변수임을 기억하는가? 두 정수를 나누면 어떻게 될까? 맞다, 정수 나눗셈이 진행된다. 만일 1/2 을 변환하려 한다면, 위 코드에서는 0 이 반환될 것이다. 이 문제는 형 변환 연산자를 사용하여, 두항 가운데 하나를 부동소수점으로 변경하여 나눗셈을 수행하면 손쉽게 해결할수 있다.

(double) numerator / denominator


형 변환 연산자의 우선순위가 높다는 사실을 기억하자. 먼저 numerator의 값을 먼저 double형으로 바꾸고 나눗셈이 수행된다. 산술 연산의 변환 법칙이 알아서 denominator 를 변환해 주므로 직접 할 필요는없다.

두 번째 문제는 0으로 나누는 경우를 확인하지 않는다눈 것이다. (0 으로 나누는지는 언제나 확인해야 한다). 이 메서드를 부르는 사람이 의도하지 않게 분모설정을 까먹었다거나 0 으로 설정했을 수도 있다. 그럼에도 프로그램은 비정상 종료되지 않아야 할 것이다.


다음은 수정된 convertToNum 메서드다.

-(double) convertToNum
{
    if (denominator != 0)
        return (double) numerator / denominator;
    else
        return 0.0;
}


만일 분수의 denominator 가 0 일 때는 독단적으로 0.0 을 반환하도록 결정하였다. 오류 메시지를 표시하거나, 예외를 날라는등 다른 방법도 존재하지만, 여기서는 다루지 않겠다.

이 새 메서드를 프로그램 6.2에서 사용해 보자.


프로그램 6.2


#import <Foundation/Foundation.h>

@interface Fraction: NSObject
{
    int numerator;
    int denominator;
}

-(void) print;
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;
-(int) numerator;
-(int) denominator;
-(double) convertToNum;
@end

@implementation Fraction
-(void) print
{
    NSLog (@"%i/%i ", numerator, denominator);
}

-(void) setNumerator: (int) n
{
    numerator = n;
}

-(void) setDenominator: (int) d
{
    denominator = d;
}

-(int) numerator
{
    return numerator;
}

-(int) denominator
{
    return denominator;
}

-(double) convertToNum
{
    if (denominator != 0)
        return (double) numerator / denominator;
    else
        return 0.0;
}
@end

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    Fraction *aFraction = [[Fraction alloc] init];
    Fraction *bFraction = [[Fraction alloc] init];

    [aFraction setNumerator: 1]; // 첫 번째 분수는 1/4 이다.
    [aFraction setDenominator: 4];

    [aFraction print];
    NSLog (@"=");
    NSLog (@"%g", [aFraction convertToNum]);

    [bFraction print]; // 값을 할당하지 않았음.
    NSLog (@"=");
    NSLog (@"%g", [bFraction convertToNum]);
    [aFraction release];
    [bFraction release];

    [pool drain];
    return 0;
}

프로그램 6.2 의 출력결과


1/4
=
0.25
0/0
=
0


aFraction 을 1/4 로 설정한 후, 프로그램은 convertToNum 메서드를 사용하여 분수를 소수로 변환한다. 그러면 이값은 0.25 로표시된다.

두 번째 경우에 bFraction 의 값이 명시적으로 설정되지 않았기 때문에 분자, 분모가 0 으로 초기화된다. 따라서 print 메서드의 결과가 0이 된다. convertToNum 메서드에 있는 if 문도 출력 결과에서 보듯이 0 을반환한다.


if-else 구문

만일 누군가 당신에게 특정 숫자가 홀수인지 짝수인지 묻는다면, 수의 마지막 자릿수를 보고 대답할 것이다. 만일 0, 2, 4, 6, 8 이라면 짝수라고, 아니라면 홀수라고 말할 것이다.

컴퓨터에게는 특정한 수가 홀수인지 짝수인지 알아내기 위해 마지막 자릿수를 확인하는 것보다 쉬운 방법이 있다. 바로 그 수가 2 로 나누어 떨어지는지 알아보는 것이다. 나누어 떨어지면 짝수이고 아니면 홀수다.

이미 나머지 연산자 % 를 사용하여 정수 나눗셈의 나머지를 계산해 보았다. 이 연산자를 사용하면 특정 정수가 2로 나누어 떨어지는지 손쉽게 알아낼수있다. 만일 2 로 나눈 나머지가 0 이라면 짝수이고 그렇지 않다면 홀수다.

이제 사용자가 입력한 정수가 홀수인지 짝수인지 알아내고, 터미널에 적절한 메시지를 표시하는 프로그램을 작성하자.


프로그램 6.3


// 숫자가 홀수인지 짝수인지 알아내는 프로그램

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int number_to_test, remainder;

    NSLog (@"Enter your number to be tested: ");
    scanf ("%i", &number_to_test);

    remainder = number_to_test % 2;

    if ( remainder == 0 )
        NSLog (@"The number is even.");

    if ( remainder != 0 )
        NSLog (@"The number is odd.");

    [pool drain];
    return 0;
}

프로그램 6.3 의 출력결과


Enter your number to be tested:
2455
The number is odd.

프로그램 6.3 의 출력결과(재실행)


Enter your number to be teste:
1210
The number is even.


숫자를 입력받은 후 2 로 나눈 나머지가 계산된다. 첫 if 문은 나머지 값이 0 과 같은지 확인한다. 만일 참이라면 "The number is even." 이라는 메시지가 표시된다.

두 번째 if 문은 나머지가 0 와 같지 않은지 확인하고, 그런 경우에는 숫자가 홀수라는 메시지를 표시한다.

첫 번째 if 문이 성립하면 두번째 if 문은 반드시 성립하지 않아야하고, 반대의 경우도 마찬가지다. 이 절의 앞에서 홀수, 짝수에 대해 논의했던 것을 기억하는가? 수를 2 로 나누어 떨어지면 짝수이고 그렇지 않으면 홀수다.

프로그램을 작성할 때 이 '그렇지 않으면(else)'이라는 개념은 매우 자주 사용된다. 대부분의 현대 프로그래밍 언어에서는 else 상황을 다루는 특별한 구조를 제공한다 Objective-C 에서는이를 if-else 구문이라고 하며, 그 형태는 다음과 같다.

if ( expression )
    program statement 1
else
    program statement 2


if-else 구문은 사실 일반 if 문을 확장한 버전이다. 표현식을 평가한 결과가 참이면 program statement 1 이 실행되고 그렇지 않으면 program statement 2 가 실행된다. 어느 경우든 program statement 1 과 program statement 2 가운데 하나가 실행되고 두개가 모두 실행되지는 않는다.

앞 프로그램에 if-else 문을 한 개 사용하면 if 문 두 개를 대치할수있다. 이렇게 새로 작성한 프로그램을 보면 복잡성은 줄어든반면, 가독성은 높아졌다.


프로그램 6.4


// 숫자가 홀수인지 짝수인지 알아내는 프로그램 (Ver. 2)

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int number_to_test, remainder;

    NSLog (@"Enter your number to be tested:");
    scanf ("%i", &number_to_test);

    remainder = number_to_test % 2;

    if ( remainder == 0 )
        NSLog (@"The number is even.");
    else
        NSLog (@"The number is odd.");

    [pool drain];
    return 0;
}

프로그램 6.4 의 출력결과


Enter your number to be tested:
1234
The number is even.

프로그램 6.4 의 출력결과(재실행)


Enter your number to be tested:
6551
The number is odd.


= 부호는 대입 연산자이고, == 부호는 두 항이 같은지 확인한다는 것을 잊지 말자. 이것을 깜빡하고 if 문에서 비교 연산자 대신 대입 연산자를 실수로 사용하면 매우 골치아픈 문제가 발생하기도 한다.


복합 관계 테스트

지금껏 if 문을사용하여 두 숫자의 관계를 간단히 테스트해 보았다. 프로그램 6.1 은 number를 0 과 비교했고 프로그램 6.2는 분수의 분모를 0 과 비교했다. 그런데 좀더 복잡한 테스트가 필수는 아니더라도 바람직한 경우가 있다. 예를 들어, 시험점수가 70 점에서 79 점 사이인 과목의 수를 세고자 한다고 해보자. 이런경우,시험점수 값을 한쪽 경계를 기준으로 비교할 뿐 아니라 70 점과 79 점 양쪽 경계를 기준으로 비교해야 할 것이다.

Objective-C 는 이런 복합 관계 테스트를 수행할 기법을 제공한다. 복합 관계 테스트란, 그저 하나 이상의 단일 관계 테스트가 논리 AND 혹은 논리 OR 연산자로 이어진 테스트를 말한다. 이 연산자들은 각각 && 와 || 로표시된다. 예를들어, 다음 Objective-C 명령문은 grade 의 값이 70 보다 크거나 같고, 동시에 79 보다 작거나 같을 때만 grades_70_to_79 의 값을 증가시킨다.

if ( grade >= 70 && grade <= 79 )
   ++grades_70_to_79;


이와 유사하게 다음 명령문은, index의 값이 0 보다 작거나, 혹은 99 보다 클 때 NSLog 명령문을 실행한다.

if ( index < 0 1 1 index > 99 )
   NSLog (@"Error - index out of range");


Objective-C 에서는 복합 연산자를 사용하면 매우 복잡한 표현식을 생성할 수 있다. Objective-C 는 표현식을 만드는데 뛰어난 유연성을 주는데, 이런 특징을 프로그래머들이 자주 오남용한다. 대개 간단한 표현식이 읽기에도 디버깅하기에도 쉽다.

복합 관계 표현식을 작성할 때는, 자유로이 괄호를 사용하자. 가독성도 높이고 연산자의 우선순위를 잘못 사용할 때 생기는 문제도 피할 수 있다(&& 연산자는 모든 산술 연산자나 비교 연산자보다 우선순위가 낮지만 || 보다는 우선순위가 높다). 빈칸을 넣어 표현식의 가독성을 더 높일 수 있다. && 와 || 연산자 앞뒤로 빈칸을 넣어 주면 이 연산자가 결합하는 다른표현식과 시각적으로 구분할수 있다.

복합 관계 테스트의 예로, 윤년을 확인하는 프로그램을 작성해 보자. 흔히 윤년이 4 로 나누어 떨어진다고 알고있다. 그러나 100 으로 나누어 떨어지는 해가 400 으로 나누어 떨어지지 않는다면, 그해는 윤년이 아니라는 것은 몰랐을 수도 있다.

이런 조건을 어떻게 테스트해야 할지 생각해 보자. 먼저 연도를 4, 100, 400.으로 나눈 나머지를 각각 rem_ 4, rem_l00, rem_400이라는 변수에 저장한다. 이 나머지 값으로 윤년이 될 조건인지를 확인할 수 있다.

윤년의 정의를 다시 보자. 윤년은 4 로 나누어 떨어지지만 100 으로 나누어 떨어지지 않거나, 400 으로 나누어 떨어져야 한다. 이 문장을 좀 생각해 보고 앞에서 언급한 정의와 맞는지 살펴보라. 이제 윤년을 이러한 용어로 다시 정의했으므로, 다음과 같은 프로그램 명령문으로 바꾸는 일은 꽤 간단해진다.

if ((rem_4 == 0 && rem_100 != 0) || rem_400 == 0)
    NSLog (@"It's a leap year.");


다음의 내부표현식 주변에는 괄호가 필요 없다.

rem 4 == 0 && rem 100 != 0


&& 가 || 보다 우선순위가 높아서 괄호 없이도 원하는 순서대로 동작하기 때문이다. 물론 이 예에서는 다음코드도 아무 문제없이 잘 동작한다.

if ( rem_4 == 0 && ( rem_100 != 0 || rem_400 == 0 ) )


이 테스트 앞에 변수를 선언하고 사용자에게서 키보드 입력을 받는코드를 추가한다. 프로그램 6.5 와 같이 입력받은 해가 윤년인지 테스트하는 프로그램이 완성되었다.



프로그램 6.5


// 주어진 해가 윤년인지 알아내는 프로그램

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int year, rem_4, rem_100, rem_400;

    NSLog (@"Enter the year to be tested: ");
    scanf ("%i", &year);

    rem_4 = year % 4;
    rem_100 = year % 100;
    rem_400 = year % 400;

    if ( (rem_4 == 0 && rem_100 != 0) || rem_400 == 0)
        NSLog (@"It's a leap year.");
    else
        NSLog (@"Nope, it's not a leap year.");

    [pool drain];
    return 0;
}

프로그램 6.5 의 출력결과


Enter the year to be tested:
1955
Nope, it's not a leap year.

프로그램 6.5 의 출력결과(재실행)


Enter the year to be tested:
2000
It's a leap year.

프로그램 6.5 의 출력결과(재실행)


Enter the year to be tested:
1800
Nope, it's not a leap year.


이 예제에서는 4 로 나누어 떨어지지 않는 해 (1955년), 400 으로 나누어 떨어져서 윤년이 되는 해 (2000년), 100 으로 나누어 떨어지지만 400 으로는 나누어 떨어지지 않기 때문에 윤년이 아닌 해 (1800년)를 사용하였다. 테스트를 완성하려면 4 로 나누어 떨어지지만 100 으로는 나누어 떨어지지 않는 해도 테스트해야 할 것이다. 마지막 경우는 연습문제로 남겨 두었다.

앞서 말한 대로 Objective-C 는 개발자가 표현식을 작성할 때 엄청난 유연성을 지원해 준다. 예를 들어, 프로그램 6.5에서 증가 결과로 rem_4, rem_100, rem_400 를 사용할 필요 없이 바로 다음과 같이 if 문에서 계산해도 된다.

if ( ( year % 4 == 0 && year % 100 1= 0 ) || year % 400 == 0 )


다양한 연산자 사이에 적절히 빈칸을 넣으면 이런 복잡한 수식도 읽기 편하다. 만일 빈칸과 괄호 없이 이 표현식을 쓰면 다음과 같을 것이다.

if (year%4==0&&year%100!=01""year%400==0)


중첩 if 문

일반적인 if 문의 형태에서 , 괄호안의 표현식이 참이면바로 뒤따르는 명령문이 바로 실행된다. 다음과 같이 그 명령문이 또다른 if 문이어도 아무문제 없다.

if ( [chessGame isOver] == NO )
    if ( [chessGame whoseTurn] == YOU )
        [chessGame yourMove] ;


만일 chessGame 에 isOver 메시지를 보내 반환된 값이 NO라면, 다음 명령문이 실행된다. 그 명령문도 if 문이다. 이 if 문은 YOU의 값을 whoseTurn 과 비교한다. 만일 두 값이 동일하면, chessGame 객체에 yourMove 메시지가 보내진다. 따라서 yourMove 메시지는 게임이 끝나지 않았고 당신 차례가 되었을 때만 보내진다. 사실 이 코드는 복합관계를 사용하여 다음과 같이 표현할 수도 있다.

if ( [chessGame isOver] == NO && [chessGame whoseTurn] == YOU )
    [chessGame yourMove];


좀더 실용적인 중첩 if 문을 예로 들어보자. 이 예제에 else 문을추가한다.

if ( [chessGame isOver] == NO )
    if ( [chessGame whoseTurn] == YOU )
        [chessGame yourMove];
    else
        [chessGame myMove];


이 명령문을 실행하면 위에서 설명한 내용과 동일하게 동작한다. 그러나 게임이 끝나지 않고 당신 차례가 아니라면 else 문이 실행되어 chessGame 에 myMove 메시지가 보내진다. 게임이 종료되면 if 문과 거기에 속한 else 문까지 포함해 전체를 건너뛰게 된다.

여기서 else 문이 게임 종료 여부를 테스트하는 if 문이 아니라 whoseTurn 메서드의 반환 값을 가지고 테스트하눈 if 와 연계되어 있음에 주의하자. else 문은 일반적으로 else 를 포함하지 않는 가장 마지막 if 에 연계된다.

한 단계 더 나아가 가장 바깥의 if 문에 else 문을 추가할 수도 있다. 만일 게임이 끝났다면 이 else 문이 실행된다.

if ( [chessGame isOver] == NO )
    if ( [chessGame whoseTurn] == YOU )
        [chessGame yourMove];
    else
        [chessGame myMove];
else
    [chessGame finish];


들여쓰기를 했으니 Objective-C 가 명령문을 프로그래머의 의도대로 해석할 거라고 생각할 수도 있으나, 사실은 그렇지 않다. 예를 하나 들어 보자. 다음 코드처럼 안쪽 else 문을 제거한다고 해서, 들여쓰기된 모양대로 프로그램이 실행되지는 않는다.

if ( [chessGame isOver] == NO )
    if ( [chessGame whoseTurn] == YOU )
        [chessGame yourMove];
else
    [chessGame finish];


오히려 프로그램은다음과같이 해석되어 실행된다.

if ( [chessGame isOver] == NO )
    if ( [chessGame whoseTurn] == YOU )
        [chessGame yourMove];
    else
        [chessGame finish];


왜 이렇게 되는것일까? else 문이 else 가 붙지 않은 마지막 if 에 연계되기 때문이다. 중괄호를 사용하면, else 가 붙지 않은 안쪽 if 문이 있더라도 바깥쪽 if 에 else를 연계시킬 수 있다. 중괄호는 if 문을 닫는 효과가 있으므로, 다음 코드는 원하는 대 로동작하게된다.

if ( [chessGame isOver] == NO ) {
    if ( [chessGame whoseTurn] == YOU )
        [chessGame yourMove];
    }
else
    [chessGame finish];


else if 구문

지금까지 숫자가 홀수인지 짝수인지를 테스트하거나, 윤년 여부를 테스트하는 등 가능한 조건 값 두 개를 테스트해야 할 때 else 문을 어떻게 사용하는지 알아보았다. 그러나 프로그래밍에서 의사결정이 언제나 혹백 중 하나를 고르는 문제일 수는 없다. 예를 들어, 사용자가 입력한 값이 O 보다 작으면 -1 을, 0 이면 0 을, 0 보다 크면 1 을 표시하는 프로그램을 작성한다고 생각해 보자(부호함수가 보통 이렇게 구현되어 있다). 당연히 입력받은 숫자가 음수인지 0 인지 양수인지를 확인하는 테스트를 세 개 작성해야 할 것이다. 간단한 if-else 구문으로는 문제를 해결할 수 없다. 물론 if 문 세 개를 따로 작성하여 이 문제를 해결해도 되겠지만, 테스트가 상호 배타적일 때는 이런 방법을 쓰지 못할 것이다.

else 문에 if 문을추가하여 이 문제를 처리하는 방법을 생각해 볼 수도 있다. else 뒤에 붙는 명령문은 유효한 Objective-C 명령문이기만 하면 된다. 그러므로 다른 if 문을 쓰는 방법도 가능할 것이다. 따라서, 일반적인 경우에는 다음과 같이 작성할 수 있다.

if ( expression 1 )
    program statement 1
else
    if ( expression 2 )
        program statement 2
    else
        program statement 3


이 코드는 if 문으로 논리 의사결정을 할 때 두값 가운데 선택하는 상황을 세 가지 값 중에서 택하는 상황으로 확장해 준다. 이 방식과 마찬가지로 if 문을 else 문에 보태 값을 n 가지로 구분하는 논리 의사결정으로 확장할수 있다.

else if 구문이라는 이런 구조는 매우 많이 사용되며 다음 형태로 작성한다.

if ( expression 1 )
    program statement 1
else if ( expression 2 )
    program statement 2
    else
        program statement 3


이 방법을 쏘연 코드를 읽기도 쉽고 셋 가운데 하나를 고르는 의사결정임이 더 분명해진다.

다음 프로그램은 앞에서 잠깐 언급한 부호 합수에서 else if 구문을 사용하는 예다.


프로그램 6.6


// 부호 함수를 구현하는 프로그램

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int number, sign;

    NSLog (@"Please type in a number: ");
    scanf ("%i", &number);

    if ( number < 0 )
        sign = -1;
    else if ( number == 0 )
        sign = 0;
    else // 양수임
        sign = 1;

    NSLog (@"Sign = %i", sign);
    [pool drain];
    return 0;
}

프로그램 6.6 의 출력결과


Please type in a number:
1121
Sign = 1

프로그램 6.6 의 출력결과(재실행)


Please type in a number:
-158
Sign = -1

프로그램 6.6 의 출력결과(재실행)


Please type in a number:
0
Sign = 0


입력된 수가 0 보다 작으면 sign 에 -1 이 대입되고 , 0 과 같으면 sign 에 0 이 대입된다. 이 두 경우에 해당하지 않으면 입력된 수가 0 보다 큰값 이므로 sign 에 1 이 대입된다.

프로그램 6.7은 터미널에서 입력된 문자를 분석하여 알파벳 (a-z , A-Z) 인지, 숫자(0-9) 인지, 혹은 특수문자(그 외 모든 것)인지 구분한다. 터미널에서 문자 하나를 입력받기 위해 scanf 호출에서 %c 를사용한다.



프로그램 6.7


// 이 프로그램은 키보드에서 입력받은
// 문자 하나를 분류한다.

#import <Foundation/Foundation.h>

int main(int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    char c;

    NSLog (@"Enter a single character:");
    scanf ("%c", &c);

    if ( (c >= 'a'&& c <= 'z') || (c >= 'A'&& c <= 'Z') )
        NSLog (@"It's an alphabetic character.");
    else if ( c >= '0'&& c <= '9')
        NSLog (@"It's a digit.");
    else
        NSLog (@"It's a special character.");

    [pool drain];
    return 0;
}

프로그램 6.7 의 출력결과


Enter a single character:
&
It's a special character.

프로그램 6.7 의 출력결과(재실행)


Enter a single character:
8
It's a digit.

프로그램 6.7 의 출력결과(재실행)


Enter a single character:
B
It's an alphabetic character.


문자를 읽은 후, 첫 테스트는 char형 변수 c 가 알파벳 문자인지 확인한다. 문자가 소문자인지, 혹온 대문자인지를 테스트한다. 먼저 소문자는 다음 표현식으로 확인한다.

( c >= 'a' && c <= 'z' )


만일 c 가문자 a 부터 z 사이에 있다면 표현식의 결과값은 참이 된다. 이 말은 c 가 소문자라는 뜻이다. 대문자인지는 다음 표현식 으로 확인한다.

( C >= 'A' && c <= 'Z' )


만일 c 가 문자 A 에서 Z 사이에 있을때 결과값은 참이 된다. 이 말은 c가 대문자임을 의미한다. 이 테스트는 문자를 ASCII 포맷으로 저장하는 컴퓨터 시스템에서 정상동작한다.

만일 변수 c 가 알파벳 문자라면 첫 if 문의 테스트 결과가 성공하여 "It' an alphabetic character." 가 표시된다. 만일 테스트 결과가 실패한다면 else if 문이 실행된다. 이 구문은 입력받은 문자가 숫자인지 확인한다. 이 테스트에서는 터미널에서 입력받은 문자가 정수 0 과 9 사이가 아니라 문자 '0'과 '9' 사이에 있는지를 확인한다. 그 이유는 터미널에서 문자를 입력받았고 문자 '0' 부터 '9' 까지는 숫자 0 부터 9 까지와 다르기 때문이다. 사실 ASCII 에서 문자 '0' 은 내부적 으로 시스템 혹은 메모리 상에는 숫자 48 로 저장되고 문자 '1' 은 숫자 49 로 저장되며 그 다음 문자들도 숫자형태로 바뀌어 저장된다.

만일 c가 숫자라면 "It's a digit." 라는 문장이 표시된다. 그 외에는 c 가 문자도 아니고 숫자도 아니라는 뜻이 된다. 결국 마지막 else 문이 실행되어 " It's a specia character." 라는 문장을 터미널에 표시한다. 그 다음에 프로그램의 실행이 종료된다.

scanf 를 사용하여 한 문자만 입력받았지만 여전히 엔터 키를 눌러야 입력한 문자가 프로그램에 전송되니, 주의하자. 대개 터미널에서 데이터를 입력받을 때 프로그램은 엔터 키를 누르기 전까지 입력된 데이터를 전혀 알지 못한다.

한 예로, 사용자가 다음 형태로 간단한 표현식을 입력할 수 있는 프로그램을 작성해보자.


숫자 연산자 숫자


이 프로그램은 표현식을 계산하여 결과를 터미널이 표시할 것이다. 인식 가능한 연산자는 일반적인 덧셈, 뺄셈, 곱셈, 나눗셈이다. 4장 「데이터 형과 표현식」의 프로그램 4.6에서 Calculator 클래스를 가져와 사용하자. 각 표현식은 Calculator 클래스를 사용해 계산할 것이다.

이 프로그램은 거대한 if 문과 많은 else if 문을써서 어떤 연산이 수행될지 판단할 것이다.


objc2_notice_01
islower 와 isupper 라는 표준 라이브러리 안의 루틴을 사용하여 내부표현 방식 문제를 아예 피하는 것이 좋다. 이를 위해서 프로그램에 #import <ctype.h> 를 추가해 주어야 한다. 우리 예제는 설명을 위해 일부러 이 루틴을 사용하지 않았다.

프로그램 6.8


// 아래 형식의 표현식을 평가하는 프로그램:
// 숫자 연산자 숫자

// Calculator 클래스 구현

#import <Foundation/Foundation.h>

@interface Calculator: NSObject
{
    double accumulator;
}

// 누산기 메서드
-(void) setAccumulator: (double) value;
-(void) clear;
-(double) accumulator;

// 산술 연산 메서드
-(void) add: (double) value;
-(void) subtract: (double) value;
-(void) multiply: (double) value;
-(void) divide: (double) value;
@end

@implementation Calculator
-(void) setAccumulator: (double) value
{
    accumulator = value;
}

-(void) clear
{
    accumulator = 0;
}

-(double) accumulator
{
    return accumulator;
}

-(void) add: (double) value
{
    accumulator += value;
}

-(void) subtract: (double) value
{
    accumulator -= value;
}

-(void) multiply: (double) value
{
    accumulator *= value;
}

-(void) divide: (double) value
{
    accumulator /= value;
}
@end

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    double value1, value2;
    char operator;
    Calculator *deskCalc = [[Calculator alloc] init];
    NSLog (@"Type in your expression.");
    scanf ("%lf %c %lf", &value1, &operator, &value2);

    [deskCalc setAccumulator: value1];
    if ( operator == '+')
        [deskCalc add: value2];
    else if ( operator == '-')
        [deskCalc subtract: value2];
    else if ( operator == '*')
        [deskCalc multiply: value2];
    else if ( operator == '/')
        [deskCalc divide: value2];

    NSLog (@"%.2f", [deskCalc accumulator]);
    [deskCalc release];

    [pool drain];
    return 0;
}

프로그램 6.8 의 출력결과


Type in your expression.
123.5 + 59.3
182.80

프로그램 6.8 의 출력결과(재실행)


Type in your expression.
198.7 / 26
7.64

프로그램 6.8 의 출력결과(재실행)


Type in your expression.
89.3 * 2.5
223.25


scanf 호출은값을세 개 읽어서 변수 value1, operator, value2 에 저장한다. %lf 포맷 문자를 사용하여 double 값을 읽을 수 있다. 이 포맷은 사용자가 입력한 표현식의 첫째 항인 변수 value1 의 값을 읽을 때도 사용된다.

다음으로 연산자를 읽는다. 연산자는 숫자가 아니라 문자(+, -, *, /)이므로 문자변수로 읽어 저장한다. %c 포맷 문자는 시스템에게 터미널에서 다음 문자를 읽어오도록 지시한다. 포맷 스트링에 빈칸이 있으며 입력 시에 빈칸(들)을 사용할 수 있다. 이를 통해 사용자는 빈칸으로 연산자와 항을 구분해서 입력할수 있다.

두 값과 연산자를 읽은 후, 프로그램은 첫 번째 값을 계산기의 누산기에 저장한다. 그 후 operator 의 값이 가능한 연산자 네 개 중에서 무엇인지 확인한다. 어떤 연산자인지 찾아내면, 그에 해당하는 적절한 메시지가 계산기에 보내져 연산을 수행한다. 마지막으로 NSLog 에서 누산기의 값을 받아 표시한다. 그 후 프로그램 실행이 완료된다.

이 시점에서 프로그램의 완전성에 대해 몇 마디 하겠다. 우리가 수행하고자 했던 작업을 잘 진행하고 있지만, 사용자가 실수할 경우에 대해서는 전혀 대처하지 않았다. 예를 들어 사용자가 연산자로 '?' 를 입력하면 어떻게 될까? 프로그램은 아마 if 문을 건너뛰고, 터미널에 사용자가 표현식을 잘못 입력했다는 경고메시지를 띄우지도 않을 것이다.

또 한 가지 빠뜨린 사항은 사용자가 0 으로 나누었을 경우다. Objective-C 에서 수를 0 으로 나눠서는 안 된다는 것을 이미 알고 있을 것이다. 이 경우를 대비해 나누는 값을 확인해야 한다.

프로그램이 오작동하거나 원치 않는 결과가 발생하는 상황을 예상하고, 이를 예방하는 조치를 취하는 과정은 안정되고 만족스러운 프로그램을 개발하기 위해 반드시 거쳐야 한다. 프로그램에 대해 충분한 횟수로 테스트 케이스를 진행하면, 예상치 못했던 상황을 일부 발견할 수 있을 것이다. 그러나 그 이상이 필요하다. 프로그램을 작성할때 언제나 "만약이렇게 되면어떻게 되지?" 라는 질문을 해야한다. 그리고 문제를 적절히 해결하는 코드를 추가하는 습관을 들여야한다.

프로그램 6.8A 는 프로그램 6.8 에서 '0으로 나누는 문제'와 '알지 못하는 연산자 입력문제' 를 처리한 버전이다.


프로그램 6.8A


// 아래 형태의 간단한 표현식을 평가하는 프로그램:
// 값 연산자 값

#import <Foundation/Foundation.h>

// Calculator 클래스의 인터페이스 및 구현 부분을 이곳에 추가하라.

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    double value1, value2;
    char operator;
    Calculator *deskCalc = [[Calculator alloc] init];

    NSLog (@"Type in your expression.");
    scanf ("%lf %c %lf", &value1, &operator, &value2);

    [deskCalc setAccumulator: value1];

    if ( operator == '+')
        [deskCalc add: value2];
    else if ( operator == '-')
        [deskCalc subtract: value2];
    else if ( operator == '*')
        [deskCalc multiply: value2];
    else if ( operator == '/')
        if ( value2 == 0 )
            NSLog (@"Division by zero.");
        else
            [deskCalc divide: value2];
    else
        NSLog (@"Unknown operator.");
    
    [deskCalc release];

    [pool drain];
    return 0;
}

프로그램 6.8A 의 출력결과


Type in your expression.
123.5 + 59.3
182.80

프로그램 6.8A 의 출력결과(재실행)


Type in your expression.
198.7 / 0
Division by zero.
198.7

프로그램 6.8A 의 출력결과(재실행)


Type in your expression.
125 $ 28
Unknown operator.
125


연산자가 슬래시(/, 나눗셈)라면 value2 의 값이 0 인지 검사하는 테스트가 추가로 진행된다. 만일 0 이라면 적절한 메시지가 터미널에 표시된다. 0 이 아닐 때는, 나눗셈 연산이 실행되고 결과가 표시된다. if 문이 어떻게 중첩되었는지, 또 else 문은 어느 if 문에 연계되었는지 주의하여 살펴보자.

프로그램 맨 마지막 부분에 있는 else 문은 앞의 코드를 모두 건너뛴 경우에 실행된다. 따라서 operator 의 값이 네 문자 가운데 어느 것과도 동일하지 않다면 else 문이 실행되어 "Unknown operator." 가 터미널에 표시된다.

0 으로 나누는 문제의 경우, 더 좋은 해결 방법은 나눗셈을 처리하는 메서드 내에서 처리하는 것이다. devide: 메서드를 다음과 같이 수정하자.

-(void) divide: (double) value
{
    if (value != 0.0)
        accumulator /= value;
    else {
        NSLog (@"Division by zero.");
        accumulator = 99999999.;
    }
}


value 가 0 이 아니면, 나눗셈을 수행한다. 그 외의 경우에는 메시지를 표시하고 누산기를 999999999로 설정한다. 이 값은 임의로 설정한 값이다. 원한다면 0 으로 설정하거나 오류를 나타내는 특정 변수를 사용해도 된다. 메서드가 예외 상황을 처리하는 편이 그 메서드를 사용하는 개발자가 알아서 처리하는 것보다 대개 나은 방법이다.


switch 문

이전 예제에서 연쇄된 if-else 문을 사용해 한 변수의 값을 다른 값들과 비교했다. 프로그램 개발 시에 이런 식으로 if-else 문을 사용해야 하는 경우가 매우 많다. 그래서 Objective-C 에서는 이런 작업을 처리하는 프로그램 명령문을 따로 제공한다. 이 명령문은 switch 문이라 하고 형태는 다음과 같다.

switch ( expression )
{
    case value1:
        program statement
        program statement
        ...
        break;
    case value2:
        program statement
        program statement
        ...
        break;
    case valuen:
        program statement
        program statement
        ...
        break;
    default:
        program statement
        program statement
        ...
        break;
}


괄호로 둘러싸인 expression 은 단순한 상수이거나 상수표현식인 value1, value2, ,,,, valuen 값과 연속해서 비교된다. expression 의 값과 비교하는 값이 같은 경우 (case)가 발견되면, 해당 case 문 다음에 오는 명령문이 실행된다. 이런 명령문이 여러 개 와도 중괄호를 쓰지 않아도 된다.

break 문은 특정 case가 끝났음을 알리고 switch 문을 종료시킨다. 모든 case 끝에 break 문을 넣어야 한다. 깜빡 잊고 빠뜨리면 해당 case가 실행될 때마다 다음 case 도 실행되는 문제가 발생한다. 때로는 일부러 break 문을 사용하지 않는다. 이때는 주석을 달아 의도를 분명히 밝혀 두자.

선택적으로 사용할 수 있는, 특수한 case인 default 는 expression 값이 어느 case 의 값과도 일치하지 않을 때 실행된다. 개념상으로 이 코드는 이전 예제에서 모든 경우에 해당하지 않았을 때 실행되는 else 와 동일하다. 사실 switch 문의 일반적인 형태는다음과 같이 if 문으로 동일하게 표현할수 있다.

if ( expression == value1 )
{
    program statement
    program statement
    ...
}
else if ( expression == value2 )
{
    program statement
    program statement
    ...
}
else if ( expression == valuen )
{
    program statement
    program statement
...
}
else
{
    program statement
    program statement
    ...
}


이전의 코드를 염두에 두고, 프로그램 6.8A 의 거대한 if 문을 동일한 switch 문으로 변경할 수 있다. 프로그램 6.9 를 보자.


프로그램 6.9


// 아래 형태의 간단한 표현식을 평가하는 프로그램:
// 값 연산자 값

#import <Foundation/Foundation.h>

// Calculator 클래스의 인터페이스 및 구현 부분을 이곳에 추가하라.

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    double value1, value2;
    char operator;
    Calculator *deskCalc = [[Calculator alloc] init];

    NSLog (@"Type in your expression.");
    scanf ("%lf %c %lf", &value1, &operator, &value2);
    [deskCalc setAccumulator: value1];

    switch ( operator) {
    case '+':
        [deskCalc add: value2];
        break;
    case '-':
        [deskCalc subtract: value2];
        break;
    case '*':
        [deskCalc multiply: value2];
        break;
    case '/':
        [deskCalc divide: value2];
        break;
    default:
        NSLog (@"Unknown operator.");
        break;
    }

    NSLog (@"%.2f", [deskCalc accumulator]);
    [deskCalc release];

    [pool drain];
    return 0;
}

프로그램 6.9 의 출력결과


Type in your expression.
178.99 - 326.8
-147.81


사용자가 입력한 표현식을 읽은 뒤 operator 의 값은 각 case 에 지정된 값에 대해 연속 비교된다. 일치하는 값이 발견되면, 해당 case에 포함된 명령문이 실행된다. 그후 break 문으로 switch 문을 빠져나오고 프로그램 실행이 종료된다. 만일 연산자의 값 중 일치하는 것이 없다면 default case 가 실행되어 'Unknown operator." 가 표시된다.

뒤이어 나오는 case 가 없기 때문에 default case 에서는 보통 break 문이 필요 없다. 그럼에도 모든 case 마지막에 break 을 추가하는 습관을 들이는 것이 좋다.

switch 문을 작성할때, case 값 두 개가 동일할 수 없다 - 는 사실을 기억해야 한다. 그러나 특정 프로그램 명령문에 case 값을하나이상 연계시킬수는 있다. 그저 여러 case 값을 (case마다, case 키워드와 값, 세미콜론을 함께 사용하여) 연달아 나열하고 공통으로 사용할 프로그램 명령문을 입력하면 된다. 다음 예제 코드를 보자. operator 의 값이 별표(*)나 소문자 x와 동일하면 multiply: 메서드가 실행된다.

switch ( operator )
{
    ...
    case '*':
    case 'x':
        [deskCalc multiply: value2];
        break;
    ...
}


불리언 변수

프로그래밍을 배우는 사람들은 거의 모두 오래 지나지 않아 소수(素數, prime number)의 표를 만드는 프로그램을 작성한다. 새로 상기하는 차원에서 설명하면, 양의 정수 p 가 1 과 자기 자신을 제외한 어떤 정수로도 나누어떨어지지 않는 경우, 그 정수를 소수라고 한다. 맨 처음 소수는 2다. 그 다음 소수는 3 이다. 4 는 2로 나누어 떨어지므로 소수가 아니다.

소수를 표로 만드는 방법은 몇 가지가 있다. 예를 들어 1부터 50 사이에 있는 소수를 찾는다고 해보자. 가장 분명한 (그리고 단순한) 알고리즘은 각 정수 p 를 2 에서 p-1 사이의 모든 숫자로 나누어 떨어지는지 확인해 보는 것이다. 만일 정수 p 를 나누어 떨어지는 수가 있다면, p는 소수가 아니다. 만일 나누어 떨어지지 않는다면 p 는 소수다.

프로그램 6.10은 2 부터 50 사이에 있는 소수를 나열한다.



프로그램 6.10


// 소수 표를 생성하는 프로그램

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int p, d, isPrime;

    for ( p = 2; p <= 50; ++p ) {
        isPrime = 1;

        for ( d = 2; d < p; ++d )
            if ( p % d == 0 )
                isPrime = 0;

        if ( isPrime != 0 )
            NSLog (@"%i ", p);
    }

    [pool drain];
    return 0;
}

프로그램 6.10 의 출력결과


2
3
5
7
11
13
17
19
23
29
31
37
41
43
47


프로그램 6.10 에서 몇 가지를 주의하여 살펴봐야 한다. 먼저, 가장 바깥 for 문은 정수 2 부터 50 사이를 도는 반복문을 설정한다. 반복 변수 p 는 지금 수가 소수인지를 확인하는 값이다. 반복문에 있는 첫 명령문에서는 변수 isPrime 에 1 을 할당한다. 이 변수의 용도는 곧 밝혀진다.

두 번째 반복분은 p를 정수 2 에서 p-1 사이의 정수로 나눈다. 반복문 내에서 p 를 d 로 나눈 나머지가 0 인지 확인하는 테스트가 수행된다. 만일 나머지가 0 이라면, p 는 자기 자신과 1 외에도 나누어 떨어지는 수가 있는 셈이므로 소수가 아니다. p 가 소수 후보가 아님을 나타내기 위해 변수 isPrime 의 값이 0 으로 설정된다.

안쪽 반복문의 실행이 끝나면, 변수 isPrime 의 값을 확인한다. 만일 값이 0 과 같지 않다면 p 를 나누어 나머지가 0 으로 떨어지는 정수가 발견되지 않았다는 의미다. 즉, p는소수이고, 해당값이 표시된다.

눈치했겠지만 변수 isPrime 은 값을 0 과 1 중 하나만 받는다. p 가 소수가 되면 isPrime 의 값은 1 이 되지만, 나누어 떨어지는 정수가 발견되는 순간 값이 0 으로 설정된다. 그리고 p 가 더는 소수가 아님을 나타낸다. 이런 식으로 사용되는 변수는 보통 '불리언' 변수라고 한다. 플래그는 보통 두값 가운데 하나만 존재한다고 가정한다. 또한 플래그의 값은 일반적으로 켜져 있는지 (TRUE 혹은 YES) ,아니면 꺼져 있는지 (FALSE 혹은 NO) 인지 확인하는데, 한 번 이상 테스트된다. 그리고 결과에 따라 특정한 액션을 취한다.

Objective-C 에서는 TRUE 와 FALSE 를 각각 1 과 0 으로 해석한다. 따라서 프로그램 6.10 의 반복문에서 isPrime 의 값을 1 로 지정하면 p 는 '소수다(is prime)'라고 나타내도록 TRUE 로 설정하는 것이다. 안쪽 for 문을 실행하다 나누어 떨어지는 수가 발견되면 isPrime 의 값은 FALSE 로 설정된다. 그러면 p 는 더는 '소수가 아니다'를 나타낸다.

값 1 이 보통 TRUE 또는 켜진 상태를 나타내는 데 사용되고, 0 이 FALSE 또는 꺼진 상태를 나타냄은 우연이 아니다. 이 표현은 컴퓨터의 비트하나에 해당한다. 그 비트가 켜져 있으면 값은 1 이고, 꺼져 있으면 값이 0 이다. 그런데 Objective-C 에서는 이런 논리 값을 사용하는 데 좀더 설득력 있는 이유가 있다. 그것은 Objective-C 언어가 TRUE 와 FALSE 의 개념을 다루는 방식에 관련된다.

이 장을 시작하면서 if 문에 있는 조건이 만족되면 바로 뒤따르는 명령문이 실행된다고 하였다. 그런데 '만족되면' 이라는 말이 정확히 무엇을 의미하는 것일까? Objective-C 에서 '만족되면'은 0 이 아님을 뜻한다. 다음 코드를 보자.

if ( 100 )
    NSLog (@"This will always be printed. ");


이 코드는 NSLog 문을 실행시킨다. 그 이유는 if 문의 값이 0 이 아니묘로, 조건을 충족하기 때문이다.

이 장의 각 프로그램 예제에서는 '0 이 아니면 조건을 충족한다'와 '0 이면 조건이 충족되지 않는다' 라는 개념을 사용하였다. 그 이유는, Objective-C 에서 관계 표현식이 평가될 때마다, 충족하면 결과가 1 이고 충족하지 않으면 결과가 0 이기 때문이다. 이를 염두에 두고 다음코드를 살펴보자.

if ( number < 0 )
    number = -number;


이 코드는 다음과 같이 동작한다. 'number < 0' 이 평가된다. 이 조건이 만족되면 (즉, number가 O 보다 작으면) 이 표현식의 값은 1 이다. 그외 상황에서는 0 이다.

if 문은 이 표현식을 평가한 결과를 테스트한다. 만일 값이 0이 아니라면 바로 뒤따르는 명령문이 실행되고, 0 일 경우에는 해당명령문을 건너뛴다.

바로 앞에서 설명한 내용은 for 문, while 문, do 문에서 조건을 평가할 때도 동일하게 적용된다.다음과 같은 복합 관계 표현식 도 앞서 설명한 대로 평가된다.

while ( char != 'e' && count != 80 )


만일 지정한 두 조건이 모두 유효하다면 결과는 1 이 된다. 한 조건이라도 성립하지 않으면 결과는 0 이 된다. 그 후 평가 결과가 확인된다. 결과가 0 이면 while 문은 종료되고 0 이 아닐때는 계속 실행된다.

프로그램 6.10 과 플래그의 개념으로 돌아가자. Objective-C 에서 다음 표현식을 사용해서 플래그의 값이 TRUE 인지 확인할 수 있다.

if ( isPrime )


이 표현식은 다음 식과 동일하다.

if ( isPrime != 0 )


플래그의 값이 FALSE 인지 확인하려면 논리 부정 연산자(!)를 사용한다. 다음 표현식에서 이 연산자를 써서 isPrime 의 값이 FALSE 인지 확인한다("만일 isPrime 이 아니라면"이라고 읽는다).

if ( ! isPrime )


일반적으로 이런 표현식은 expression 의 논리값을 부정한다.

! expression


따라서 expression 값이 0 이라면 논리 부정 연산의 결과는 1 이다. 또, 값이 0이 아니면 논리 부정 연산의 결과는 0이다.

논리 부정 연산자를 사용하여 플래그의 값을 다음 표현식처럼 손쉽게 바꿀 수 있다.

my_move = ! my_move;


예상했겠지만, 이 연산자는 단항 뺄셈 연산자와 우선순위가 같다. 이 말은, 모든 이항산술 연산자와 비교연산자보다 우선순위가높다는 이야기다. 예컨대 변수 x 의 값이 변수 y 의 값보다 작지 않음을 확인하려 한다고 해보자.

! ( x < y )


이처럼 괄호를 사용해야 표현식이 제대로 평가된다. 물론 위 표현식을 다음과같이 써도 의미는 동일하다.

x >= y


Objective-C 는 몇가지 기능으로 불리언 변수를 좀더 쉽게 사용하도록 지원한다. 그 가운데 하나는 특수 형인 BOOL 이다. BOOL형 변수는 참과 거짓의 값을 담는데 사용한다. 다른기능은 YES 와 NO 값이다. 이 값은 미리 정의되어 있어서, 이를 이용하면 프로그램을 작성하기도, 읽기도 모두 쉬워진다. 이 기능들을 사용해 프로그램 6.10 을 프로그램 6.10A 로 다시 작성해 보았다.


objc2_notice_01
BOOL 형은 '전처리기(macro)'라는 기법으로 추가되었다.



프로그램 6.10A


// 소수 표를 생성하는 프로그램
// BOOL형과 이미 선언된 값들을 사용하는 두 번째 버전

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int p, d;
    BOOL isPrime;

    for ( p = 2; p <= 50; ++p ) {
        isPrime = YES;

        for ( d = 2; d < p; ++d )
            if ( p % d == 0 )
                isPrime = NO;

        if ( isPrime == YES )
            NSLog (@"%i ", p);
    }

    [pool drain];
    return 0;
}

프로그램 6.10A 의 출력결과


2
3
5
7
11
13
17
19
23
29
31
37
41
43
47


조건 연산자

Objective-C 언어에서 가장 보기 드문 연산자는 아마도 조건 연산자일 것이다. 다른 연산자와 달리, 조건 연산자는 삼항 연산자다. 즉, 단항 혹은 이항 연산자와 달리 항을 세 개 받는다는 이야기다. 이 연산자를 나타내는 기호는 두 가지가 있는데, 바로 물음표(?)와 콜론(:)이다. 첫 항은 ? 앞에 있고 둘째 항은 ? 와 : 사이에, 마지막항은 : 뒤에있다.


조건 연산자는 대개 다음과 같은 형태를 가진다.

condition ? expression1 : expression2


이 문법에서 condition 은 보통 관계 표현식이다. Objective-C 언어가 조건 연산자를 만났을때 condition 을 제일 먼저 평가한다. 만일 condition의 결과가 TRUE 라면(즉, 0 이 아니면) 표현식 expression1 이 실행되고, 이 표현식의 결과가 이 연산의 결과가 된다. 만일 condition 이 FALSE(즉, 0)로 평가되면, expression2 가 실행 된다. 그리고 그 결과가 이 연산의 결과가 된다.

조건 표현식은 특정 조건에 따라 두 값 중 하나를 변수에 할당할 때 가장 많이 사용한다. 예를 들어, 정수 변수 x 와 s 가 있다고 하자. x 가 O 보다 작으면 s 에 -1 을 대입하고, 그 외에는 x(2제곱) 을 s 에 대입하고 싶다고 하자. 명령문은 다음과 같이 작성하면 된다.

s = ( x < 0 ) ? -1 : x * x;


이 표현식에서는 조건 x < O 이 먼저 테스트된다. 명령문의 가독성을 높이기 위해 보통 조건 표현식에 괄호를 치는 편이 좋다. 하지만 조건 연산자의 우선순위가 매우 낮기 때문에(대입 연산자와 콤마 연산자를 제외한 모든 연산자보다 우선순위가 낮다) 반드시 해야하는건 아니다.

만일 x 의 값이 0 보다 작다면, ? 바로 뒤의 표현식이 평가된다. 이 경우는 정수 상수값 -1 이고, x 가 O 보다 작다면 변수 s 에이 값이 대입된다.

만일 변수 x 값이 0 보다 작지 않다면, : 바로뒤의 표현식이 평가되어 s 에 대입된다. 따라서, x 가 0 보다 크거나 같다면, x * x 의 값이 s 에 대입된다.

조건 연산자의 다른 예를 살펴보자. 다음 표현식은 a, b 가운데 큰 값을 변수 max_value 에 담는다.

max_value = ( a > b ) ? a : b;


만일 콜론(:) 뒤의 표현식('그렇지 않으면'에 해당하는부분)에 다른조건 연산자를 넣어 else if 구문과 동일한 효과를 낼 수도 있다. 예를 들어, 프로그램 6.6 의부호 함수는 조건 연산자 두개를 쓰면 딱 한줄로도 구현할 수 있다.

sign = ( number < 0 ) ? -1 : (( number == 0 ) ? 0 : 1);


number 가 0 보다 작다면 sign 에는 -1 이 들어간다. 만일 number 가 0 이라면 sign 에는 0 이 대입된다. 그 외의 경우에는 1 이 대입된다. 앞의 표현식에서 '그렇지 않으면' 부분에 씌운 괄호가 실제로 필요하지는 않다. 조건 연산자는 오른쪽에서 왼쪽으로 결합되어 나간다. 다음과 같이 조건 연산자를 여러 개 사용할 경우를 보자.

e1 ? e2 : e3 ? e4 : e5


이 식은 오른쪽에서 왼쪽으로 그룹 지어져 다음과 같이 평가된다.

e1 ? e2 : ( e3 ? e4 : e5 )


조건 연산자를 꼭 대입 연산자 우측에서만 사용해야 하는 것은 아니다. 표현식을 쓸 수 있는 곳이라면 어디서든 사용 가능하다. 다음 코드처럼 number 의 부호를 다른 변수에 대입하지 않고도 NSLog 를 사용하여 표시할 수 있다.

NSLog (@"Sign = %i" / ( number < 0 ) ? -1
                : ( number == 0 ) ? 0 : 1);


조건 연산자는 Objective-C 의 전처리기 매크로를 작성할 때 매우 유용하다. 이에 대해서는 12장 「전처리기」에서 상세히 다룰 것이다.


연습문제

1. 사용자가 정수값을 두 개 입력하는 프로그램을 작성하라. 첫 번째 숫자가 두번째 숫자로 나누어 떨어지는지 테스트하고 터미널에 적절한 메시지를 표시하라.


2. 프로그램 6.8A 는 부적절한 연산자가 입력되거나 0 으로 나누는 문제가 생겨도 누산기의 값을 표시한다. 이 문제를 고쳐 보라.


3. Fraction 클래스의 print 메서드를 고쳐서 정수는 정수로 표시되게 하라(예 : 분수 5/1 는 5 로 표시). 또한, 분모가 0 인 분수는 0 으로 표시하도록 수정하라.


4. 간단한 출력 계산기 프로그램을 작성하라. 이 프로그램에서는 사용자가 다음 형태로 표현식을 입력할 수 있어야 한다.

number operator

또 프로그램은 다음 연산자를 인식할수 있어야 한다.

+ - * / S E

S 연산자는 누산기의 값을 입력받은 숫자로 설정하고, E 연산자는 프로그램 실행을 종료시킨다. 산술 연산은 누산기가 있는 값과 입력받은 두 번째 항 사이에 계산된다. 다음은 이 프로그램이 어떻게 동작할지를 보여 주는 예다.

Begin Calculations
10 S
= 10.000000
2 /
= 5.000000
55 -
= -50.000000
100.25 S
= 100.250000
4 *
= 401.000000
0 E
= 401.000000
End of Calculations.

프로그램이 0 으로 나누는 경우와 알지 못하는 연산자를 받는 경우를 처리하라. 프로그램 6.8 에서 개발한 Calculator 클래스를 사용해 계산하라.


5. 프로그램 5.9 는 터미널에 입력한 정수의 자리를 거꾸로 뒤집는다. 그러나 사용자가 음수를 입력하면 이것은 작동하지 않는다. 이럴 때 어떤 문제가 발생하는지 알아보고, 음수가 정상 처리되도록 프로그램을 수정하라. 만일 -8645 가 입력되었다면, 프로그램의 출력결과는 5468- 가 되어야 한다.


6. 터미널에서 정수 값을 입력받아 각 자리의 정수 값을 영어로 표시하는 프로그램을 작성하라. 예컨대, 사용자가 932 를 입력하면 프로그램은 다음처럼 표시해야 한다(사용자가 0 을 입력하면 zero 를 표시해야 한다).

nine
three
two

이 연습문제는 어렵다.


7. 프로그램 6.10은 불충분한점이 몇가지 있다. 그중 하나는 짝수를 확인할때다. 2 보다 큰 짝수는 모두 소수가 될 수 없다. 그러므로 모든 짝수는 가능한 소수나 나누는 수로 처리하지 않아도 된다. 내부 for 문 역시 p 값을 언제나 2 에서 p-1 사이에 있는모든 d 로나누는데, 이는 비효율적인 면이 있다. 만일 for 문의 조건에 isPrime의 값을 테스트하는 걸 추가한다면 이런 비효율성을 제거할 수 있다. 이런 방식으로, 나누어 떨어지는 수를 찾지 못하고 d 가 p 보다 작은 동안에는 for 문이 반복되도록 설정할 수 있다. 프로그램 6.10을 수정하여 이 두가지를 바꿔보고 프로그램을 실행시켜 정상 작동하는지 확인하라.


Notes