복합문(Compound statement)

• 복합문(Compound statement), 또는 블록(Block)이라고 부르는 것은 0개 이상의 문장들을 하나로 묶은 그룹을 말해요.

• 아주 중요한 점은, 컴파일러(Compiler)가 이 묶음을 마치 하나의 문장인 것처럼 취급한다는 거예요.


• 우리가 함수를 만들 때 이미 블록을 사용해 왔답니다.

• 함수의 몸체(Body)가 바로 블록이니까요.


• 함수 안에 또 다른 함수를 정의할 수는 없지만, 블록 안에 또 다른 블록을 넣는 것은 가능해요. 이걸 '중첩된다'라고 표현하죠.

• 블록이 중첩되었을 때, 바깥쪽을 감싸고 있는 블록을 외부 블록(Outer block)이라 하고,

• 그 안에 들어있는 블록을 내부 블록(Inner block) 혹은 중첩 블록(Nested block)이라고 부릅니다.


조건에 따라 여러 문장 실행하기

• 기본적으로 if 문은 조건이 '참(True)'일 때 딱 하나의 문장만 실행하게 되어 있어요.

• 하지만 조건이 맞았을 때 여러 가지 일을 한꺼번에 처리하고 싶다면 어떻게 해야 할까요?

• 이때 그 '하나의 문장' 자리에 문장들의 묶음인 '블록'을 넣어주면 된답니다.


블록 중첩 레벨 (Nesting levels)

• 블록 안에 블록을 넣고, 그 안에 또 블록을 넣는 것도 가능할까요?네, 가능합니다!

• 여기서 중첩 레벨(Nesting level) 혹은 중첩 깊이(Nesting depth)라는 용어가 나와요.

• 이건 함수 내의 어떤 지점에서 '지금 내가 최대 몇 개의 블록 안에 감싸여 있는가'를 나타내는 숫자예요.


• C++ 표준(Standard)에 따르면 컴파일러는 무려 256단계의 중첩까지 지원해야 한다고 해요.

• 하지만 모든 컴파일러가(예: 작성 시점 기준 Visual Studio 등) 이를 완벽히 지원하는 건 아닐 수 있어요.


• 기술적으로 가능하다고 해서 깊게 만드는 게 좋을까요?

• 아닙니다. 중첩 레벨은 3단계 이하로 유지하는 것이 좋습니다.


• 함수의 길이가 너무 길어지면 여러 개의 작은 함수로 나누는 '리팩토링(Refactoring)'을 하듯이,

• 블록의 중첩이 너무 깊어지면 코드를 읽기가 아주 힘들어져요.

• 이럴 때도 가장 깊은 곳에 있는 블록들을 떼어내어 별도의 함수로 만드는 리팩토링을 하는 것이 좋습니다.

af5d2416ebd52cb46bba5455e2d23638325fbcb79e116d277409a38851767a4163a46df3a9f0b05ff0df50a1cfd1b837a7e6fcf3e767ae4b55c9ec2d657d806d

나만의 네임스페이스 정의하기

• C++에서는 namespace라는 키워드를 사용해 우리만의 네임스페이스를 만들 수 있어요.

• 이렇게 여러분이 직접 만든 네임스페이스를 흔히 사용자 정의 네임스페이스(User-defined namespaces)라고 불러요.

• 더 정확히는 '프로그램 정의 네임스페이스'라고 하는 게 맞겠지만요.


• 문법은 아주 간단해요.

• namespace 키워드를 쓰고, 그 뒤에 네임스페이스의 이름(식별자)을 적은 다음, 중괄호 { } 안에 내용을 넣으면 끝이에요.


namespace NamespaceIdentifier

{

    // 네임스페이스의 내용이 여기에 들어갑니다

}


• 역사적으로 네임스페이스 이름은 소문자로 쓰는 게 관례였고, 많은 스타일 가이드가 여전히 그걸 권장해요.

• 하지만 최근의 추세는 조금 다릅니다.


네임스페이스 이름을 대문자로 시작하는 것을 선호하는 이유

• 사용자가 정의한 '타입(Type)'들은 대문자로 시작하는 게 관례예요.

네임스페이스도 똑같이 대문자로 시작하면 일관성이 생기죠.

(특히 Foo::x처럼 쓸 때 Foo가 네임스페이스인지 클래스인지 헷갈리지 않게 해줘요.)


• 시스템이나 라이브러리가 제공하는 소문자 이름들과 겹치는 걸 막아줘요.

• C++20 표준 문서와 C++ Core 가이드라인 문서에서도 이 스타일을 사용하고 있어요.


범위 지정 연산자 (::)로 네임스페이스 접근하기

• 특정 네임스페이스 안에 있는 식별자를 찾으라고 컴파일러에게 지시하는 가장 좋은 방법은 범위 지정 연산자(::)를 사용하는 거예요.


std::cout << Goo::doSomething(4, 3) << '\n'; // Goo 네임스페이스에 있는 doSomething()을 사용


이름 없이 범위 지정 연산자 사용하기 (::identifier)

• 범위 지정 연산자 앞에 아무런 네임스페이스 이름도 적지 않을 수도 있어요 (예: ::doSomething).

• 이렇게 하면 컴파일러는 전역 네임스페이스(Global Namespace)에서 그 이름을 찾으라는 뜻으로 이해합니다.


네임스페이스 내부에서의 식별자 찾기

• 네임스페이스 안에서 어떤 식별자를 사용했는데, 앞에 범위 지정(Foo:: 같은 것)을 안 해줬다면 어떻게 될까요?

• 컴파일러는 일단 지금 있는 그 네임스페이스 안에서 정의를 찾으려고 노력해요.

• 만약 못 찾으면? 그 네임스페이스를 감싸고 있는 상위 네임스페이스를 차례대로 뒤져보고, 마지막으로 전역 네임스페이스까지 확인합니다.


#include <iostream>


void print() // 전역 네임스페이스의 print()

{

std::cout << " there\n";

}


namespace Foo

{

void print() // Foo 네임스페이스의 print()

{

std::cout << "Hello";

}


void printHelloThere()

{

print();   // Foo 네임스페이스 안의 print()를 먼저 호출함

::print(); // 전역 네임스페이스의 print()를 호출함

}

}


int main()

{

Foo::printHelloThere();

return 0;

}


네임스페이스 내용의 전방 선언(Forward declaration)

• 헤더 파일을 사용해서 전방 선언을 할 때도 주의할 점이 있어요.

• 네임스페이스 안에 있는 식별자를 전방 선언하려면,

• 선언도 똑같은 네임스페이스 안에서 해줘야 해요.


7de98868f5dc3f8650bbd58b3682746aea8d


7de98968f5dc3f8650bbd58b3682746b4e85


7de88068f5dc3f8650bbd58b3682706c1f7d


여러 개의 네임스페이스 블록

• 같은 이름의 네임스페이스 블록을 여러 곳(여러 파일 또는 같은 파일 내 여러 곳)에 나눠서 작성해도 괜찮아요.

• 모두 같은 네임스페이스의 가족으로 취급됩니다.


중첩된 네임스페이스 (Nested namespaces)

• 네임스페이스 안에 또 다른 네임스페이스를 만들 수도 있어요.

• C++17부터는 이렇게 중첩된 네임스페이스를 훨씬 간단하게 선언할 수 있어요.

• Goo가 Foo 안에 있으니까 Foo::Goo::add라고 주소를 길게 써서 접근해야 해요.


#include <iostream>


namespace Foo::Goo // Foo 안에 Goo가 있다는 것을 한 번에 표현 (C++17 스타일)

{

    int add(int x, int y)

    {

        return x + y;

    }

}


int main()

{

    std::cout << Foo::Goo::add(1, 2) << '\n';

    return 0;

}


네임스페이스 별칭 (Namespace aliases)

• 중첩된 네임스페이스 이름이 너무 길어서 타이핑하기 힘들다면, 네임스페이스 별칭을 써서 짧은 별명을 붙여줄 수 있어요.

• 별칭의 좋은 점은 코드를 유지 보수할 때도 드러납니다.


• 만약 Foo::Goo의 기능을 다른 곳(V2)으로 옮기고 싶다면, 별칭이 가리키는 곳만 V2로 바꿔주면 돼요.

• 코드 전체를 뒤져서 Foo::Goo를 일일이 바꿀 필요가 없죠.


#include <iostream>


namespace Foo::Goo

{

    int add(int x, int y)

    {

        return x + y;

    }

}


int main()

{

    namespace Active = Foo::Goo; // 이제 Active는 Foo::Goo를 가리킵니다


    std::cout << Active::add(1, 2) << '\n'; // 실제로는 Foo::Goo::add()가 호출됨


    return 0;

} // Active 별칭은 여기서 끝납니다


네임스페이스 사용 팁

• C++의 네임스페이스는 원래 정보를 계층적으로 정리하라고 만든 게 아니라, 주로 이름 충돌을 막기 위해 설계되었어요.

• 개인적인 작은 프로그램을 만들 때는 굳이 네임스페이스를 안 써도 괜찮아요.

• 하지만 외부 라이브러리를 많이 갖다 쓰는 큰 프로젝트라면 이름 충돌을 피하기 위해 코드를 네임스페이스로 감싸주는 게 좋습니다.


• 다른 사람에게 배포할 코드는 반드시 네임스페이스를 사용해서, 통합될 때 충돌이 나지 않게 해야 합니다.

• 보통 Foologger처럼 최상위 네임스페이스 하나면 충분해요.

• 이렇게 하면 사용자가 Foologger라고 쳤을 때 자동 완성 기능으로 라이브러리의 모든 기능을 쉽게 볼 수 있다는 장점도 있죠.

af5d2416ebd52cb46bba5455e2d23638325fbcb79e116d277409a38851767a4163a46df3a9f0b05ff0df50a1cfd1b837a7e6fcf3e767ae4b55c9ec2d657d806d

지역 변수 (Local variables)

• 지난 2챕터 — 지역 범위(Local scope) 소개에서 우리는 지역 변수(Local variable)에 대해 배웠습니다.

• 함수 내부(함수의 매개변수 포함)에 정의된 변수들을 말하죠.


• 우리는 2.5강에서 범위(Scope)라는 개념도 배웠어요.

• 어떤 식별자(변수 이름 등)의 범위(Scope)란 소스 코드 내에서 그 식별자에 접근할 수 있는 영역을 뜻해요.


• 접근할 수 있다면 "범위 내에 있다(In scope)"라고 하고,

• 접근할 수 없다면 "범위를 벗어났다(Out of scope)"라고 합니다.

• 범위는 컴파일 타임(Compile-time)에 결정되는 속성이라서, 범위를 벗어난 식별자를 사용하려고 하면 컴파일 에러가 발생해요.


지역 변수는 블록 범위(Block Scope)를 가집니다

• 지역 변수는 블록 범위(Block scope)를 가집니다.

• 즉, 변수가 정의된 지점부터 그 변수가 포함된 블록이 끝나는 지점까지만 유효하다는 뜻이에요.


지역 변수는 자동 저장 기간(Automatic Storage Duration)을 가집니다

• 변수의 저장 기간(Storage duration)은 변수가 언제 생성되고(메모리에 할당되고) 언제 소멸될지를 결정하는 규칙이에요.

• 대부분의 경우, 이 저장 기간이 변수의 수명(Lifetime)을 결정합니다.


• 지역 변수는 자동 저장 기간(Automatic storage duration)을 가집니다.

• 쉽게 말해, 변수가 정의되는 시점에 자동으로 생성되고, 정의된 블록이 끝날 때 자동으로 소멸된다는 뜻이죠.

• 이런 이유로 지역 변수를 자동 변수(Automatic variable)라고 부르기도 해요.


지역 변수는 연결(Linkage)이 없습니다

• 식별자(변수 이름 등)에는 연결(Linkage)이라는 또 다른 속성이 있어요.

• 어떤 식별자의 연결이란, 다른 범위에서 동일한 이름으로 선언된 식별자가 '같은 객체(또는 함수)'를 가리키는지 아닌지를 결정하는 성질이에요.

• 지역 변수는 연결이 없습니다(No linkage). 즉, 연결이 없는 식별자는 이름이 같더라도 각각의 선언이 서로 다른 고유한 객체나 함수를 의미합니다.


변수는 가장 제한적인 범위에서 정의해야 합니다

• 변수가 특정 중첩 블록 안에서만 사용된다면, 그 변수는 반드시 그 중첩 블록 안에서 정의해야 합니다.

• 변수의 범위를 제한하면 활성화된 변수의 수가 줄어들어 프로그램의 복잡도가 낮아집니다. 

• 블록 안에 정의된 변수는 그 블록(과 그 내부 블록) 안에서만 영향을 미치므로 프로그램을 이해하기가 더 수월해집니다.


#include <iostream>


int main()

{

    // 여기에 y를 정의하지 마세요.


    {

        // y는 오직 이 블록 안에서만 사용되므로, 여기서 정의합니다.

        int y { 5 };

        std::cout << y << '\n';

    }


    // 그렇지 않으면 y가 필요 없는 이 곳에서도 y를 사용할 수 있게 됩니다.


    return 0;

}

af5d2416ebd52cb46bba5455e2d23638325fbcb79e116d277409a38851767a4163a46df3a9f0b05ff0df50a1cfd1b837a7e6fcf3e767ae4b55c9ec2d657d806d