고정폭 정수형
고정폭 정수형은 정수 자료형의 크기를 보장한다. 표준형에서 쓰인 애매한 표기들을 정확하게 표현하기 위하여 만들어졌다.
int8_t Score = Human01->GetID();
C++11에서는 아래와 같은 고정폭 정수형 자료형이 지원된다.
- int8_t / uint8_t
- int16_t / uint16_t
- int32_t / uint32_t
- int64_t / uint64_t
- intptr_t / uintptr_t
enum
기존 enum은 enum 멤버를 int형 변수에 대입하거나 다른 enum 멤버와의 비교가 가능하여 엉망이 될 수 있었다.
// main.cpp
enum EPosition
{
TOP,
JUNGLE,
MID,
SUPPORT,
BOTTOM
};
enum EChamp
{
LEESIN,
LULU,
ZED,
BLITZ,
MISSFORTUNE
};
int main()
{
EPosition MyPosition = SUPPORT;
EChamp MyChamp = BLITZ;
int Number = MyPosition; // 컴파일 에러 안납니다. enum to int 암시적 형변환 해줌.
if (MyPosition == ZED) // 컴파일 에러 안납니다. 서로 다른 enum 끼리 비교 연산 지원.
{
/* ... */
}
return 0;
}
enum class
c++ 11부터 지원되는 독자적인 자료형.
- 정수형으로의 암시적 캐스팅이 없고, 필요하면 프로그래머가 명시적 캐스팅 해야함.
- enum class에는 기저 정수형을 지정할 수 있음.
- enum class 열거형명 : uint8_t과 같은 식으로 지정 가능하며 8비트 크기로 제한.
// Main.cpp
enum class EPosition
{
TOP,
JUNGLE,
MID,
SUPPORT,
BOTTOM
};
enum class EChamp
{
LEESIN,
LULU,
ZED,
BLITZ,
MISSFORTUNE
};
int main()
{
EPosition MyPosition = EPosition::SUPPORT;
EChamp MyChamp = EChamp::BLITZ;
int Number = MyPosition; // 컴파일 에러. enum class는 int 자료형으로의 암시적 형변환이 안됨.
if (MyPosition == EChamp::ZED) // 컴파일 에러. 서로 다른 enum class 간의 비교 연산 불가.
{
/* ... */
}
for (int i = EPosition::TOP; i <= EPosition::BOTTOM; ++i)
{
}
// 컴파일 에러. C언어에서 배운 (int) 형변환 연산자를 쓰거나
// static_cast<int>로 우회 가능.
for (int i = (int)(EPosition::TOP); i <= static_cast<int>(EPosition::BOTTOM); ++i)
{
}
// 컴파일 성공.
return 0;
}
static_cast<int>를 사용하여 enum을 int로 변경할 수도 있다.
for(int i=static_cast<int>(Eposition::TOP); i <= static_cast<int>(Eposition::BOTTOM); i++)
{
COUT << static_cast<int>MyPosition << endl;
}
// 또는
for(int i=(int)(Eposition::TOP); i <= (int)(Eposition::BOTTOM); i++)
{
COUT << (int)MyPosition << endl;
}
enum에 할당할 바이트 크기를 정하는 방법
#include <cstdint> // uint8_t를 위한 인클루드
enum class EPosition : uint8_t
{
TOP,
JUNGLE,
MID,
SUPPORT,
BOTTOM = 0x100
// 컴파일 경고가 나면서 8비트 부분만 떼서 0으로 저장되거나,
// 컴파일 에러가 날 수도 있습니다.
};
enum class의 바이트 크기 설정
// Pixel.h
#pragma once
#include <cstdint>
enum class EColor : uint8_t
{
Red = 0,
Green = 1,
Blue = 2,
};
class Pixel
{
public:
Pixel(const EColor InColor)
: Color(InColor)
{
}
~Pixel()
{
}
inline EColor GetColor() const
{
return Color;
}
void PrintColor() const;
private:
EColor Color;
};
// Pixel.cpp
#include "Pixel.h"
#include <iostream>
void Pixel::PrintColor() const
{
switch (Color)
{
case EColor::Red:
std::cout << "RED" << std::endl;
break;
case EColor::Green:
std::cout << "GREEN" << std::endl;
break;
case EColor::Blue:
std::cout << "BLUE" << std::endl;
break;
default:
std::cout << "INVALID" << std::endl;
break;
}
}
// Main.cpp
#include "Pixel.h"
int main()
{
Pixel Pixel01(EColor::Red);
Pixel Pixel02(EColor::Blue);
Pixel Pixel03(EColor::Green);
Pixel01.PrintColor();
Pixel02.PrintColor();
Pixel03.PrintColor();
return 0;
}
enum 대신 enum class 사용할 것!
auto 키워드
컴파일러가 자료형을 자동으로 추론하게 된다. 컴파일 타임에 자료형이 정해져야하므로, 조건을 만족해야 사용 가능하다.
- auto 변수는 반드시 초기화해야한다.
- 한번 자료형이 결정되면 더는 자료형을 바꾸지 못한다.
// 1. auto 변수는 반드시 초기화를 해야합니다.
auto X; // 컴파일 에러
auto X = "Hello, world!"; // 컴파일 성공.
// 2. 한 번 자료형이 결정되면 더는 자료형을 바꾸지 못합니다.
auto X = "Hello, world!"; // 컴파일 성공. X는 const char* 자료형.
X = true; // 컴파일 에러.
auto로 포인터와 참조를 받을 수도 있다.
- 포인터를 받을 때: auto 또는 auto*
- 참조를 받을 때: auto&
// Main.cpp
Cat* MyCat = new Cat(2, "Calido");
auto MyCatPtr01 = MyCat;
// 안 좋은 코딩 습관. 포인터인지 아닌지 알 수 없습니다.
auto* MyCatPtr02 = MyCat;
// 가독성을 위해 포인터를 받을 때는 auto*를 씁시다.
// Main.cpp
Cat MyCat(3, "Calido");
Cat& MyCatRef = MyCat;
auto AnotherCatRef01 = MyCatRef;
// 이때 AnotherCatRef01은 참조 자료형이 아니고 값 자료형 Cat입니다.
// 만약 기본 복사 생성자가 동작하고, 하필 또 포인터 자료형의 멤버 변수가 있다면 얕은 복사가 일어납니다.
// 그럼 같은 메모리 주소를 공유하게 되고, 소멸자에서 메모리 이중 해제가 발생할 수도 있습니다.
// 포인터 자료형의 멤버 변수가 없다고 해도, 값 자료형이므로 의도치 않은 값복사로 인한 성능저하도 우려됩니다.
auto& AnotherCatRef02 = MyCatRef;
// 이렇게 사용하는 것을 추천합니다.
const 변수는 auto 말고 const auto&로 받는 게 좋다.
const int A = 7;
auto& ARef01 = A;
// ARef01의 자료형은 const int&가 됩니다.
// 그럼에도 auto&에는 const가 안 붙어있기 때문에 프로그래머가 볼 때 헷갈릴 수 있습니다.
const auto& ARef02 = A;
// 이렇게 항상 작성합시다.
컴파일러가 알아낼 수 있기 때문에 auto는 모든 변수를 받을 수 있지만, 사람의 가독성을 위해서 연산자를 달아준다.
auto를 사용하면 좋은 경우
1. 반복자
for (std::map<std::string, std::string>::const_iterator Iter = Map01.begin(); Iter != Map01.end(); ++Iter)
{
// 너무 길다.
}
for (auto Iter = Map01.begin(); Iter != Map01.end(); ++Iter)
{
// 속이 뻥. 단, 본인이 iterator를 잘 이해함과 동시에 자료형 추론이 가능한 경우에만 사용해야 합니다.
}
2. 템플릿 매개변수가 두 개 이상인 경우
MyPair<int, std::string>* Pair01 = new MyPair<int, std::string>(110, "Kim");
// 우항에 자료형(MyPair<int, std::string>)이 보이기 때문에 auto를 사용하는 것도 좋은 방법입니다.
auto* Pair02 = new MyPair<int, std::string>(110, "Lee");
// 자료형이 보이면 auto 키워드를 써도 됩니다.
default 키워드
생성자를 하나도 작성하지 않으면 암시적 기본생성자가 구현된다. default 키워드를 사용하면 컴파일러가 기본 생성자, 연산자 및 소멸자의 암시적 버전을 만든다. 그래서 기본 생성자나 소멸자를 직접 구현하지 않고 기본 생성자나 소멸자를 그대로 활용할 수 있다.
// Main.cpp
class Pet
{
public:
Pet() = default;
// 컴파일러에게 기본 생성자를 구현하라고 명시합니다.
// 사람이 볼 때, 명시적으로 기본 생성자가 있구나 추측 가능합니다.
// 또한 다른 생성자를 정의해서 기본 생성자가 생성되지 않는 상황도 피할 수 있습니다.
// default 키워드를 사용하면 다른 생성자가 정의되었어도 기본 생성자를 만들어냅니다.
};
int main(void)
{
Pet MyPet;
return 0;
}
delete 키워드
특정 생성자/연산자를 사용하지 못하게 명시적으로 금지하는 키워드.
private에다 생성자를 추가하는 방법도 있지만, delete를 사용하여 명시적으로 표현해주는 것이 좋다.
class Dog
{
private:
Dog() {} // 생성자를 호출하지 못하게 하는 방법.
};
private 접근 제어자로 특정 생성자 연산자를 숨기면 접근제어자 에러메세지가 난다. delete로 호출을 금지하면 좀 더 올바른 호출 금지 에러 메세지가 뜬다.
// Main.cpp
#include <string>
class Pet
{
public:
Pet() = default;
Pet(const Pet& InOther) = delete;
};
int main()
{
Pet MyPet01;
Pet MyPet02(MyPet01);
// 컴파일 에러.
return 0;
}
final 키워드
더이상의 상속을 금지하는 키워드. 클래스의 final을 붙이면 상속이 불가능해진다.
가상함수 뒤에 붙이면 더이상 오버라이딩 금지를 의미한다. 가상함수를 자식 클래스에서 오버라이딩 못하도록 해주는 키워드이다.
// Main.cpp
#include <iostream>
class Dog
{
public:
Dog(int InAge)
: Age(InAge)
{
}
virtual void SetAge(int InAge) final
{
Age = InAge;
}
virtual int GetAge(void) const final
{
return Age;
}
virtual void Wag() const = 0;
private:
int Age;
};
class Corgi final : public Dog
{
public:
Corgi(int InAge)
: Dog(InAge)
{
}
//virtual void SetAge(int InAge) override;
virtual void Wag() const final
{
std::cout << "Corgi is now wagging." << std::endl;
}
};
//class JindoCorgi : public Corgi
//{
//};
int main()
{
Corgi Choco(10);
Choco.Wag();
return 0;
}
jave의 final과 다른 점
Java final
→ 값 변경 / 상속 / 오버라이드 금지
C++ final
→ 상속 / 오버라이드만 금지 (값은 상관 없음)
java
final int x = 10;
x = 20; // ❌ 불가능
final class A {}
class B extends A {} // ❌
class A {
final void foo() {}
}
class B extends A {
void foo() {} // ❌ 오버라이드 금지
}
C++
- 값에는 final 붙일 수 없음.
- 클래스는 이름 뒤에, 함수에선 선언() 뒤에 붙여야 함
- override와 같이 사용할 경우 override 뒤에 붙.
class A final {};
class B : public A {}; // ❌
class A {
virtual void foo() final;
};
class B : public A {
void foo() override; // ❌
}
class B : public A {
void foo() override final;
};
override 키워드
자식 클래스에 가상함수 오버라이딩을 하려고 할 때, override 키워드를 붙여주면 해당 함수의 부모 클래스를 체크해줌.
잘못된 오버라이딩을 막기 위해 사용.
override 조건
- 부모 함수가 virtual
- 함수 이름 동일
- 파라미터 / const / 반환형 동일
// Main.cpp
class Animal
{
public:
void Move()
{
}
virtual void MakeSound()
{
}
virtual void Eat()
{
}
};
class Dog : public Animal
{
public:
void Move() override
{
}
// 오버라이딩이 맞을까요?
void MakeSound(int InCount) override
{
}
// 오버라이딩이 맞을까요?
void Eat() override
{
}
// 오버라이딩이 맞을까요?
};
int main()
{
return 0;
}
람다 함수(Lambda Function)
이름없는 함수 객체, 또는 익명 함수라고도 많이 부름.

캡쳐의 종류
- []
- 캡쳐하지 않음
- [=]
- 모든 외부 변수를 캡쳐
- 값에 의한 캡쳐이므로, 캡쳐해온 원본 변수 수정 불가능
- [&]
- 모든 외부 변수를 캡쳐
- 참조에 의한 캡쳐이므로, 람다 함수 내에서 캡쳐해 온 변수 원본 수정 가능
- [변수명]
- 특정 변수를 값에 의한 캡쳐
- [&변수명]
- 특정 변수를 참조에 의한 캡쳐
// Main.cpp
#include <iostream>
#include <limits> // std::numeric_limits
int main(void)
{
{
auto NoCaptureLambda01 = []() -> void { std::cout << "NoCaptureLambda01" << std::endl; };
// void (*NoCaptureLambda01)() = []() -> void { std::cout << "NoCaptureLambda01" << std::endl; };
NoCaptureLambda01();
[]() -> void { std::cout << "NoCaptureLambda02" << std::endl; }();
}
{
float Score01 = 98.f;
float Score02 = 99.f;
// auto WrongGetMax = []() -> float { return Score01 < Score02 ? Score02 : Score01; };
// std::cout << "Max: " << []() -> float { return Score01 < Score02 ? Score02 : Score01; }() << std::endl;
// 컴파일 에러. 외부 변수 사용 불가. 캡쳐가 필요합니다.
auto GetMax01 = [Score01, Score02]() -> float { return Score01 < Score02 ? Score02 : Score01; };
std::cout << "Max: " << GetMax01() << std::endl;
auto GetMax02 = [=]() -> float { return Score01 < Score02 ? Score02 : Score01; };
std::cout << "Max: " << GetMax02() << std::endl;
}
{
float Score = 99.f;
// auto IncreaseScore = [Score]() -> void { Score += 1.f; };
// 값에 의한 캡쳐이기 때문에 수정은 불가능.
std::cout << "Before Score: " << Score << std::endl;
auto IncreaseScore = [&Score]() -> void { Score += 1.f; };
IncreaseScore();
std::cout << "After Score: " << Score << std::endl;
float Score01 = 98.f;
float Score02 = 99.f;
float Score03 = 100.f;
auto IncreaseAllScores = [&]() -> void { Score01 += 1.f; Score02 += 1.f; Score03 += 1.f; };
IncreaseAllScores();
}
{
float MaxScore = std::numeric_limits<float>::lowest();
float Score01 = 98.f;
float Score02 = 99.f;
float Score03 = 100.f;
std::cout << "Before MaxScore: " << MaxScore << std::endl;
auto GetMaxScore = [=, &MaxScore]() -> void
{
// MaxScore만 참조 캡쳐, 나머지는 값 캡쳐.
MaxScore = std::max(MaxScore, Score01);
MaxScore = std::max(MaxScore, Score02);
MaxScore = std::max(MaxScore, Score03);
};
GetMaxScore();
std::cout << "After MaxScore: " << MaxScore << std::endl;
}
return 0;
}
매개변수 목록은 생략 가능
auto PrintHelloWorld = [] { cout << "Hello, world!" << endl; };
// () 괄호는 생략할 수도 있습니다. 그럼에도 불구하고 적어두는 것이 좋습니다.
// Main.cpp
#include <iostream>
int main(void)
{
float Score01 = 100.0f;
float Score02 = 90.0f;
auto AddScore = [](float InLHS, float InRHS) -> float { return InLHS + InRHS; };
std::cout << "Score01 + Score02: " << AddScore(Score01, Score02) << std::endl;
return 0;
}
익명 함수와 sort() 함수
// Main.cpp
#include <iostream>
#include <vector>
#include <algorithm> // std::sort()
struct Comparer
{
bool operator()(float InLHS, float InRHS)
{
return (InLHS < InRHS);
}
};
bool Sort(float InLHS, float InRHS)
{
return (InLHS > InRHS);
// 전역 함수로 Sort() 함수를 선언하고 사용하면 다른 동료도 Main.cpp를 읽다가 Sort() 함수를 쓸 수도 있습니다.
// 나중에 Sort() 함수를 수정 할 일이 생긴다면, Sort() 함수를 사용 중인 모든 프로그래머들과 의논해야합니다.
// 다른 사람과 굳이 공유하지 않을 코드라면 숨기는 것이 좋긴 합니다.
}
int main(void)
{
std::vector<float> Scores01 = { 100.f, 10.f, 90.f, 20.f, 80.f, 30.f, 70.f, 40.f, 60.f, 50.f };
Comparer MyComparer;
std::sort(Scores01.begin(), Scores01.end(), MyComparer);
auto LeftIter01 = Scores01.begin();
while (LeftIter01 != Scores01.end())
{
std::cout << *LeftIter01 << ' ';
++LeftIter01;
}
std::cout << std::endl;
std::vector<float> Scores02 = { 100.f, 10.f, 90.f, 20.f, 80.f, 30.f, 70.f, 40.f, 60.f, 50.f };
std::sort(Scores02.begin(), Scores02.end(), Sort);
auto LeftIter02 = Scores02.begin();
while (LeftIter02 != Scores02.end())
{
std::cout << *LeftIter02 << ' ';
++LeftIter02;
}
std::cout << std::endl;
return 0;
}
익명 함수 활용
// Main.cpp
#include <iostream>
#include <vector>
#include <algorithm>
int main(void)
{
std::vector<float> Scores = { 100.f, 10.f, 90.f, 20.f, 80.f, 30.f, 70.f, 40.f, 60.f, 50.f };
std::sort(Scores.begin(), Scores.end(), [](float InLHS, float InRHS) -> bool { return InLHS > InRHS; });
auto LeftIter01 = Scores.begin();
while (LeftIter01 != Scores.end())
{
std::cout << *LeftIter01 << ' ';
++LeftIter01;
}
std::cout << std::endl;
return 0;
}
// STL 알고리즘과 함께
vector<int> v = {3, 1, 4, 1, 5};
// sort에 비교 함수로 전달
sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // 내림차순
});
// find_if에 조건 함수로 전달
auto it = find_if(v.begin(), v.end(), [](int x) {
return x > 3; // 3보다 큰 첫 번째 요소
});
장점
- 간단하면서도 로직에 최적화된 함수 작성에 좋음
- 전역 함수로 작성하면 다른 사람도 호출 가능하므로 유지보수에 좀더 신경써야함.
단점
- 디버깅 불편
- 함수 재사용성이 낮음
기본적으로는 이름 있는 함수를 사용하는 게 좋음
자잘한 함수, 예를 들어 한 줄 짜리 함수나 정렬 함수처럼 STL 컨테이너에 매개변수로 전달할 함수같은 경우는 람다로 쓰는 것이 좋음.
mutable
일반적으로 값을 복사 캡처([x])하면, 람다는 내부적으로 그 값을 상수(const)로 보관하는데, mutable을 붙이면 이를 해제해준다.
int x = 10;
// mutable이 없다면 x = 99; 에서 컴파일 에러 발생!
auto f = [x]() mutable {
x = 99; // 이제 수정 가능
std::cout << "내부 x: " << x << std::endl;
};
f(); // 내부 x: 99 출력
std::cout << "외부 x: " << x << std::endl; // 외부 x: 10 출력 (복사본만 바꿨으므로)
- 외부에는 영향 없음: mutable로 바꾼 값은 람다 내부에 저장된 복사본일 뿐, 람다 밖의 원본 x는 여전히 10.
- 상태의 지속성: 람다를 다시 호출하면 x는 10으로 초기화되는 게 아니라, 이전에 수정된 99를 기억. 즉, 람다가 '상태'를 가지게 됨.
- 외부의 값 자체를 바꾸고 싶다면 [&x] 처럼 참조 값을 캡처해야한다. 이때 mutable은 필요없다.
람다를 함수 인자로 넘기는 방법
// 람다를 변수처럼 다른 함수에 넘길 수 있다!
void apply(/* 여기에 람다를 받는 타입 */) {
f(3, 4); // 넘겨받은 람다 실행
}
apply([](int a, int b) { return a + b; }); // 람다를 인자로 전달
// C++20 이전에는 함수 매개변수에 auto 불가!
void func(auto f) { } // ❌ C++17까지 컴파일 에러
// C++20부터는 가능
void func(auto f) { } // ✅ C++20
// 가장 일반적인 방법
template <typename F>
void func(F f) {
f(3, 4);
}
func([](int a, int b) { return a + b; }); // ✅
func([](int a, int b) { return a * b; }); // ✅
// F가 람다 타입으로 자동 추론됨
// b번: std::function으로 받기
void func(function<int(int, int)> f) { f(3, 4); }
// ✅ 가능하지만 약간 느림 (타입 변환 오버헤드)
// d번: template으로 받기
template <typename F>
void func(F f) { f(3, 4); }
// ✅ 가능하고 더 빠름 (컴파일 타임에 타입 확정)
function 타입 변수
#include <functional>
// 일반 함수
int add(int a, int b) { return a + b; }
// std::function에 저장
function<int(int, int)> f1 = add; // 일반 함수
function<int(int, int)> f2 = [](int a, int b) { return a + b; }; // 람다
function<반환타입(매개변수타입들)>
function<int(int, int)>
// ^^^ ^^^ ^^^
// 반환 인자1 인자2
function<void()> // 반환 없음, 인자 없음
function<bool(int)> // bool 반환, int 인자 1개
function<int(int, int)> // int 반환, int 인자 2개
// auto는 타입을 나중에 바꾸기 어려움
auto f = [](int a, int b) { return a + b; };
// function은 나중에 다른 함수로 교체 가능!
function<int(int, int)> f;
f = [](int a, int b) { return a + b; };
f = [](int a, int b) { return a * b; }; // 교체 가능!
// 함수를 인자로 넘길 때도 유용
void apply(function<int(int, int)> func) {
cout << func(3, 4) << endl;
}
apply([](int a, int b) { return a + b; }); // 7
apply([](int a, int b) { return a * b; }); // 12'코딩 학습 > C와 C++' 카테고리의 다른 글
| TA 코스 2주차 과제 - 프로시저 셰이더 응용하기 (0) | 2026.04.20 |
|---|---|
| C++ 기초 - 구조체 집합 초기화, 포인터 매개변수, 연속 선언, array (0) | 2026.04.10 |
| C++ 기초 - 템플릿 프로그래밍 (0) | 2026.04.08 |
| C++ 기초 - Casting, inline 키워드, static 키워드, 예외 처리 (1) | 2026.04.08 |
| C++ 기초 - string, File I/O (0) | 2026.04.08 |