0개 이상의 문장들을 하나로 묶은 그룹을 말해요.if (1)
std::cout << "하나의 문장";
if (1) { //내부 블록
std::cout << "하나의 문장";
std::cout << "하나 이상의 문장";
}
256단계의 중첩까지 지원해야 한다고 해요.
namespace라는 키워드를 사용해 우리만의 네임스페이스를 만들 수 있어요.namespace 키워드를 쓰고, 그 뒤에 네임스페이스의 식별자를 적은 다음, 중괄호 { } 안에 내용을 넣으면 끝이에요.namespace NamespaceIdentifier
{
// 네임스페이스의 내용이 여기에 들어갑니다
}
::를 사용하는 거예요.std::cout << Goo::doSomething(4, 3) << '\n'; // Goo 네임스페이스에 있는 doSomething()을 사용
::doSomething 처럼 아무런 네임스페이스 이름도 적지 않을 수도 있어요.#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;
}
// add.h
#ifndef ADD_H
#define ADD_H
namespace BasicMath
{
// add() 함수는 BasicMath 네임스페이스의 일부입니다
int add(int x, int y);
}
#endif
// add.cpp
#include "add.h"
namespace BasicMath
{
// add() 함수를 BasicMath 네임스페이스 안에서 정의합니다
int add(int x, int y)
{
return x + y;
}
}
// main.cpp
#include "add.h" // BasicMath::add()를 위해 포함
#include <iostream>
int main()
{
std::cout << BasicMath::add(4, 3) << '\n';
return 0;
}
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;
}
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 별칭은 여기서 끝납니다

#include <iostream>
int main()
{
// 여기에 y를 정의하지 마세요.
{
// y는 오직 이 블록 안에서만 사용되므로, 여기서 정의합니다.
int y { 5 };
std::cout << y << '\n';
}
// 그렇지 않으면 y가 필요 없는 이 곳에서도 y를 사용할 수 있게 됩니다.
return 0;
}

#include 구문들 바로 아래의 전역 네임스페이스에 선언해요.int g_x; // 명시적 초기화 없음 (기본적으로 0으로 초기화됨)
int g_y {}; // 값 초기화 (결과적으로 0으로 초기화됨)
int g_z { 1 }; // 특정 값으로 리스트 초기화됨
#include <iostream>
const int g_x; // 오류: 상수 변수는 반드시 초기화되어야 합니다
constexpr int g_w; // 오류: constexpr 변수는 반드시 초기화되어야 합니다
const int g_y { 1 }; // const 전역 변수 g_y, 특정 값으로 초기화됨
constexpr int g_z { 2 }; // constexpr 전역 변수 g_z, 특정 값으로 초기화됨
void doSomething()
{
// 전역 변수는 파일 내 어디서든 보고 사용할 수 있습니다
std::cout << g_y << '\n';
std::cout << g_z << '\n';
}
int main()
{
doSomething();
// 전역 변수는 파일 내 어디서든 보고 사용할 수 있습니다
std::cout << g_y << '\n';
std::cout << g_z << '\n';
return 0;
}
// 여기서 g_y와 g_z의 범위가 끝납니다

{} 은 자신만의 유효 범위(Scope)를 가집니다. #include <iostream>
int main()
{ // 바깥쪽 블록 시작
int apples { 5 }; // 바깥쪽 블록의 apples 변수입니다.
{ // 안쪽(중첩된) 블록 시작
// 여기서는 아직 안쪽 apples가 정의되지 않았으므로, 바깥쪽 apples를 가리킵니다.
std::cout << apples << '\n'; // 바깥쪽 apples의 값(5)을 출력합니다.
int apples{ 0 }; // 안쪽 블록의 유효 범위에 새로운 apples를 정의합니다.
// 이제부터 apples는 안쪽 블록의 apples를 가리킵니다.
// 바깥쪽 블록의 apples는 일시적으로 가려집니다(hidden).
apples = 10; // 바깥쪽이 아닌, 안쪽 블록의 apples에 10을 할당합니다.
std::cout << apples << '\n'; // 안쪽 블록의 apples 값을 출력합니다.
} // 안쪽 블록이 끝나며 안쪽 apples 변수는 소멸(Destroy)됩니다.
std::cout << apples << '\n'; // 다시 바깥쪽 블록의 apples 값을 출력합니다.
return 0;
} // 바깥쪽 블록이 끝나며 바깥쪽 apples 변수도 소멸됩니다.
apples라는 이름의 변수를 선언했습니다. 5가 정상적으로 출력됩니다.apples 변수를 선언하면 상황이 달라집니다. apples라는 이름은 바깥쪽 변수가 아니라 새로 만든 '안쪽 변수'를 가리키게 됩니다.
static 키워드를 사용하면 됩니다.const와 constexpr 전역 변수는 기본적으로 내부 링크를 가집니다. static 키워드를 붙일 필요가 없어요.#include <iostream>
static int g_x{}; // 상수가 아닌 전역 변수는 기본적으로 외부 링크를 가지지만, static 키워드를 통해 내부 링크를 부여할 수 있습니다.
const int g_y{ 1 }; // const 전역 변수는 기본적으로 내부 링크를 가집니다.
constexpr int g_z{ 2 }; // constexpr 전역 변수는 기본적으로 내부 링크를 가집니다.
int main()
{
std::cout << g_x << ' ' << g_y << ' ' << g_z << '\n';
return 0;
}
static 키워드를 사용하면 내부 링크를 가지도록 설정할 수 있습니다.
extern 키워드를 사용할 수 있어요.extern으로 표시할 필요가 없습니다.int g_x { 2 }; // 상수가 아닌 전역 변수는 기본적으로 외부 연결을 가집니다 (extern 키워드 불필요)
extern const int g_y { 3 }; // const 전역 변수는 extern으로 정의하여 외부 연결을 갖게 만들 수 있습니다
extern constexpr int g_z { 3 }; // constexpr 전역 변수도 extern으로 정의할 수 있습니다 (하지만 별로 유용하지 않아요. 다음 섹션의 경고를 참고해 주세요)
int main()
{
return 0;
}
extern 키워드를 사용해요 (이때 초기화 값은 넣지 않습니다).#include <iostream>
extern int g_x; // 이 extern은 어딘가 다른 곳에 정의된 g_x라는 변수의 전방 선언입니다.
extern const int g_y; // 이 extern은 어딘가 다른 곳에 정의된 g_y라는 const 변수의 전방 선언입니다.
int main()
{
std::cout << g_x << ' ' << g_y << '\n'; // 2 3을 출력합니다.
return 0;
}
// 전역 변수 정의
int g_x { 2 }; // 상수가 아닌 전역 변수는 기본적으로 외부 연결을 가집니다.
extern const int g_y { 3 }; // 이 extern 키워드는 g_y에게 외부 연결을 부여합니다.
참고로 함수 전방 선언에는
extern키워드가 필요하지 않습니다. 컴파일러는 함수 본문(Body)이 제공되는지 여부에 따라 새로운 함수를 정의하는 것인지, 아니면 전방 선언을 하는 것인지 쉽게 구별할 수 있거든요.
ㅤ
반면, 변수 전방 선언은extern키워드가 반드시 필요합니다. 왜냐하면 초기화되지 않은 변수 정의와 변수 전방 선언이 겉보기에는 똑같이 생겼기 때문에 이를 구별해 주어야 하거든요.
// 상수 아님 (Non-constant)
int g_x; // 변수 정의 (초기화 없음)
int g_x { 1 }; // 변수 정의 (초기화 있음)
extern int g_x; // 전방 선언 (초기화 없음)
// 상수 (Constant)
extern const int g_y { 1 }; // 변수 정의 (const는 반드시 초기화가 필요합니다)
extern const int g_y; // 전방 선언 (초기화 없음)

- 기존 함수 안에 그 코드를 직접 작성하는 방법 (이를 '제자리(in-place)' 또는 '인라인(inline)'으로 코드를 작성한다고 해요).
- 해당 작업을 처리할 새로운 함수(필요하다면 하위 함수들까지)를 만드는 방법.
- 전체 프로그램의 맥락에서 코드를 읽고 이해하기가 훨씬 쉬워집니다.
- 함수는 본질적으로 모듈화되어 있기 때문에 재사용하기가 쉽습니다.
- 코드를 한 곳에서만 수정하면 되므로 업데이트하기가 편해집니다.
컴파일러에는 이런 오버헤드 비용을 피할 수 있는 방법이 있어요. inline이라는 키워드를 제공했어요. inline 키워드를 사용해 선언된 함수를 인라인 함수(Inline function) 라고 부릅니다. #include <iostream>
inline int min(int x, int y) // inline 키워드는 이 함수가 인라인 함수임을 의미해요
{
return (x < y) ? x : y;
}
int main()
{
std::cout << min(5, 6) << '\n';
std::cout << min(3, 2) << '\n';
return 0;
}
inline 키워드를 사용하지 않습니다.inline이라는 용어는 진화하여 "이 함수는 여러 번 정의해도 허용됩니다" 라는 뜻을 가지게 되었습니다. 컴파일러는 인라인 함수가 사용되는 각 번역 단위마다 그 함수의 '전체 정의'를 볼 수 있어야 합니다.
단, 한 번역 단위 내에서는 하나의 정의만 존재해야 합니다. 그렇지 않으면 컴파일 오류가 납니다.
(기본적으로 함수가 가지는) 외부 연결성을 가진 인라인 함수에 대한 모든 정의는 완전히 동일해야 합니다.
조금이라도 다르면 정의되지 않은 동작이 발생해요.
//main.cpp
#include <iostream>
double circumference(double radius); // 전방 선언(Forward declaration)
inline double pi() { return 3.14159; }
int main()
{
std::cout << pi() << '\n';
std::cout << circumference(2.0) << '\n';
return 0;
}
//math.cpp
inline double pi() { return 3.14159; }
double circumference(double radius)
{
return 2.0 * pi() * radius;
}
pi() 함수가 정의되어 있는 것을 눈여겨보세요. inline으로 표시되어 있기 때문에 문제가 되지 않고, 링커가 알아서 중복을 제거해 줍니다. pi() 정의에서 inline 키워드를 지워버린다면, 인라인이 아닌 함수를 중복 정의한 것이 되므로 ODR 위반 에러가 발생할 거예요.#include 할 수 있으니까요. 
원주율 파이(pi) 아보가드로 수 가 될 수도 있고, 특정 프로그램에 맞춰진 "조정용" 값 마찰 계수 중력 계수 이 될 수도 있죠.constexpr 변수들을 inline으로 만들면, 헤더 파일에 한 번 정의해 두고 필요한 모든 .cpp 파일에 자유롭게 #include 할 수 있습니다. 이 방법을 사용하면 ODR 위반도 피하고, 변수가 불필요하게 중복 복사되는 단점도 해결할 수 있습니다!
static 키워드를 사용하면, 그 지속 기간이 자동에서 정적(Static)으로 바뀝니다. #include <iostream>
void incrementAndPrint()
{
static int s_value{ 1 }; // static 키워드를 통해 정적 지속 기간을 가집니다. 이 초기화 코드는 단 한 번만 실행됩니다.
++s_value;
std::cout << s_value << '\n';
} // s_value는 여기서 소멸되지 않지만, 범위를 벗어나므로 접근할 수는 없게 됩니다.
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
return 0;
}
s_value는 static으로 선언되었기 때문에 프로그램이 시작될 때 한 번만 생성됩니다.0으로 초기화된답니다.s_value는 1이라는 상수 표현식 초기화 값을 가지기 때문에 프로그램 시작 시점에 초기화됩니다.핵심 통찰 (Key insight)
- 정적 지역 변수는 여러 번의 함수 호출 사이에서도 지역 변수의 값을 기억해야 할 때 사용합니다.
- 정적 지역 변수는 꼭 초기화해 주세요.
- 정적 지역 변수는 전체 프로그램 동안 한 번만 초기화되며, 이후 함수 호출에서는 초기화되지 않고 기존 값을 유지합니다.
const constexpr로 선언할 수 있습니다.const 정적 지역 변수가 유용하게 쓰이는 대표적인 경우는, 함수에서 어떤 상수 값을 써야 하는데 그 객체를 생성하거나 초기화하는 비용이 매우 클 때입니다.const/constexpr 정적 지역 변수를 사용하면, 비용이 많이 드는 객체를 딱 한 번만 생성해서 초기화한 후, 함수가 호출될 때마다 계속 재사용할 수 있습니다.
std::를 반복해서 타이핑하는 수고를 덜어주는 첫 번째 방법은 using 선언문을 활용하는 것입니다.#include <iostream>
int main()
{
using std::cout; // 이 using 선언은 컴파일러에게 cout이 std::cout을 의미한다고 알려줍니다.
cout << "Hello world!\n"; // 따라서 여기서는 std:: 접두사가 필요하지 않아요!
return 0;
} // 이 using 선언은 현재 스코프가 끝나는 시점에서 효력이 만료됩니다.
#include <iostream>
int main()
{
using namespace std; // 이제 std 네임스페이스의 모든 이름을 한정자 없이 접근할 수 있습니다.
cout << "Hello world!\n"; // 따라서 여기서는 std:: 접두사가 필요하지 않아요.
return 0;
} // 이 using 지시자는 현재 스코프가 끝나는 시점에서 만료됩니다.
using namespace std; 라는 지시자는 컴파일러에게 std 네임스페이스 안의 모든 이름들을 현재 스코프(이 경우 main() 함수 내부)에서 한정자 없이 접근할 수 있도록 하라고 지시합니다.using 선언이나 using 지시자가 어떤 블록 {} 안에서 사용되었다면, 그 이름들은 오직 해당 블록 안에서만 유효합니다#include 지시자 앞에서는 절대로 using 문을 사용해서는 안 됩니다.#include 하는 모든 다른 파일들도 강제로 그 using 문을 갖게 됩니다.