CPP 기본 개념
CPP 에서 흔히 사용되는 명령어들의 해석 정리
CPP 기본 개념
C++ 핵심 개념 및 문법 요약서
1. 네임스페이스와 헤더 파일 제약
헤더 파일에서 using namespace std;를 사용하지 않는 이유
- 이름 충돌 방지
- 헤더 파일은 다른 수많은 소스 파일(
.cpp)에 포함(include)된다. 헤더에서 네임스페이스를 열어버리면 이를 포함하는 모든 파일이 강제로std네임스페이스의 영향을 받게 되어 논리적 범위가 오염되고 이름 충돌 가능성이 극대화된다.
- 헤더 파일은 다른 수많은 소스 파일(
- 리소스 격리
- 특정 네임스페이스 내에서 식별자 문제가 발생하더라도, 다른 독립된 네임스페이스 및 전역 공간에 영향을 주지 않도록 논리적으로 분리 차단한다.
- 접근 통제 및 명확성
- 무분별한 네임스페이스 개방을 제한함으로써 코드가 어떤 라이브러리의 기능을 참조하는지 출처를 명확히 규정한다.
string 등에 std::를 명시하는 이유
std::string,std::vector와 같이 접두어를 명시하면 STL(Standard Template Library) 구성 요소와 사용자가 정의한 클래스 및 서드파티 라이브러리 간의 이름 충돌을 원천 차단할 수 있다.
2. const와 constexpr의 차이
C++의 상수는 생성되는 시점과 의미에 따라 크게 두 가지로 분류된다.
- 컴파일 타임 상수: 컴파일 시점에 값이 결정되어 전역 상수 스토어 등에 배치되거나 인라인화된다.
- 런타임 상수: 프로그램 실행 중에 값이 결정되며, 한 번 초기화되면 이후 변경이 불가능하다.
특징 및 차이점 비교
const(Read-only)- 컴파일 타임 상수와 런타임 상수를 모두 표현할 수 있다.
- 객체가 “수정 불가” 상태임을 보장하는 의미가 강하다.
constexpr(Constant Expression)- 오직 컴파일 타임 상수만 표현할 수 있다.
- 컴파일 시점에 반드시 계산이 완료되어야 하는 템플릿 인자, 배열의 크기 지정 등에 사용하면 의미가 명확해진다.
올바른 표현과 오류 예시
1
2
3
4
5
6
7
8
9
const int age1 = 1; // 컴파일 시점에 생성 가능
constexpr int age2 = 1; // 컴파일 시점에 생성 가능
// 오류 발생 예시
int add(const int a, constexpr int b) {
return a + b;
}
- 오류 이유: 함수의 매개변수(
b)는 함수가 호출되는 런타임에 스택 메모리에 할당되는 값이다. 컴파일러는 함수 정의 단계에서 매개변수b에 들어올 값을 예측할 수 없으므로constexpr을 매개변수 타입 앞에 지정할 수 없다.
3. C++ 입출력 스트림 연산자 (<<, >>)의 원리
cout << 연산자의 의미
- 연산자 오버로딩 (Operator Overloading)
- 비트 왼쪽 이동 연산자(
<<)가std::ostream클래스 내부에서 입출력용으로 오버로딩된 형태다. - 스트림 버퍼와 체이닝(Chaining) 원리
std::cout은ostream클래스의 스트림 버퍼를 소유한 전역 객체다.- 연산이 수행되면 우측의 데이터가 스트림 버퍼에 채워지고, 연산 결과로
*this(데이터가 채워진 cout 객체 자신)를 참조 리턴한다. - 이 참조 리턴 메커니즘 덕분에
cout << 값1 << 값2;와 같은 연속적인 체이닝 표현이 가능하다.
cin >> 연산자의 의미
- 대칭적 스트림 연산
std::cin은istream클래스의 스트림 버퍼를 관리하는 객체다.>>연산자는 스트림 버퍼로부터 데이터를 추출하여 우측 변수에 값을 전달(입력)한 뒤, 버퍼에서 해당 데이터를 제거하고 자기 자신(*this)을 리턴한다. 이 역시 동일한 원리로 체이닝이 지원된다.
std::endl과 \n의 결정적 차이
\n: 스트림 버퍼에 줄바꿈(개행) 문자만 추가한다. 속도가 빠르다.std::endl: 줄바꿈 문자를 추가한 뒤, 스트림 버퍼를 즉시 비우는 출력 플러시(flush) 명령을 강제로 수행한다. 디버깅에는 유리하나 빈번한 호출 시 디스크 I/O 유발로 성능 저하의 원인이 된다.
스트림 버퍼가 출력되는(Flushing) 시점:
- 버퍼가 제한된 용량만큼 가득 찼을 때
std::cin을 통해 입력을 요구받을 때 (출력 버퍼가 먼저 비워짐)std::endl또는std::flush를 만났을 때- 프로그램의
main함수가 정상 종료될 때
4. 람다 함수 (Lambda Function)
람다 함수는 이름이 없는 익명 함수이자, 선언된 자리에서 즉시 실행하거나 변수에 할당할 수 있는 클로저(Closure)다.
람다 함수의 기본 구조
1
2
3
[캡처_블록] (매개변수_선언) -> 반환타입 { 함수_본문 } (호출_인자);
캡처(Capture) 형태별 비교 및 특징
- 특정 변수 값 캡처 (복사)
1
2
[result](int _a) { /* result의 값을 읽을 수만 있음. 수정 불가 */ }(a);
- 외부 변수를 값으로 복사하여 가져온다. 람다 본문 내에서 기본적으로 수정이 불가능하며, 수정하려면 람다 선언 뒤에
mutable키워드를 붙여야 한다. (단,mutable을 붙여 변경해도 외부 원래 변수 값은 변하지 않는다.) - 특정 변수 참조 캡처
1
2
[&result](int _a) { result += _a; }(a);
- 외부 변수의 메모리 주소를 참조로 가져온다. 람다 내부에서 수정 시 외부 변수의 원본 데이터도 함께 변경된다.
- 포인터 캡처
1
2
3
int* p = &result;
[p](int _a) { *p += _a; }(a);
- 포인터 변수 자체를 복사하여 전달하므로, 역참조(
*p)를 통해 외부 원본 데이터에 접근 및 수정이 가능하다. - 기본 캡처 모드
[=]: 현재 범위(Scope)에 있는 사용 가능한 모든 외부 지역 변수를 값 복사 형태로 캡처한다.[&]: 현재 범위에 있는 사용 가능한 모든 외부 지역 변수를 참조 형태로 캡처한다.[=, &result]: 기본적으로는 모두 값 복사로 가져오되,result변수만 예외적으로 참조로 캡처한다.
람다 함수의 장점
- 간결성 및 가독성 : 단발성으로 사용되는 알고리즘의 서브 로직을 별도 함수 정의 없이 인라인으로 짧게 작성할 수 있다.
- 클로저(Closure) 지원 : 함수가 선언된 시점의 환경(외부 변수 상태)을 유지한 채 독립적으로 동작할 수 its다.
- 코드의 지역성 향상 : 호출부 바로 옆에 로직이 위치하므로 전체적인 제어 흐름 파악이 용이하다.
STL 알고리즘 연계 사용 예시 (std::sort)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> v = {3, 2, 4, 1};
// 람다 함수를 비교자(Predicate)로 사용하여 내림차순 정렬
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
for (auto val : v)
{
std::cout << val << "\n";
}
}
- *비교 함수 작성 시 주의사항
- 반환 조건식은 엄격한 약한 순서(Strict Weak Ordering)를 만족해야 하므로 정밀한
true또는false를 반환해야 한다. - 비교 연산자에
>=나<=처럼 동등 조건(=)을 포함하여 모호한 판단 구조를 만들면, 내부 정렬 알고리즘 수행 중 메모리 범위를 초과하거나 데이터가 손상되는 정의되지 않은 행동(Undefined Behavior)이 발생할 수 있다.
5. Parameter와 Argument의 엄밀한 차이
int main(void) 문맥에서 void 지정을 바탕으로 두 개념을 구분한다.
- Parameter (매개변수 / 인자)
- 함수 정의부에 명시된 선언문으로, 함수 내부로 값이 들어오는 통로 역할을 하는 “변수”다.
int main(void)에서void는 아무런 값도 받지 않음을 의미하는 명확한 자료형 선언이므로 Parameter에 해당한다.- Argument (전달인자 / 실인자)
- 함수를 실제로 호출할 때 매개변수에 주입하는 실제 “값” 또는 “데이터”다.
This post is licensed under CC BY 4.0 by the author.