리얼 개발

[C++] 3. 제어문 (control_statement) 본문

C++/어려운 C++

[C++] 3. 제어문 (control_statement)

econo-my 2024. 6. 26. 19:10

저번 포스팅에서 변수가 저장되는 방법을 살펴보았다.

 

보통의 코드는 순차적으로 진행되지만 중간중간 흐름이 바뀔 수 있다. 이렇게 코드의 흐름을 바꾸는 명령을 control statement 즉, 제어문이라고 한다. 이번에는 분기(Branch)와 반복(Loop)에 대해 알아보겠다.

 

Branch

  • if
  • switch
  • goto
  • 논리, 관계연산자


모든 일이 순차적으로 흘러가지는 않는다. 적어도 C++ 프로그램에선 반드시 특정 동작을 선택할 때가 온다.

if문은 선택이 분기될 때 대표적으로 사용할 수 있는 제어문이다. ( ) 안 test-condition이 참이라면 { } 안의 코드가 실행되며, 거짓이라면 { } 안의 코드가 실행되지 않는다.

if(test-condition)
{
	//코드
}
else
{
	//코드
}

if(test-condition)
{
	//코드
}
else if(test-condition)
{
	//코드
}
else
{
	//코드
}

 

위와 같은 코드가 if문의 기본형이다. 앞서 컴퓨터는 0은 거짓, 0이 아닌 값을 참으로 인식한다고 하였다. 아래 코드를 보며 몇개가 출력될지 생각해보자. 

#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	if (true || false) cout << "1." << endl;
	if (1) cout << "2." << endl;
	if ('a') cout << "3." << endl;
	if (-1) cout << "4." << endl;
	if (0) cout << "5." << endl;

	if (0 == 0) cout << "6." << endl;
	if (5 < a < 10) cout << "7." << endl;
	if (0 != 1 && a == 10) cout << "8." << endl;
	return 0;
}

 

답은 5를 제외한 모든게 실행된다. if문의 test-condition에 들어오는 값이 0만 아니라면 반드시 몸체에 있는 코드가 실행되는 것이다. 'a'의 경우는 아스키 코드로 97이다. 97은 0이 아닌 값으로 if문 안의 코드가 실행된다.

관계 연산자와 논리 연산자는 반드시 참(1), 거짓(0) 값을 결과값으로 남긴다.

5 < a < 10 의 경우 정상적으로 작동되는 것처럼 보이지만 다르다. 먼저 5 < a 부분이 연산된다. a는 10이고 5보단 크니 관계 연산자의 결과에 따라 참이 나온다. 컴파일러는 참을 1로 치환하기에 5 < a 대신 1이 들어가게 된다. 그럼 1 < 10이 test-condition으로 남게 되고 결과적으로 참을 반환해 if문 안의 코드가 실행되게 된다.

어떤 수 사이에 있는 값을 찾고 싶다면 아래처럼 써주면 된다.

5 < a && a < 10

 

if 문은 여러가지 구조가 있다.

  • if - else
    • if 에서 조건을 만족하지 못한다면 else 로 이동한다.
    • else의 참 조건은 if 조건의 여집합이다.
  • if - else if - else
    • if 에서 조건을 만족하지 못한다면 else if 로 이동하며 else if는 여러개가 될 수 있다.
    • else의 참 조건은 if, else if 조건의 여집합이다.
  • if - else if
    • if, else if 에서 조건을 만족하지 못한다면 해당 조건문은 실행되지 않는다.

 

switch

switch문은 if - else if 여러개를 다른 형태로 바꿔놓은 형태라 생각하면 쉽다. 기본형은 다음과 같이 생겼다.

switch (integer-expression)

{

    case label1 : statement (s)

    case label2 : statement (s)

      ...

    default : statements (s)

}

switch문의 ( ) 안에는 정수형 변수, 상수만 들어갈 수 있다. 이 integer-expression과 같은 label이 있다면 코드를 실행하게 된다. 여기서 주의할 점은 label의 조건이 맞아 코드를 실행하게 되면 아래의 모든 코드를 실행하게 된다. 따라서 break; 문을 사용해 switch문을 끝내야 한다. default는 else와 같다. 모든 조건이 맞지 않다면 default 안에 있는 코드를 실행하게 된다.

 

#include <iostream>
using namespace std;

int main()
{
	int price;
	cin >> price;

	switch (price)
	{
	case 100:
		cout << "price : 100" << endl;
	case 500:
		cout << "price : 500" << endl;
	case 1000:
		cout << "price : 1000" << endl;
		break;
	default:
		cout << "price : ??" << endl;
		break;
	}
	return 0;
}

실행결과
100
price : 100
price : 500
price : 1000

 

if문은 내부적으로 조건이 맞는지 확인하기 위해 값을 증가해가며 맞는지 비교하는 작업이 필요하다. 하지만 switch문은 내부적으로 테이블을 만들어낸다. case 조건들이 저장되는 배열 형태이기 때문에 바로 접근 가능하다. 보통 4개 미만의 조건 블록이 있다면 if - else if 가 더 빠르며 그 이상으로 간다면 switch문이 더 빠르다.

 

goto

사용자가 직접 코드의 흐름을 바꿀 수 있는 키워드이다.

#include <iostream>
using namespace std;

int main()
{
    cout << "1" << endl;
    goto bye;
    cout << "2" << endl;
bye:

    cout << "3" << endl;
    return 0;
}

 

8번째 줄에서 goto를 만나게 되면 bye가 있는 12번째 줄로 이동하게 된다. 중간에 있는 문자열은 출력되지 않는다. 언뜻 보기에 편리해보이는 기능이지만 goto를 남발하다 보면 코드를 짜는 자기 자신도 코드의 흐름을 파악하기 힘들어질 수 있다. 그렇기에 많은 서적, 유명한 프로그래머들은 goto 사용을 금기시 하고 있지만 리눅스 커널에도 사용되며, if문의 내부코드를 살펴보면 goto로 이루어져 있는걸 보면 반드시 기피해야할 키워드는 아니며 어떻게 사용하느냐가 중요해보인다.

 

 

Loop

  • for
  • while

어떤 문장을 5번 출력하라는 요구사항을 받았다고 가정해보자. 그럼 프로그래머는 다음과 같이 코드를 짤 수 있다.

#include <iostream>
using namespace std;

int main()
{
    cout << "1" << endl;
    cout << "2" << endl;
    cout << "3" << endl;
    cout << "4" << endl;
    cout << "5" << endl;
    return 0;
}

 

5번 정도라면 직접 타이핑해 작성할 수 있다. 하지만 코드를 짤 때에는 최악의 상황을 가정하고 짜는 것이 좋다. 5번이 아니라 1억번이 될 수도 있는 작업이다. 이럴땐 반복문을 사용해보자.

 

for

for( 1. initialization; 2. test-expression; 4. update-expression )
{
    3. statement
}

for문의 기본형이다. 아래 코드를 보며 동작 순서를 알아보자.

#include <iostream>
using namespace std;

int main()
{
    for (int i = 0; i < 5; ++i)
    {
        cout << "For" << endl;
    }
    return 0;
}

1. int i = 0;  초기값을 설정한다.

2. i < 5;  i가 5보다 작은지 검사한다.

3. { } 안의 코드를 실행한다.

4. 코드가 끝나면 ++i (i 가 1 증가)

5. i가 다시 num보다 작은지 검사 후 참이면 코드 실행, 거짓이면 반복문에서 빠져나온다.

 

초기값을 설정할 때 for문에서 설정하게 되면 해당 for문 안에서만 변수를 사용할 수 있다. for문이 끝나면 해당 변수가 메모리에서 사라지기 때문에 for문 바깥에서 선언해 사용하는 것보다 조금이나마 메모리를 아낄 수 있다.

 

update-expression 자리에는 따로 중괄호가 필요한 키워드 제외한 입출력, 연산 등 다양한 코드가 들어갈 수 있다.

for (int i = 0; i < 5; ++i, cout << "잘가" << endl)
	{
		cout << "안녕?" << endl;
	}

 

이 외에도 ( ) 안의 다양한 표현식들이 존재한다.

for(int i = 0; ; ++i) //test-condition 을 비워도 된다.
{
    cout << "for" << endl;
    if(i > 10) break;
}

for(int i = 0; i < 10; ) //update-expression 을 비워도 된다.
{
    cout << "for" << endl;
    ++i;
}

for( ; ; ) //무한반복
{
    cout << "for" << endl;
}

 

while

while(test - condition)
{
    statements
}

while문의 기본형이다. 아래 코드를 보며 동작 순서를 알아보자.

 

#include <iostream>
using namespace std;

int main()
{
    int i = 0;
    while (i < 5)
    {
        cout << "While" << endl;
        ++i;
    }
    return 0;
}

1. while( ) 괄호 안 조건 검사

2. 참이라면 { } 안 코드 실행

3. 거짓이라면 빠져나온다.

 

while문에도 다른 형태가 있다.

int i = 0;
do
{
	cout << "안녕" << endl;
	++i;
} while (i < 5);

 

do - while 이라고 하며 do의 { } 안 코드는 반드시 한 번은 실행된다. 그 뒤 while의 조건이 맞다면 거짓이 될때까지 반복해서 실행한다.

 

후위 연산자, 전위 연산자

보다보면 i++ 대신 ++i를 사용하고 있다. 전위 연산자와 후위 연산자는 겉으로 보기에는 단순 1만 더하는 것 같지만 내부적으로는 다르게 동작하고 있다. int 와 같은 원시형이라면 전위나 후위나 속도 차이가 없지만 만약 사용자 정의형을 다룰때 후위가 전위보다 느려질 수 있다. 다음 코드를 봐보자. type은 임의의 클래스다.

 

type& type::operator++()   //++i
{
    this-> data += 1;
    return *this;
}

type type::operator++(int ignored_dummy_value)   //i++
{
    type tmp(*this);   
    ++(*this);
    return tmp;
}

 

전위연산은 단순히 1을 더하고 반환한다. 하지만 후위연산은 이루어지기 전의 값을 저장하기 위해 임시변수를 만든다. 이때 복사생성자를 실행시키는데 이때 복사생성자는 컴파일러에 의해 자동으로 최적화 되지 않는다.

따라서 속도의 차이도 있고 나중에 값을 사용하지 않을거면 굳이 ++i 대신 i++를 사용하여 값을 임시변수에 복사해놓고 현재의 값을 증가시키고, 임시변수를 버릴 필요가 없다.

'C++ > 어려운 C++' 카테고리의 다른 글

[C++] 5. 배열과 구조체 (Array and Struct)  (0) 2024.06.26
[C++] 4. 함수(Function)  (0) 2024.06.26
[C++] 2. Dealing with Data  (0) 2024.06.26
[C++] 1. Hello World  (0) 2024.06.26
[C++] 0. 소개  (2) 2024.06.26