ProgrammingInObjectiveC:Appendix B

From 흡혈양파의 번역工房
Jump to navigation Jump to search
부록 B
Objective-C 2.0 언어 요약

부록 B :: Objective-C 2.0 언어 요약

이 부록에서는 금방 참고하기 쉬운 형태로 Objective-C 언어를 요약한다. 언어를 완벽히 설명하기보다는 언어의 기능을 알기 쉽게 설명하고자 하였다. 책을 다 읽은 후에 이 부록을 철저히 읽는 것이 좋다. 지금까지 배운 내용을 보강할 뿐 아니라 이 언어에 대해 더 광범위하게 이해하게 될 것이다.

이 글은 ANSI C99 (ISO/IEC 9899:1999) 표준과 Objective-C 2.0 언어 확장을 기반으로 한다. 이 글을 쓰는 당시, Mac OS X 10.5.5 시스템에서 사용하는 GNU gcc 컴파일러의 최신 버전은 4.0.1 이다.


다이그래프와 식별자

다이그래프 문자

다음 표와 나온, 연속된 이중 문자('다이그래프')는 그 옆에 있는 단일 문자 구분기호와 동일하다.

다이그래프 의미
<: [
:> ]
<% {
%> }
%: #
%:%: ##


식별자

Objective-C 에서 '식별자(identifier)'는 연속된 문자(대소문자)나 유니버설 문자 이름, 숫자 또는-문자로 구성되어 있다. 외부 식별자로는 31 자까지 사용할 수 있고 내부 식별자나 매크로 이름은 63자 까지 사용할 수 있다.


유니버설 문자 이름

유니버설 문자(universal character) 이름은 \u 뒤에 붙는 네 개의 16진수나 \U 뒤에 붙는 여덟 개의 16진수와 같은 모양이다. 식별자의 첫 번째 문자는 유니버설 문자로 지정되는데, 그 값은 숫자일 수 없다. 식별자 이름에 사용되는 유니버설 문자의 값은 AO(0) 보다 작은 값(24(16), 40(16) 또는 60(16)은 예외다)이나 D800(16)에서 DFFF(16) 사이의 문자일 수는 없다(D800(16)과 DFFF(16)도 사용하지 못한다).


유니버설 문자 이름은 식별자 이름, 문자 상수, 문자 스트링에 사용될 수 있다.


키워드

여기에 나열된 식별자는 Objective-C 컴파일러가 특별한 의미로 받아들이는 키워드이다.

_Bool enum return
_Complex extern self
_Imaginary float short
auto for signed
break goto sizeof
bycopy if static
byref in struct
case inline super
char inout switch
const int typedef
continue long union
default oneway unsigned
do out void
double register volatile
else restrict while


지시어

컴파일러 지시어는 @ 부호로 시작하며, 표 B.1 에 요약된 대로 클래스와 객체를 다룰때 주로 사용된다.

지시어 의미
@"chars" NSString 문자 스트링 상수 객체를 정의한다(인접한 스트링은 이어진다. NSString *url = @"http://www.kochan-wood.com";
@class c1, c2, ... c1, c2, ...을 클래스로 선언한다. @class Point, Rectangle;
@defs (class) class를 위한 구조체 변수의 목록을 반환한다. struct Fract { @defs{Fraction}; } *fractPtr: fractPtr = (struct Fract *) [[Fraction alloc] init];
@dynamic names names의 접근자 메서드가 동적으로 제공될 수 있다. @dynamic drawRect;
@encode (type) type을 스트링으로 인코딩한다. @encode (int *)
@end 인터페이스 부분이나 구현 부분, 혹은 프로토콜 부분을 끝낸다. @end
@implementation 구현 부분을 시작한다. @implementation Fraction
@interface 인터페이스 부분을 시작한다. @interface Fraction: NSObject <Copying>
@private 하나 또는 그 이상의 인스턴스 변수의 범위를 정의한다. '인스턴스 변수' 참조(598쪽).
@protected 하나 또는 그 이상의 인스턴스 변수의 범위를 정의한다. '인스턴스 변수" 참조(598쪽).
@public 하나 또는 그 이상의 인스턴스 변수의 범위를 정의한다. '인스턴스 변수' 참조(598쪽).
@property (list) names names에 대한, list 속성을 갖는 프로퍼티를 선언한다. @property (retain, nonatomic) NSString *name;
@protocol 지정한 protocol에 대한 프로토콜 객체를 생성한다. @protocol (Copying) {...} if ([myObj conformsTo:(protocol)])
@protocol name name에 대한 프로토콜 정의를 시작한다. @protocol Copying
@selector (method) 지정한 method에 대한 SEL 객체이다. if ([myObj respondsTo: @selector (allocF)] {...}
@synchronized (object) 단일 스레드에서 실행될 블록을 시작한다. object는 상호 배타적(mutex) 세마포로 알려져 있다.
@synthesize names names에 대한 접근자 메서드가 따로 제공되지 않으면 자동 생성한다. @synthesize name,email; '인스턴스 변수' 참조(598쪽).
@try 예외를 잡는 블록을 시작한다. '예외 처리' 참조(619쪽).
@catch (exceptions) 예외를 처리할 블록을 시작한다. '예외 처리' 참조(619쪽).
@finally 여기서 시작되는 블록은 앞의 @try 블록에서 예외를 던지면 실행된다. '예외 처리' 참조(619쪽).
@throw 예외를 던진다. '예외 처리' 참조(619쪽).
표 B.1 컴파일러 지시어


미리 정의된 식별자

표 B.2 는 Objective-C 에서 특별한 의미가 있는 식별자를 나열한 것이다.

식별자 의미
_cmd 메서드에 자동으로 정의되는 지역 변수로, 해당 메서드의 셀렉터를 포함한다.
__func__ 함수나 메서드에서 자동으로 정의되는 지역 문자 스트링 변수로, 함수나 메서드의 이름을 포함한다.
BOOL 불리언 값으로, 보통 YES 또는 NO와 함께 쓰인다.
Class 클래스 객체 형
id 일반 객체 형
IMP id형 값을 반환하는 메서드의 포인터
nil Null 객체
Nil Null 클래스 객체
NO (BOOL) 0으로 정의되어 있다.
NSObject <Foundation/NSObject.h>에 정의된 루트 Foundation 객체
Protocol 프로토콜에 대한 정보를 저장하고 있는 클래스의 이름
SEL 컴파일된 셀렉터
self 메서드에서 자동으로 정의되는 지역 변수로, 해당 메시지의 수신자를 참조한다.
super 메시지 수신자의 부모
YES (BOOL) 1로 정의되어 있다.
표 B.2 미리 정의된 특수 식별자


주석

프로그램에 주석을 입력하는 방법은 두 가지가 있다.우선, 슬래시 문자 두개(//)로 시작할수 있다. 이 경우,그 줄에 뒤따르는 문자는 컴파일러에서 모두 무시된다.

또한, /* 로 시작해서 */ 로 끝낼 수도 있다. 이 사이에는 어느 문자든 포함될 수 있고, 여러줄로 입력할 수도 있다. 주석은 빈칸이입력되는 곳이면 어디서든 사용할 수 있다. 그러나 중첩해서 사용할 수는 없다. 다시 말해, /* 문자가 얼마나 많이 등장한다고 해도 */ 가 처음 나타나는 곳에서 그 주석이 끝난다.


상수

정수 상수

정수 상수는 연속된 숫자이고, 덧셈 부호(+)와 뺄셈 부호(-)가 앞에 붙을 수 있다. 만일 첫 숫자가 0 이라면 8진수가 되므로, 그 뒤에 오는 모든 숫자는 0 부터 7 까지중 하나여야 한다. 첫 번째 숫자가 0 이고 그 뒤에 문자 x(혹은 X)가 나타난다면, 이 정수는 16진수 상수가 되고, 그 뒤에 따르는 숫자들은 0 부터 9 까지거나 a 부터 f지(혹은 A부터 F까지)여야 한다.

십진법의 정수 상수 뒤에 접미어 문자로 l(또는 L을)붙여서 long int 상수를 만들수 있다. 만일 값이 long int 에 들어갈 수 없다면, long long int 로 처리된다. 접미어 l(또는 L)이 8진수나 16진수 상수에 붙을 경우, 크기가 맞는다면 long int 로 처리되고, 그 크기보다 크다면 long long int 로 여겨진다.long long int 에도 들어가지 않는다면 unsigned long long int 상수로 처리된다.

접미어 ll(또는 LL)을 정수 상수 뒤에 붙이면 long long int 가 된다. 8진수나 16진수 상수 뒤에 볼일 경우 먼저 long long int 로 처리되고, 크기가 이보다 크다면 unsigned long long int 상수로 처리된다.

접미어 u(또는 U)를 정수 상수 뒤에 붙이면 unsigned 정수가 된다. 만일 상수가 unsigned int 에 들어가기에 크다면 unsigned long int 로 처리 된다. unsigned long int 에 넣기 에도 너무 크다면 unsigned long long int 가 된다.

unsigned 접미어와 long 접미어를 정수 상수에 붙여 unsigned long int 로 만들 수 있다. 만일 상수가 unsigned long int 에 넣기에 크면 unsigned long long int 가 된다.

unsigned 접미어와 long long 접미어를 동시에 정수 상수에 추가하면 unsigned long long int 가 된다.

접미어가 붙지 않은 정수 상수가 부호를갖는 int에 넣기에 크다면 long int 로 처리된다. long int 에 넣기에도 크다면 long long int 로 처리된다.

접미어가 붙지 않은 8진수나 16진수 정수 상수가 부호를 갖는 int 에 넣기에 크다면 unsigned int 로 처리된다. unsigned int 에도 크다면 long int 로 처리되고, long int 에도 크다면 unsigned long int 로 처리된다. unsigned long int 에 넣기에도 크다면 long long int 로 처리된다. 마지막으로 long long int 에도 크면 이 상수는 unsigned long long int 로 처 리된다.


부동소수 상수

부동소수 상수는 십진수, 소수점, 다른 십진수가 연속되어 구성된다. 빼기 부호(-)가 값 앞에 불으면 음수를 나타낸다. 소수점 앞이나 뒤에 있는 숫자 중에 하나를 생략할 수 있지만 둘 모두 생략할 수는 없다.

만일 부동소수 상수에 e(혹은 E)가 불으면, 상수는 과학적 표기법으로 표기된 것이다. 이때, e(혹은 E)가 뒤에 부호를 갖는 정수가 불기도 하고 붙지 않기도 한다. 이 정수('지수')는 문자 e('가수')앞에 나타나는 값이 곱해지는 10 의 거듭제곱을 나타낸다(예를 들어, 1.5e-2 는 1.5x10-2 혹은 .015 를 나타낸다).

'16진수' 부동 소수 상수는 앞에 Ox(또는 OX)가 불는다. 그 뒤에 하나 또는 그 이상의 십진수나 16진수가 붙고 그 뒤에 p(또는 p)가붙는다. 이 뒤에 부호를 갖는 이진 지수가 붙을 수 있다. 예를 들어 Ox3p1O 은 값 3x2(10)을 의미한다. 부동소수 상수는 컴파일러에 의해 double 정확도 값으로 처리된다. 접미어 f(또는 F)를 붙여 double 대신 float 상수를 만들 수 있고 접미어 l(또는 L)을 사용하면 long double 상수가 된다.


문자 상수

작은따옴표로 둘러싸인 문지를 '문자' 상해고 한다. 하나 이상의 문자가 작은 따옴표 안에 포함되면 어떻게 처리할지는 구현에서 정의하기 나름이다. 유니버설 문자가 표준 문자 세트에 포함되지 않은 문자를 지정하는 문자 상수로 사용되기도 한다.


확장 문자열

특수 확장 문자열온 슬래시 문자(\)를 사용함으로써 인식되고 동시에 슬래시 문자에서 시작된다. 이 확장 문자열들을 다음 표에 정리하였다.

문자 의미
\a 경고음
\b 백스페이스
\f 폼 피드
\n 새 줄
\r 캐리지 리턴
\t 수평 탭
\v 수직 탭
\\ 백슬래시
\" 큰따옴표
\' 작은따옴표
\? 물음표
\nnn 8진법 문자 값
\unnn 유니버설 문자 이름
\Unnnnnnnn 유니버설 문자 이름
\xnn 16진법 문자 값


8진법 문자의 경우, 8진수를 하나부터 세 개까지 지정할 수 있다. 마지막 세 개의 확장 문자열에는 16진수가 사용된다.


와이드 문자 상수

'와이드 문자 상수'는 L'x'로 쓴다. 이 형은 표준 헤더파일 stddef.h 에 정의된 대로 wchar_t 상수이다. 와이드 문자 상수를 이용하면 일반 char형으로 표현할 수 없는 문자세트에 있는 문자를 표현할 수 있다.


문자 스트링 상수

큰따옴표로 둘러싸인 문자들이 문자 스트링 상수다. 이 스트링 안에 유효한 캐릭터가 포함될 수 있고, 앞에 나열된 확장 문자열도 사용될 수 있다. 컴파일러는 스트링의 끝에 널 문자(\n)를 자동으로 삽입해 준다.

보통, 컴파일러는 스트링의 첫 번째 문자를 가리키는 포인터를 만드는데, 그 형은 'char 의 포인터'이다. 그러나 스트링 상수가,문자배열초기화 때문에 sizeof 연산자와 함께 사용되거나 & 연산자와 함께 사용되면, 스트링 상수의 형은 'char 의 배열'이다.

문자 스트링 상수는 프로그램에 의해 수정되지 못한다.


문자스트링 연결

전처리기는 인접한 스트링 상수를 자동으로 연결한다. 이 스트링들은 공백 문자로 분리될 수 있다. 다음 세 스트링을 보자.

"a" " character "
"string"


이 세 스트링은 연결된 후에, 다음에 나오는 하나의 스트링과 동일하다.

"a character string"


다중 바이트 문자

구현에서 정의하는 연속된 문자를 사용하여 문자 스트링의 상태를 시프트하면, 다중 바이트 문자가 포함되도록 할 수 있다.


와이드 문자 스트링 상수

확장된 문자 세트에서 문자스트링 상수는 L'...' 형식으로 표현된다. 이런 상수는 'wchar_t 의 포인터' 이고, wchar_t 는 혜더파일 stddef.h 에 정의되어 있다.

문자스트링 상수객체

문자스트링 상수 객체는 문자스트링 상수 앞에 @ 문자를 붙여 만든다. 이 객체의 형은 NSConstantString 이다.

인접한 스트링 상수 객체는 서로 결합된다. 다음 스트링 상수 객체들을 보자.

@"a" @" character "
@"string"


위의 스트링 상수객체 세 개는다음에 나오는하나의 스트링 상수객체와같다.

@"a character string"


열거형 상수

열거형 값으로 선언된 식별자는 컴파일러에 의해 해당형에 대한 상수로 처리되거나 int 형으로 처리된다.


데이터 형과 선언

이 절은 기본 데이터 형,파생 데이터 형, 열거 데이터 형, typedef 를 요약 설명한다. 또한, 변수 선언의 형태도 요약한다.


선언

특정 구조체, 공용체, 열거 데이터 형, typedef 를 정의할 때, 컴파일러가 자동으로 저장 공간을 생성해 주지 않는다. 이 정의는 컴파일러에게 특정 데이터 형 에 대한 정보와 (선택적으로) 거기에 연결된 이름을 알려줄 뿐이다. 이 정의는 함수나 메서드의 안쪽 또는바깥에서 만들어질 수 있다. 정의가 안에서 만들어질 경우,함수나 메서드만 그 데이터 형의 존재를 알게 된다. 밖에서 만들어질 경우, 파일 전체에서 그 존재를 알 수있다.

정의가 만들어진 후, 그 특정 데이터 형으로 변수를 선언할 수 있다. 어느 데이터 형이 되도록 선언된 변수는 그 데이터 형을 위해 예약된 저장 공간을 받게 된다. extern 선언인 경우, 저장 공간이 생성될 수도 있고 생성되지 않을 수도 있다 (596 쪽의 '저장클래스와범위' 참조).

특정 구조체,공용체, 열거 데이터 형이 정의됨과 동시에 저장공간을 생성할 수도 있다. 그저 정의를 마치는 세미콜론 전에 변수를 나열해 주기만하면 된다.


기본 데이터 형

Objective-C 의 기본 데이터 형이 표 B.3 에 요약되어 있다. 다음 형식을 사용하여 변수를 특정 데이터 형으로 선언할 수 있다.

type name = initial_value;


초기값을 변수에 대입할 수도 있고 하지 않을 수도 있으며, 대입할 때에는 597쪽의 '변수' 부분에 요약된 규칙에 따라야 한다. 다음과같은 일반적인 형식을 사용하여 하나 이상의 변수를 동시에 선언할 수 있다.

type name = initial_value, name = initial_va1ue, ... ;


형 선언 전에, '변수' 부분에 요약된 대로 선택적으로 저장 클래스를 지정할 수 있다. 만일 저장 클래스가 지정되고 변수의 형이 int 라면, int 는 생략할 수 있다. 예를 들어 다음 코드는 counter 가 static int 형 변수가 되도록 선언한다.

static counter;


의미
int 정수 값, 즉, 소수점을 포함하지 않는 값을 의미한다. 최소 32비트의 정확도를 보장한다.
short int 정확도가 줄어든 정수 값. 특정 시스템에서는 int에 비해 절반의 메모리를 차지한다. 최소 16비트의 정확도를 보장한다.
long int 확장된 정확도를 갖는 정수 값. 최소 32비트의 정확도를 보장한다.
long long int 추가로 확장된 정확도를 갖는 정수 값. 최소 64비트의 정확도를 보장한다.
unsigned int 양의 정수 값. int의 두 배만큼 큰, 양의 정수 값을 저장할 수 있다. 최소 32비트의 정확도를 보장한다.
float 부동소수점 값. 소수점을 포함하는 값이다. 최소 6자리의 정확도를 보장한다.
double 확장된 정확도의 부동소수점 값. 최소 10자리의 정확도를 보장한다.
long double 추가로 확장된 정확도를 갖는 부동소수점 값. 최소 10자리의 정확도를 보장한다.
char 하나의 문자 값. 몇몇 시스템에서는 표현식에서 사용되면 부호 확장이 일어날 수도 있다.
unsigned char 정수 진급의 결과로 부호가 확장되지 않는다는 것만 제외하면 char와 동일하다.
signed char 정수 진급의 결과로 부호가 확장된다는 것만 제외하면 char와 동일하다.
_Bool 불리언 형, 값 0이나 1을 담기에 충분히 크다.
float _Complex 복소수
double _Complex 확장된 정확도를 갖는 복소수
long double _Complex 추가로 확장된 정확도의 복소수
void 형 없음. 함수나 메서드가 값을 반환하지 않을 때 사용하며 값을 반환하는데, 표현식에서 그 결과 값을 무시하는 것처럼 사용한다. 일반 포인터 형(void *)으로도 사용된다.
표 B.3 기본 데이터 형의 요약


signed 수식어가 short int 형, int 형, long int 형, long long int 형 앞에 붙을 수 있음에 주의하자. 이런 형들이 기본적으로 부호를 가지므로 이 수식어를 불이더라도 효과는 없다.

_Complex와 _Imaginary 데이터 형은 복소수와 허수를 선언한다. 그리고 이 형들에 대한 연산을 지원하는 라이브러리의 함수와함께 사용하면 복소수와 허수를 다룰 수 있다. 보통 complex.h 파일을 프로그램에 포함하는데, 이 파일은 복소수와 허수를 다루는 데 사용하는 매크로를 정의하고 함수를 선언한다. 예를 들어, double_Complex 형 변수 c1 을 다음과 같이 선언하고 초기값으로 5 + 1O.5i 로 설정해줄 수 있다.

double_Complex c1 = 5 + 10.5 * I;


이제 creal, cimag 같은 라이브러리 루틴을 사용하여 c1 의 실수부와 허수부를 각각 뽑아낼 수 있다.

구현에서 _Complex 형과 _Imaginary 형을 지원해야 하는 것은 아니며, 하나는 지원하지만 다른 하나는 지원하지 않을 수도 있다.


파생 데이터 형

'파생된' 데이터 형은 하나 이상의 기본 데이터 형에서 만들어진 것이다. 파생된 데이터 형에는 배열, 구조체, 공용체, 포인터(객체 포함)가 있다. 지정된 형의 값을 반환하는 함수나 메서드도 파생된 데이터 형으로 여겨진다. 함수와 메서드를 제외한 나머지가 다음에 요약되어 있다. 함수와 메서드는 각각 '함수' 부분(599쪽)과 '클래스' 부분(602쪽)에서 다룬다.


배열
일차원 배열

배열은 어느 기본 데이터 형이나 파생된 데이터 형을 담도록 정의될 수 있다. 함수의 배열은 허용되지 않는다(함수 포인터의 배열은 허용된다).

배열 선언의 기본 형식은 다음과 같다.

type name[n] = { initExpression, initExpression, .. };


n 은 배열 name 에 포함되는 원소 개수를 결정하는데, 초기값 목록을 지정하면 생략할 수 있다. 그런 경우, 배열의 크기는 나열된 초기값의 개수에 의해 결정되고, 지정된 초기화를 사용하는 경우 참조된 원소의 인덱스 중 가장 큰 것에 의해 결정된다.

만일 전역 배열이 정의되고 있다면 각 초기값은 상수 표현식이어야 만한다. 초기화 목록의 값이 배열의 원소 수보다 적을 수 있다. 이 경우, 그 수만큼의 원소만 초기화되고 나머지 원소들은 0 으로 설정된다.

배열 초기화의 특별한 경우로 문자 배열이 있는데, 문자 스트링 상수로 초기화할 수 있다. 예를 들어, 다음 코드는 today 가 문자 배열이 되도록 선언한다.

char today[] = "Monday";


이 배열은문자 'M', 'o', 'n', 'd', 'd', 'y', '\0' 으로 초기화 된다.

문자배열의 크기를 명시적으로 지정하고, 종료하는 널 문자를 위한 자리를 만들어 주지 않으면, 컴파일러가 배열의 끝에 널을 입력하지 않는다.

char today[6] = "Monday";


이 코드는 today 를 여섯 문자크기의 배열로 선언하고 그 원소의 값을각각 'M','o', 'n', 'd', 'a', 'y' 로 설정한다.

원소 번호를 대괄호로 지정해 주면 지정된 배열 원소를 순서에 상관없이 초기화할 수 있다. 다음 코드를 보자.

int x = 1233;
int a[] = { [9] = x + 1, [2] = 3, [1] = 2, [0] = 1 };


이 코드는 (배열에서 가장 높은 인덱스에 따라) 10 개 원소를 가지는 배열 a 를 정의하고 마지막 원소를 x + 1 의 값(1234)로 초기화한 후, 첫 번째 원소 세 개를 1, 2, 3 으로 설정한다.


크기가 변하는 배열

함수,메서드, 블록 내에서 변수 크기를 포함하는 표현식으로 배열 크기를 지정할수 있다. 이 경우, 배열 크기는 런타임 시에 결정된다. 예를 들어, 다음 함수는 자동(automatic) 배열 valArray 를 원소 n 의 크기로 정의한다.

int makeVals (int n)
{
    int valArray[n];
    ...
}


여기서 n 은 런타임 시에 평가되고 함수 호출마다 다를수있다. 크기아 변할수 있는 배열은 초기화할 수 없다.


다차원 배열

다차원 배열을 선언하는 일반적인 형식은 다음과 같다.

type name[d1][d2] ... [dn]=initializationList;


이 배열 name은 지정한 형의 원소 d1xd2x...xdn 개를 담고 있도록 정의된다. 예를 들어, 다음 코드는 200(즉, 5x2x20)개의 정수를 담고 있는 3차원 배열 three_d 를 정의한다.

int three_d [5][2][20]


각 차원의 원하는 첨자를 대괄호 안에 지정해 주는 것으로 다차원 배열의 특정 원소를 참조할 수 있다. 그 예로, 다음 명령문은 배 열 three_d 의 지정한 원소에 100 을 저장한다.

three_d [4][0][15] = 100;


다차원 배열은 일차원 배열과 마찬가지로 초기화할 수 있다. 중첩된 중괄호로 사용하여 배열내 원소의 값을 대입하는 것을 조절할 수 있다.

다음 코드는 matrix 를 네 개의 행과 세 개의 열을 담는 이차원 배열로 선언한다.

int matrix[4][3] =
        { {1, 2, 3 },
        { 4, 5, 6 },
        { 7, 8, 9 } };


matrix 의 첫행의 원소는값 1, 2, 3 으로 각각 설정되었고, 두 번째 행은 4, 5, 6 으로 설정 되었으며, 세 번째 행은 7, 8, 9 로 설정되었다. 네 번째 행의 경우값이 따로 설정되지 않았으므로 0 으로 설정되었다. 다음선언은 matrix 를 위 선언과 동일한 값으로 초기화한다.

int matrix[4][3] =
        { 1, 2, 3, 4, 5, 6, 7, 8, 9 };


그 이유는 다차원 배열의 원소는 '차원순'으로 초기화 되기 때문이다.즉, 가장 좌측 차원에서 우측 차원으로 초기화된다.

다음선언은 matrix 의 첫 행의 첫 원소를 1 로 설정하고 두번째 행의 첫 번째 원소를 4 로 설정하고 세 번째 행의 첫 번째 원소를 7 로 설정한다. 나머지 원소들은 기본값 0 으로 설정된다.

int matrix[4][3] =
        { { 1 },
        { 4 },
        { 7 } };


마지막으로 다음 선언은 martix 에서 지정한 원소를 지정한 값으로 초기화해 준다.

int matrix[4][3] = { [0][0] = 1, [1][1] = 5, [2][2] = 9 };
구조체
일반 형식
struct name
{
    memberDeclaration
    memberDeclaration
    ...
} variableList;


name 구조체는 각 numberDeclaration 에서 지정한 멤버들을 포함하도록 정의되었다. 각 선언은 형 지시자 하나와, 그 뒤를 따르는 멤버 이름 목록으로 구성되며, 이 목록에는 하나 또는 그 이상의 멤버 이름이 들어 있다.

변수는구조체가 정의될 때, 종료하는 세미콜론 전에 나열하여 선언할수 있고, 혹은 다음 형태로 나중에 선언할 수도 있다.

struct name variableList;


구조체가 정의될 때 name 이 생략되었다면 이 형식을 사용할 수 없다. 이런 경우 그 구조체 형의 모든 변수는 구조체가 정의됨과 동시에 선언되어야만 한다.

구조체 변수의 초기화 형식은 배열과 유사하다. 대괄호 안에 초기값을 나열하여 구조체의 멤버들을 초기화할 수 있다. 만일 전역 구조체를 초기화 한다면 나열된 각 값은 상수 표현식이어야 한다.

다음 선언은 point 구조체를 정의하고 struct point 형 변수 start 를 지정된 초기값으로 정의한다.

struct point
{
    float x;
    float y;
} start = {100.0, 200.0};


다음 표기법으로 특정 멤버를 순서에 상관없이 초기화 하도록 지정할 수 있다.

.member = value


이 표기법을 다음과 같이 초기화 목록에서 사용한다.

struct point end = { .y = 500, .x = 200 };


다음 코드는 dictionary 가 1,000 개의 entry 구조체를 담도록 선언한다.

struct entry
{
    char *word;
    char *def;
} dictionary[1000] = {
    {"a", "first letter of the alphabet"},
    {"aardvark", "a burrowing African mammal"},
    {",aback", "to startle" }
};


이 가운데 처음 세 개의 원소는 지정된 문자 스트링 포인터로 초기화된다. 지정된 초기화를 사용하면 다음과 같이 작성할수도 있다.

struct entry
{
    char *word;
    char *def;
} dictionary[1000] = {
    [0].word = "a", [0].def = "first letter of the alphabet",
    [1].word = "aardvark", [1].def = "a burrowing African mammal",
    [2].word = "aback", [2].def = "to startle"
};


혹은 다음과같이 작성해도 동일하다.

struct entry
{
    char *word;
    char *def;
} dictionary[1000] = {
    { {.word = "a", .def = "first letter of the alphabet"},
    {.word = "aardvark", .def = "a burrowing African mammal"},
    {.word = "aback", .def = "to startle"}
}


다음과 같이 자동 구조체 변수를 동일한 형의 다른 구조체로 초기화할 수도 있다.

struct date tomorrow = today;


이 코드는 date 구조체 변수 tomorrow 를 선언하고 이미 선언된 date 구조체 변수 today 의 내용으로 초기화한다.

다음 형식의 memberDeclaration 은 구조체 내에서 n 비트 만큼의 너비를 갖는 field 를 정의한다.

type fieldName : n


여기서 n 은 정수 값이다. 필드는 시스템 환경에 따라 왼쪽에서 오른쪽으로 혹은 오른쪽에서 왼쪽으로 압축(pack)될 수 있다. 만일 fieldName 이 생략되었다면, 지정된 비트 수가 예약되기는 하지만 참조될 수는 없다. fieldName 생략되고 n 도 0 이라면, 그 뒤에 따르는 필드는 다음 저장 unit 경계로 정렬된다. 여기서 unit 은 구현의 정의에 따른다. 필드의 형은 int 형이나 signed int 형 또는 unsigned int 형일 수 있다. int 필드가 부호를 갖느냐 갖지 않느냐는 구현에 정의된 것에 따른다. 주소 연산자(&)는 필드에 적용될수 없으며, 필드의 배열은 정의할 수 없다.


공용체
일반 형식
union name
{
    memberDeclaration
    memberDeclaration
    ...
} variableList;


이 코드는 각 memberDeclaration 으로 지정된 멤버를 갖는, name 이라는 공용체를 정의한다. 이 공용체의 각 멤버는 동일한 저장 공간을 공유하고, 컴파일러는 공용체의 가장 큰 멤버를 담기에 충분한 공간을 예약해 둔다.

공용체가 정의될때 변수를 선언할 수도 있고, 다음 표기법으로 정의 이후에 선언할 수도 있다.

union name variableList;


이렇게 선언하려면 공용체가 정의될 때 이름을 지정해야 한다.

공용체에서 받은 값이 공용체에 들어 있는 마지막 값과 일치하도록 하는 것은 프로그래머의 책임이다. 공용체의 첫 번째 멤버는 함께 넣은 초기값으로 초기화할 수 있는데, 전역 공용체 변수의 경우, 중괄호안에 상수표현식을 입력해야만 한다.

union shared
{
    long long int 1;
    long int w[2];
} swap = { 0xffffffff };


다음과 같이 멤버 이름을 지정하여 다른 멤버를 초기화할 수도 있다.

union shared swap2 = {.w(0) = 0x0, .w(1) = 0xffffffff};


이 코드는 공용체 변수 swap 을 선언하고 멤버 1 을 16진수 ffffffff 로 설정한다. 자동 공용체 변수는 다음과 같이 동일한 형의 공용체로 초기화할 수도 있다.

union shared swap2 = swap;


포인터

포인터 변수선언의 기본형식은 다음과 같다.

type *name;


식별자 name 은 type 의 포인터 형으로 선언되었는데, 여기서 type 은 기본 데이터형 일 수도 있고, 파생된 데이터 형일 수도 있다. 다음 예를보자.

int *ip;

struct entry *ep;

Fraction *myFract;


이 코드는 ip 가 int 의 포인터가 되도록 선언한다. 또 ep가 entry 구조체의 포인터가 되도록 선언한다.

만일 Fraction 이라는 클래스가 정의되었다면,이 코드는 myFract 가 Fraction 형의 객체가 되도록 선언한다. 다시 말해서, 객체의 인스턴스가 생성되고 변수에 대입된후, 그 객체의 데이터 구조를 가리키는 포인터를 담는데 myFract 가 사용된다는 뜻이다.

배열의 원소를 가리키는 포인터는 배열에 포함된 원소의 형을 가리키도록 선언된다. 예를들어, 앞의 ip 선언은 정수의 배열을 가리키는 포인터로 사용될 수도 있다.

더 고급스러운 포인터 선언도 가능하다. 다음을보자.

char *tp[100];


이 선언은 문자포인터 100 개 의 배열 tp 를 선언한다.

struct entry (*fnPtr) (int);


이 선언은 fnPrt 를, 하나의 int 를 인수로 받고 entry 구조체를 반환하는 함수를 가리키는 포인터로 선언한다.

값이 0 인 상수 표현식과 포인터를 비교하면 그 포인터가 널 인지 확인할 수 있다. 내부적으로 널 포인터를 0 이 아닌 다른 값으로 표시하도록 구현에서 결정할 수 있다. 그러나 이렇게 내부 구현에서 널 포인터를 표시한 값과 상수값 0 을 비교하면 동일한 값으로 평가되어야만 한다.

포인터가 정수로, 정수가 포인터로 변환되는 방식은, 포인터를 담는 데 필요한 정수의 크기가 머신에 따라 다르듯이 머신마다 다르다.

'void의 포인터' 형은 일반포인터 형이다. 언어는 void 포인터에 어느형의 포인터든 대입할 수 있고, 값을 다시 받아올 때 포인터 값에 변화가 없다는 것을 보장한다. id 형은 일반 객체 포인터이다. 어느 클래스의 객체든 id 변수에 대입될 수 있고, id 변수에서 어느형의 객체든 꺼내올 수 있다.

이런 특수한 두 경우를 제외하고, 다른 형의 포인터를 대입하는 것은 허용되지 않는다. 만일 이를 시도한다면 컴파일러가 경고 메시지를 표시할 것이다.


열거 데이터 형
일반 형식
enum name { enum_1, enum_2, .. } variableList;


열거형 name 은 열거형 값 emun_1, enum_2, ... 로 정의되는데, 그 값 각각이 식별자이거나, 등호와 상수 표현식이 뒤따르는 식별자로 이루어진다. variableList 는 enum name 형으로 선언되는 변수 목록인데 선택사항이다(또한 초기값을 부여해줄 수도 있다).

컴파일러는 열거 식별자 0 부터 정수 값을 순차적으로 대입한다. 만일 식별자에 등호(=)와 상수 표현식이 뒤따른다면, 그 표현식의 값이 해당 식별자에 대입된다. 그 뒤의 식별자에는 해당 상수 표현식의 값에 1 씩 더해져 대입된다. 컴파일러는 열거 식별자를 정수 상수값으로 처리한다.

만일 이미 정의된(혹은 이름을 붙인) 열거형으로 변수를 선언하고 싶다면 다음 구조를 사용한다.

enum name variableList;


특정 열거형으로 선언된 변수는 동일한 데이터 형의 값만 대입할 수 있다. 다만, 그렇게 하지 않더라도 컴파일러가 오류 메시지를 내지 않는다.


typedef

typedef 명령문은 기본 데이터 형이나 파생된 데이터 형에 새로운 이름을 부여하는 데 사용된다. typedef 가 새로운 형을 선언하는 것이 아니고 그저 기존 형에 새로운 이름을 부여하는 것 뿐이다. 따라서, 컴파일러에게 새 이름으로 선언된 변수는 그 이름에 연결된 기존의 형으로 선언된 변수와 동일하게 여겨진다.

typedef 정의를 만들 때도 일반 변수 선언을 하는 것과 마찬가지 방식으로 진행한다. 보통 변수 이름이 나오는 곳에 새 형 이름을 넣는다. 마지막으로, 모든 것 앞에 typedef 키워드를 써두면 된다.

다음 예를 보자.

typedef struct
{
    float x;
    float y;
} POINT;


이 코드는 POINT 라는 이름을, 두 개의 부동소수 멤버 x 와 y 를 갖는 구조체에 연결한다. 이제 다음과 같이 POINT 형 변수를 선언할 수 있다.

POINT origin = { 0.0, 0.0 };


형 수식어 : const, volatile, restrict

키워드 const를 형 선언 앞에 적어주면, 컴파일러에게 그 값이 수정할 수 없다고 지시하게된다. 다음 선언을 보자.

const int x5 = 100;


이 선언은 x5 를 정수 상수로 선언한다(다시 말해서,프로그램이 실행되는 동안 다른 값으로 설정될 수 없다는 것이다). const 변수의 값을 바꾸려 시도하더라도 컴파일러는 경고를 표시하지 않을것이다.

volatile 수식어는 컴파일러에게 해당 값이 (보통 동적으로) 변한다는 것을 명시적으로 표시한다. volatile 변수가 표현식에 사용되면 등장하는 곳마다 그 값에 접근하게 된다.

port17을 'char 의 volatile 포인터' 형으로 선언하려면 다음과 같은 코드를 작성한다.

char *volatile port17;


restrict 키워드는 포인터 에 사용할 수 있다. 이 키워드는 컴파일러에게 최적화 하라는 힌트를 준다(변수에 사용하는 register 키워드와 유사하다). restrict 키워드는 컴파일러에게 해당 포인터가 특정 객체의 유일한 참조라고 지정해 준다. 즉, 같은 범위 내에서 다른 어떤 포인터도 그 객체를 참조하지 않을것 이라는 뜻이다.

int * restrict intPtrA;
int * restrict intPtrB;


이 코드는 컴파일러에게 intPtrA 와 intPtrB 가 정의된 범위 내에서 이 변수들이 동일한 값에 접근하지 않았다고 알려준다. (예를 들어, 배열 안과 같은 범위 내에서)정수를 가리키는 데 이것들을 사용하는 것은 상호 배타적이다.


표현식

변수명, 함수명, 메시지 표현식, 배열 이름, 상수, 함수 호출, 배열 참조, 구조체 및 공용체 참조는 모두 표현식으로 여겨진다. 이것들에서 둘 이상을 이항 연산자나 삼항 연산자로 결합해도 표현식이고, 이 표현식들에서 적절한 곳에 단항 연산자를 적용하는 것도 표현식이다. 마지막으로, 괄호로 둘러싸인 표현식도 표현식이다. void 를 제외한 다른 데이터 객체를 식별하는 형의 표현식은 'lvalue' 라고 부른다. 만일 여기에 값을 대입할 수 있다면 '수정 가능한 lvalue'라고 한다.

어떤 경우에 수정 가능한 lvalue 표현식이 필요할까? 대입 연산자의 왼쪽에 있는 표현식은 반드시 수정 가능한 lvalue 여야 한다. 단항 주소 연산자는 수정 가능한 lvalue나 함수 이름에만 적용할 수 있다. 마지막으로 증가 연산자 및 감소 연산자도 수정 가능한 lvalue 에만 적용된다.


Objective-C 연산자 요약

Objective-C 언어의 다양한 연산자를 표 B.4 에 요약하였다. 이 연산자들은 우선순위 순으로 나열되어 있으며, 우선 순위가 동일한 연산자들은 한 그룹으로 묶여 있다.

표 B.4 를 사용하는 방법의 예로 다음 표현식을 보도록 하자.

b | c & d * e


표 B.4 에서 곱셈 연산자가 다른 두 연산자보다 먼저 나타나기 때문에, 곱셈 연산자는 비트 OR 연산자와 비트 AND 연산자보다 우선순위가 높다. 또한 표에서 나타나는 순서대로 비트 AND 연산자는 비트 OR 연산자보다 우선순위가 높다. 따라서, 이 표현식은 다음과 같이 평가된다.

b | ( c & ( d * e) )


다음 표현식을 살펴보자.

b % c * d


표 B.4 에서 나머지 연산자와 곱셈 연산자는 동일한 그룹에 존재하므로 우선순위가 동일하다. 이 연산자들의 결합 방향은 왼쪽에서 오른쪽이므로 이 표현식은 다음과 같이 평가된다.

( b % c ) * d


다른 예를 살펴보자.

++a->b


이 표현식에서는 ++ 연산자보다 -> 연산자의 우선순위가 높으므로, 다음과 같이 평가된다.

연산자 설명 결합 방향
() 함수 호출
[] 배열 원소 참조나 메시지 표현식
-> 구조체 멤버 레퍼런스의 포인터 왼쪽에서 오른쪽
. 구조체 멤버 참조나 메서드 호출

- 단항 빼기
+ 단항 더하기
++ 1 증가
-- 1 감소
! 논리 부정
~ 1의 보수 오른쪽에서 왼쪽
* 포인터 참조(간접)
& 주소
sizeof 객체의 크기
(type) 형 변환

* 곱셈
/ 나눗셈 왼쪽에서 오른쪽
% 나머지

+ 덧셈 왼쪽에서 오른쪽
- 뺄셈

<< 왼쪽 시프트 왼쪽에서 오른쪽
>> 오른쪽 시프트
< 더 작다

<= 더 작거나 같다 왼쪽에서 오른쪽
> 더 크다
>= 더 크거나 같다

== 같다 왼쪽에서 오른쪽
!= 같지 않다

& 비트 AND 왼쪽에서 오른쪽

^ 비트 XOR 왼쪽에서 오른쪽

| 비트 OR 왼쪽에서 오른쪽

&& 논리 AND 왼쪽에서 오른쪽

¦¦ 논리 OR 왼쪽에서 오른쪽

?: 조건 오른쪽에서 왼쪽

= <<= >>= 대입 연산자 오른쪽에서 왼쪽

, 콤마 연산자 오른쪽에서 왼쪽
표 B.4 Objective-C 연산자 요약


++(a->b)


마지막으로,다음 예를 보자.

a = b = 0;


대입 연산자가 오른쪽에서 왼쪽으로 결합되기 때문에, 명령문은 다음과 같이 평가된다.

a = (b = 0);


그 결과, a와 b의 값이 모두 0 으로 설정될 것이다. 다음 표현식의 경우에는 어떨까?

x[i] + ++i


컴파일러가 덧셈 연산자의 왼쪽을 먼저 연산할지, 아니면 오른쪽을 먼저 연산할지 정해지지 않았다. 어느 쪽이 먼저 연산되느냐에 따라 x[i] 가 평가되기 전에 i 의 값이 달라지므로 평가순서가 결과에 영향을 미치게 된다.

다음표현식도 평가순서가 정해지지 않은 예이다.

x[i] = ++i


이 경우, i의 값이 x 의 인덱스로 사용되기 전에 증가할지, 아니면 사용된후에 증가할지 정해지지 않았다.

함수와 메서드 인수의 평가 순서도 정해지지 않았다. 따라서, 다음 함수 호출이나, 그다음 메시지 표현식에서

f (i, ++i);
[myFract setTo: i over: ++i];


i의값이 먼저 증가되면 함수나 메서드에 두 인수로 동일한 값이 넘겨질 수도 있다.

Objective-C 언어에서 && 와 || 는 왼쪽에서 오른쪽으로 평가된다. && 의 경우, 첫 번째 피연산자가 0 이라면 두 번째 연산자는 평가되지 않는다. || 의 경우, 첫 번째 피연산자가 0 이 아니라면 두번째 피연산자는 평가되지 않을 것이다. 다음 표현식에서 이를 활용하고 있다.

if ( dataFlag 11 [myData checkData] )


이 경우, dataFlag 가 0 인 경우에만 checkData 가 호출된다. 다른 예로, 원소 n 개짜리로 정의한 배열객체 a 가 있다면

if ( index >= 0 && index < n && ([a objectAtIndex: index) == 0))
    ...


index 가 그 배열에서 유효한 첨자일 경우에만 배열에 포함된 원소를 참조한다.

상수 표현식

'상수' 표현식은 각 항이 상수값인 표현식이다. 상수 표현식은 다음 경우에 필요하다.

  1. switch 문에서 case 뒤의 값
  2. 배열 크기를 지정할때
  3. 열거형 식별자의 값을 대입할 때
  4. 구조체 정의에서 비트 필드 크기를 지정할 때
  5. 외부 변수나 정적 변수에 초기값을 대입할 때
  6. 전역 변수에 초기값을 지정할 때
  7. #if 전처리 명령문 안에서 #if 에 뒤따르는 표현식


처음 네 경우에 상수 표현식은 정수 상수, 문자 상수, 열거 상수, sizeof 표현식 으로만 구성될 수 있다. 연산자로는 산술 연산자, 비트 연산자, 비교 연산자, 조건 표현식 연산자, 형 변환 연산자만 사용할 수 있다.

다섯 번째와 여섯 번째 경우, 이미 말한규칙 외에도주소 연산자를사용할수 있다. 그러나 외부 변수나 정적 변수 혹은 함수에만 사용할수 있다. 따라서, 다음 표현식은 x 가 외부변수 이거나 정적 변수인 경우, 유효한 상수 표현식이다.

&x + 10


다음 표현식은 a 가 외부배열 이거나 정적배열인 경우에 유효한 상수 표현식이다.

&a[10] - 5


마지막으로 &[0] 가 표현식 a 와 동일하므로, 다음 표현식도 유효한 상수 표현식이 된다.

a + sizeof (char) * 100


상수 표현식이 필요한 마지막 경우(#if 뒤)에, sizeof 연산자, 열거 상수, 형 변환 연산자를 사용할 수 없다는 것을 제외하면 규칙은 처음 네 경우와 동일하다. 그러나 defined 연산자는 사용할 수 있다(623쪽의 '#if 지시어' 참조).


산술 연산자

a, b 는 void 가 아닌 기본 데이터 형의 표현식이고, i, j 가 정수 데이터 형의 표현식일 때,

-a a의 값을 부정한다.
+a a의 값을 반환한다.
a + b a와 b를 더한다.
a - b a에서 b를 뺀다.
a * b a에 b를 곱한다.
a / b a를 b로 나눈다.
i % j i를 j로 나눈 나머지를 반환한다.


각 표현식에서 피연산자에 통상적인 산술 변환이 수행된다(595쪽의 '기본 데이터 형의 변환' 참조). 만일, a 에 부호가 붙지 않는다면, -a 는 정수 진급이 먼저 적용된후, 진급된 형의 가장 큰값에서 그 수를 뺀다음, 그 결과에 1 을 더한다.

만일 두 개의 정수 값이 나뉜다면, 결과는 잘린다. 두 피연산자 중 하나가 음수라면 생략의 방향은 정의되어 있지 않다(이 말은, -3/2 이 어떤 시스템에서는 -1 이되고 다른 시스템에서는 -2 가 될 수 있다는 것을 의미한다). 그 외의 경우에는 언제나 0 쪽으로 생략된다(3/2는 언제나 1 이다). 포인터의 산술연산은 '포인터의 기본 연산' 부분(592쪽)에 요약되어 있다.


논리 연산자

a, b 가 void 를 제외한 기본데이터 형의 표현식 이거나 둘 모두 포인터일 때,

a && b a와 b 모두 0이 아니라면 이 값은 1이고, 그 외에는 0이다
(a가 0이 아닌 경우에만 b가 평가된다).
a ¦¦ b a와 b 중 하나가 0이 아니면 이 값은 1이고, 그 외에는 0이다
(a가 0인 경우에만 b가 평가된다).
!a a가 0이면 이 값은 1이고, 그 외에는 0이다.


a 와 b 에 통상적인 산술 변환이 적용된다(595쪽의 '기본 데이터 형의 변환' 참조). 모든 경우에 결과는 int 형이다.


비교 연산자

a , b 가 void 를 제외한 기본데이터 형의 표현식 이거나 둘 모두 포인터일 때,

a < b a가 b보다 작다면 이 값은 1이고, 그 외에는 0이다.
a <= b a가 b보다 작거나 같다면 이 값은 1 이고, 그 외에는 0이다.
a > b a가 b보다 크다면 이 값은 1이고, 그 외에는 0이다.
a >= b a가 b보다 크거나 같다면 이 값은 1이고, 그 외에는 0이다.
a == b a와 b가 같다면 이 값은 1이고, 그 외에는 0이다.
a != b a와 b가 같지 않다면 이 값은 1이고, 그 외에는 0이다.


a 와 b 에는 통상적인 산술변환이 수행된다(595쪽의 '기본데이터 형의 변환' 참조). 처음 네 개의 관계 테스트는 두 포인터가 동일한 배열을 가리키고 있거나 동일한 구조체나 공용체의 멤버를 가리키고 있는 경우에만 의미를 갖는다. 결과는 모두 int 형이다.


비트 연산자

i, j, n이 정수 데이터 형의 표현식일 때,

i & j i와 j의 비트 AND 연산을 수행한다.
i ¦ j i와 j의 비트 OR 연산을 수행한다.
i ^ j i와 j의 비트 XOR 연산을 수행한다.
~i i의 1의 보수를 반환한다.
i << n i 를 왼쪽으로 n비트만큼 시프트한다.
i >> n i를 오른쪽으로 n비트만큼 시프트한다.


정수 진급이 각 항에 수행되는 << 와 >> 를 제외하면, 통상적인 산술 변환이 각 항에 수행된다(595쪽의 '기본 데이터 형의 변환' 참조). 시프트 카운트가 음수거나, 시프트되는 객체에 포함된 비트 수보다 크거나 같다면, 결과가 어떻게 될지는 정해져 있지 않다. 어떤 시스렘에서는 우측 시프트가 산술 시프트(부호를 갖는 채움)이고 다른 시스템에서는 논리 시프트(0 으로 채움)이다. 시프트 연산의 결과 형은 좌측 피연산자가 진급된 형이다.

증가 및 감소 연산자

l 이 const 가 아닌 수정 가능한 lvalue 표현식일 때,

++l l을 증가시킨 후, 그 값을 표현식의 값으로 사용한다.
l++ l을 표현식의 값으로 사용하고 그 값을 증가시킨다.
--l l을 감소시킨 후, 그 값을 표현식의 값으로 사용한다.
l-- l을 표현식의 값으로 사용하고 그 값을 감소시킨다.


592쪽의 '포인터의 기본 연산' 부분에서 포인터에 대한 이들 연산을 설명한다.


대입 연산자

l이 const 가 아닌 수정 가능한 lvalue 표현식이고,

op 가 대입 연산자로 사용가능한 연산자이고(표 B.4 참조),

a가 표현식일 때,

l = a a의 값을 l에 저장한다.
l op= a op를 l 과 a에 적용하여 결과를 l 에 저장한다.


첫 번째 표현식에서 a 가 (void를 제외한) 기본 데이터 형 가운데 하나라면, l 의 형과 일치하도록 변환된다. 만일 l 이 포인터라면, a 도 l 과 동일한 형의 포인터가 되거나 void 포인터 또는 널 포인터가 되어야 한다.

만일 l 이 void 포인터라면, a 는 어느 포인터 형이든지 상관없다. 두 번째 표현식은 l = l op (a) 처럼 처리되는데, l 은 단 한 번만 평가된다(x[i++] += 10을 생각해 보라).

조건 연산자

a, b, c 가 표현식일 때,

a ? b : c a가 0이 아닌 경우 그 값은 b이고, 그 외에는 c이다. 표현식 b와 c 가운데 하나만 평가된다.


표현식 b 와 c 는 동일한 데이터 형이어야 한다. 만일 동일하지 않지만, 둘 다 산술 데이터 형이라면 산술 변환이 적용되어 형이 일치된다. 만일 하나가 포인터고 다른 하나는 0 이라면 뒤쪽 표현식은 앞쪽 표현식과 동일한 형의 널 포인터로 처리된다. 하나가 void 포인터고 다른 하나는 다른 형의 포인터라면, 뒤쪽 표현식과 결과 형은 모두 void 포인터로 전환된다.


형 변환 연산자

type 이 기본 데이터 형, (emun 키워드가 앞에 불은) 열거 데이터 형, typedef 로 선언된 형, 혹은 파생된 데이터 형의 이름이고,

a 가 표현식일 때,

( type ) a a를 지정한 형으로 변환한다.


한가지 주의할 것이 있다. 메서드 선언이나 정의에서 괄호로 둘러싸인 형을 사용하는 것은 형 변환 연산자의 예가 아니다.


sizeof 연산자

type 이 기본 데이터 형, (enum 키워드가 앞에 붙은) 열거 데이터 형, typedef 로 선언된 형,혹은 파생된 데이터 형의 이름이고,

a 가 표현식 일때,

sizeof ( type ) 지정한 형의 값을 담는 데 필요한 바이트 수를 값으로 반환한다.
sizeof a a의 평가 결과를 담는 데 필요한 바이트 수를 값으로 반환한다.


만일 type 이 char 형이라면 결과는 1이다. a가 (명시적으로나 초기화 시에 암묵적으로) 크기가 정해진 배열이고 a 가 공식 매개변수나 크기가 정해지지 않은 extern 배열이 아니라면, sizeof 는 a 안의 원소들을 저장하는 데 필요한 바이트 수를 반환한다.

만일 a가 클래스 이름이라면, sizeof(a)는 a 의 인스턴스 하나를 담는 데 필요한 데이터 구조의 크기를 반환한다.

size_t 는 sizeof 연산자로 생성한 정수 형인데, 표준 헤더파일 stddef.h 에 정의되어 있다.

만일 a 가 가변 길이 배열이라면, 이 표현식은 런타임 시에 평가된다. 그렇지 않다면 컴파일 시에 평가되고 상수 표현식에서 사용될 수 있다(586쪽의 '상수 표현식' 참조).


콤마 연산자

a, b 가 표현식일 때,

a, b a를 평가하고서 b를 평가한다. 이 표현식의 형과 값은 b의 형과 값이다.


배열의 기본연산

a 가 원소 n 개 짜리 배열로 선언되어 있고,

i 가 정수 데이터 형의 표현식이고,

v 가 표현식일 때,

a[0] a의 첫 번째 원소를 참조한다.
a[n-1] a의 마지막 원소를 참조한다.
a[i] a의 i번째 원소를 참조한다.
a[i] = v v의 값을 a[i]에 저장한다.


각 경우 결과의 형은 a 에 포함된 원소의 형과 같다. '포인터의 기본 연산' 부분에서 배열과 포인터의 연산을 요약하고 있다.


구조체의 기본연산

objc2_notice_01
이 규칙은 공용체에도 적용된다

x가 struct S 형의 수정 가능한 lvalue 표현식이고,

y가 struct S 형의 표현식이고,

mO] struct S 의 멤버 중 하나의 이름이고,

obj 가 객체이고,

M 메서드 이고,

v 가 표현식 일때,

x struct S형의 구조체 전체를 참조한다.
y.m 구조체 y의 멤버 m을 참조한다. m이 선언된 형이다.
x.m = v v를 x의 멤버 m에 대입한다. v의 형은 멤버 m의 선언된 형이다.
x = y y를 x에 대입하고, struct S형이다.
f(x) 구조체 y의 내용을 인수로 건네며 함수 f호출한다(f 안에서 공식 매개변수는 struct S형으로 선언되어야 한다).
[obj M: y] 객체 obj의 메서드 M을 호출하며 구조체 y의 내용을 인수로 넘긴다(메서드 내에서 매개변수는struct S형으로 선언되어야 한다).
return y: 구조체 y를 반환한다(함수나 메서드의 반환 형은 struct S로 선언되어야 한다.)


포인터의 기본연산

x 가 t 형의 lvalue 표현식이고,

pt가 't의포인터' 형의 수정 가능한 lvalue 표현식이고,

v 가 표현식일 때,

&x 't의 포인터' 형으로 x를 가리키는 포인터를 생성한다.
pt = &x 't의 포인터' 형으로 pt가 x를 가리키도록 설정한다.
pt = 0 pt에 널 포인터를 대입한다.
pt == 0 pt가 널인지 확인한다.
*pt pt가 가리키는 값을 참조하고 t형을 갖는다.
*pt = v pt가 가리키는 위치에 v의 값을 저장하고 t형을 갖는다.


배열의 포인터

a 가 t 형의 원소를 갖는 배열이고,

pa1 은 't의 포인터' 형의 수정 가능한 lvalue 표현식으로, a 의 원소를 가리키고

pa2 은 't의 포인터' 형의 lvalue 표현식으로, a 의 원소를 가리키거나 a 의 마지막 원소를 하나 지나친 것을 가리키고,

v 는 표현식이고,

n 이 정수 표현식 일때,

a, &a, &a[0] 각각 첫 번째 원소를 가리키는 포인터를 생성한다.
&a[n] a의 n번째 원소를 가리키는 포인터를 't의 포인터' 형으로 생성한다.
*pa1 pa1이 가리키는 a의 원소를 t형으로 참조한다.
*pa1 = v pa1이 가리키는 원소에 v의 값을 t형으로 저장한다.
++pa1 a에 포함된 원소의 형과 상관없이 pa1이 a의 다음 원소를 가리키도록 설정한다. pa1은 't의 포인터' 형이다.
--pa1 a에 포함된 원소의 형과 상관없이 pa1이 a의 이전 원소를 가리키도록 설정한다. pa1은 't의 포인터' 형이다.
*++pa1 pa1을 증가시키고 pa1이 가리키는 a 안의 값을 t형으로 참조한다.
*pa1++ pa1이 가리키는 a 안의 값을 참조하고 pa1을 증가시킨다. pa1은 t형이다.
pa1 + n 현재 pa1이 가리키는 a의 원소에서 n번째 다음 원소를 가리키는 포인터를 만든다. pa1은 't의 포인터' 형이다.
pa1 - n 현재 pa1이 가리키는 a의 원소에서 n번째 앞의 원소를 가리키는 포인터를 만든다. pa1은 't의 포인터' 형이다.
*(pa1 + n) = v v의 값을 pa1 + n이 가리키는 원소에 t형으로 저장한다.
pa1 < pa2 pa1이 a에서 pa2보다 더 앞의 원소를 가리키는지 확인한다. int형을 갖는다(두 포인터의 비교에 어느 비교 연산자든 사용할 수 있다).
pa2 - pa1 (pa2가 a에서 pa1보다 더 뒤의 원소를 가리킨다고 할 때) a에서 포인터 pa2과 pa1 사이에 존재하는 원소의 수를 반환한다. 정수 형을 갖는다.
a + n a의 n번째 원소의 포인터를 만들고 't의 포인터' 형을 갖는다. 표현식 &a[n];과 동일하다.
*(a + n) a의 n번째 원소를 참조하고 t형을 갖는다. a[n]과 동일하다.


두 포인터 간에 뺄셈 연산을 수행하면 생성되는 정수의 실제 형은 표준 헤더파일 stddef.h에 정의된 ptrdiff_t 로 지정된다.


구조체의 포인터

x가 struct S형의 lvalue 표현식이고,

ps 가 'struct S의 포인터' 형의 수정 가능한 lvalue 표현식이고,

m 이 구조체 s 의 멤버 이름이고 t 형이고,

v 가 표현식 일때,

&x x의 포인터를 만들고 'struct S의 포인터' 형이다.
ps = &x ps가 x를 가리키도록 하고 'struct S의 포인터' 형이다.
ps->m ps가 가리키는 구조체의 멤버 m을 참조하고 t형이다.
(*ps).m 마찬가지로 멤버 m을 참조하고, ps->m과 동일하다.
ps->m = v v의 값을 ps가 가리키는 구조체의 멤버 m에 저장하고 t형이다.


복합 리터럴

'복합 리터럴'은 괄호로 둘러싸인 형 이름과 초기화 목록이다. 지정한 형의 이름없는 값을 생성하는데, 생성된 블록 내로 범위가 제한되거나, 모든 블록 밖에서 정의되는 경우 전역 범위를 갖게 된다. 모든 블록 밖의 경우, 초기화 값은 모두 상수 표현식 이어야 한다.

다음 표현식을 보자.

(struct point) {.x = 0, .y = 0}


이것은 지정한 초기값을 갖는 struct point 형 구조체를 생성하는 표현식이다. 이것을 다음과 같이 다른 struct point 구조체에 대입할수도 있다.

origin = (struct point) {.x = 0, .y = 0 };


혹은 다음과 같이 struct point 를 인수로 받는 함수나 메서드에 건네줄 수도 있다.

moveToPoint ((struct point) {.x = 0, .y = 0});


구조체 외의 다른 형도 정의할 수 있다. 예를들어, intPtr 가 int* 형이라면,

intPtr = (int [100)) {[0) = 1, [50) = 50, [99) = 99 };


이 코드는 intPtr 가 정수 100 개 크기의 배열을 가리키고 지정한 3개의 값이 초기화되도록 한다(이 코드는 프로그램 어디서든 사용 가능하다).

만일 배열 크기가 지정되지 않았다면, 초기화 목록에 의해 결정된다.

기본데이터형의 변환

Objective-C는 '통상적인 산술변환' 으로 알려진 순서에 의해 산술표현식의 피연산자들을 변환한다.

  1. 만일 피연산자 둘 중 하나가 long double 형이라면, 다른 하나도 long double 로 변환되고 결과 형도 마찬가지로 변환된다.
  2. 만일 피연산자 둘 중 하나가 double 형이라면, 다른 하나도 double 로 변환되고 결과 형도 마찬가지로 변환된다.
  3. 만일 피연산자 둘 중 하나가 float 형이라면, 다른 하나도 double 로 변환되고 결과 형도 마찬가지로 변환된다.
  4. 만일 피연산자 둘 중 하나가 _Bool, char, short int, int 비트 필드이거나 열거 데이터 형이라면, int 가 그 값을 모두 표현할 수 있는 경우에 한해 int 로 변환된다. 그렇지 않은 경우 unsigned int 로 변환된다. 두개의 피연산자의 형이 같다면, 결과형도 이와 같다.
  5. 만일 연산자 둘 중 하나가 부호를 갖거나 둘 모두 부호를 갖지 않는다면, 작은 정수형이 큰 정수형으로 변환되고, 결과형도 큰 정수형이 된다.
  6. 만일 부호를 갖지 않는 피연산자가 부호를 갖는 피연산자와 크기가 같거나 그보다 크다면, 부호를 갖는 피연산자는 부호를 갖지 않는 피연산자의 형으로 변환되고 결과 형도 이 형이 된다.
  7. 만일 부호를 갖는 피연산자가 부호를 갖지 않는 연산자의 모든 값을 표시할 수 있다면, 후자가 전자의 형으로 변환되고, 결과 형도 이와 같다.
  8. 만일 이 단계까지 왔다면, 두 연산자는 모두 부호를 갖는 형에 해당하는, 부호를 갖지 않는 형으로 변환된다.


4단계는 공식적으로 '정수 진급' 으로 더 잘알려져 있다.

피연산자의 변환은 대부분의 경우에 설명한 대로 이루어지지만, 다음을 주의해야한다.

  1. char 의 int 로의 변환은, char 가 unsigned 로 선언되지 않았다면, 시스템에 따라 부호 확장이 발생할 수도 있다.
  2. 부호를 갖는 정수를 그보다 긴 정수로 변환하면, 부호가 왼쪽으로 확장된다. 부호를 갖지않는 정수를 그보다 긴 정수로 변환하면, 왼쪽으로 0 이 채워진다.
  3. 어느 값이든 _Bool 로 변환하면, 값이 0 인 경우 0 이 되고 그 외의 경우는 모두 1 이 된다.
  4. 어떤 정수를 그보다 짧은 정수로 변환하면, 정수의 왼쪽값이 잘린다.
  5. 부동소수값을 정수로 변환하면, 소수점 이하 값이 잘린다. 정수가 변환된 부동소수값을 담기에 충분히 크지 않다면, 음의 부동소수 값을 부호를 갖지 않는 정수로 변환하는 것과 마찬가지로, 결과는 정의되어 있지 않다.
  6. 부동소수 값을 그보다 짧은 값으로 변환하면, 값이 잘리기 전에 반올림 될 수도 있다.


저장 클래스와 범위

'저장 클래스(storage class)'는 변수의 경우, 컴파일러에 의해 메모리가 할당되는 방식을 말하고, 특정 함수나 메서드 정의의 경우, 그 범위를 말한다. 저장 클래스는 auto, static, extern, register 이다. 저장 클래스는 선언 시에 생략될 수 있고, 다음에 설명할 기본 저장 클래스가 대입될 것이다.

'범위' 라는 용어는 프로그램에서 특정 식별자의 의미가 유효한 범위를 나타낸다. 모든 함수, 메서드, 명령문 블록(여기서는 BLOCK으로 부른다) 바깥에서 정의된 식별자는 그 파일 어디서든 참조할 수 있다. BLOCK 내에서 정의된 식별자는 그 BLOCK 에서만 유효하고 그 밖에서 정의된 식별자를 로컬에서 재정의할 수 있다. 공식 매개변수 이름과 마찬가지로 레이블 이름은 BLOCK 전체에서 유효하다. 레이블, 인스턴스 변수, 구조체, 구조체 멤버 변수이름, 공용체, 공용체 멤버 이름, 열거형 이름은 각각 구분되거나 변수, 함수, 메서드 이름과 구분될 필요가 없다. 그러나 열거 식별자는 동일한 범위에서 정의된 변수 이름이나 다른 열거 식별자와 달라야 한다. 클래스 이름은 전역 범위를 갖기 때문에 동일 범위 내의 변수명이나 형 이름과 달라야 한다.


함수

함수가 정의될 때 저장 클래스가 지정된다면 static 이나 extern 중 하나여야 한다. static 으로 선언된 함수는 그 함수를 담고 있는 동일한 파일 내에서만 참조할수 있다. extern 으로 지정된 함수(혹은 클래스가 지정되지 않은 함수)는 다른 파일의 함수나 메서드에서 호출할수 있다.


변수

표 8.5는 변수 선언, 범위 지정, 메서드의 초기화에 사용할 수 있는 다양한 저장 클래스를 요약하고 있다.

저장 클래스 변수 선언 위치 참조할 수 있는 위치 초기화 설명
static 모든 BLOCK 바깥 그 파일 내 어디든 상수 표현식만 가능 변수는 프로그램 실행 초기에 한 번만 초기화 된다. BLOCK에서 값이 유지된다. 기본 값은 0이다.
BLOCK 내부 그 BLOCK 내부
extern 모든 BLOCK 바깥 그 파일 내 어디든 상수 표현식만 가능 변수는 extern 키워드 없이 적어도 한 번은 선언되어야 한다. 혹은, extern키워드를 사용하여 한 곳에서 선언되고 초기값을 대입해야 한다.
BLOCK 내부 그 BLOCK 내부
auto LOCK내부 그 BLOCK 내부 유효한 표현식 변수는 BLOCK에 들어설 때마다 초기화된다. 기본값은 없다.
register BLOCK 내부 그 BLOCK 내부 유효한 표현식 register 대입은 보장되지 않는다. 변수의 형에 대해 다양한 제약이 선언될 수 있다. register변수의 주소를 받을 수 없다. BLOCK에 들어설 때마다 초기화된다. 기본값은 없다.
omitted 모든 BLOCK 바깥 그 파일 내 어디든
혹은 적절한 선언을
포함하는 다른 파일
상수 표현식만 가능 이 선언은 한 곳에서만 나타난다. 이 변수는 프로그램 실행의 초기에 초기화된다. 기본 값은 0이고 auto로 설정된다.
BLOCK 내부 (auto 참조) (auto 참조)
표 B.5 변수: 저장 클래스, 범위, 초기화의 요약


인스턴스 변수

인스턴스 변수는 그 변수를 명시적으로 정의한 interface 부분이나 그 클래스의 카테고리에서 정의된 클래스의 모든 인스턴스 메서드에서 접근할 수 있다. 상속된 인스턴스 변수는 특별한 선언 없이 직접 접근할 수 있다. 클래스 메서드는 인스턴스 메서드에 접근할수 없다.

특별 지시어 @private, @protect, @public 은 인스턴스 변수의 범위를 조절하는데 사용한다. 이 지시어가 나타난 다음에는 닫는 중괄호(})가 나타나기 전까지 혹은 이 특별 지시어 중 다른 하나가 나타날 때까지 나오는 인스턴스 변수에 효과가 지속된다. 예를 들어, 네 개의 인스턴스 변수를 담고 있는 Point 클래스의 인터페이스 선언을 보자.

@interface Point: NSObject
{
@private
    int internalID;
@protected
    float x;
    float y;
@public
    BOOL valid;
}


이 지시어 뒤에서 변수가 선언되면 다음 경우에 참조 가능하다 설명
@protected 클래스 내의 인스턴스 메서드, 서브클래스의 인스턴스 메서드, 클래스의 카테고리 확장의 인스턴스 메서드. 기본 값이다.
@private 클래스 내의 인스턴스 메서드, 클래스의 카테고리 확장의 인스턴스 메서드에서 참조 가능하다. 그러나 서브클래스에서는 참조할 수 없다. 클래스 내의 인스턴스 메서드, 서브클래스의 인스턴스 메서드, 클래스 카테고리 확장의 인스턴스 메서드, 클래스의 인스턴스에 구조체 포인터 간접 참조 연산자(->)를 사용하고 그 뒤에 인스턴스 변수 이름을 붙이는 것으로(myFract->numerator) 다른 함수나 메서드에서 접근 가능하다. 클래스 자신만 접근 가능하도록 제한한다.
표 B.6 인스턴스 변수의 범위


internalID 변수는 private 이고, 변수 x, y 는 (기본 값인) protected 이고, valid 변수는 public이다.

이 지시어들은 표 B.6 에 요약되어 있다.


함수

이 부분은 함수의 문법과 동작을 요약한다.


함수 정의

일반 형식
returnType name( type1 param1, type2 para2, .. )
{
    variableDeclarations

    programStatement
    programStatement
    ...
    return expression;
}


name 함수는 returnType 형의 값을 반환하고공식 매개변수 param1, param2, ...을 받도록 정의되었는데, param1 은 type1 형이고, param2 는 type2 형으로 선언되어 있다.

지역 변수는 보통 함수가 시작할때 선언하는데, 반드시 그런 것은 아니다. 어디서든 선언할 수 있으며, 함수에서 이것들이 선언된 뒤에 나오는 명령문으로 접근이 제한된다.

만일 함수가값을 반환하지 않는다면 returnType 은 void형으로 지정된다.

괄호 안에 void 만 지정되면 그 함수는 인수를 받지 않는다. 매개변수 목록에 ... 이 마지막으로 사용되거나 홀로 사용된다면, 이 함수의 인수 개수는 가변적이다.

int printf (char * format, ...)
{
    ...
}


일차원 배열 인수의 선언에서는 배열에 포함된 원소 개수를 지정해 주지 않는다. 다차원 배열의 경우, 첫 번째 차원의 크기만 제외하고 나머지는 지정해야한다.

return 문에 대한 설명을 'return 문' 부분(617쪽)에서 보자.

예전 방식으로 함수를 정의할 수도 있다. 일반적인 형태는 다음과 같다.

returnType name (param1, param2, .. )
param_declarations
{
    variableDeclarations
    programStatement
    programStatement
    ...
    return expression;
}


여기서 매개변수 이름만 괄호 안에 나열된다. 인수가 없다면 괄호 안에 아무것도 입력하지 않는다. 각 매개변수의 형은 괄호 바깥, 함수 정의를 시작하는 중괄호({) 전에 나타난다. 예를 들어 다음 코드는 두 인수 value 와 n 을 받는 함수 rotate 를 정의한다.

unsigned int rotate (value, n)
unsigned int value;
int n;
{
    ...
}


첫 번째 인수는 unsigned int 형 이고 두 번째 인수는 int 형이다.

inline 키워드를 함수 정의에 사용하여 컴파일러에게 힌트를 줄 수 있다. 어떤 컴파일러는 함수 호출을 함수 자신의 실제 코드로 대체하여 더 빠르게 실행되도록 한다.다음 예를 보자.

inline int min (int a, int b)
{
    return ( a < b ? a : b );
}


함수 호출

일반 형식
name ( arg1, arg2, .. )


값 argl, arg2, ... 이 인수로 넘겨지며 함수 name 이 호출된다. 함수가 인수를 받지않는다면 (initilize() 에서와 같이) 괄호가 그냥 붙는다.

함수 호출뒤나 다른 파일에서 정의된 함수를 호출한다면, 다음과 같은 일반적인 형태를 갖는 함수의 프로토타입 선언을 해줘야 한다.

returnType name ( type1 param1, type2 para2, .. );


이 코드는 컴파일러에게 함수의 반환 형, 함수가 받는 인수의 개수, 각 인수의 형을 알려준다. 예로 다음 코드를 살펴보자.

long double power (double x, int n);


이 코드는 함수 power가 long double 형을 반환하고 두 개의 인수를 받도록 선언한다. 첫 번째 인수는 double 형이고 두 번째는 int 형이다. 괄호 내 인수명은 더미 이름으로 원한다면 생략하여 다음과 같이 작성할 수도 있다.

long double power (double, int);


컴파일러가 해당 함수의 정의나 프로토타입 선언과 이미 마주쳤다면, 함수가 호출될 때 각 인자의 형은 그 함수가 기대하는 인자의 형으로 (가능한 경우) 자동 변환된다.

만일 컴파일러가 함수의 정의나 프로토타입 선언을 미리 발견하지 못했다면 컴파일러는 함수가 int 형 값을 반환한다고 가정하며, 모든 float 인수를 double 형으로 자동 변환하고, '기본 데이터 형의 변환'(595쪽)에서 설명한 대로 정수 인수에 대해 정수 진급을수행한다. 함수의 다른 인수는 변환 없이 넘겨진다.

가변 인수를받는 함수는 그렇게 선언되어야만 한다. 그렇게 하지 않으면, 컴파일러는 함수가 실제 호출될 때의 인수 개수만큼 고정된 수를 인수로 받는다고 멋대로 가정한다.

만일 함수가 예전 스타일로 정의되었다면(599쪽의 '함수 정의' 참조), 이 함수의 선언은 다음과 같은 형태일 것이다.

returnType name ();


이런 함수의 인수는 이전 문단에 설명한대로 변환된다.

반환 형이 void 로 선언된 함수의 반환 값을 받아 사용하려 하면, 컴파일러가 경고를 띄울 것이다.

인수는 함수에 건너질 때 모두 값으로 넘어간다. 따라서, 그 함수가 그 인수의 값을 바꿀 수 없다. 다만, 함수에 포인터가 넘겨진 경우라면, 그 함수가 포인터가 참조하는 값을 변경할수는 있지만, 포인터 변수자체의 값을 수정할 수는 없다.


함수 포인터

뒤에 괄호가 붙지 않은 함수 이름은 그 함수의 포인터를 생성한다. 또한, 주소 연산자를 함수 이름에 사용하여 함수의 포인터를 생성할수 있다.

만일 fp 가 어떤 함수의 포인터라면, 다음 두가지 코드중 하나로 해당함수를 호출할 수 있다.

fp ()
(*fp) ()


만일, 이 함수가 인수를 받는다면, 괄호 안에 인수를 나열 하면 된다.


클래스

이 절에서는 클래스에 관련된 문법을 요약한다.


클래스 정의

클래스 정의는 인스턴스 변수와 메서드를 선언하는 인터페이스 부분과, 각 메서드를 정의하는 코드를 담고있는 구현 부분으로 구성된다.


인터페이스 부분
일반 형식
@interface className : parentClass (protocol, ...>
{
    instanceVariableDeclarations
}
methodDeclaration
methodDeclaration
    ...
@end


이 className 클래스는 parentClass 를 부모 클래스로 갖도록 선언되었다. 만일 className 이 하나 이상의 공식 프로토콜을 채택한다면 parentClass 뒤에 꺾쇠 (< >) 안에 프로토콜 이름들도 나열되었을 것이다. 이 경우, 나열된 프로토콜에 해당하는 메서드의 정의를 클래스의 구현부분에서 담고 있어야만 한다.

콜론과 parentClass 가 모두 생략되면, 새로운 루트 클래스가 선언된다.


인스턴스 변수 선언

선택적으로 instanceVariableDeclarations 부분에 클래스에 속한 인스턴스 변수의 형과 이름을 나열할 수 있다. className 의 각 인스턴스는 parentName 에서 상속받은 변수들과 이 변수들을 갖는다. 이런 변수들은 모두 이름이나 className 에 정의한 인스턴스 메서드 또는 className 의 서브클래스로 접근할 수 있다. 만일 @private 지시어로 접근이 제한되어 있다면, 서브클래스에서는 이 변수들에 접근할수 없다(598쪽의 '인스턴스 변수' 참조).

클래스 메서드는 인스턴스 변수에 접근할수 없다.


프로퍼티 선언
일반 형식
@property (attributes) nameList;


이 코드에서 선언하는 프로퍼티는 쉼표로 구분되는 속성 목록을 갖는다.

nameList 는 선언된 형의 프로퍼티 이름 목록이고 쉼표로 구분한다.

(type) propertyName1, propertyName2, propertyName3, ...


@property 지시어는 클래스, 프로토콜, 카테고리의 메서드 선언부 어디서든 나타날 수 있다.

assign, copy, retain 중 하나의 속성만 지정해줄 수 있다. 만일 가비지 컬렉션을 사용하지 않는다면, 이 가운데 하나의 속성이 명시적으로 사용되어야만 한다. 그렇지 않으면 컴파일러가 불평할 것이다. 가비지 컬렉션을 사용하면서 이 세 속성중 어느것도 지정해 주지 않는다면, 기본속성 assign 이 사용된다. 이때에는,클래스가 NSCopying 프로토콜을 채택한 경우에만 컴파일러가 불평할 것이다(이 경우 프로퍼티를 대입하는 대신 복사하고 싶은것일 수 있기 때문이다).

copy 속성을 사용하면, 자동 생성된 메서드에서 객체의 copy 메서드가 사용될 것이다. 그 결과로 수정 불가능한 사본이 만들어진다. 만일 수정 가능한 사본이 필요하다면, 직접 세터 메서드를 제공해 줘야한다.

속성 의미
assign 세터 메서드에서 인스턴스 변수의 값을 간단히 대입한다(기본 속성).
copy copy 메서드를 사용하여 인스턴스 변수의 값을 설정한다.
getter=name 자동 생성된 게터 메서드의 기본 이름인 propertyName 대신 name 을 사용한다.
nonatomic 자동 생성된 게터 메서드의 값은 바로 반환할 수 있다. 만일 이 속성이 선언되지 않으면, 접근자 메서드는 어토믹하다(atomic). 즉, 이 인스턴스 변수로의 접근이 뮤텍스 락이 걸린다는 이야기이다. 이를 통해 멀티스레드 환경에서 값을 가져오거나 설정하는 작업이 단일 스레드에서 돌아가도록 보장한다. 이에 더해, 가비지 컬렉션이 아닌 환경에서 자동 생성된 게터 메서드는 값을 반환하기 전에 프로퍼티를 리테인하고 오토릴리스한다.
readonly 프로퍼티의 값을 설정할 수 없다. 컴파일러는 세터 메서드를 기대하지 않고, 자동 생성해 주지도 않는다(기본 속성).
readwrite 프로퍼티의 값을 받고 설정할 수 있다. 컴파일러는 게터와 세터 메서드를 모두 요구하고, @synthesize가 사용되면 게터와 세터 메서드 둘 모두 자동으로 생성한다.
retain 프로퍼티는 대입 시 리테인되어야 한다. Objective-C 형 변수에만 지정할 수 있다.
setter=name 자동 생성된 세터 메서드의 기본 이름인 propertyName 대신 name을 사용한다.
표 B.7 프로퍼티 속성


메서드 선언
일반 형식
mType (returnType) name_1 : (type1) param1 name_2 : (type2) param2, ...;

메서드 name_1:name_2:... 은 returnType 형의 값을 반환하고 공식 매개변수 parnm1, parnm2, ... 을 인자로 받는다. param1 은 type1 형이고, param2 는 type2 형으로 선언되는 식이다.

콜론(:)으로 위치를 표기하고 메서드 이름으로 사용되기만 한다면, name_1(name_2, ...) 뒤에 오는 이름은 생략 가능하다(다음 예제를 보라).

mType 이 (+)라면 클래스 메서드가 선언되고, (-) 라면 인스턴스 메서드가 선언된다.

선언되는 메서드가 부모 클래스에서 상속받은 것이라면,부모 클래스의 정의가 새 정의로 재정의된다. 이 경우, super 에 메시지를 보내면 부모 클래스의 메서드도 여전히 접근할 수 있다.

클래스 메서드는 해당 메시지가 클래스 객체에 보내질 때 호출되고, 인스턴스 메서드는 메시지가 클래스의 인스턴스에 보내질 때 호출된다. 클래스 메서드와 인스턴스 메서드는 동일한 이름을 사용할 수 있다.

다른 클래스 간에 동일한 메서드 이름을 사용하는 것도 가능하다. 다른 클래스의 객체가 동일한 이름의 메서드에 응답할 수 있는 기능을 '다형성' 이라고 한다.

만일 메서드가 반환값을 갖지 않는다면, returnType 은 void 이다. 함수가 id 형 값을 반환하면, returnType 은 생략할 수 있다. 그렇지만 id 를 지정해 주는 것이 더 좋은 프로그래밍 습관이다.

만일, 다음과 같이 ... 이 매개변수 목록의 마지막에 나타나거나 유일한 매개변수라면, 이 메서드는 가변 인자를 받는다.

-(void) print: (NSString *) format, ...
{
    ...
}


클래스 선언의 예로 다음 인터페이스 선언 부분을 살펴보자. 이 부분에서는 NSObject 를 부모 클래스로 가지는 Fraction 클래스를 선언한다.

@interface Fraction: NSObject
{
    int numerator, denominator;
}
+(Fraction *) newFract;
-(void) setTo: (int) n : (int) d;
-(void) setNumerator: (int) n andDenominator: (int) d;
-(int) numerator;
-(int) denominator;
@end


Fraction 클래스는 정수 인스턴스 변수 numerator 와 denominator 를 갖는다. 또한, Fraction 객체를 반환하는 newFract 라는 클래스 메서드 하나도 포함한다. 두개의 인수를 받고 값을 반환하지 않는 인스턴스 메서드 setTo:: 과 setNumerator:andDenominator: 도 포함한다. 인수를 받지 않고 int 형을 반환하는 인스턴스 메서드 numerator 와 denominator 도 포함한다.


구현부분
일반 형식
@implementation className;
    methodDefinition
    methodDefinition
    ...
@end


className 이라는 클래스가 정의된다. (가능하긴 하지만) 부모 클래스와 인스턴스 변수는 보통 구현 부분에서 다시 선언되지 않는다. 인스턴스 부분에서 이미 선언했기 때문이다.

구현하는 메서드가 카테고리의 메서드인 경우(608쪽의 '카테고리 정의' 참조)를 제외하면, 인터페이스 부분에서 선언한 모든 메서드는 구현 부분에서 정의해줘야 한다. 인터페이스 부분에 하나 이상의 프로토콜이 나열되었다면, 프로토콜 의 메서드는 상속을 통하는 암묵적인 방법이나 구현부에서 명시적으로 정의하는 방법으로 모두 정의 되어야만 한다.

각 methodDefinition은 메서드가 호출되면 실행될 코드를 담고 있다.


메서드 정의
일반 형식
mType (returnType) name_1 : (type1) param1 name_2 : (type2) param2, ...
{
    variableDeclarations

    programStatement
    programStatement
    ...
    return expression;
}

메서드 name_1:name_2:... 은 returnType 형의 값을 반환하고 공식 매개변수 param1, param2,... 으로 정의된다. param1 은 type1 형으로 선언되고, param2 는 type2 형으로 선언되는 식이다. 만일, mType 이 (+) 라면 클래스 메서드가 정의되고 mType 이 (-) 라면 인스턴스 메서드가 정의된다. 이 메서드 정의는 인터페이스 부분의 메서드 선언이나 이전에 정의된 프로토콜 정의와 일치해야 한다.

인스턴스 메서드는 클래스의 인스턴스 변수와 상속받은 변수를 이름으로 참조할수 있다. 만일 클래스 메서드를 정의한다면, 인스턴스 변수를 참조할 수 없다.

식별자 self는 메서드 내에서 사용하여 메서드가 호출된 객체, 즉 메시지의 수신자를 참조할 수 있다.

returnType 이 void 가 아니라면, 하나 혹은 그 이상의 return 문이 returnType 의 표현식과 함께 나타나야 한다. 만일 returnType 이 void 라면 return 명령문을 사용할 수도 있고 사용할 수 없기도 하는데, 사용하더라도 값을 반환할 수는 없다.

메서드 정의의 예로, 다음은 setNumerator:andDenominator: 메서드를 선언에 일치하도록 정의한다(604쪽의 '메서드선언' 참조).

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


이 메서드는 두개의 인스턴스 변수를 제공한 인자로 설정하고,아무값도 반환하지 않겠다고 선언하였으므로 return 문을 실행하지 않는다.

일차원 배열 인수의 선언 시, 배열 크기를 지정해줄 필요 없다. 그러나 다차원배열의 경우,첫 번째 차원을 제외한 나머지차원의 크기를 지정해 줘야한다.

지역 변수를 메서드 안에서 선언할 수 있는데 보통 메서드 정의 초기 부분에서 선언된다. 자동 지역변수는 메서드가 호출될 때 할당되고 메서드가 종료할 때 할당이 해제된다.

'return 문' 부분(617쪽)에서 return 명령문에 대해 설명한다.


자동 생성 접근자 메서드
일반 형식
@synthesize property_1, property_2, ...


위 코드는 나열한 프로퍼티 property_1, property_2,... 의 메서드를 자동 생성하라고 지시한다.

property=instance_var


나열한 프로퍼티 목록에서 위 표기법을 사용하면 지정한 property 가 인스턴스변수 instance_var 와 연결된다. 자동 생성된 메서드는 @property 지시어에서 해당 프로퍼티에 설정한 속성을 갖게 된다.


카테고리 정의

일반 형식
@interface className (categoryName) <protocol,...>
    methodDeclaration
    methodDeclaration
    ...
@end


위 코드는 지정한 클래스 classN때le에 나열한 메서드를 갖는 categotyName 카테고리를 정의한다. 만일 프로토콜이 하나 이상 나열되면 이 카테고리가 해당 프로토콜을 채택하게 된다. 컴파일러는 이전에 클래스의 @interface 선언을 통해 className 을 알고 있어야 한다.

원하는 수만큼 소스파일을 사용할 수 있는 것처럼, 원하는 수만큼의 카테고리를 정의할 수 있다. 나열된 메서드는 클래스와 서브클래스로 상속받은 클래스의 일부가 된다.

카테고리는 className/categoryName 묶음으로 구분된다. 예를 들어, 한 프로그램상에서 NSArray (Private) 카테고리는 단 하나만 존재한다. 그렇지만 각각의 카테고리 이름은 재사용할 수 있다. 따라서, 주어진 프로그램에 NSArray (Private) 카테고리와 NSString (Private) 카테고리가 공존할 수 있고, 둘은 서로 구분된다.

카테고리에 정의된 메서드중 사용할 계획이 없는 메서드는 구현하지 않아도 된다.

카테고리는 클래스에 새로운 메서드를 추가하거나 기존의 메서드를 재정의하여 클래스를 확장할수 있다. 클래스에 새로운 인스턴스 변수를 정의할 수는 없다.

만일 하나 이상의 카테고리가 동일한 클래스에 동일한 이름의 메서드를 선언한다면, 메서드 호출시 어떤 메서드가 실행될지는 알 수 없다.

예를 들어, 다음 코드는 Complex 클래스에 ComplexOps 카테고리를 네 개의 메서드를 포함하도록 정의한다.

#import "Complex.h"
@interface Complex (ComplexOps)
-(Complex *) abc;
-(Complex *) exp;
-(Complex *) log;
-(Complex *) sqrt;
@end


아마도 어디선가 해당하는구현 부분에서 이 메서드들 중에서 하나 이상을 구현하고 있을 것이다.

#import "ComplexOps.h"
@implementation Complex (ComplexOps)
-(Complex *) abs
{
    ...
}
-(Complex *) exp
{
    ...
}
-(Complex *) log
{
    ...
}
-(Complex *) sqrt
{
    ...
}
@end


카테고리 중 다른 서브클래스가 구현하도록 된 카테고리는 '비공식' 프로토콜 혹은 '추상' 카테고리라고 한다. 공식 프로토콜과 달리, 컴파일러는 비공식 프로토콜을 지키는지 확인해 주지 않는다. 런타임 시에 객체가 각각의 메서드에 대해 비공식 프로토콜을 지키는지 테스트할 수 도 있고하지 않을 수 도 있다. 예를들어, 동일한 프로토콜에서 런타임 시에 어떤 메서드는 필요하고 다른 메서드는 필요하 지않을수있다.


프로토콜 정의

일반 형식
@protocol protocolName <protocol, ...>
    methodDeclarations
@optional
    methodDeclarations
@required
    methodDeclarations
...
@end


protocolName 프로토콜은 연결된 메서드와 함께 정의된다. 만일 다른 프로토콜이 나열되어 었다면, protocolName 은 나열한 프로토콜을 채택한다.

이런 정의를 '공식 프로토콜 정의'라고한다.

protocolName 프로토콜을 채택하는 클래스가 프로토콜에 선언된 모든 필수 메서드와 다른 나열된 프로토콜의 모든 메서드를 구현하거나 상속받을 경우, protocolName 프로토콜을 따른다고 한다. 컴파일러는 프로토콜을 따르는지 확인하고, 만일 클래스가 선언된 공식 프로토콜을 따르지 않는다면 경고를 표시한다. 런타임 시 객체는 공식 프로토콜을 따르는지를 테스트할 수도 있고 하지 않을 수 도있다.

@optional 지시어는 선택적으로 구현할수 있는 메서드 목록 앞에 나온다. 그 뒤에 @required 지시어를 사용하면 프로토콜을 따르기 위해 반드시 구현해야 하는 메서드의 목록을 이어서 정의할수 있다.

프로토콜은 보통, 특정 클래스와 연계되지 않지만 클래스 간에 공유하는 공통 인터페이스를 정의할 방법을 제공한다.


특수 형 수식어

프로토콜에 선언된 메서드 매개변수와 반환 형은 표 B.8에 나열된 형 수식어를 사용할수 있다. 이 수식어들은 분산객체 프로그램에서 사용한다.

속성 의미
in 인수는 값이 송신자에 의해 바뀌고 수신자에게 (복사되어) 되돌려지는 객체를 참조한다.
out 인수는 값이 송신자에 의해 바뀌고 수신자에게 (복사되어) 되돌려지는 객체를 참조한다.
inout 인수는 값이 송신자와 수신자 모두에 의해 바뀌고 수신자와 송신자 모두에게 되돌려지는 객체를 참조한다(기본 값).
oneway 반환 형 선언에 사용된다. 보통 (one way void)를 사용하여 이 메시지의 호출자가 반환 값을 기다릴 필요가 없음을 의미한다. 이것은 메서드가 비동기적으로 동작할 수 있다는 것을 의미한다.
bycopy 인수 혹은 반환 값은 복사된다.
byref 인수 혹은 반환 값은 레퍼런스로 건네지고 복사되지 않는다.
표 B.8 특수 프로토콜 형 수식어


객체 선언

일반 형식
className *var1, *var2, ...


이 코드는 var1, var2,... 을 className 클래스의 객체로 정의한다. 이 코드에서 주의할점이 하나 있다. 여기서는 포인터 변수를 선언하고 각 객체에 포함될 실제 데이터 공간을 예약하지는 않는다. 다음 선언을 보자.

Fraction *myFract;


이 선언은 myFract를 Fraction 객체, 혹은 기술적으로는, 그 객체를 가리키는 포인터로 정의한다. Fraction 의 데이터 구조를 위한 실제 공간을 할당하려면 클래스에 alloc 이나 new 메서드를 호출해야 한다.

myFract = [Fraction alloc];


이 코드는 Fraction 객체에 충분한 공간을 예약하고, 그 공간을 가리키는 포인터를 반환하고 myFract 에 대입한다. 변수 myFract 는 흔히 객체나 Fraction 클래스의 '인스턴스' 라고 불린다. 루트 객체의 alloc 메시지가 정의된 대로 새로 할당된 객체는 모든 인스턴스 변수가 0 으로 설정된다. 그러나 이것만으로 객체가 적절히 초기화되지는 않는다. 따라서 객체 사용 전에 (init 과 같은) 초기화 메서드를 호출해줘야 한다.

myFract 변수가 Fraction 클래스의 객체임이 명시적으로 선언되었으므로 이 변수는 '정적'으로 타이핑 되었다고 한다. 컴파일러는 정적으로 타이핑된 변수의 일치성을 클래스 정의에 물어봐서 메서드와 그 인수 및 반환 값이 제대로 사용되었는지 확인할 수 있다.


id 객체선언
일반 형식
id <protocol, ..> var1, var2, ...;


이 코드는 varl, var2,... 을 꺾쇠 (< >) 안에 나열된 프로토콜을 따르는, 부정(不定, indeterminate) 클래스의 객체로 선언한다. 프로토콜 목록은 사용할 수도 있고 사용하지 않을 수도 있다.

어느 클래스의 객체든 id 형 변수에 대입될 수 있다. 만일 하나 이상의 프로토콜이 나열되었다면 컴파일러는 선언된 변수에 나열된 프로토콜로부터 메서드가 일관성 있게 사용되었는지 검사한다. 즉, 공식 프로토콜에 선언된 인수와 반환 형을 확인한다는 이야기이다.

다음 명령문을 보자.

id <MathOps> number;
    ...
result = [number add: number2];


컴파일러는 MathOps 프로토콜이 add: 메서드를 정의하는지 확인한다. 만일 정의하고 있다면, 인자와 반환 형의 일치 여부를 확인한다. 따라서, add: 메서드가 정수 인수를 받고 위에서 Fraction 객체를 건네 준다면 컴파일러가 불평할 것이다.

시스템은 각 객체가 어느 클래스에 속하는지 알고있다. 따라서 런타임 시에 객체의 클래스를 결정하고 호출할 적절한 메서드를 선택한다. 이 두 단계를 각각 '다형성' 과 '동적 바인딩' 이라고 한다.


메시지 표현식
형식 1
[receiver name1: arg1 name2: arg2 name3: arg3 .. ]


receiver.로 지정한 클래스에 메서드 namel:name2:name~: ... 이 호출되고 값 arg1, arg2, arg3,... 을 인수로 건넨다. 이것을 '메시지 표현식' 이라고 한다.

이 표현식의 값은 메서드에서 반환하는 값이고, 메서드가 void 로 선언되고 아무값도 반환하지 않는다면 void 이다. 이 표현식의 형은 호출된 메서드가 정의된 형과 같다.


형식 2
[receiver name];


메서드가 인자를 받지 않으면, receiver.로 지정한 클래스에 메서드 이름으로 호출하는 이 형식이 사용된다.

만일 receiver 가 id 형이면, 컴파일러는 선언된 클래스나 상속받은 클래스에서 지정한 메서드의 정의를 찾는다. 정의를 찾지 못하면 컴파일러는 경고를 내고, 수신자는 지정한 메시지에 응답하지 못할 수도 있다. 이에 더해 컴파일러는 메서드가 id 형 값을 반환한다고 가정하고, 부동소수 인수를 double 값으로 변환하고 정수 인수의 경우 '기본 데이터 형의 변환' (595쪽)에서 설명한 대로 정수 진급을 수행한다. 다른 메서드 인수는 변환 없이 넘겨진다.

receiver 가 클래스 객체라면(클래스 이름만 지정해 주면 된다), 지정된 클래스 메서드가 호출된다. 그 외의 경우, receiver 가 클래스의 인스턴스라면 해당하는 인스턴스 메서드가 호출된다.

receiver 가 정적으로 타이핑된 변수이거나 표현식이라면, 컴파일러는 클래스 정의에서 (혹은 상속받은 메서드 중에서) 그 메서드를 찾고, (가능한 경우) 메서드에 맞도록 인수들을 변환한다. 따라서, 메서드가 부동소수점 값을 기대하고 있는데 정수 값이 건네졌다면 그 인수는 메서드가 호출될 때 자동으로 변환된다.

만일 receiver 가 널 객체 포인터라면(nil 이라도 된다), 메시지를 받을 수 있다. 메시지에 연결된 메서드가 객체를 반환한다면 메시지 표현식의 결과는 nil 이 된다. 만일 메서드가 객체를 반환하지 않는다면, 표현식의 값이 무엇이 될지는 알 수 없다. 동일한 메서드가 두개 이상의 클래스에 (명시적 정의나 상속으로) 정의되었다면, 컴파일러는 클래스간의 인자와 반환 형이 일치하는지 확인한다.

메서드에 건네지는 모든 인자는 값으로 건네진다. 따라서 이 인자들의 값이 메서드에 의해 변할 수 없다. 포인터가 메서드에 건네진다면 메서드는 포인터가 참조하는 값을 바꿀 수 있지만, 여전히 포인터 자체의 값을 바꿀 수는 없다.


형식 3
receiver.property


이 코드는 이 표현식 이 lvalue(네 번째 형식 참조)으로 사용되지 않는 한은, receiver 의 게터 메서드(기본적으로 property)를 호출한다. 게터 메서드 이름은 @property 이름으로 수정할 수 있는데, 이 경우 그 이름의 메서드가 호출된다.

만일 기본 게터 메서드 이름이 사용된다면, 앞의 표현식은 다음 표현식과 동일하다.

[receiver property]


형식 4
receiver.property = expression


이 코드는 property 프로퍼티와 연결된 세터 메서드를 expression 의 값을 인수로 하여 호출한다. 앞에서 @property 지시어를 사용해 다른 세터 메서드 이름을 제공하지 않으면, 기본값으로 세터 메서드 setProperty: 가호출된다.

만일 기본 세터 프로퍼티 이름이 사용되면, 앞의 표현식은 다음 코드와 동일하게 된다.

[receiver setProperty: expression]


명령문

프로그램 명령문은 세미콜론이 바로 뒤에 붙는 유효한 표현식 혹은 다음에 설명하는 특수 명령문 중의 하나이다. 명령문 앞에 선택적으로 레이블을 놓아두고 콜론을 바로 뒤에 붙이면 식별자를 생성할 수 있다(goto 명령문참조).


복합 명령문

중괄호({ })로 둘러싸인 프로그램 명령문은 '복합' 명령문이나 '블록'이라고 하고, 단일 명령문이 나을수 있는 곳이면 어디서든 나타날 수 있다. 블록은 자신의 변수 선언을 가질 수 있고, 블록 바깥에 동일한 이름의 변수가 있다면 재정의하게 된다. 이런 지역 변수의 범위는 자신이 정의된 블록내로 제한된다.


break 명령문

일반 형식
break;


break 명령문을 for, while, do 혹은 switch 문에서 실행시키면, 해당 명령문의 실행이 바로 종료된다. 프로그램 실행은 반복문이나 switch 뒤에 바로 따르는 명령문으로 이어진다.


continue 명령문

일반 형식
continue;


반복문 내에 continue 문을 수행하면 반복문 내에서 continue 뒤에 따르는 명령문을 건너 뛰게 된다. 그 다음 반복문의 실행은 일반적인 경우와 동일하다.


do 명령문

일반 형식
do
    programStatement
while ( expression );


programStatement 는 expression 이 0 이 아닌 값으로 평가되는 동안 계속 수행된다. expression 이 programStatement 가 매번 실행되고 난 후에 평가되므로, programStatement는 적어도 한 번 실행된다.


for 명령문

형식 1
for ( expression_1; expression_2; expression_3 )
    programStatement


expression_1 은 반복문이 실행될 때 한 번 평가된다. 그 다음, expression_2 가 평가된다. 값이 0 이 아니면 programStatement 가 실행 되고, 그 다음에 expression_3 가 평가된다. programStatement 의 실행과 그 후의 expression_3 의 평가는 expression_2 가 0 이 아닌 동안 계속 반복된다. expression_2 가 매번 programStatement 가 실행되기 전에 평가되므로 반복문이 시작될 때 expression_2 의 값이 0 이면 programStatement 는 결코 실행되지 않을 것이다. expression_1 에서 for 문에만 속하는 변수를 선언할 수 있다. 이런 변수의 범위는 for 반복문의 범위와같다. 예로 다음 코드를 보자.

for ( int i = 0; i < 100; ++i )
    ...


이 코드는 반복문이 실행될 때, 정수 변수 i 를 선언하고 그 초기값을 0 으로 설정한다. 이 변수는 반복문안의 어느 명령문에서든 접근 가능하지만, 이 반복문이 종료되면 더는 접근할 수 없다.


형식 2
for ( var in expression )
    programStatement


for 문의 이런 변형은 빠른 열거를 시작한다. var 는 변수로, 형을 지정해 주면 for 문의 지역 변수가 된다. expression 은 NSFastEnumeration 프로토콜을 따르는 결과를 내는 표현식이다. 보통 expression 은 배열이나 딕셔너리와 같은 컬렉션이다.

for 문이 반복될 때마다, expression 의 초기 평가에서 다음객체가생성되어 var 에 대입되고 반복분의 몸체인 programStatement 가 실행된다. 반복문은 expression 안의 모든 객체가 열거된 뒤에 종료된다.

for 문에서 컬렉션의 내용을 바꿀수 없음에 주의하자. 만일 이를 시도하면 예외가 발생할 것이다.

배열은 그 원소가 순서대로 열거된다. 하지만, 딕셔너리를 열거하면 각 키 안의 객체들이 열거되지만, 순서는 따로 정해져 있지 않다. 세트를 열거하면 마찬가지로 특정한 순서 없이 세트의 각 멤버가 열거된다.


goto 명령문

일반 형식
goto identifier;


goto를 실행하면 컨트롤이 바로 identifier 레이블이 불은 명령문으로 이동한다. 레이블 명령문은 goto 와 동일한 함수나 메서드에 있어야 한다.


if 명령문

형식 1
if ( expression )
    programStatement


만일 expression 의 평가 결과가 0 이 아니라면, programStatement 가 실행된다. 그 외의 경우는 그냥 건너뛴다.


형식 2
if ( expression )
    programStatement_1
else
    programStatement_2


expression 의 값이 0 이 아니라면 programStatement_1 이 실행된다. 그 외의 경우에는 programStatement_2 가 실행된다. 만일 programStatement_2 가 다른 if 문이라면 다음과 같은 if-else if 연결에 영향을 미친다.


일반 형식
if ( expression_1 )
    programStatement_1
else if ( expression_2 )
    programStatement_2
    ...
else
    programStatement_n


else 구문은 언제나 else 를 포함하지 않는 마지막 if 문에 연계된다. 필요하다면 괄호를 사용하여 이 연계를 바꿀 수 있다.


null 명령문

일반 형식
;


널 명령문을 수행해도 아무 일도 일어나지 않는다. 보통, for, do, while 반복문에서 필요한 프로그램 명령문을 채워넣는 데 사용된다. 다음 명령문은 from 이 가리키는 문자열을 to 가 가리키도록 복사한다.

while ( *to++ = .from++ )
    ;


이 명령문에서 널 명령문을 사용하여 while 의 반복 표현식 다음에 필요한 프로그램 명령문 조건을 충족시켰다.


return 명령문

형식 1
return;


return 문을 수행하면 프로그램 실행이 호출한 함수나 메서드로 즉각 돌아가게 된다. 이 형식은 값을 반환하지 않는 함수나 메서드에서만 사용할 수 있다.

만일 함수나 메서드 수행이 return 문을 만나지 않은 채로 끝에 도달하면, 이 형식의 return 문이 수행된 것과 같이 반환한다. 따라서 , 그런 경우 아무런 값도 반환되지 않는다.


형식 2
return expression


expression 의 값이 호출한 함수나 메서드에 반환된다. expression 의 형 이 함수나 메서드를 선언할 때의 반환 형과 일치하지 않으면 값은 자동으로 선언된 형으로 변환되고 반환된다.


switch 명령문

일반 형식
switch ( expression )
{
    case constant_1:
        programStatement
        programStatement
        ...
        break;
    case constant_2:
        programStatement
        programStatement
        ...
        break;
    ...
    case constant_n:
        programStatement
        programStatement
        ...
        break;
    default:
        programStatement
        programStatement
        ...
        break;
}


expression 은 평가되어 상수표현식 값인 constant_1, constant_2, ..., constant_n 과 비교된다. 만일 expression 의 값이 이 case 값중 하나와 일치한다면,그 뒤의 명령문이 바로 실행된다. 만일 case 값 중 expression 의 값과 일치하는 것이 없다면 default case가 (존재하는 경우) 실행 된다. default case 가 포함되지 않았다면, switch문의 어느 명령문도 실행되지 않는다.

expression 평가의 결과는 정수형 이어야만 하고, 두 개의 case 가 동일한 값을 가질수 없다. 특정 case 문에 break 문을 빼먹으면 그 다음 case 가 이어서 실행된다.


while 명령문

일반 형식
while ( expression )
    programStatement


programStatement 는 expression 이 0 이 아닌 동안 계속 실행된다. expression 이 programStatement 가 실행되기 전에 평가되므로 programStatement 는 한 번도 실행되지 않을 수도 있다.


예외 처리

예외를 발생시킬 수 있는 명령문들을 @try 블록 안에 넣는 것으로 예외를 처리할수있다. 일반적인 형태는 다음과 같다.

@try
    programStatement 1
@catch (exception)
    programStatement 2
@catch (exception)
    ...
@finally
    programStatement n


만일 예외가 progrnmStatement 1 에서 발생한다면, 뒤따르는 @catch 문이 테스트되어 해당하는 exception 이 발생한 예외와 일치하는지 알아본다. 만일 일치한다면, 해당하는 programStatement 가 수행된다. 예외가 발생하고 잡히는 지와 상관없이, @finally 블록을 사용하면 그 안의 코드는 무조건 수행된다.


전처리기

전처리기는 컴파일러가 코드를보기 전에 소스코드를 먼저 분석한다. 전처리기는 다음과 같은 작업을 수행한다.

  1. 3중음자를 동의어로교체한다(614쪽의 '복합명령문' 참조).
  2. 백슬래시 문자(\)로 끝나는 줄들을 한줄로 합친다.
  3. 프로그램을 토큰 시퀀스로 분리한다.
  4. 주석을 제거하고 단일 줄로 대체한다.
  5. 전처리기 지시어를 처리하고 매크로를 확장한다(620쪽의 '전처리기 지시어'참조).


3중음자 시퀀스

ASCII 문자가아닌 분자를 처리하려면 다음의 '3중음자' 시퀀스(trigraph 라고한다)가 인식되고 프로그램(과 문자열)에서 나타날 때마다 특별히 처리된다.

3중음자 의미
??= #
??( [
??) ]
??< {
??> }
??/ \
??' ^
??! ¦
??- ~


전처리기 지시어

#define 지시어
형식 1
#define name text

이 코드는 전처리기에 식별자 이름을 정의하고, name 뒤의 첫 공백 다음에 나오는 text 를(그 라인이 끝날 때까지를) 그 이름에 연계한다. 이후에 프로그램에서 name 을 사용하면 그 지점에 text 가 대체되어 들어간다.


형식 2
#define name(param_1, param2, ... , param_n) text

매크로 name은 각각 식별자인 param_1, param_2, ..., param_n 을 인수로 받는다. 이후에 프로그램에서 인수와 함께 name 을 사용하면 그곳에 text 가 대신 들어가고, text 내에 매개변수가 나타나는 지점에 건네준 인수로 대체되어 나타난다.

만일 매크로가 가변 길이 인수를 받는다면 인수 목록 마지막에 ... 을 사용할 수 있다. 목록의 나머지 인수들은 집단적으로 _VA_ARGS_ 라는 특별 식별자로 매크로 정의 안에서 참조할 수 있다. 예를 들어, 다음 코드는 가변 길이 인수를 받는 myPrintf 라는 매크로를 정의한다.

#define myPrintf (...) printf ("DEBUG: " _VA_ARGS_);


다음은 이 매크로를 제대로사용한 예들이다.

myPrintf ("Hello world \n");
myPrintf ("i = %i, j = %i\n",i ,j);


매크로 정의에 한줄 이상이 필요하다면, 뒤따르는 줄이 있는 각 줄은 백슬래시 문자(\)로 끝나야한다. 이름은 정의된 뒤에 파일내 어디서든 사용할 수 있다.

인수를 받는 #define 지시어에서 # 연산자와 뒤따르는 인수의 이름을 매크로에서 사용할 수 있다. 전처리기는 매크로가 호출될 때 건네지는 값 주변에 큰 따옴표를 씌워준다. 즉, 문자열이 되는것이다. 예를 들어, 다음 정의가 있다고 하자.

#define printint(x) printf (# x "= %d\n" , x)


이 매크로를 다음과 같이 호출한다고 하자.

printint (count);


전처리기는 이를 다음과 같이 확장한다.

printf ("count" "= %i\n" , count);


아래 코드와도 동일하다.

printf ("count = %i \n" , count);


전처리기는 이러한 스트링 만들기 작업을 수행할 때 큰따옴표(")나 백슬래시 문자 (\) 앞에 백슬래시 문지를 추가한다. 다음 정의를 보자.

#define str(x) # x


이 매크로를 다음 코드로 호출해 보자.

str (The string "\t" contains a tab)


전처리기는 이 코드를 다음과 같이 확장할 것이다.

"The string \"\\t\"contains a tab"


인수를 받는 #define 지시어에서 ## 연산자를 사용할 수도 있다. 이 연산자 앞뒤에 매크로의 인수 이름이 나타날 수 있다. 전처리기는 매크로가 호출될 때 건네지는 값을 받아 매크로의 인수와 뒤따르는 (혹은 앞서는) 토큰으로 단일 토큰을 생성한다. 예로 다음 매크로 정의를 보자.

#define pr'intx(n) printf ("%i\n" , x ## n ];


이 매크로를 다음 코드로 호출해 보자.

printx (5)


그 결과는 다음과 같을 것이다.

printf ("%i\n" , x5);


다음 매크로 정의를 보자.

#define printx(n) printf ("x"# n "= %i\n" , x ## n);


다음 코드로 이 매크로를 호출하자.

printx(10)


대체와 문자열 병합이 모두 처리된 후의 결과는 다음과 같다.

printf ("x10 = %i\n" , x10);


# 연산자와 ## 연산자 주변에 공백은 허용되지 않는다.


#error 지시어
일반 형식
#error text
    ...


지정한 text는 전처리기에 의해 오류 메시지로 작성된다.


#if 지시어
형식 1
#if constant_expression
    ...
#endif


constant_expression 의 값이 평가된다. 결과가 0 이 아니라면 #endif 지시어가 나올때 까지의 모든 코드가 처리된다. 그 외의 경우, 그 사이의 코드는 모두 생략되고 전처리기나 컴파일러에 의해 처리되지 않는다.


형식 2
#if constant_expression_1
    ...
#elif constant_expression_2
    ...
#elif constant_expression_n
    ...
#else
    ...
#endif


만일 constant_expression_1 이 0 이 아니라면 #elif 전까지의 모든 코드가 처리되고 #endif 까지 남은 코드들은 생략된다. 그 외의 경우, constant_expression_2 가 0 이 아니라면 다음 #elif 까지의 코드가 처리되고 #endif 까지 남은 코드는 생략된다. 이들 상수 표현식 중 어느 것도 0 이 아닌 값으로 평가되지 않는다면, #else 가 있는 경우 #else 뒤의 코드가 처리된다.

특수 연산자 defined 를 상수 표현식의 일부로 사용할 수 있다.

#if defined (DEBUG)
    ...
#endif


이 코드는 식별자 DEBUG 가 이미 정의된 경우(다음의 #ifdef 지시어 참조) #if 와 #endif 사이의 코드를 처리한다. 식별자 주변에 괄호는 없어도 된다.

#if defined DEBUG


따라서 위의 코드도 정상동작한다.


#ifdef 지시어
일반 형식
#ifdef identifier
    ...
#endif


identifier 의 값이 이미 정의되어 있다면 (#define 이나 프로그램 컴파일 시 커맨드라인 -D 옵션으로) #endif 가 나을 때까지의 코드들이 처리된다. 그 외의 경우, 이 코드들은 무시된다. #if 지시어와 마찬가지로 #ifdef 지시어와 함께 #elif 와 #else 지시어를 사용할 수 있다.


#ifndef 지시어
일반 형식
#ifndef identifier
    ...
#endif


identifier 의 값이 이미 정의되지 않았다면, #endif 까지의 코드들이 처리된다. 그 외의 경우, 이 코드들은 무시된다. #if 지시어와 마찬가지로 #ifndef 지시어와 함께 #elif 와 #else 지시어를 사용할 수 있다.


#import 지시어
형식 1
#import "fileName"


fineName 으로 지정한 파일이 이미 프로그램에 포함 되었다면,이 명령문은 무시된다. 그 외의 경우, 전처리기는 구현에서 정의한 디렉터리에서 먼저 fileName 파일을 찾는다. 보통, 소스파일과 같은 폴더를 먼저 찾고, 파일을 찾지 못하면 구현에서 정의한 표준 디렉터리들을 검색한다. 파일을 찾은후, 파일의 내용물이 프로그램의 #import 지시어가 나타나는 그 위치에 포함된다.

포함된 파일 내의 전처리기 지시어도 분석된다. 따라서, 포함된 파일 자신도 #import 나 #include 지시어를 포함하고 있을수 있다.


형식 2
#import <fileName>


만일 이 파일이 이미 포함되지 않았다면 전처리기는 표준 검색 위치에서만 지정한 파일을 찾는다. 특히, 현재 소스 디렉터리는 검색에 포함되지 않는다. 파일을 찾은후 수행되는 작업은 위와 동일하다.

이 둘 가운데 어느 형식이든, 이전에 정의된 이름을 사용할 수도 있다. 따라서, 다음 코드도 정상 동작한다.

#define ROOTOBJECT <NSObject.h>
    ...
#import ROOTOBJECT


#include 지시어
  1. import 와 동일하게 동작하는데, 지정한 헤더파일이 이전에 이미 포함되었는지를 확인하지 않는다.


#line 지시어
일반 형식
#line constant "fileName"


이 지시어는 컴파일러가 이후의 코드 라인을 소스파일 명이 fileName인 것처 럼 여기도록 하고, 줄 번호가 constant 부터 시작하는 것처럼 처리하게 한다. fileName 지정하지 않는다면, #line 지시어로 마지막으로 지정한 파일명이나 (이전에 지정된 파일명이 없다면)소스파일 이름이 사용된다.

  1. line 지시어는 컴파일러가 발행하는 오류 메시지에 표시되는 파일명과 줄 번호를 조절하는 데 주로사용한다.


#pragma 지시어
일반 형식
#pragma text


이 코드는 전처리기가 구현에 정의된 작업을 수행하도록 한다. 예를들어,다음에 나온 pragma 는 특별한 반복문 최적화를 특정 컴파일러가 수행하도록 한다.

#pragma loop_opt (on)


만일 loopt_opt pragma 를 이해하지 못하는 컴파일러가 이 pramga 를 본다면 그냥 무시한다.


#undef 지시어
일반 형식
#undef identifier


지정한 identifier 는 전처리기에서 정의가 취소된다. 이후의 #ifdef 나 #ifndef 지시어는 마치 해당 식별자가 정의된적이 없는 것처럼 동작한다.


#지시어

널 지시어로, 전처리기는 이를 무시한다.


미리 정의된 식별자

다음 식별자는 전처리기가 정의한 것들이다.

식별자 의미
__LINE__ 현재 컴파일되는 줄 번호
__FILE__ 현재 컴파일되는 소스파일 이름
__DATE__ 파일이 컴파일되는 날짜. 'Mmm dd yyyy' 형식
__TIME__ 파일이 컴파일되는 시간. 'hh:mm:ss' 형식
__STDC__ 컴파일러가 ANSI 표준을 지키면 1이고 그 외의 경우는 0으로 정의된다.
__STDC_HOSTED__ 호스트 구현이면 1이고 호스트 구현이 아닌 경우는 0이다.
__STDC_VERSION__ 199901L로 정의되어 있다.


Notes