devseop08 님의 블로그

[Basic] 연산자와 흐름 제어 구문 본문

Language/C++

[Basic] 연산자와 흐름 제어 구문

devseop08 2025. 7. 16. 21:50

연산자

연산자의 우선 순위 및 결합 방향

산술 연산자

  • 사칙 연산자

  • 나머지 연산자

  • 증감 연산자

증감 연산자와 사이드 이펙트

  • 전위 증감 연산자는 값을 증가 또는 감소시킨 후에 값을 평가한다.
int x = 5;
int y = ++x; // x = 6, y =6
  • 후위 증감 연산자를 사용하는 경우엔 컴파일러가 변수의 임시 복사본을 만든 후에 원래 변수를 증가 또는 감소시키고 변수의 임시 복사본을 평가한다.
  • 평가가 종료되면 임시 복사본을 삭제시킨다.
int x = 5;
int y = x++; // x = 6, y = 5
int x = 5, y = 5;
std::cout << x << " " << y << "\n";
std::cout << ++x << " " << --y << "\n";
std::cout << x << " " << y << "\n";
std::cout << x++ << " " << y-- << "\n";
std::cout << x << " " << y << "\n";

// 5 5
// 6 4
// 6 4
// 6 4
// 7 3
사이드 이펙트
  • 사이드 이펙트란, 함수나 표현식이 자신의 본래 목적(값 반환) 외에 프로그램의 상태나 외부 세계에 영향을 미치는 모든 행위를 말한다.
  • 즉, 사이드 이펙트란 함수 또는 표현식이 특정 상태를 수정하거나 입력 또는 출력하거나 다른 함수를 호출하는 것을 말한다.
  • 사이드 이펙트는 대부분 유용하다.
  • 대표적인 사이드 이펙트 예시
예시 설명
전역 변수 수정 함수가 전역 변수 값을 바꾼다. 예: globalValue++
I/O 수행 파일 쓰기, 콘솔 출력(print()), 네트워크 요청 등
예외 발생 함수 내에서 예외를 던지면 프로그램 흐름에 영향을 준다
외부 시스템 상태 변경 DB에 insert/update, 다른 서비스에 API 호출 등
뮤텍스(lock) 획득 스레드 동기화를 위해 잠금을 걸고 해제
x = 5; 
++x;
std::out << x;
  • 위 예제에서 할당 연산자(=)는 x의 값을 변경하는 사이드 이펙트를 가진다.
  • 명령문이 실행 완료된 후에도 x는 값이 5이다.
  • 연산자 ++는 x값을 증가 시키는 사이드 이펙트가 있다.
  • x의 출력은 콘솔을 수정하는 사이드 이펙트가 있다.
  • 사이드 이펙트는 예상치 못한 문제를 일으킬 수 있다.
  • 정의되지 않은 동작이란 프로그래밍 언어에서 동작이 제대로 정의되지 않은 코드를 실행한 결과를 말한다.
int add(int x, int y)
{
    return x + y;
}

int main()
{
    int x = 5;
    int value = add(x, ++x); 

    std::cout << value; 
    return 0;
}
  • C++은 함수 인수를 평가하는 순서를 정의하지 않았다.
  • 왼쪽 인수를 먼저 평가하면 add(5, 6)을 호출하여 11을 반환하고
  • 오른쪽 인수를 먼저 평가하면 add(6, 6)을 호출하여 12를 반환한다.
  • add 인수 중 하나가 사이드 이펙트를 가지고 있으므로 이런 문제가 생기는 것이다.
int main()
{
    int x = 1;
    x = x++;
    std::cout << x;

    return 0;
}
  • 할당 연산(=) 전에 ++ 연산자가 먼저 적용되면 1이 출력(후위 연산자 ++는 변수 x의 임시 복사본을 만들고 원본을 증가시키는데, 할당 연산 시엔 변수 x의 임시 복사본을 평가하기 때문)
  • 할당 연산(=) 후에 ++ 연산자가 나중에 적용되면 2가 출력( x=x로 평가한 다음 ++ 연산자를 적용하면 x가 1에서 2로 증가하여, 출력 시에 2로 평가되기 때문)

논리 연산자

관계 연산자

  • 좌측 피연산자가 우측 피연산자 보다 크다(>), 작다(<), 크거나 같다(>=), 작거나 같다(<=), 같다(==), 같지 않다(!=) 등의 관계에 대한 참 또는 거짓을 계산한다.

비트 단위 논리 연산자

  • 피연산자의 각 비트 단위로 논리합(|), 논리곱(&), 배타적 논리합(^), 부정(~) 연산을 한다.
  • 만약 x와 y가 unsigned char형 변수면서 각각 0x35(2진수 00110101), 0xf0(11110000)일 때, 비트 단위 논리 연산의 결과

비트 단위 이동 연산자

  • 좌측 피연산자의 각 비트를 우측 피연산자로 지정된 비트 수만큼 좌(<<)또는 우(>>)로 이동시킨다.
  • 좌측 이동 연산을 하는 경우, 우측의 비는 비트를 0으로 채운다.
  • 우측 이동 연산을 하는 경우, unsigned 정수형이거나 signed 정수형이면서 저장된 값이 음수가 아니라면 좌측의 비는 비트를 0으로 채운다.
  • 반면 음수가 저장된 signed 정수형인 경우엔 좌측의 비는 비트를 부호를 나타내는 비트로 채운다.
  • unsigned char형 변수 x와 signed형 변수 y의 값이 모두 0x96(2진수 10010110)일 때, 비트 이동 연산의 결과

대입 연산자

  • 대입 연산자(=)는 우측 피연산자에 해당되는 수식의 값을 좌측 피연산자에 저장한다.
  • 좌측 피연산자는 변수와 같이 값을 저장하는 l-value여야 한다.
  • 우측 피연산자에는 저장할 값을 제공하는 변수, 상수 또는 이들을 포함하는 수식인 r-value가 사용될 수 있다.
  • 대입 연산자의 결과는 l-value에 저장되는 값이며, 이 값이 또 다른 l-value에 저장되도록 대입 연산자를 연속하여 사용할 수 있다.
int a, b;
a = b = 10; // b = 10의 결과 b에 저장되는 10을 a에 저장

조건 연산자

기타 연산자

자료형의 변환

  • 형 변환이 일어나는 상황
      1. 값을 저장할 변수의 자료형과는 다른 자료형의 값으로 초기화 또는 할당하는 경우
      double d(3);
      d = 6;
      1. 함수 매개 변수의 자료형과는 다른 자료형의 값을 전달하는 경우
      void doSomething(long l)
      {
      
      }
      
      doSomething(3);
      1. 함수에서 함수의 반환값 자료형과는 다른 자료형의 값을 반환하는 경우
      float doSomething() 
      {
        return 3.0; // return double type value; 
      }
      1. 서로 다른 자료형의 피연산자가 있는 이항 산술 연산자를 사용하는 경우
      double division = 4.0 / 3;
  • 위와 같은 모든 경우들에서 C++은 데이터를 한 자료형에서 다른 자료형으로 변환하기 위해 형변환을 한다.
  • 기본적인 형 변환의 두 가지 유형
      1. 암시적 형변환: 컴파일러가 자동으로 하나의 기본 자료형을 다른 자료형으로 변환한다. 자동 형 변환이라고 불린다.
      1. 명시적 형변환: 형 변환을 하기위해 형 변환 연산자를 사용한다.

암시적 형 변환

  • 암시적인 형 변환은 하나의 기본 자료형이 예상되지만, 다른 기본 자료형이 제공될 때마다 수행된다.
  • 변환을 수행하는 방법을 컴파일러에 명시적으로 알려주지 않는다.
숫자 승격
  • 한 자료형의 값이 더 큰 유사한 자료형의 값으로 변환하는 경우, 숫자 승격이 일어난다.
  • 숫자 승격은 항상 안전하며, 데이터 손실이 발생하지 않는다.
long l(64);
double d(0.12f);
숫자 변환
  • 큰 자료형의 값이 더 작은 유사한 자료형의 값으로 변환하거나 서로 다른 자료형 간에 변환이 일어나는 경우 숫자 변환이 일어난다.
short s = 2; // 더 작은 유사한 자료형의 값으로 변환
double d = 3; // 서로 다른 자료형 간에 변환
  • 항상 안전한 숫자 승격과는 달리 숫자 변환은 데이터가 손실되거나 그렇지 않을 수도 있다.
  • 이로 인해 숫자 변환이 일어나는 경우엔 컴파일러가 항상 경고를 보낸다.
  • 숫자 변환 시 데이터 손실이 일어나는 경우
      1. 범위가 충분히 크기 않은 자료형으로 변환하는 경우
      int main()
      {
      int i = 30000;
      char c = i; // int에서 char로 숫자 변환: 30000을 저장하고 표현하기엔 부족
      
      std::cout << static_cast<int>(c);
      
      return 0;
      }
      1. 부동 소수점 숫자에서 정수로 변환하는 것은 분수 값을 모두 손실시킨다.
      int i = 3.5;
      std::cout << i;
      산술 표현식 평가
  • 표현식을 평가할 때, 컴파일러는 각 표현식을 개별 하위 표현식으로 나눈다.
  • 산술 연산자의 피연산자는 모두 같은 자료형이어야 하므로 컴파일러는 산술 연산을 평가할 시 다음과 같은 규칙을 사용한다.
      1. 피연산자의 자료형이 int보다 작은 정수인 경우, int또는 unsigned int로 승격된다.
      1. 피연산자의 자료형이 여전히 같지 않으면, 컴파일러는 피연산자 중 더 높은 우선순위의 자료형으로 선언된 피연산자의 자료형으로 다른 피연산자를 암시적 형 변환을 통해 일치시킨다.
  • 피연산자 자료형의 우선순위
    • long double(highest)
    • double
    • float
    • unsigned long long
    • long long
    • unsigned long
    • long
    • unsigned int
    • int(lowest)

명시적 형변환

  • 10 / 4 를 실행할 경우 해당 산술 연산에서는 숫자 승격이 일어나지 않는다. 왜냐하면 10과 4 모두 정수 int 자료형의 값으로 서로 동일한 자료형의 값이기 때문이다.
  • 따라서 10 / 4 의 결과는 정수 2로, 부동 소숫점 숫자가 아니기 때문에 float 자료형의 변수에 대입을 하더라도 소수 부분을 정확히 표현한 값이 저장되지 않고, 2.0이 저장된다.
  • 이렇게 정수가 아닌 부동 소숫점으로 연산함을 표현하는 것이 필요한 경우에 사용할 수 있는 형 변환이 명시적 형 변환이다.
C - style cast
int i1 = 10;
int i2 = 4;
float f = float(i1) / i2;
static_cast
int i1 = 10;
int i2 = 4;
float f = static_cast<float>(i1) / i2; // result : 2.5
  • static_cast는 하나의 자료형을 다른 자료형으로 변환하는 데 가장 좋은 방법이다.
  • static_cast의 주요 장점은 컴파일 타임에 타입 검사를 제공하여 부주의한 오류를 만들기가 더 어렵다는 것이다.
  • satic_cast는 C-style cast보다 덜 강력하여서 실수로 const를 제거하는 등 의도하지 않은 작업을 할 확률을 줄여준다.
  • 암시적 형 변환 중에서 숫자 변환이 일어날 때 컴파일러는 경고문을 띄우는 데, 이 때 암시적 형 변환이 일어나게 두는 것이 아닌 static_cast를 이용해서 명시적 형 변환을 하면 컴파일러가 경고문을 띄우지 않는다.

흐름 제어 구문

조건문

  • 조건문은 지정된 조건에 따라 실행 흐름을 제어하는 문장이다.
  • C++에서 사용할 수 있는 조건문은 if문과 switch 문이 있다.
if 문 : 조건의 참, 거짓에 따라 문장을 선택적으로 실행할 수 있도록 하는 구문
if(조건)
    문장 1; // 조건이 참일 때 실행할 문장
else 
    문장 2; // 조건이 거짓일 때 실행할 문자
if(a < b) {
    int t = a; // t는 블록 내에서만 사용되는 지역 변수
    a = b;
    b = t;
}
switch 문 : 만약 정수 자료형에 해당되는 수식의 값에 따라 해당되는 처리를 하고자 할 때에는 if문 대신 switch 문을 사용하면 편리하다.
switch(정수형_수식) {
case 값1:
    문장1; // 정수형 수식의 값이 값1일 때 실행할 문장들 나열
    break; // switch 문을 빠져 나가게 함
case 값2:
    문장2; // 정수형 수식의 값이 값2일 때 실행할 문장들 나열
    break; // switch 문을 빠져 나가게 함
case 값3:
    문장3; // 정수형 수식의 값이 값3일 때 실행할 문장들 나열
    break; // switch 문을 빠져 나가게 함
...
default: // 정수형 수식의 값과 일치하는 case 값이 없을 때
    문장 n; // 실행할 문장들을 나열
}
switch(score/10){
case 10:          // score가 100이면 아래로 진행
cases 9:          // score가 90~100인 경우
    grade = 'A';
    break;
cases 8:          // score가 80~89인 경우
    grade = 'B';
    break;
cases 7:          // score가 70~79인 경우
    grade = 'C';
    break;
cases 6:          // score가 60~69인 경우
    grade = 'D';
    break;
default:          // 그 외의 모든 경우
    grade = 'F';
}

반복문

  • 반복문은 일정 범위의 문장을 반복하여 실행하고자 할 때 사용하는 구문으로, for, while, do - while 구문이 있다.
    for 문
for(초기화; 반복 조건; 증감)
    문장;
int val, total = 0;
for(int i = 0; i < 10; i++) {
    std::cin >> val;
    total += val;
}
for(원소 선언: 데이터 집합)
    문장;
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = 0;
for(int a: arr)
    sum += a;
std::cout << "합계 = " << sum << endl;
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for(int &a: arr) {
    ++a;
    std::cout << "변한 원소값 = " << a << endl;
}
while 문
while(반복 조건)
    문장;
int val, total = 0;
cin >> val;
while(val > 0) {
    total += val;
    cin >> val;
}
do - while 문
do {
    문장;
}while (반복 조건);
break 및 continue 명령
  • 반복문의 흐름은 필요에 따라 break 및 continue 명령으로 바꿀 수 있다.
  • break 명령은 반복문을 빠져나가게 하며, continue 명령은 반복문 블록의 나머지 부분을 건너뛰게 한다.
while (조건) {
    반복 문장1;
    반복 문장2;
    break; // 아래 문장을 실행하지 않고 while 문을 빠져나가고 다음 문장 실행
    반복문장3;
    반복문장4;
}
다음문장;
while (조건) {
    반복 문장1;
    반복 문장2;
    continue; // 아래 문장을 실행하지 않고 while문의 조건 검사로 이동
    반복문장3;
    반복문장4;
}
다음문장;
goto 문
  • goto 문은 CPU가 코드의 다른 지점으로 점프하도록 하는 제어 흐름 명령문
  • 점프하는 지점은 문 라벨(statement label) :을 이용하여 식별된다.
#include <iostream>
#include <cmath> // for sqrt() function

int main()
{
    double x;
tryAgain: // this is a statement label
    std::cout << "Enter a non-negative number"; 
    std::cin >> x;

    if (x < 0.0)
        goto tryAgain; // this is the goto statement

    std::cout << "The sqrt of " << x << " is " << sqrt(x) << std::endl;
    return 0;
}
  • 만약 음수를 입력하면 프로그램은 goto 문을 사용하여 tryAgain 라벨로 다시 이동한다.
  • goto 문과 문 라벨 :은 같은 함수 내에 나타나야 한다.
  • goto문과 동일한 블록에서 초기화된 변수를 건너뛰어 점프할 수는 없다.
int main()
{
    goto skip; // invalid forward jump
    int x = 5;
skip:
    x += 3; // what would this even evaluate to?
    return 0;
}
  • 일반적으로 goto의 사용은 스파게티 코드를 유발하므로 C++에서 해로운 것으로 간주한다

'Language > C++' 카테고리의 다른 글

[OOP] 상속과 다형성  (1) 2025.07.16
[Basic] 함수와 스코프  (0) 2025.07.16
[Basic] 구조체와 열거형  (1) 2025.07.15
[OOP] 상속과 다형성  (0) 2025.07.15
[Basic] 상수와 리터럴  (0) 2025.07.07