기타 연산자
대입·조건·sizeof·주소·간접 참조·콤마 연산자의 평가 순서, 결과 타입과 값 범주, 사용 전제를 구분합니다.
이 장에서는 값을 저장하는 대입, 두 경로 중 하나를 고르는 조건 연산자, 타입과 객체의 크기를 묻는 sizeof, 주소와 객체를 오가는 &·*, 두 표현식을 순서대로 잇는 콤마 연산자를 다룹니다.
같은 기호라도 선언 문법과 표현식의 연산자는 구분해야 합니다. 또한 결합 방향은 식이 묶이는 방법이고, 평가 순서는 실제 계산과 부수 효과의 순서입니다.
초기화와 대입
선언과 함께 첫 값을 주는 코드는 초기화입니다. 이미 존재하는 객체의 값을 바꾸는 = 표현식이 대입입니다.
int age = 20; // copy-initialization
age = 21; // assignment expression기본 제공 대입 연산자는 수정 가능한 lvalue를 왼쪽 피연산자로 요구합니다. 오른쪽 피연산자는 왼쪽 피연산자보다 먼저 평가되며, 오른쪽 값은 왼쪽 객체의 타입으로 변환된 뒤 저장됩니다. 대입식의 결과는 왼쪽 객체를 가리키는 lvalue입니다.
대입 연산자는 오른쪽에서 왼쪽으로 묶입니다. 따라서 다음 식은 x = (y = (z = 10))으로 해석됩니다. 이것은 모든 하위 표현식을 일반적으로 오른쪽부터 평가한다는 뜻이 아닙니다.
int x = 0;
int y = 0;
int z = 0;
x = y = z = 10;
int n = 0;
double d = 0.0;
d = n = 3.8; // n은 3, d는 3.0두 번째 연쇄에서는 3.8이 int로 변환되어 n에 저장되고, n = 3.8의 결과가 다시 읽혀 double로 변환됩니다. 각 대입 단계가 왼쪽 타입의 변환 경계입니다.
ASSIGNMENT IS A STORE CONTRACT
오른쪽 결합은 묶임을 정하고, 각 대입은 오른쪽 값 계산·타입 변환·저장·왼쪽 lvalue 결과를 차례로 만듭니다.
x = y = z = 10의 네 단계
구문 묶임
x = (y = (z = 10))· 오른쪽에서 왼쪽으로 그룹화합니다.z = 1010을z타입으로 변환해 저장하고, 식은z를 가리키는 lvalue가 됩니다.y = z직전 결과에서 값을 읽어
y타입으로 변환·저장하고, 결과는ylvalue입니다.x = y같은 계약으로
x를 갱신합니다. 일반적인 모든 하위식의 평가 방향을 뜻하지는 않습니다.
단계마다 변환
d = n = 3.8에서 먼저 n은 3, 이어 d는 3.0을 저장합니다.
결과는 왼쪽 lvalue
기본 제공 대입식은 왼쪽 피연산자의 타입이며 바로 그 객체를 가리킵니다.
복합 대입의 차이
E1 op= E2는 대응하는 대입과 같은 동작을 하되 위치 표현식 E1을 한 번만 평가합니다.
int age = 20;은 초기화이고, 이미 존재하는 객체에 쓰는 age = 21;이 대입 표현식입니다.
복합 대입
기본 제공 E1 op= E2는 E1 = E1 op E2와 같은 동작을 하되 E1을 한 번만 평가합니다. 단순한 문자열 치환이나 성능 힌트가 아니라, 왼쪽 위치를 한 번만 찾는 언어 계약입니다.
std::size_t index();
int values[4]{};
values[index()] += 3; // index()는 한 번 호출된다+=, -=, *=, /=, %=와 비트 복합 대입은 해당 연산의 타입·범위 전제를 그대로 따릅니다. 예를 들어 0으로 나누기, 유효하지 않은 시프트 수, 표현 범위를 벗어난 signed 산술은 복합 대입으로 바꾼다고 안전해지지 않습니다.
조건 연산자
조건 연산자 condition ? second : third는 첫 피연산자를 bool로 문맥 변환합니다. 조건을 먼저 평가한 뒤 두 번째와 세 번째 피연산자 중 선택된 하나만 평가합니다.
std::string label = score >= 60 ? "합격" : "불합격";
int& selected = use_left ? left : right;
selected = 0; // 선택된 객체를 바꾼다결과가 언제나 새 임시 값인 것은 아닙니다. 두 선택지가 같은 타입·값 범주의 glvalue라면 조건식도 그 값 범주를 유지할 수 있습니다. 그 밖에는 두 피연산자의 변환 규칙으로 공통 결과 타입을 정합니다. 복잡한 부수 효과가 두 분기에 섞이면 if 문이나 이름 있는 함수로 분리하는 편이 낫습니다.
sizeof
sizeof(type) 또는 sizeof expression은 해당 타입 객체가 차지하는 바이트 수를 std::size_t prvalue로 만듭니다. 표현식 피연산자는 미평가 피연산자이므로 일반적인 부수 효과가 실행되지 않습니다.
int value = 7;
static_assert(sizeof(value++) == sizeof(int));
// value++는 실행되지 않아 value는 여전히 7이다.
int numbers[5]{};
static_assert(sizeof(numbers) == 5 * sizeof(numbers[0]));sizeof(char)는 항상 1이지만 C++의 1바이트가 반드시 8비트라는 뜻은 아닙니다. 다른 기본 타입의 크기와 클래스 패딩은 구현에 따라 달라질 수 있습니다.
배열 피연산자에는 배열-포인터 변환이 적용되지 않으므로 실제 배열의 전체 크기를 얻습니다. 그러나 함수 매개변수로 받은 포인터에 sizeof를 적용하면 포인터 크기만 얻습니다. 배열 요소 수는 가능하면 std::size(numbers)처럼 의도를 직접 드러내는 표준 도구를 사용합니다.
주소 취득과 간접 참조
표현식의 단항 &는 객체나 함수의 주소를 가리키는 포인터 값을 만듭니다. 표현식의 단항 *는 포인터를 따라가 그 대상 객체나 함수를 나타내는 lvalue를 만듭니다.
int value = 100;
int* pointer = &value;
int& alias = *pointer;
alias = 200; // value도 200int* pointer의 *는 포인터 타입을 만드는 선언자 일부이고, *pointer의 *는 간접 참조 연산자입니다. 간접 참조에는 포인터가 허용된 대상을 가리킨다는 전제가 필요합니다. null, 수명이 끝난 객체, 또는 그 밖의 유효하지 않은 대상을 값처럼 읽거나 쓰면 정의되지 않은 동작으로 이어질 수 있습니다.
주소를 출력한 문자열의 형식과 실제 값은 구현과 실행 환경에 따라 달라질 수 있으므로 특정 16진수 주소를 프로그램 계약으로 삼지 않습니다.
OBJECT ↔ POINTER VALUE
&는 객체를 가리키는 포인터 값을 만들고, *는 포인터 값이 가리키는 대상의 lvalue를 만듭니다.
대상 객체
int value = 100;은 저장 공간과 수명을 가진 객체입니다.포인터 값 생성
&value는value를 가리키는int*prvalue를 만듭니다.포인터 객체 저장
int* pointer = &value;가 그 포인터 값을 저장합니다.대상 lvalue 복구
*pointer는value를 나타내므로 읽거나 대입할 수 있습니다.
- 실제 저장 객체
- 주소와 대상 사이의 변환
간접 참조의 전제는 포인터가 허용된 대상을 가리키는 것입니다. null 또는 그 밖의 유효하지 않은 포인터를 값처럼 읽거나 쓰지 않습니다.
콤마 연산자
기본 제공 콤마 연산자 left, right는 왼쪽 피연산자를 먼저 평가하고 그 값을 버린 뒤 오른쪽 피연산자를 평가합니다. 결과의 타입·값·값 범주는 오른쪽 피연산자의 것을 그대로 따릅니다.
int x = 10;
int y = 20;
int result = (++x, y += 5, x + y); // x=11, y=25, result=36
for (int i = 0, j = 10; i < 5; ++i, --j) {
// 선언부의 첫 콤마는 선언자 구분자,
// 반복식의 콤마는 기본 제공 콤마 연산자다.
}함수 인자 목록, 선언자 목록, 초기화 목록, 템플릿 인자 목록의 콤마는 문법 구분자이며 여기서 설명한 콤마 연산자가 아닙니다. 그런 문맥에서 콤마 연산자를 쓰려면 괄호로 하나의 표현식임을 밝혀야 합니다.
EVALUATION → RESULT → PRECONDITION
기호 이름보다 먼저 실제로 평가되는 피연산자, 만들어지는 결과, 안전하게 쓰기 위한 전제를 확인합니다.
E1 = E2 · 대입
평가 · E2가 E1보다 먼저입니다.
결과 · 변환 후 저장하며 결과는 E1 lvalue입니다.
c ? a : b · 조건
평가 · c 뒤 선택된 한 분기만 평가합니다.
결과 · 두 분기의 타입·값 범주 규칙으로 정합니다.
sizeof e · 크기
평가 · 표현식 e는 미평가 피연산자입니다.
결과 · 바이트 수인 std::size_t prvalue입니다.
&object · 주소
입력 · 객체나 함수를 나타내는 lvalue입니다.
결과 · 대상을 가리키는 포인터 prvalue입니다.
*pointer · 간접 참조
평가 · 포인터 값을 따라 대상을 찾습니다.
결과 · 대상 타입의 lvalue이며 유효한 대상이 전제입니다.
left, right · 콤마
평가 · 왼쪽을 끝낸 뒤 값을 버리고 오른쪽을 평가합니다.
결과 · 오른쪽의 타입·값·값 범주를 유지합니다.
이 비교는 기본 제공 연산자 계약입니다. 함수 인자·선언자·초기화 목록의 콤마는 구분자이며, 결합 방향만으로 일반 평가 순서를 추론하지 않습니다.
짧은 식이라도 먼저 무엇을 평가하는가, 어떤 결과 타입과 값 범주를 만드는가, 어떤 객체를 바꾸는가, 유효성 전제가 무엇인가를 분리해 읽습니다. 우선순위와 결합 방향은 다음 장에서 별도로 다룹니다.