ProgrammingInObjectiveC:Chapter 04: Difference between revisions

From 흡혈양파의 번역工房
Jump to navigation Jump to search
 
Line 1,298: Line 1,298:




8. 프로그램 4.6의 add:, subtract:, multiply:, divide: 메서드를 수정하여 누산기의 결과값을 반환하도록 만들자. 새 메서드를시험해 본다.
8. 프로그램 4.6의 add:, subtract:, multiply:, divide: 메서드를 수정하여 누산기의 결과값을 반환하도록 만들자. 새 메서드를 시험해 본다.





Latest revision as of 12:19, 2 August 2013

4장
데이터형과표현식

4장 :: 데이터형과 표현식

이 장에서는 Objective-C 의 기본 데이터 형을 살펴보고 산술표현식을 구성하는데 어떤 기본규칙이 있는지 설명한다.


데이터 형과 상수

앞에서 이미 Objective-C 기본 데이터 형인 int 를 다뤘다. 기억하겠지만 int 형으로 선언된 변수는 정수값만 저장할수 있다.

Objective-C 프로그래밍 언어는 기본 데이터 형 세 가지를 더 지원한다. 바로 float 형, double 형, char 형이다. 우선 float 형을보자. 이 데이터 형으로 선언된 변수는 부동소수(소수점을 포함하는 값)를 저장하는 데 사용한다. double 형은 float 형과 동일하나, 정확도가 두 배다. 마지막으로 char 데이터 형은 문자 a와 같은 단일문자, 6과 같은 숫자문자, 혹은 세미콜론(이에 대해서는 나중에 설명한다)을 저장 하는데 쓴다.

Objective-C 에서 어떤 수, 단일 문자, 혹은 문자 스트링은 '상수(constant)' 라고 한다. 예를 들어 숫자 58은 상수 정수 값을 나타낸다. 스트링 @"Programming in Objective-C is fun.\n" 은 문자열 상수 객체의 한 예다. 상수 값으로만 구성된 표현식은 '상수표현식 (constant expression)'이라고 부른다. 다음 표현식은 각항이 상수값으로 구성 되었기 때문에 상수표현식이다.

128 + 7 - 17


그러나 만일 정수 변수 i 가 선언 되었다면, 다음 문장은 상수 표현식이 아니다.

128 + 7 - i


int형

Objective-C 에서 정수 상수는 하나 이상의 연속된 숫자로 구성된다. 연속된 숫자앞에 빼기 부호가 붙으면 이 값이 음수라는 뜻이다. 158, -10, 0 모두 유효한 정수 상수 값의 예가 된다. 숫자 사이에는 띄어쓰기가 있어서는 안 되며, 천 단위 구분자 쉼표(,)도 허용되지 않는다(따서 값 12,000 은 유효한 정수 상수 값이 아니며 12000 으로 써야 한다)

Objective-C 에서는 정수 상수를 십진법 외에도 두 가지 형태로 표시할 수 있다. 만일 정수의 첫 숫자 값이 0이라면 이 정수를 8진법의 수로여긴다. 이때, 8진법에 맞아야 하므로 나머지 숫자는 0과 7 사이의 수가 되어야 한다. 따라서 Objective-C 에서 8진법으로 50(십진법으로는 40)을 나타내려면 050이라고 표현한다. 이와 마찬가지로 8진법 상수 0177 은 십진수 127(1 x 64 + 7 x 8 + 7)이다. NSLog 호출에서 정수 값을 8진법으로 표시하려면 포맷 문자 %o 를 포맷 스트링에서 사용한다. 이 경우, 8진법 으로 표시되는 값은 앞에 0이 표시되지 않는다.. 0을 표시하려면 포맷 문자 %#o 를 사용해야 한다.

만일 정수 상수가 0과 x(대소문자 모두)로 시작된다면, Objective-C 에서는 이 값을 16진수로 표현했다고 여긴다. x 다음으로 나오는 수는 16진법의 값이며, 0에서 9까지의 숫자와 문자 a 에서 f 까지(혹은 A에서 F까지)의 값이 와야 한다. 문자는 10부터 15까지의 값을 나타낸다. 따라서 16진법의 값 FFEF0D 를 rgbColor 는 정수 변수에 대입하려면 다음과 같이 한다.

rgbColor = 0xFFEF0D;


포맷 문자 %x 는 16진법의 수에서 앞의 0x 를 빼고 문자 a 부터 f 까지를 소문자로 표시해 준다. 0x 를표시하려면 다음과 같이 %#x 를 포맷 문자로 사용한다.

NSLog ("Color is %#x\n" , rgbColor);

%X 나 %#X 처럼 대문자 X 를 사용하면 0x 와 16진법의 수가 대문자로 표시된다.


문자(charcter)이건, 정수이건, 부동소수점 수이건 상관없이 모든 값에는 표현가능한 범위가 있다. 이 범위는 특정한 데이터의 형을 저장하는 데 할당되는 저장소의 크기와 관련이 있다. 보통, 언어 자체에서 이 크기를 정해 놓지는 않는다. 이는 프로그램이 실행되는 컴퓨터에 달려 있다. 이 때문에 이 범위는 '구현종속적' 혹은 '기계 종속적'이라고도 한다. 예를 들어, 컴퓨터 에서 정수는 32비트를 차지할수도, 64비트를차지할수도있다.

결코 데이터 형의 크기를 어림짐작해서 프로그램을 작성해서는 안 된다. 그러나 기본 데이터 형마다 필요한 최소 저장 공간은 확보할 수 있다. 예를 들어, 정수값은 언제나 저장 공간이 최소 32비트는 된다. 다시 말하지만, 언제나 이것이 보장되지는 않는다.[1] 부록 B의 표 B.3 '기본 데이터 형의 요약'에서 데이터 형의 크기에 대해 설명하니 참고하자.


float형

float형으로 선언한 변수는 소수점이 있는값을 저장할수 있다. 부동소수점 상수는 소수점의 유무로 구별한다. 소수점 앞뒤에 있는 정수를 생략할 수 있지만 당연히 둘 다 생략할 수는 없다. 3., 125.8, -.0001 은 모두 유효한 부동소수점 상수다. 부동소수점 값을 표시할 때는 NSLog 에서 변환 문자인 %f를 사용하면 된다.

부동소수점 상수는 '과학적 기수법(scientific notation)'이라는 방식으로 표현해도 된다. 1.7e4 는 이 방식으로 표현한 부동소수점 값으로, 1.7X10(4승)을 나타낸다. 문자 e 앞에 나타난값은 가수이며, e 뒤에 나타나는 값은 지수다. 지수에는 더하기나 빼기 부호가 불을 수 도 있는데, 이는 10의 멱제곱에 가수가 곱해진 값을 나타낸다. 따라서 상수 2.25e-3 에서는 2.25 가 가수이며 -3 이 지수의 값이다. 이 상수는 값 2.25XlO(-3승) 혹은 0.00225 를 나타낸다. 가수와 지수를 구분하는 문자 e 는 대소문자 모두사용할 수 있다.

과학적 기수법으로 값을 표시하려면 포맷 문자 %e 를 NSLog 의 포맷 스트링에서 사용해야 한다. 포맷 문자 %g 를 사용하면 NSLog 가 부동소수점 값을 표기할 때 일반 부동소수 표기법에 따를지 아니면 과학적 표기법에 따를지 결정하도록 지시한다. 이것은 지수의 값에 따라 결정된다. 만일 지수가 -4 보다 작거나 5 보다 크다면 %e 포맷(과학적표기법)이 사용되고 그 외에는 %f 포맷이 사용된다.

'16진법' 부동소수점 상수는 0x 혹은 0X 가 앞에 나온 다음,숫자나 16진수가 하나 이상나온다. 그다음에 p 나 P 가나온후 이진 지수가 이어지는데, 이 지수에는 부호가 붙을 수도 있고 붙지 않을수도 있다. 즉, 0x0.3p10 은 값 3/16 X 2(10승) = 192 를 나타낸다.


double형

double 형은 float 형과 비슷한데, float 의 범위가 충분하지 않을 때 사용한다. double 형 으로 선언된 변수는 float 형 변수가 저장할 수 있는 유의미한 수의 거의 두배를 저장할 수 있다. 대부분의 컴퓨터는 64비트를사용하여 double 값을 표시한다.

따로 언급하지 않으면 Objective-C 컴파일러는 모든 부동소수점 상수를 double 값으로 여긴다. 명시적 으로 float 상수를 표현하려면 다음과 같이 숫자 뒤에 f 나 F 를 붙여야 한다.

12.5f

double 값은 float 값을 나타낼 때 쓴 포맷 문자(%f, %e, %g)를 그대로 사용해 표시한다.


char형

char 변수를 사용하여 문자하나를 저장할 수 있다. 문자 상수는 작은 따옴표로 둘러싸인 문자로 형성된다. 따라서 ';'과 'a', '0'은 모두 유효한 문자 상수다. 첫 상수는 세미콜론을 나타내며 두번째는 문자 a 를, 세번째는 숫자 0이 아닌 문자 0 을 나타낸다. 작은 따옴표로 감싼 문자 상수를, 큰 따옴표로 감싸서 여러 문자를 표현하는 C 스타일의 문자열(character string)과 구분하자. 앞 장에서 언급했듯이 @ 문자 다음에 큰따옴표로 감싼문 자열은 NSString 문자열 객체다.


objc2_notice_01
부록 B에서는 확장 문자 세트에서 특수 확장 문자열, 유니버설 문자, 와이드 문자 등을포 함 하는 메서드를 설명한다.


문자상수 '\n'은 새 줄 문자다. 앞에서 설명한 규칙에는 맞지 않는듯 보이지만 유효한 문자 상수다. 백슬래시(\)는 Objective-C 시스템에서 특별한 문자로, 그 자체를 한개씩의 문자로 취급하지는 않는다. 다시 말해, Objective-C 컴파일러는 '\n' 이 두개의 문자로 구성 되었지만, 이를 하나의 문자로 취급한다. 다른 특수 문자도 백슬래시로 시작한다. 부록 B에 이 문자 목록이 나와 있다. 포맷 문자 %c 를 쓰면 NSLog 호출에서 char 변수 값을 표시할 수 있다.

프로그램 4.1 에서는 Objective-C 기본 데이터 형을 사용한다.



프로그램 4.1


#import <Foundation/Foundation.h>

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

  int integerVar = 100;
  float floatingVar = 331.79;
  double doubleVar = 8.44e+11;
  char charVar = 'W';

  NSLog (@"integerVar = %i", integerVar);
  NSLog (@"floatingVar = %f", floatingVar);
  NSLog (@"doubleVar = %e", doubleVar);
  NSLog (@"doubleVar = %g", doubleVar);
  NSLog (@"charvar = %c", charVar);

  [pool drain];
  return 0;
}

프로그램 4.1 의 출력결과


integerVar = 100
floatingVar = 331.790009
doubleVar = 8.440000e+11
doubleVar = 8.44e+11
charVar = 'W'


프로그램 출력 결과의 두 번째 줄을 보자. 331.79가 floatingVar 에 할당될 때는 결과가 331.790009 로 표시된다. 그 이유는 컴퓨터 내부에서 숫자를 표현히는 방식의 부정확성 때문이다. 아마 계산기를 다뤄봤다면 이와 비슷한 부정확성을 발견한 적이 있을 것이다. 계산기에서 1을 3으로 나누면 결과는 0.33333333처럼 나온다. 이런 결과는 계산기가 1을 3으로 나눈 근사값을 표현하는 방식이다. 이론적으로는 3이 무한대로 계속 이어져야 하지만 계산기는 제한된 수만 취급할 수 있다. 즉, 계산기는 태생적으로 부정확한 기계인 것이다. 동일한 부정확성이 컴퓨터 에도 적용된다. 특정 부동소수점 값은 컴퓨터 메모리에 정확히 표시될 수 없다.


수식어 : long, long long, short, unsigned, signed

수식어 long이 int 선언 바로 앞에 자리 잡으면, 선언된 정수 변수는 몇몇 컴퓨터 시스템에서 범위가 확장된다. 예를들어 longint 선언은 다음과 같을 것이다.

long int factorial;


이 코드는 변수 factorial이 long 정수 변수가 되도록 선언한다, float형과 double형처럼 long 변수의 정확도는 컴퓨터 시스템에 따라 달라진다. 많은 시스템에서, int형과 long int형은 범위가 동일하며, 정수 값을 최대 32비트 넓이 (2(31승)-1 혹은 2,147,483,647)까지 저장할 수 있다.

long int형 상수 값은 정수 상수 뒤에 L(대소문자 모두)을 선택적으로 추가하여 형성된다. 숫자와 L 사이에 공백이 있어서는 안 된다. 따라서 변수 numberOfPoints 가 long int 형에 초기값 131,0기,100 이 되도록 선언하려면 다음과 같이 한다.

long int numberOfPoints = 1310기100L;


NSLog를 사용하여 long int의 값을 표시하려면 정수 포맷 문자 i, o ,x 앞에 수식어 l을 사용한다. 이는 포맷 문자 %li를 사용하여 long int형 값을 십진법으로 표시할 수 있고, %lo를 사용하면 8진법으로, 또 %lx 로는 16진법으로 표시할 수 있다는 뜻이다.

long long 정수 데이터 형은 다음처럼 써도 된다.

long long int maxAllowedStorage


이렇게 선언하면 지시한 변수가 지정된 확장 정확도를 갖게 되는데, 이때 최소 64비트의 넓이를 보장한다. NSLog 문자열이 long long 정수를 표현할 때는 '%lli' 처럼 l 을 하나가 아닌 두 개 써서 표현한다.

long 수식어는 다음과 같이 double 선언에서도사용할 수 있다.

long double US_deficit_2004;


부동소수점 상수로표현된 long double 에서도 다음과 같이 l 혹은 L이 공백 없이 바로 불는다.

1.234e + 7L


long double 을 표시하려면 L 변경자를 사용한다. 따라서 %Lf 는 long double 값을 부동소수점 표기법으로 나타내고, %Le는 동일한 값을 과학적 기수법으로 표시 하며, %Lg는 NSLog 가 두 포맷사이에서 결정하도록 한다.

수식어 short 는 int 선언 앞에 자리 잡을 때, Objective-C 컴파일러에게 해당 변수가 상당히 작은 정수 값을 저장할 거라고 알려 준다. short 변수는 메모리 공간 사용을 줄이려는 목적으로 쓴다. 메모리를 많이 쓰는 프로그램이나 사용가능한 메모리가 적은 경우 short 변수가 매우 중요할 수 있다.

특정한 기기에서는 short int 를 쓰면 일반 int가 쓰는 메모리 공간의 절반만 있으면 된다. 어느 경우든, shortint 가 차지하는 메모리 공간은 16비트 이상이다.

Objective-C 내에서 short int형 상수를 명시적으로 쓰는 방법은 없다. short int 변수를 표시하려면 보통 정수-변환 문자 앞에 h 를 쓴다. 바로 %hi, %ho, %hx 와 같이 쓰면 되는 것이다. 혹은 그냥 아무 정수-변환 문자를 써서 shortint 를 표시할 수 있다. NSLog 루틴에 인수로 넘겨질때 정수로 변환될 수 있기 때문이다.

int 앞에 나을 마지막 수식어는 int 변수가 양수 값만 저장하는 경우에 사용된다. 다음과 같이 선언하면 변수 counter 를 양수 값만 저장하는 데 쓴다고 컴파일러에게 알려주게 된다.

unsigned int counter;


정수 변수를 양수값만 저장 하도록 제한하면 정수값의 정확도를높일수 있다. unsigned int 상수는 상수 뒤에 u 혹은 U를 붙여서 만든다.

0x00ffU


정수 상수를 쓸 때 u(혹은 U)와 l(혹은 L)을 결합하여 컴파일러에게 상수 20000 을 unsigned long 형으로 처리하도록 알려줄 수 있다.

20000UL


정수 상수 뒤에 u, U, 1, L 중아무것도붙지 않았지만 일반 int에 담기에는 수가 너무 클 경우, 이 수는 컴파일러에 의해 unsigned int 로 처리된다. 만일 unsigned int 에 들어가기에도 크다면, 컴파일러는 long int 로 처리한다. long int 에도 들어가지 않는다면, unsigned long int 로 처리한다.

변수를 long int, short int, unsigned int 가운데 하나로 선언한다면 키워드 int 는 생략할 수 있다. 따라서 unsigned 변수인 counter 를 다음과 같이 선언해도 된다.

unsigned counter;


char 변수도 unsigned 로 선언할 수 있다.

signed 수식어를 붙이면 컴파일러에게 명시적으로 특정 변수에 부호가 있다고 지시하는 것이다. signed는 주로 char 선언 앞에 사용되는데 이에 대한 상세한 설명은 이 책의 범위를 벗어나므로 여기서 마치겠다.

id 형

id 데이터 형은 어느 형태의 객체든 저장할 수 있다. 한마디로, 일반 객체 형인 셈이다. 예를 들어 보자. 다음줄은 number가 id형 변수가 되도록선언한다.

id number;


메서드도 id형을반환할수 있다.

-(id) newObject: (int) type;


이 선언은 newObject 라는 인스턴스 메서드가 type이라는 정수 인수를 하나 받고, id형 값을 반환 한다는 뜻이다. id가 반환과 인수 형 선언의 기본값임에 주의하자. 따라서 다음 클래스 메서드는 id형 값을 반환한다.

+allocInit;


id형은중요한 데이터 형이라서 이 책에서 자주사용된다. 이 형은 뒤에서 자세히 살펴보겠지만, 일단 데이터 형을 모두 다룬다는 의미로 이곳에서 간단히 언급하였다. id형은 (다형성과 동적 바인딩으로 알려진) Objective-C 의 매우 중요한 기능을 이루는 근간이다. 이 기능들은 9장 「다형성,동적 타이핑,동적 바인딩」에서 상세히 살펴본다.

표 4.1 은 기본데이터 형과 수식어를 요약한 것이다.

상수 예 NSLog 문자
char 'a','\n' %c
short int __ %hi, %hx, %ho
unsigned short int __ %hu, %hx, %ho
int 12,-97,0xFFE0,0177 %i, %x, %o
unsigned int 12u,100U,0XFFu %u, %x, %o
long int 12L, -2001, 0xffffL %li, %lx, %lo
unsigned long int 12UL,100ul,0xffeeUL %lu, %lx, %lo
long long int 0xe5e5e5e5LL, 500ll %lli, %llx, &llo
unsigned long long int 12ull,0xffeeULL %llu, %llx, %llo
float 12.34f,3.1e-5f,
0x1.5p10, 0x1P-1
%f, %e, %g, %a
double 12.34, 3.1e-5, 0x.1p3 %f, %e, %g, %a
long double 12.341, 3.1e-5l %Lf, $Le, %Lg
id nil %p
표 4.1 기본 데이터 형


산술 표현식

거의 모든 프로그래밍 언어와 마찬가지로 Objective-C 에서도 더하기 부호(+)는 두값을 더하는 데 사용하고, 빼기 부호(-)는 두 값을 빼는 데, 별표(*)는 두 값을 곱하는데, 슬래시(/)는 두 값을 나누는데 사용한다. 이 연산자들은 두 값 혹은 두 항을 연산하기 때문에 '이항' 산술 연산자로 알려져 있다.


연산자 우선순위

지금까지 Objective-C 로 덧셈 같은단순한 연산을 어떻게 수행하는지 보았다. 다음 프로그램은 빼기,곱하기,나누기 연산을 설명한다. 이 프로그램에서 수행되는 마지막 연산 두 개는 연산자 사이에 우선순위가 있음을 알려 준다. 사실 Objective-C 의 각 연산자는 우선순위가 있다.


이 우선순위에 따라 연산자가 하나 이상 있는 표현식이 어떤 식으로 계산될지 결정된다. 우선순위가 높은 연산자가 먼저 계산된다. 우선순위가 동일한 연산자일 경우, 연산자에 따라 왼쪽에서 오른쪽으로, 혹은 그 반대 방향으로 계산된다. 이것을 연산자의 '결합' 속성이라고 한다. 부록 B에 연산자 우선순위와 결합 법칙을 모두 다룬 자료가 있으니 참고하자.


프로그램 4.2


// 다양한 산술 연산 사용의 예

#import <Foundation/Foundation.h>

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

  int a = 100;
  int b = 2;
  int c = 25;
  int d = 4;
  int result;

  result = a - b; // 뺄셈
  NSLog (@"a - b = %i", result);

  result = b * c; // 곱셈
  NSLog (@"b * c = %i", result);

  result = a / c; // 나눗셈
  NSLog (@"a / c = %i", result);

  result = a + b * c; //우선순위
  NSLog (@"a + b * c = %i", result);

  NSLog (@"a * b + c * d = %i", a * b + c * d);

  [pool drain];
  return 0;
}

프로그램 4.2 의 출력결과


a - b = 98
b * c = 50
a / c = 4
a + b * c = 150
a * b + c * d = 300


어떻게 처리되었는지 살펴보자. 정수 변수 a , b , c , d , result 를 선언한 후, 프로그램은 a에서 b를 뺀 값의 결과를 result에 대입하고 나서 적절히 NSLog를 호출해 그 값을 표시한다.

다음 명령문은 b의 값과 c의 값을곱하여 그 결과를 result에 대입한다.

result = b * c;


이제 꽤나 익숙해졌을 NSLog 를 호출해 이 곱셈의 결과를 표시한다.

프로그램의 다음 명령문은나눗셈 연산자인 슬래시(/)를 소개한다. NSLog 명령문은 a를 C로 나눈 직후, 즉 100을 25로 나눈 결과값인 4를 표시해 준다.

수를 0으로 나누려고 시도하면 프로그램이 비정상 종료되거나 예외가 발생한다. 만일 프로그램이 비정상 종료되지 않더라도 아무 의미가 없는 결과 값을 받게 될 것이다.6장 「의사결정하기」에서 나눗셈 연산을하기에 앞서 분모가 0 인지부터 검사하는 방법을 배울 것이다. 만일 나누는 수가 0이라면 적절히 대처하고 나눗셈을 수행하지 않아야 한다.

다음 표현식을 실행하면 결과 값 2550(102 x 25) 를 생성하지 않는다. 대신, 해당하는 NSLog 명령문의 결과값이 150 으로 표시된다.

a + b * c


이것은 대부분의 프로그래밍 언어와 마찬가지로 Objective-C 역시 한 표현식에 연산자 혹은 항이 여러 개 있을때 계산하는 순서 규칙이 있기 때문이다. 표현식은 보통 왼쪽에서 오른쪽으로 평가된다. 그러나 곱셈과 나눗셈은 덧셈과 뺄셈보다 우선순위가 높다. 다음표현식을보자.

a + b * c


시스템은 이 표현식을 다음과 같이 계산한다.

a + (b * c)


(대수의 기본규칙과 동일한 방식이 표현식에 적용된다.)


만일 표현식에 있는 항을 계산하는 순서를 바꾸고 싶다면 괄호를 사용한다. 사실 위 표현식들은 모두 Objective-C 에서 유효하다. 따라서 , 프로그램 4.2의 명령문을 다음 명령문으로 대체해도 결과는 동일할 것이다.

result = a + (b * c);


그러나 이 표현식이 사용 되었다면 resule에는 2550 이라는 값이 대입되었을 것이다.

result = (a + b) * c;


이 것은 a의 값(10이과 b의 값(2)의 덧셈이 먼저 진행된 다음 C의 값(25)에 곱해지기 때문이다. 괄호는 중첩해서 사용할 수 있으며, 그 경우 가장 안쪽 괄호부터 계산이 시작된다.

프로그램 4.2의 마지막 명령문에서 표현식의 결과 값을 변수에 할당하지 않고 NSLog 에 인수로 넘겨도 완벽히 유효하다는 점에 유의하자. 다음표현식을보자.

a * b + c * d


이 표현식은 위에서 설명한 규칙에 따라 다음과 같이 계산된다.

(a * b) + (c * d)


또는 다음처럼 계산된다.

(100 * 2) + (25 * 4)


계산이 끝나면 결과값 300 이 NSLog 루틴에 넘겨진다.


정수 산술과 단항 뺄셈 연산자

프로그램 4.3에서는 방금 전에 논의한 사항을 보강하고 정수 연산의 개념을 소개한다.


프로그램 4.3


// 산술 표현식 추가

#import <Foundation/Foundation.h>

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

  int a = 25;
  int b = 2;
  int result;
  float c = 25.0;
  float d = 2.0;

  NSLog (@"6 + a / 5 * b = %i", 6 + a / 5 * b);
  NSLog (@"a / b * b = %i", a / b * b);
  NSLog (@"c / d * d = %f", c / d * d);
  NSLog (@"-a = %i", -a);

  [pool drain];
  return 0;
}

프로그램 4.3 의 출력결과


6 + a / 5 * b = 16
a / b * b = 24
c / d * d = 25.000000
-a = -25


첫 명령문 세 줄에서 int와 a, b, result 선언 사이에 추가 공백을 넣어 각 변수를 선언한 문장의 열을 맞추었다. 이렇게 하여 프로그램의 가독성을 더 높일 수 있다. 혹시 눈치 했을지 모르겠지만 지금껏 나온 예제 프로그램에서도 각 연산자 앞뒤로 공백이 있다. 이것 역시 꼭 해야 하는건 아니지만, 알아보기 쉽게 하려고 넣은 것이다. 일반적으로 어디든 공백이 하나라도 허락된다면 공백을 더 추가해도 된다. 스페이스 바를 몇 번 더 눌러서 프로그램이 읽기 쉬워진다면 그 정도 노력은 들일만 하지 않은가.


프로그램 4.3의 첫 NSLog 호출에 있는 표현식은 연산자 우선순위의 개념을 보강한다. 이 표현식은 다음과 같이 계산된다.

  1. 덧셈보다 나눗셈의 우선순위가 높기 때문에 a의 값 25는 먼저 5로 나쉰다. 그 결과는 5다.
  2. 곱셈 또한 덧셈보다 우선순위가 높으므로 나눗셈의 결과 5가 b의 값 2와 곱해져서 결과는 10이 된다.
  3. 마지막으로 6과 10의 덧셈이 계산되어 최종 결과는 16이 된다.


두 번째 NSLog 명령문에서는 새로운 기법을 썼다. a를 b로 나눈 뒤 다시 b로 곱하면 a의 값이 결과가 되므로 25일 거라고 추측했을 것이다. 그러나 출력 결과에 나타나듯이 결과 값은 24다. 컴퓨터가 어디션가 비트 하나를 잃어버린 것일까? 그럴 가능성은 거의 없다. 이 표현식이 정수 계산이었다는 데 문제가 있다.

변수 a, b를 선언한 부분으로 돌아가 보자. 두가지 모두 int 형으로 선언되었음을 기억할 것이다. 정수 두개로 이루어진 표현식에서 항이 계산될때마다 Objective-C 시스템은 정수 계산을 사용해 연산을 수행한다. 그런 경우, 소수점 이하의 값은 모두 없어진다. 따라서 a의 값을 b의 값으로 나눌 때, 다시 말해 25를 2로 나눌 때 결과값은 12.5가 아니라 12가 된다. 이 증가 결과에 2를 곱하면 최종 결과가 24가 되어 잃어버린 숫자가 발생하는 것이다.

프로그램 4.3을 다시 보자. 마지막에서 두 번째 NSLog 명령문에서 볼 수 있듯이 동일한 연산을 정수 대신 부동소수점 값으로 수행하면 예상대로 결과가 나온다.

float 변수를 쓸지 int 변수를 쓸지는 변수를 사용하는 목적에 따라 결정해야 한다. 만일 소수점 아래 정보가 필요 없다면, 정수 변수를사용하자. 그 결과, 프로그램이 더 효율적으로 실행될 것이다. 쉽게 말해 많은 컴퓨터에서 프로그램이 더 빠르게 돌아갈 것이다. 반면에, 소수 자리까지 정확도가 필요하다면 선택은 명백하다. 이때 정해야 할 것은 float 인가 아니면 double 인가 정도이다. 이 질문에 대한 답은 정확도를 어느 정도로 요구하는지와, 얼마나 큰 수를 다루는지에 달려 있다.

마지막 NSLog 명령문에서 변수 a의 값은 마이너스 단항 연산자를 사용해서 음수가 되었다. 이항 연산자가 두 값으로 연산하는 것과 달리 단항 연산자는 하나의 값에 연산을 수행한다. 이때 뺄셈 부호는 두 가지 역할을 맡는다. 이항 연산자일때는 두 값을 빼는 역할을, 단항 연산자일 때는 값을 음수로 바꾸는 역할을 한다(값이 음수일 때는 양수로 바꾼다).

단항 뺄셈 연산자는 우선순위가 동일한 단항 덧셈 연산자(+)를 제외하고, 나머지 다른 산술 연산자보다는 우선순위가 높다. 따라서 다음 표현식은 -a와 b의 곱셈이다.

c = -a * b;


다시 말하지만, 부록 B에서 다양한 연산자와 우선순위를 표로 요약해 놨다.

나머지 연산자

이 장에서 다룰 마지막 산술 연산자는 나머지 연산자로서, 퍼센트기호(%)로 표시한다.

프로그램 4.4의 출력 결과를 분석하여 이 연산자가 어떻게 동작하는지 알아보자.


프로그램 4.4


// 나머지 연산자

#import <Foundation/Foundation.h>

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

  int a = 25, b = 5, c = 10, d = 7;

  NSLog (@"a %% b = %i", a % b);
  NSLog (@"a %% c = %i", a % c);
  NSLog (@"a %% d = %i", a % d);
  NSLog (@"a / d * d + a %% d = %1", a / d * d + a % d);

  [pool drain];
  return 0;
}

프로그램 4.4 의 출력결과


a % b = 0
a % c = 5
a % d = 4
a / d * d + a % d = 25


main 내의 한 명령문에서 변수 a, b, c, d 를 한번에 정의하고 초기화했다.

이미 잘 알고 있듯이 NSLog는 퍼센트 기호 바로 뒤 문자를 사용하여 다음 인수를 어떻게 출력해야 하는지 결정한다. 그러나그 뒤에 퍼센트 기호가 또 이어서 나오면, NSLog 루틴은 퍼센트 기호 자체를 출력할 것으로 인식하고 출력 결과의 적절한 위치에 퍼센트 기호를 삽입한다.

나머지 연산자 %가 첫 값을 둘째 값으로 나눈 나머지를 나타낸다고 짐작했다면 제대로 맞춘 것이다. 첫 번째 예에서, 25를 5로 나눈 나머지는 0이므로 0이 표시되었다. 만일 25를 10으로 나눈다면 나머지는 5이고, 이는 출력 결과의 둘째 줄에서 확인된다. 25를 7로나눈나머지는 4이고,이 역시 출력 결과 세번째 줄에서 확인할 수 있다.

이제 마지막 명령문의 산술 표현식을 살펴보자. Objective-C 에서 두 정수 값에 수행되는 연산은 어느 연산이든 정수로 계산됨을 기억할 것이다. 따라서, 두 정수 값을 나눈 나머지는 버려질 뿐이다. 표현식 'a / d', 즉 25를 7로 나누면 증가 결과값은 3이 된다. 이 값과 d의 값 7을 곱한 증가 값은 21 이 된다. 마지막으로 표현식 'a % d', 즉 a를 d로 나눈 나머지를 더하면 결과값은 25가 된다. 당연히 변수 a의 값과 이 결과 값이 동일해진다. 일반적으로 다음 표현식에서 a , b가 모두 정수 값 이라고 가정하면 결과값이 a의 값과 통일할 것이다.

a / b * b + a % b


사실 나머지 연산자 % 는 정수값 에서만 사용할 수 있도록 정의되었다.

나머지 연산자는 곱셈 연산자와 나눗셈 연산자와 우선순위가 같다. 한 예로 다음 표현식을 보자.

table + value % TABLE_SIZE


이 표현식도 다음과 같이 계산됨을 의미한다.

table + (value % TABLE_SIZE)


정수와 부동 소수점 수 변환

Objective-C 프로그램을 효과적으로 개발하려면, 부동소수점과 정수 값이 암묵적으로 변환되는 규칙을 이해해야 한다. 프로그램 4.5는 숫자데이터 형 간의 간단한 변환 예를 보여준다.


프로그램 4.5


// Objective-C의 기본 변환

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  float f1 = 123.125, f2;
  int i1, i2 = -150;

  i1 = f1; // 부동소수점 수의 정수 변환
  NSLog (@"%f assigned to an int produces %i", f1, i1);

  f1 = i2; // 정수의 부동소수점 수로 변환
  NSLog (@"%i assigned to a float produces %f", i2, f1);

  f1 = i2 / 100; // 정수로 정수를 나눔
  NSLog (@"%i divided by 100 produces %f", i2, f1);

  f2 = i2 / 100.0; // 부동소수점 수로 정수를 나눔
  NSLog (@"%i divided by 100.0 produces %f", i2, f2);

  f2 = (float) i2 / 100; // 형 변화 연산자
  NSLog (@"(float) %i divided by 100 produces %f", i2, f2);

  [pool drain];
  return 0;
}

프로그램 4.5 의 출력결과


123.125000 assigned to an int produces 123
-150 assigned to a float produces -150.000000
-150 divided by 100 produces -1.000000
-150 divided by 100.0 produces -1.500000
(float) -150 divided by 100 produces -1.500000


Objective-C 내에 있는 정수 변수에 부동소수점 값이 대입되면 소수점 이하 부분은 잘려 나간다. 따라서 프로그램 4.5에서 f1 의값이 i1 에 대입될 때, 숫자 123.125 는 축소되어 정수 부분 123 만이 i1 에 저장되었다. 프로그램 출력 결과에서 첫 줄을 보면 이를 확인할 수 있다.

반면 정수 변수를 부동소수점 변수에 대입해도 숫자값에 아무런 변화가 생기지 않는다. 시스템은 그저 값을 부동소수점으로 변환하여 변수에 저장한다. 프로그램 출력 결과의 둘째 줄에서 i2(-150)의 값이 정상적으로 float 변수 f1 에 저장되었음을 확인할 수 있다.

프로그램 출력 결과에서 둘째 줄 다음의 두줄은 산술표현식을 쓸 때 주의해야 할점을 다시 상기시킨다. 그 첫번째 줄은 이 장에서 이미 다룬 정수산술과 관련되어 있다. 한개의 표현식 안에서 피연산자 두개가 정수라면(short, unsigned, long 정수도 포함된다) 이 연산은 정수 산술법칙을 따른다. 따라서, (프로그램에서 했듯이)계산 결과가 부동소수점 변수에 대입되더라도 나눗셈 연산 결과로 발생한 소수점 이하수는 전부 버려진다.정수변수 i2가 정수 상수 100으로 나누어지면 시스템은 정수 나눗셈을 수행한다. 그 결과, -150 / 100 의 결과로 -1 이 나오고 float 형 변수 f1 에 결과값이 저장된다.

그 다음에 나오는 나눗셈 연산은 정수 변수와 부동소수점 상수 간에 이뤄진다. Objective-C 에서 두 값을 연산할 때 둘 중 하나만 부동소수점 변수 혹은 상수여도 부동소수점 연산을 수행한다. 따라서, i2 의 값을 100.0 으로 나누면 이 나눗셈은 부동소수점 나누기로 처리되어 결과가 -1.5 가 되고, float 변수 f1 에 대입된다.


형 변환 연산자

앞서 메서드를 선언하고 정의할 경우, 반환 값이나 인수 형을 선언할 때 괄호로 둘러쌓인 형 이름을 사용한다고 배웠다. 그런데 이것을 표현식 안에서 사용하면 전혀 다른 용도로 쓰인다.

다음에 나오는 프로그램 4.5의 마지막 연산은 형 변환 연산자를 소개한다.

f2 = (float) i2 / 100; // 형 변환 연산자


이 형 변환 연산자는 표현식을 계산하고자 변수 i2 의 값을 float 형으로 변환한다. 이 연산자가 변수 i2 에 계속 영향을 끼치지는 못한다. 이 연산자는 단항 연산자로, 다른단항 연산자와 마찬가지로 동작한다. 표현식 -a 가 a 의 값에 지속적인 영향을 미치지 않는 것과 마찬가지로 표현식 (float) a 도 그 영향력이 지속되지 않는다.

형 변환 연산자는 단항 연산자 +, - 를 제외한 모든 산술 연산자보다 우선순위가 높다. 물론, 필요하다면 괄호를 씌워 원하는 대로 연산순서를 지정할수 있다.

형 변환 연산자의 다른 예로 다음 표현식을 보자.

(int) 29.55 + (int) 21.99


이 식은 부동소수점 값을 정수로 형 변환하였으므로, Objective-C 에서 다음과 같이 계산된다.

29 + 21


또,다음 표현식을 보자.

(float) 6 / (float) 4


이 식은 다음 표현식과 동일하게 결과가 1.5 이다.

(float) 6 / 4


형 변환 연산자는 대체로 id형 객체를 특정 클래스의 객체로 강제로 변환하는 경우에 사용된다. 예를 들어 다음 코드는 id형 변수 myNumber 를 Fraction 객체로 변환한다.

id myNumber;
Fraction *myFraction;
...
myFraction = (Fraction *) myNumber;

변환 결과가 Fraction 변수인 myFraction 에 대입된다.

대입 연산자

Objective-C 에서는 일반적으로 다음 형태를 써서 산술 연산자와 대입 연산자를 결합해 사용할 수 있다.

op=


이 형태에서 op 에는 +,-,*,/,% 중 어느 연산자든 들어갈수 있다. 추가로 op 에는 이후에 설명할 시프트와 마스킹 비트 연산자도사용할 수 있다.

다음 명령문을 살펴보자.

count += 10;


+ = 는 '상수 값만큼 증가' 시키는 연산자다. 연산자 오른쪽에 있는 표현식을 연산자 왼쪽의 표현식에 더하여 그 결과를 연산자 왼쪽의 변수에 저장한다. 따라서, 이 명령문은 다음 명령문과 동일하다.

count = count + 10;


다음 표현식은 '상수 값만큼 감소' 시키는 대입 연산자를 사용하여 counter 의 값에서 5를 뺀다.

counter -= 5;


이 역시 다음 표현식과 동일하다.

counter = counter - 5;


다음은 좀 더 복잡한 표현식이다.

a /= b + c


이 표현식은 등호 오른쪽에 있는 값(혹은 b 와 c 의 합)으로 a 를 나눈 다음, 그 결과값을 a 에 저장한다. 여기서 덧셈부터 실행하는 이유는 대입 연산자의 우선순위가 더하기 연산자보다 낮기 때문이다. 사실 쉼표 연산자{,)를 제외하고는 모든 연산자는 대입 연산자보다 우선순위가 높다.

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

a = a / (b + c)


대입 연산자를 사용하는 이유는 세 가지다. 첫째, 연산자 왼쪽에 나타나는 것이 오른쪽에 또 나타날 필요가 없으므로 프로그램을 작성하기가 쉽다. 둘째, 일반적으로 표현식이 더 읽기 쉬워진다. 셋째, 대입 연산자들을 사용하면 프로그램 실행이 좀더 빨라질 수 있다. 컴파일러가 표현식을 계산하기 위해 더 적은 코드를 생생하게 되기도 하기 때문이다 .


Calculator 클래스

이제 새로운 클래스를 정의할 때가 되었다. 간단한 덧셈, 뺄셈, 곱셈, 나눗셈을 수행할 수 있는 Calculator 클래스를 만들어 보자. 보통 계산기와 마찬가지로, 누계 혹온 보통 누산기를 유지해야 한다. 따라서 누산기를 특정 값으로 설정하고, 값을 없애고(혹은 0으로 설정하고), 작업을 마쳤을 때 값을 받아 오는 메서드들이 필요하다. 프로그램 4.6 에는 새 클래스를 정의하고, 계산기를 시도해 볼 테스트 프로그램이 담겨있다.


프로그램 4.6


// 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];
  Calculator *deskCalc;

  deskCalc = [[Calculator alloc] init];

  [deskCalc clear];
  [deskCalc setAccumulator: 100.0];
  [deskCalc add: 200.];
  [deskCalc divide: 15.0];
  [deskCalc subtract: 10.0];
  [deskCalc multiply: 5];

  NSLog (@"The reuls is %g", [deskCalc accumulator]);
  [deskCalc release];

  [pool drain];
  return 0;
}

프로그램 4.6 의 출력결과


The result is 50


Calculator 클래스에는 누산기의 값을 double형 값으로 저장하는 인스턴스 변수 하나만 있다. 메서드 정의는 그 자체로 충분히 직관적이므로 쉽게 이해할 수 있을 것이다.

multiply 메서드를 호출하는 메시지를 살펴보자.

[deskCalc multiply: 5];


인수로 정수가 건네지는데, 이 메서드는 double 값을 예상하고 있다. 그러나 메서드로 넘겨지는 숫자 인수는 자동으로 예상되는 형에 맞춰 변환되기 때문에 아무런 문제도 발생하지 않는다. multiply: 에서는 double 을 예상하므로, 정수 값 5는 함수가 호출될 때 자동으로 배정밀도 부동소수점 값으로 변환된다. 이런 자동 변환이 있지만, 메서드를 호출할 때 정확한 인수 형을 건네주는 편이 더 좋은 프로그래밍 습관이다.

객체를 여러 개 만들어 사용하는 Fraction 클래스와는 달리, Calculator 는 프로그램 내에서 객체를 단하나만 만들어 사용한다. 그렇지만 이 객체를 더 쉽게 다루기위해 새로 클래스를 정의해도 된다. 언젠가는 이 계산기에 그래픽 유저 인터페이스를 추가하여 사용자가 직접 스크린의 버튼을 눌러 사용하게끔 하고 싶을 수도 있다. 시스템이나 휴대전화에 설치된 계산기 프로그램처럼 말이다.

몇 번만 연습하면, Calculator 클래스를 정의하고 확장히는 것이 쉽다는 사실을 알게 될 것이다.


비트연산자

Objective-C 언어에서 숫자내 특정 비트에 대해 동작하는 다양한 연산자들을 제공한다. 표 4.2는 이 연산자의 목록이다.

기호 연산
& 비트 논리곱(AND)
¦ 비트 포함 논리합(OR)
^ 비트 배타적 논리합(XOR)
~ 1의 보수
<< 왼쪽 시프트
>> 오른쪽 시프트
표 4.2 비트 연산자


표 4.2에 나열된 연산자는 1의 보수 연산자(~)를 제외하면 모두 이항 연산자로 두 개의 항을 받는다. 비트 연산은 모든 정수 값에 수행할 수 있지만, 부동소수점값에는 수행할 수 없다.


비트 AND 연산자

두 값이 AND 연산을 거치면, 값의 바이너리 표현이 비트별로 비교된다. 첫 번째 값과 두번째 값의 비트다 모두 1인 비트는 해당비트의 결과값이 1 이 되고,그 외에는 모두 0이 된다. 다음 진리표에서 만일 b1과 b2가 각각 비트롤 나타내는 항일때, b1과 b2의 가능한 AND 연산값을모두 보여 준다.

b1 b2 b1 & b2

0 0 0
0 1
1 0 0
1 1 1


예를 들어, w1과 w2가 short int로 정의되고 w1 은 16진수 15, w2 는 16진수 0c 였다고 하자. 다음 C 명령문은값 0x04 를 w3 에 대입한다.

w3 = w1 & w2;


w1, w2, w3 를 바이너리 값으로 다루면 더 쉽게 이해할수 있다. 16비트 크기의 short int를다룬다고 가정하자.

w1 0000 0000 0001 0101 0x15
w2 0000 0000 0000 1100 & 0x0c

w3 0000 0000 0000 0100 0x04


비트 AND 연산은 보통 마스킹 연산에 사용된다. 다시말해서,이 연산자로 데이터의 특정 비트를 0으로 설정하기가 쉽다는 이야기다. 예를 들어, 다음 명령문은 w3에 w1과 상수 3의 AND 연산 결과를 대입한다.

w3 = w1 & 3;


이 명령문의 결과로 w3 의 맨 오른쪽 두개 비트를 제외한 나머지를 0 으로 설정하고, 오른쪽 두개 비트는 w1 의 비트대로 유지한다.

Objective-C 의 다른 이항 산술 연산자와 마찬가지로, 이항 비트 연산자도 = 부호를 붙여 대입 연산자로 사용할 수 있다. 다음 명령문을 보자.

word &= 15;


이는 다음 연산과동일하다.

word = word & 15;


비트 포함 OR 연산자

Objective-C 에서 두 값이 비트 포함 OR 연산을 거치면 두값의 바이너리 표현이 비트별로 비교된다. 여기서는 둘중 하나만 비트가 1 이어도 해당위치의 결과값이 1 이 된다. 다음 진리표를 보자.

b1 b2 b1 ¦ b2

0 0 0
0 1 1
1 0 1
1 1 1


만일 w1, w2가 short int 형이고 각각 1~수 19와 6a 값을 갖는다고 해보자. w1 과 w2 의 비트 포함 OR 연산 결과는 다음과 같이 16진수 7b 가 된다.

w1 0000 0000 0001 1001 0x19
w2 0000 0000 0110 1010 ¦ 0x6a

0000 0000 0111 1011 0x7b


비트 포함 OR 연산은보통 비트 OR 연산이라고 부르며 특정 비트를 1로 설정하는 데 쓴다. 예를 들어, 다음 명령문은 연산이 수행될 때 w1의 값과 상관없이,맨 오른쪽 세 비트의 값을 1로 설정한다.

w1 = w1 | 07;


물론 여기서도 다음과 같이 대입 연산자를 써도 된다.

w1 |= 07;


비트 포함 OR 연산을 다루는 프로그램 예제는 좀 뒤로 미루자.

비트 배타적 OR 연산자

비트 배타적 OR 연산자는보통 XOR 연산자라고 부르며 다음과 같이 동작한다. 두 항에 해당하는각 비트중하나만 1 일 때, 즉 둘 다 1 은 아닐 때 해당 비트의 결과값은 1 이 되며, 나머지 경우에는 0 이 된다. 이 연산자의 다음 진리표를보자.

b1 b2 b1 ^ b2

0 0 0
0 1 1
1 0 1
1 1 0


만일 w1, w2가 각각 16진수 5e, d6 이었다면 w1, w2 의 XOR 연산 결과는 16진수 e8 이 될것이다.

w1 0000 0000 0101 1110 0x5e
w2 0000 0000 1011 0110 ^ 0xd6

0000 0000 1110 1000 0xe8


1 의 보수 연산자

1 의 보수 연산자는 단항 연산자로 피연산자의 비트를 간단히 바꿔 놓는다. 항의 비트 중 값이 1 인 비트는 0 으로 바뀌고 값이 0 인 비트는 1 로 바뀐다. 다음 진리표는 이를 아주 간단히 보여준다.

b1 -b1

0 1
1 0


만일 w1이 16비트 길이의 short int 이고 그 값이 16진수 a52f 라면, 1 의 보수값은 16진수 5ad0 이 될 것이다.

w1 1010 0101 0010 1111     0xa52f
-w1 0101 1010 1101 0000     0x5ad0


1의 보수 연산자는 지금 다루는 연산자의 비트 크기를 잘 알지 못할 때 유용하며, 보수 연산자를 쓰면 프로그램이 정수 데이터 형의 특정 크기에 덜 의존한다. 예를 들어, w1 이라는 int 의 맨 마지막 비트를 0으로 설정하고 싶을 때, 맨 오른쪽 비트를 제외한 나머지를 1로 AND 연산하면 된다. 따라서 다음 C 명령문은 정수가 32비트인 시스템에서 정상동작할 것이다.

w1 &= 0xFFFFFFE;


만일 앞의 명령문을 다음과 같이 바꾸면 w1 은 어느 시스템에서든 정상적으로 AND 연산이 처리될 것이다.

w1 &= -1;


이것은 1의 보수가 계산되어 int 크기 (32비트 정수 시스템의 경우, 맨 오른쪽 비트를 제외한 비트 31 개)만큼 왼쪽 비트부터 1 을 채워 넣기 때문이다.

이제 실제 프로그램 예제를 통해 다양한 비트 연산자를 사용해 보자.


프로그램 4.7


// 비트 연산자 예제

#import <Foundation/Foundation.h>

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

  unsigned int w1 = 0xA0A0A0A0, w2 = 0xFFFF0000,
    w3 = 0x00007777;

  NSLog (@"%x %x %x", w1 & w2, w1 | w2, w1 ^ w2);
  NSLog (@"%x %x %x", -w1, -w2, -w3);
  NSLog (@"%x %x %x", w1 ^ w1, w1 & -w2, w1 | w2 | w3);
  NSLog (@"%x %x", w1 | w2 & w3, w1 | w2 & -w3);
  NSLog (@"%x %x", -(-w1 & -w2), -(-w1 | -w2));

[pool drain];

return 0;
}

프로그램 4.7 의 출력결과


a0a00000 ffffa0a0 5f5fa0a0
5f5f5f5f ffff ffff8888
0 a0a0 fffff7f7
a0a0a0 ffffa0a0
ffffa0a0 a0a00000


프로그램 4.7의 각 연산을 연습하여 이 결과가 어떻게 얻어졌는지 이해하자.


네 번째 NSLog 호출을 보면 비트 AND 연산자가 비트 OR 연산자보다 우선순위가 높다는걸 알 수 있다. 이를 반드시 염두에두자. 이렇듯 우선순위가 표현식의 결과값에 영향을미 친다. 연산자의 우선순위는 부록 B에서 설명한다.

다섯 번째 NSLog 호출은 드모르간 법칙을 나타낸다. ~(~a & ~b)는 a | b 와 동일하고, ~(~a | ~b)는 a & b 와 동일하다.


왼쪽 시프트 연산자

어떤 값에 왼쪽 시프트 연산자(<<)가 수행되었다고 해보자. 그 값의 비트는 문자 그대로 왼쪽으로 이동한다{시프트된다). 즉, 이 연산은 시프트되는 값의 비트 위치가 변하는 결과를 만든다. 왼쪽으로 이동하는 만큼 오른쪽에 새롭게 추가되는 하위 비트는언제나 0 으로 들어오고 밀려나는 데이터의 상위 비트는 유실된다. 따라서 w1 의 값이 3 이라면 다음 표현식의 경우,

w1 = w1 << 1;


다음과 같이 표현될 수 있다.

w1 <<= 1;


또, 이 결과로 3이 왼쪽으로 한 자리 옮겨져 결과 값으로 6이 나오고, 이것이 w1 에 대입된다.

w1 ... 0000 0011 0x03
w1 << 1 ... 0000 0110 0x06


연산자 << 왼쪽항의 값이 이동되고, 연산자 오른쪽 항은 옮겨질 비트 수를 나타낸다. 만일, w1 을 한번 더 왼쪽으로 1비트 이동한다면 결과값은 0c 가될 것이다.

w1 ... 0000 0110 0x06
w1 << 1 ... 0000 1100 0x0c


오른쪽 시프트 연산자

이름 그대로 오른쪽 시프트 연산자(>>)는 특정 값의 비트를 오른쪽으로 이동시킨다. 제한된 하위 비트 자리 에서 밀려난 비트는 버려진다. unsigned 값을 오른쪽 시프트하면 언제나 0 이 왼쪽에서 (즉, 상위 비트로) 들어온다. 만일 signed 값일 때는, 시프트되는 값의 부호와 컴퓨터 시스템이 이 연산을 구현한 방식에 따라 왼쪽에서 넘어오는 값이 달라진다. 만일 부호 비트가 0이면(값이 양수이면) 시스템 구현 방식과 상관없이 0 이 시프트 된다. 그러나 부호 비트의 값이 1 이라면, 어떤 시스템에서는 1 이 시프트되지만, 다른 시스템 에서는 0 이 시프트되기도 한다. 전자는 산술 오른쪽 시프트라고 하고, 후자는 논리 오른쪽 시프트라고한다.


objc2_notice_01
절대로 시스템이 산술 오른쪽 시프트를 구현하였는지 논리 오른쪽 시프트를 구현하였는지를 추측해서는 안 된다. 이렇게 추측하고 연산자를 쓰면 프로그램이 부호가 있는 값을 오른쪽 4로 시프트할 경우, 어떤 시스템에서는 정상적으로 동작하지만 오류가 발생하는 시스템도 있을 것이다.


w1 이 32비트로 표현되는 unsigned int이고 16진수 F777EE22 의 값이 설정되었다고 해보자. 다음 명령문을 써서 w1 을 오른쪽으로 1비트 시프트하면, w1 은 어떤값이될까?

w1 >>= 1;


w1의 값은 16진수 7BBBF711이 될 것이다.

w1 1111 0111 0111 0111 1110 1110 0010 0010     0xF777EE22
w1 >> 1 0111 1011 1011 1011 1111 0111 0001 0001     0x7BBBF711


만일 w1 이 (signed) short int로 선언되었다면 컴퓨터 시스템 에 따라 동일한 결과가 나오기도 하고, 산술 오른쪽 시프트를 적용한 시스템의 경우 FBBBF기1 이라는 결과가 나오기도 할 것이다.

Objective-C 에서는 시프트가 적용되는 값의 비트 수보다 크거나 같은 수만큼 시프트할 경우, 정의된 결과를 보장하지 않는다. 이는 왼쪽, 오른쪽 시프트에 전부 해당된다. 예컨대 정수가 32비트로 표현되는 시스템이 있다고 해보자. 여기서 정수를 32비트 이상 시프트(왼쪽 혹은 오른쪽)하려 하면 프로그램에 어떤 결과 값이 돌아을지 보장하지 못한다. 또한, 시프트하는 비트 수를 음수로 할 경우에도 결과가 정의되어있지 않다.

형 : _Bool, _Complex, _Imaginary

이 장을마치기 전에 이 언어에서 제공하는세 가지 데이터 형에 대해 더 이야기하겠다. 바로 불리언 (0 혹은 1) 값을 다루는 _Bool, 복소수를 다루는 _Comp1ex, 허수를 다루는 _Imaginary 가 그것이다.

Objective-C 프로그래머는 보통_Bool 대신 BOOL 데이터 형을 사용하여 프로그램에서 불리언 값을 다룬다. 사실 이 '데이터 형' 은그 자체가 데이터 형은 아니고, char형의 다른 이름이다. char를 다른 이름으로 부르는 것은 10장 「변수와 데이터형에 대하여」에서 설명하는 typedef 키워드를사용한다.


연습문제

1. 다음중 유효하지 않은 상수는 무엇인가?그 이유는 무엇인가?

123.456 0x10.5 0X0G1 1.234L
0001 0xFFFF 123L 0XABCDEFL
0Xab05 0L -597.25 197u
123.5e2 .0001 +12 0xabcu
98.6F 98.7U 17777s 100U
0996 -12E-12 07777 +123
1234uL 1.2Fe-7 15,000


2. 다음식을 사용하여 화씨 27도를 섭씨로 바꾸는 프로그램을 작성하라.

C = (F - 32) / 1.8


3. 다음 프로그램의 출력 결과를 예상해 보라.

#import <Foundation/Foundation.h>

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

  c = 'd';
  d = c;
  NSLog (@"d = %c", d);
  [pool drain];
  return 0;
}


4. 다음 다항식을 계산하는 프로그램을 작성하라.

3x^3 - 5x^2 + 6

단, x = 2.55이다.


5. 다음 표현식을 계산하고 결과를 표시하는 프로그램을 작성하라(지수 표기법 으로표시하자).

(3.31 x 10^-8 + 2.01 x 10^-7) / (7.16 x 10^-6 + 2.01 x 10^-8)


6. 복소수는 실수와 허수로 구성된다. 만일 a 가 실수이고 b 가 허수라면 다음 표기법을 사용하여 이 수를 표현할 수 있다.

a + b i


Complex 라는 새로운 클래스를 정의하는 Objective-C 프로그램을 작성하라. Fraction 클래스를 만드는 패러다임을 따라 새로운 클래스를 위한 다음 메서드를 정의하라.

- (void) setReal: (double) a;
- (void) setImaginary: (double) b;
- (void) print; // a + bi 형태로 표시한다.
- (double) real;
- (double) imaginary;


새 클래스와 메서드를 시험해 볼 테스트 프로그램을 작성하라.


7. 그래픽 객체를 다루는 루틴의 라이브러리를 개발하고 있다고 하자. 먼저 Rectangle 클래스를 새로 정의하자. 일단, 사각형의 너비와 높이만 가지고 있자. 너비와 높이값을 설정하고, 받아오고, 사각형의 넓이와 둘레를 계산하는 메서드를 개발하라. 이 사각형 객체가 컴퓨터 화면 같은 정수 그리드에서 나타나는 사각형을 묘사한다고 가정하자. 이런 경우, 이 사각형의 너비와 높이는 정수값이다.

여기 Rectangle 클래스의 @interface 부분이 있다.

@interface Rectangle: NSObject
{
    int width;
    int height;
}

-(void) setWidth: (int) w;
-(void) setHeight: (int) h;
-(int) width;
-(int) height;
-(int) area;
-(int) perimeter;
@end

@implementation 부분을작성하고 테스트 프로그램을 사용하여 새 클래스와 메서드를 시험해 보자.


8. 프로그램 4.6의 add:, subtract:, multiply:, divide: 메서드를 수정하여 누산기의 결과값을 반환하도록 만들자. 새 메서드를 시험해 본다.


9 연습문제 8을 마친 다음, 다음 메서드를 Calculator 클래스에 추가하고 시험해 보자.

-(double) changeSign; // 누산기의 부호를 바꾼다.
-(double) reciprocal; // 1/accumulator
-(double) xSquared; // 누산기를 제곱한다.


10. 프로그램 4.6의 CalcuIator 클래스에 메모리 기능을 추가하자. 다음 메서드 선언을 구현하고 테스트하라.

-(double) memoryClear; // 메모리를 정리한다.
-(double) memoryStore; // 메모리의 값을 누산기에 설정한다.
-(double) memoryRecall; // 누산기의 값을 메모리에 설정한다.
-(double) memoryAdd; // 누산기를 메모리에 더한다.
-(double) memorySubtract; // 누산기를 메모리에서 뺀다.

각 메서드가 누산기의 값을 반환하도록 만들자.

Notes

  1. (옳긴이) 저장공간이 최소 32비트 이긴 하지만, 이게 언제나 그렇다고 볼 수 는 없다는 뜻이다.